import { Trans } from "@lingui/react/macro" import { Box, Divider, Group, type MantineColorScheme, Menu, SegmentedControl, type SegmentedControlItem, Slider, useMantineColorScheme, } from "@mantine/core" import { showNotification } from "@mantine/notifications" import dayjs from "dayjs" import { type ReactNode, useEffect, useState } from "react" import { TbChartLine, TbHeartFilled, TbHelp, TbLayoutList, TbList, TbListDetails, TbMoon, TbNotes, TbPower, TbSettings, TbSun, TbSunMoon, TbUsers, TbWorldDownload, } from "react-icons/tb" import { throttle } from "throttle-debounce" import { client } from "@/app/client" import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "@/app/redirect/thunks" import { useAppDispatch, useAppSelector } from "@/app/store" import type { ViewMode } from "@/app/types" import { setFontSizePercentage, setViewMode } from "@/app/user/slice" import { reloadProfile } from "@/app/user/thunks" import { useNow } from "@/hooks/useNow" interface ProfileMenuProps { control: React.ReactElement } const ProfileMenuControlItem = ({ icon, label }: { icon: ReactNode; label: ReactNode }) => { return ( {icon} {label} ) } const iconSize = 16 interface ColorSchemeControlItem extends SegmentedControlItem { value: MantineColorScheme } const colorSchemeData: ColorSchemeControlItem[] = [ { value: "light", label: } label={Light} />, }, { value: "dark", label: } label={Dark} />, }, { value: "auto", label: } label={System} />, }, ] interface ViewModeControlItem extends SegmentedControlItem { value: ViewMode } const viewModeData: ViewModeControlItem[] = [ { value: "title", label: } label={Compact} />, }, { value: "cozy", label: } label={Cozy} />, }, { value: "detailed", label: } label={Detailed} />, }, { value: "expanded", label: } label={Expanded} />, }, ] export function ProfileMenu(props: Readonly) { const [opened, setOpened] = useState(false) // close profile menu on scroll useEffect(() => { const listener = throttle(100, () => setOpened(false)) window.addEventListener("scroll", listener) return () => window.removeEventListener("scroll", listener) }, []) const now = useNow() const profile = useAppSelector(state => state.user.profile) const admin = useAppSelector(state => state.user.profile?.admin) const viewMode = useAppSelector(state => state.user.localSettings.viewMode) const forceRefreshCooldownDuration = useAppSelector(state => state.server.serverInfos?.forceRefreshCooldownDuration) const fontSizePercentage = useAppSelector(state => state.user.localSettings.fontSizePercentage) const dispatch = useAppDispatch() const { colorScheme, setColorScheme } = useMantineColorScheme() const nextAvailableForceRefresh = profile?.lastForceRefresh ? profile.lastForceRefresh + (forceRefreshCooldownDuration ?? 0) : now.getTime() const forceRefreshEnabled = nextAvailableForceRefresh <= now.getTime() const logout = () => { window.location.href = "logout" } return ( {props.control} {profile && {profile.name}} } onClick={() => { dispatch(redirectToSettings()) setOpened(false) }} > Settings } disabled={!forceRefreshEnabled} onClick={async () => { setOpened(false) try { await client.feed.refreshAll() // reload profile to update last force refresh timestamp await dispatch(reloadProfile()) showNotification({ message: Your feeds have been queued for refresh., color: "green", autoClose: 1000, }) } catch { showNotification({ message: Force fetching feeds is not yet available., color: "red", autoClose: 2000, }) } }} > Fetch all my feeds now {!forceRefreshEnabled && ({dayjs.duration(nextAvailableForceRefresh - now.getTime()).format("HH:mm:ss")})} Theme setColorScheme(e as MantineColorScheme)} mb="xs" /> Display dispatch(setViewMode(e as ViewMode))} mb="xs" /> Font size `${v}%`} mb="xs" value={fontSizePercentage} onChange={value => dispatch(setFontSizePercentage(value))} /> {admin && ( <> Admin } onClick={() => { dispatch(redirectToAdminUsers()) setOpened(false) }} > Manage users } onClick={() => { dispatch(redirectToMetrics()) setOpened(false) }} > Metrics )} } onClick={() => { dispatch(redirectToDonate()) setOpened(false) }} > Donate } onClick={() => { dispatch(redirectToAbout()) setOpened(false) }} > About } onClick={logout}> Logout ) }