Files
hydra/src/renderer/src/components/sidebar/sidebar-profile.tsx

178 lines
5.1 KiB
TypeScript

import { useNavigate } from "react-router-dom";
import { BellIcon } from "@primer/octicons-react";
import { useAppSelector, useUserDetails } from "@renderer/hooks";
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
import { Avatar } from "../avatar/avatar";
import { AuthPage } from "@shared";
import { logger } from "@renderer/logger";
import type { NotificationCountResponse } from "@types";
import "./sidebar-profile.scss";
export function SidebarProfile() {
const navigate = useNavigate();
const { t } = useTranslation("sidebar");
const { userDetails } = useUserDetails();
const { gameRunning } = useAppSelector((state) => state.gameRunning);
const [notificationCount, setNotificationCount] = useState(0);
const apiNotificationCountRef = useRef(0);
const hasFetchedInitialCount = useRef(false);
const fetchLocalNotificationCount = useCallback(async () => {
try {
const localCount = await window.electron.getLocalNotificationsCount();
setNotificationCount(localCount + apiNotificationCountRef.current);
} catch (error) {
logger.error("Failed to fetch local notification count", error);
}
}, []);
const fetchApiNotificationCount = useCallback(async () => {
try {
const response =
await window.electron.hydraApi.get<NotificationCountResponse>(
"/profile/notifications/count",
{ needsAuth: true }
);
apiNotificationCountRef.current = response.count;
} catch {
// Ignore API errors
}
fetchLocalNotificationCount();
}, [fetchLocalNotificationCount]);
// Initial fetch on mount (only once)
useEffect(() => {
fetchLocalNotificationCount();
}, [fetchLocalNotificationCount]);
// Fetch API count when user logs in (only if not already fetched)
useEffect(() => {
if (userDetails && !hasFetchedInitialCount.current) {
hasFetchedInitialCount.current = true;
fetchApiNotificationCount();
} else if (!userDetails) {
hasFetchedInitialCount.current = false;
apiNotificationCountRef.current = 0;
fetchLocalNotificationCount();
}
}, [userDetails, fetchApiNotificationCount, fetchLocalNotificationCount]);
useEffect(() => {
const unsubscribe = window.electron.onLocalNotificationCreated(() => {
fetchLocalNotificationCount();
});
return () => unsubscribe();
}, [fetchLocalNotificationCount]);
useEffect(() => {
const handleNotificationsChange = () => {
fetchLocalNotificationCount();
};
window.addEventListener("notificationsChanged", handleNotificationsChange);
return () => {
window.removeEventListener(
"notificationsChanged",
handleNotificationsChange
);
};
}, [fetchLocalNotificationCount]);
useEffect(() => {
const unsubscribe = window.electron.onSyncNotificationCount(
(notification) => {
apiNotificationCountRef.current = notification.notificationCount;
fetchLocalNotificationCount();
}
);
return () => unsubscribe();
}, [fetchLocalNotificationCount]);
const handleProfileClick = () => {
if (userDetails === null) {
window.electron.openAuthWindow(AuthPage.SignIn);
return;
}
navigate(`/profile/${userDetails.id}`);
};
const notificationsButton = useMemo(() => {
return (
<button
type="button"
className="sidebar-profile__notification-button"
onClick={() => navigate("/notifications")}
title={t("notifications")}
>
{notificationCount > 0 && (
<small className="sidebar-profile__notification-button-badge">
{notificationCount > 99 ? "99+" : notificationCount}
</small>
)}
<BellIcon size={16} />
</button>
);
}, [t, notificationCount, navigate]);
const gameRunningDetails = () => {
if (!userDetails || !gameRunning) return null;
if (gameRunning.iconUrl) {
return (
<img
className="sidebar-profile__game-running-icon"
alt={gameRunning.title}
width={24}
src={gameRunning.iconUrl}
/>
);
}
return <SteamLogo />;
};
return (
<div className="sidebar-profile">
<button
type="button"
className="sidebar-profile__button"
onClick={handleProfileClick}
>
<div className="sidebar-profile__button-content">
<Avatar
size={35}
src={userDetails?.profileImageUrl}
alt={userDetails?.displayName}
/>
<div className="sidebar-profile__button-information">
<p className="sidebar-profile__button-title">
{userDetails ? userDetails.displayName : t("sign_in")}
</p>
{userDetails && gameRunning && (
<div className="sidebar-profile__button-game-running-title">
<small>{gameRunning.title}</small>
</div>
)}
</div>
{gameRunningDetails()}
</div>
</button>
{notificationsButton}
</div>
);
}