diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index cf510df0..792a2df2 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -7,6 +7,7 @@ import { useAppSelector, useDownload, useLibrary, + useUserDetails, } from "@renderer/hooks"; import * as styles from "./app.css"; @@ -19,7 +20,6 @@ import { toggleDraggingDisabled, closeToast, } from "@renderer/features"; -import { useUserAuth } from "./hooks/use-user-auth"; export interface AppProps { children: React.ReactNode; @@ -31,7 +31,7 @@ export function App() { const { clearDownload, setLastPacket } = useDownload(); - const { updateUserAuth, clearUserAuth } = useUserAuth(); + const { updateUser, clearUser } = useUserDetails(); const dispatch = useAppDispatch(); @@ -39,9 +39,11 @@ export function App() { const location = useLocation(); const search = useAppSelector((state) => state.search.value); + const draggingDisabled = useAppSelector( (state) => state.window.draggingDisabled ); + const toast = useAppSelector((state) => state.toast); useEffect(() => { @@ -70,20 +72,24 @@ export function App() { }; }, [clearDownload, setLastPacket, updateLibrary]); + useEffect(() => { + updateUser(); + }, [updateUser]); + useEffect(() => { const listeners = [ window.electron.onSignIn(() => { - updateUserAuth(); + updateUser(); }), window.electron.onSignOut(() => { - clearUserAuth(); + clearUser(); }), ]; return () => { listeners.forEach((unsubscribe) => unsubscribe()); }; - }, [clearUserAuth, updateUserAuth]); + }, [updateUser, clearUser]); const handleSearch = useCallback( (query: string) => { diff --git a/src/renderer/src/components/sidebar/sidebar-profile.tsx b/src/renderer/src/components/sidebar/sidebar-profile.tsx index c92b3ec0..c81b24fb 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.tsx +++ b/src/renderer/src/components/sidebar/sidebar-profile.tsx @@ -1,24 +1,28 @@ import { useNavigate } from "react-router-dom"; import { PersonIcon } from "@primer/octicons-react"; import * as styles from "./sidebar.css"; -import { useUserAuth } from "@renderer/hooks/use-user-auth"; +import { useUserDetails } from "@renderer/hooks"; +import { useMemo } from "react"; export function SidebarProfile() { const navigate = useNavigate(); - const { userAuth, isLoading } = useUserAuth(); + const { userDetails, profileBackground } = useUserDetails(); const handleClickProfile = () => { - navigate(`/user/${userAuth!.id}`); + navigate(`/user/${userDetails!.id}`); }; const handleClickLogin = () => { window.electron.openExternal("https://auth.hydra.losbroxas.org"); }; - if (isLoading) return null; + const profileButtonBackground = useMemo(() => { + if (profileBackground) return profileBackground; + return undefined; + }, [profileBackground]); - if (userAuth == null) { + if (userDetails == null) { return ( <> diff --git a/src/renderer/src/components/sidebar/sidebar.css.ts b/src/renderer/src/components/sidebar/sidebar.css.ts index 6677bf63..5a96e87a 100644 --- a/src/renderer/src/components/sidebar/sidebar.css.ts +++ b/src/renderer/src/components/sidebar/sidebar.css.ts @@ -149,7 +149,9 @@ export const profileAvatar = style({ justifyContent: "center", alignItems: "center", backgroundColor: vars.color.background, + border: `solid 1px ${vars.color.border}`, position: "relative", + objectFit: "cover", }); export const profileButtonInformation = style({ diff --git a/src/renderer/src/features/index.ts b/src/renderer/src/features/index.ts index d7b75692..f3132520 100644 --- a/src/renderer/src/features/index.ts +++ b/src/renderer/src/features/index.ts @@ -4,4 +4,4 @@ export * from "./use-preferences-slice"; export * from "./download-slice"; export * from "./window-slice"; export * from "./toast-slice"; -export * from "./user-auth-slice"; +export * from "./user-details-slice"; diff --git a/src/renderer/src/features/user-auth-slice.ts b/src/renderer/src/features/user-auth-slice.ts deleted file mode 100644 index 0daf3407..00000000 --- a/src/renderer/src/features/user-auth-slice.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PayloadAction, createSlice } from "@reduxjs/toolkit"; -import type { UserAuth } from "@types"; - -export interface UserAuthState { - userAuth: UserAuth | null; -} - -const initialState: UserAuthState = { - userAuth: null, -}; - -export const userAuthSlice = createSlice({ - name: "user-auth", - initialState, - reducers: { - setUserAuth: (state, userAuth: PayloadAction) => { - state.userAuth = userAuth.payload; - }, - }, -}); - -export const { setUserAuth } = userAuthSlice.actions; diff --git a/src/renderer/src/features/user-details-slice.ts b/src/renderer/src/features/user-details-slice.ts new file mode 100644 index 00000000..af14ce56 --- /dev/null +++ b/src/renderer/src/features/user-details-slice.ts @@ -0,0 +1,32 @@ +import { PayloadAction, createSlice } from "@reduxjs/toolkit"; +import type { UserDetails } from "@types"; + +export interface UserDetailsState { + userDetails: UserDetails | null; + profileBackground: null | string; +} + +const initialState: UserDetailsState = { + userDetails: null, + profileBackground: null, +}; + +export const userDetailsSlice = createSlice({ + name: "user-details", + initialState, + reducers: { + setUserDetails: (state, action: PayloadAction) => { + state.userDetails = action.payload; + }, + setProfileBackground: (state, action: PayloadAction) => { + state.profileBackground = action.payload; + }, + clearUserDetails: (state) => { + state.userDetails = null; + state.profileBackground = null; + }, + }, +}); + +export const { setUserDetails, setProfileBackground, clearUserDetails } = + userDetailsSlice.actions; diff --git a/src/renderer/src/hooks/index.ts b/src/renderer/src/hooks/index.ts index 563e3ff1..5bc287b8 100644 --- a/src/renderer/src/hooks/index.ts +++ b/src/renderer/src/hooks/index.ts @@ -3,3 +3,4 @@ export * from "./use-library"; export * from "./use-date"; export * from "./use-toast"; export * from "./redux"; +export * from "./use-user-details"; diff --git a/src/renderer/src/hooks/use-user-auth.ts b/src/renderer/src/hooks/use-user-auth.ts deleted file mode 100644 index ad376ce7..00000000 --- a/src/renderer/src/hooks/use-user-auth.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { useAppDispatch, useAppSelector } from "./redux"; -import { setUserAuth } from "@renderer/features"; - -export function useUserAuth() { - const dispatch = useAppDispatch(); - - const [isLoading, setIsLoading] = useState(true); - - const { userAuth } = useAppSelector((state) => state.userAuth); - - const signOut = useCallback(async () => { - dispatch(setUserAuth(null)); - return window.electron.signOut(); - }, [dispatch]); - - const updateUserAuth = useCallback(async () => { - setIsLoading(true); - - return window.electron - .getMe() - .then((userAuth) => dispatch(setUserAuth(userAuth))) - .finally(() => { - setIsLoading(false); - }); - }, [dispatch]); - - useEffect(() => { - updateUserAuth(); - }, [updateUserAuth]); - - const clearUserAuth = useCallback(async () => { - dispatch(setUserAuth(null)); - }, [dispatch]); - - return { userAuth, isLoading, updateUserAuth, signOut, clearUserAuth }; -} diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts new file mode 100644 index 00000000..fe4c0505 --- /dev/null +++ b/src/renderer/src/hooks/use-user-details.ts @@ -0,0 +1,57 @@ +import { useCallback } from "react"; +import { average } from "color.js"; + +import { useAppDispatch, useAppSelector } from "./redux"; +import { + clearUserDetails, + setProfileBackground, + setUserDetails, +} from "@renderer/features"; +import { darkenColor } from "@renderer/helpers"; + +export function useUserDetails() { + const dispatch = useAppDispatch(); + + const { userDetails, profileBackground } = useAppSelector( + (state) => state.userDetails + ); + + const clearUser = useCallback(async () => { + dispatch(clearUserDetails()); + }, [dispatch]); + + const signOut = useCallback(async () => { + clearUser(); + + return window.electron.signOut(); + }, [clearUser]); + + const updateUser = useCallback(async () => { + return window.electron.getMe().then(async (userDetails) => { + if (userDetails) { + dispatch(setUserDetails(userDetails)); + + if (userDetails.profileImageUrl) { + const output = await average(userDetails.profileImageUrl, { + amount: 1, + format: "hex", + }); + + dispatch( + setProfileBackground( + `linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.7)})` + ) + ); + } + } + }); + }, [dispatch]); + + return { + userDetails, + updateUser, + signOut, + clearUser, + profileBackground, + }; +} diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index c120f9d1..600f9128 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -1,32 +1,27 @@ import { UserGame, UserProfile } from "@types"; import cn from "classnames"; -import { average } from "color.js"; import * as styles from "./user.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; -import { useMemo, useRef, useState } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; -import { useDate } from "@renderer/hooks"; +import { useDate, useUserDetails } from "@renderer/hooks"; import { useNavigate } from "react-router-dom"; -import { buildGameDetailsPath, darkenColor } from "@renderer/helpers"; +import { buildGameDetailsPath } from "@renderer/helpers"; import { PersonIcon } from "@primer/octicons-react"; import { Button } from "@renderer/components"; -import { useUserAuth } from "@renderer/hooks/use-user-auth"; const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120; + export interface ProfileContentProps { userProfile: UserProfile; } -export const UserContent = ({ userProfile }: ProfileContentProps) => { +export function UserContent({ userProfile }: ProfileContentProps) { const { t, i18n } = useTranslation("user_profile"); - const { userAuth, signOut } = useUserAuth(); - - const profileImageRef = useRef(null); - - const [backgroundColors, setBackgroundColors] = useState([]); + const { userDetails, profileBackground, signOut } = useUserDetails(); const navigate = useNavigate(); @@ -64,34 +59,28 @@ export const UserContent = ({ userProfile }: ProfileContentProps) => { navigate("/"); }; - const handleAvatarLoad = async () => { - const output = await average(profileImageRef.current!, { - amount: 1, - format: "hex", - }); + const isMe = userDetails?.id == userProfile.id; - setBackgroundColors([ - darkenColor(output as string, 0.6), - darkenColor(output as string, 0.7), - ]); - }; + const profileContentBoxBackground = useMemo(() => { + if (profileBackground) return profileBackground; + /* TODO: Render background colors for other users */ + return undefined; + }, [profileBackground]); return ( <>
{userProfile.profileImageUrl ? ( {userProfile.displayName} ) : ( @@ -102,7 +91,7 @@ export const UserContent = ({ userProfile }: ProfileContentProps) => {

{userProfile.displayName}

- {userAuth && userAuth.id == userProfile.id && ( + {isMe && (
); -}; +} diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts index 589fa17f..9bc0c950 100644 --- a/src/renderer/src/store.ts +++ b/src/renderer/src/store.ts @@ -6,7 +6,7 @@ import { searchSlice, userPreferencesSlice, toastSlice, - userAuthSlice, + userDetailsSlice, } from "@renderer/features"; export const store = configureStore({ @@ -17,7 +17,7 @@ export const store = configureStore({ userPreferences: userPreferencesSlice.reducer, download: downloadSlice.reducer, toast: toastSlice.reducer, - userAuth: userAuthSlice.reducer, + userDetails: userDetailsSlice.reducer, }, }); diff --git a/src/types/index.ts b/src/types/index.ts index acb8d10f..153fdc9e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -244,7 +244,7 @@ export interface RealDebridUser { expiration: string; } -export interface UserAuth { +export interface UserDetails { id: string; displayName: string; profileImageUrl: string | null;