diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 7d2a1fdf..7b50f7a9 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -61,6 +61,35 @@ export class HydraApi { baseURL: import.meta.env.MAIN_VITE_API_URL, }); + this.instance.interceptors.request.use( + (request) => { + console.log(" ---- REQUEST -----"); + console.log(request.method, request.url, request.data); + return request; + }, + (error) => { + console.log("request error", error); + return Promise.reject(error); + } + ); + + this.instance.interceptors.response.use( + (response) => { + console.log(" ---- RESPONSE -----"); + console.log( + response.status, + response.config.method, + response.config.url, + response.data + ); + return response; + }, + (error) => { + console.log("response error", error); + return Promise.reject(error); + } + ); + const userAuth = await userAuthRepository.findOne({ where: { id: 1 }, }); 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/helpers.ts b/src/renderer/src/helpers.ts index 9d5a4700..19d1969c 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -1,5 +1,7 @@ import type { GameShop } from "@types"; +import Color from "color"; + export const steamUrlBuilder = { library: (objectID: string) => `https://steamcdn-a.akamaihd.net/steam/apps/${objectID}/header.jpg`, @@ -40,3 +42,6 @@ export const buildGameDetailsPath = ( const searchParams = new URLSearchParams({ title: game.title, ...params }); return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`; }; + +export const darkenColor = (color: string, amount: number) => + new Color(color).darken(amount).toString(); 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 6b6eb39e..600f9128 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -1,26 +1,27 @@ import { UserGame, UserProfile } from "@types"; import cn from "classnames"; + import * as styles from "./user.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; 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 } 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 { userDetails, profileBackground, signOut } = useUserDetails(); const navigate = useNavigate(); @@ -58,11 +59,21 @@ export const UserContent = ({ userProfile }: ProfileContentProps) => { navigate("/"); }; + const isMe = userDetails?.id == userProfile.id; + + const profileContentBoxBackground = useMemo(() => { + if (profileBackground) return profileBackground; + /* TODO: Render background colors for other users */ + return undefined; + }, [profileBackground]); + return ( <>
{userProfile.profileImageUrl ? ( @@ -80,7 +91,7 @@ export const UserContent = ({ userProfile }: ProfileContentProps) => {

{userProfile.displayName}

- {userAuth && userAuth.id == userProfile.id && ( + {isMe && (
); -}; +} diff --git a/src/renderer/src/pages/user/user.css.tsx b/src/renderer/src/pages/user/user.css.ts similarity index 89% rename from src/renderer/src/pages/user/user.css.tsx rename to src/renderer/src/pages/user/user.css.ts index 2bae1b5a..6ec6b820 100644 --- a/src/renderer/src/pages/user/user.css.tsx +++ b/src/renderer/src/pages/user/user.css.ts @@ -11,12 +11,15 @@ export const wrapper = style({ export const profileContentBox = style({ display: "flex", - gap: `${SPACING_UNIT + SPACING_UNIT / 2}px`, + gap: `${SPACING_UNIT * 3}px`, + padding: `${SPACING_UNIT * 4}px ${SPACING_UNIT * 2}px`, alignItems: "center", borderRadius: "4px", border: `solid 1px ${vars.color.border}`, width: "100%", overflow: "hidden", + boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.7)", + transition: "all ease 0.3s", }); export const profileAvatarContainer = style({ @@ -29,17 +32,21 @@ export const profileAvatarContainer = style({ backgroundColor: vars.color.background, position: "relative", overflow: "hidden", + border: `solid 1px ${vars.color.border}`, + boxShadow: "0px 0px 5px 0px rgba(0, 0, 0, 0.7)", }); export const profileAvatar = style({ width: "96px", height: "96px", + objectFit: "cover", }); export const profileInformation = style({ display: "flex", flexDirection: "column", alignItems: "flex-start", + color: "#c0c1c7", }); export const profileContent = style({ 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;