From 639cbbd032f0a1f22eea93ca51ec2eca21e402dc Mon Sep 17 00:00:00 2001 From: Manuel <30572287+manuel-rw@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:24:16 +0000 Subject: [PATCH] feat: add manage nav footer --- .../nextjs/src/app/[locale]/manage/layout.tsx | 3 +- .../language/current-language-combobox.tsx | 13 ++- .../components/language/language-combobox.tsx | 22 +++-- .../manage/manage-navigation-footer.tsx | 90 +++++++++++++++++++ 4 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 apps/nextjs/src/components/manage/manage-navigation-footer.tsx diff --git a/apps/nextjs/src/app/[locale]/manage/layout.tsx b/apps/nextjs/src/app/[locale]/manage/layout.tsx index 32013221c..ec9b6c316 100644 --- a/apps/nextjs/src/app/[locale]/manage/layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/layout.tsx @@ -36,6 +36,7 @@ import { homarrLogoPath } from "~/components/layout/logo/homarr-logo"; import type { NavigationLink } from "~/components/layout/navigation"; import { MainNavigation } from "~/components/layout/navigation"; import { ClientShell } from "~/components/layout/shell"; +import { ManageNavigationFooter } from "~/components/manage/manage-navigation-footer"; export default async function ManageLayout({ children }: PropsWithChildren) { const t = await getScopedI18n("management.navbar"); @@ -194,7 +195,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) { return ( - + }> {children} ); diff --git a/apps/nextjs/src/components/language/current-language-combobox.tsx b/apps/nextjs/src/components/language/current-language-combobox.tsx index e27f34c39..1272880a4 100644 --- a/apps/nextjs/src/components/language/current-language-combobox.tsx +++ b/apps/nextjs/src/components/language/current-language-combobox.tsx @@ -6,11 +6,20 @@ import { LanguageCombobox } from "./language-combobox"; interface CurrentLanguageComboboxProps { width?: string; + showTranslatedName?: boolean; } -export const CurrentLanguageCombobox = ({ width }: CurrentLanguageComboboxProps) => { +export const CurrentLanguageCombobox = ({ width, showTranslatedName }: CurrentLanguageComboboxProps) => { const currentLocale = useCurrentLocale(); const { changeLocale, isPending } = useChangeLocale(); - return ; + return ( + + ); }; diff --git a/apps/nextjs/src/components/language/language-combobox.tsx b/apps/nextjs/src/components/language/language-combobox.tsx index 6f4b39b33..7cd94b6d5 100644 --- a/apps/nextjs/src/components/language/language-combobox.tsx +++ b/apps/nextjs/src/components/language/language-combobox.tsx @@ -16,9 +16,17 @@ interface LanguageComboboxProps { onChange: (value: SupportedLanguage) => void; isPending?: boolean; width?: string; + showTranslatedName?: boolean; } -export const LanguageCombobox = ({ label, value, onChange, isPending, width }: LanguageComboboxProps) => { +export const LanguageCombobox = ({ + label, + value, + onChange, + isPending, + width, + showTranslatedName, +}: LanguageComboboxProps) => { const combobox = useCombobox({ onDropdownClose: () => combobox.resetSelectedOption(), }); @@ -53,7 +61,7 @@ export const LanguageCombobox = ({ label, value, onChange, isPending, width }: L variant="filled" w={width} > - + @@ -75,10 +83,12 @@ const OptionItem = ({ currentLocale, localeKey, showCheck, + showTranslatedName = true, }: { currentLocale: SupportedLanguage; localeKey: SupportedLanguage; showCheck?: boolean; + showTranslatedName?: boolean; }) => { return ( @@ -86,9 +96,11 @@ const OptionItem = ({ {localeConfigurations[localeKey].name} - - ({localeConfigurations[localeKey].translatedName}) - + {showTranslatedName && ( + + ({localeConfigurations[localeKey].translatedName}) + + )} {showCheck && localeKey === currentLocale && } diff --git a/apps/nextjs/src/components/manage/manage-navigation-footer.tsx b/apps/nextjs/src/components/manage/manage-navigation-footer.tsx new file mode 100644 index 000000000..eb483c206 --- /dev/null +++ b/apps/nextjs/src/components/manage/manage-navigation-footer.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { useCallback } from "react"; +import { useRouter } from "next/navigation"; +import { Button, Stack, useMantineColorScheme } from "@mantine/core"; +import { IconLogout, IconMoon, IconSun } from "@tabler/icons-react"; + + + +import { signOut, useSession } from "@homarr/auth/client"; +import { createModal, useModalAction } from "@homarr/modals"; +import { useScopedI18n } from "@homarr/translation/client"; + + + +import { useAuthContext } from "~/app/[locale]/_client-providers/session"; +import { CurrentLanguageCombobox } from "../language/current-language-combobox"; + + +const LogoutModal = createModal(() => { + const t = useScopedI18n("common.userAvatar.menu.logoutModal"); + + return ({ onTimeout }: { onTimeout: () => void }) => ( + +
{t("description")}
+ +
+ ); +}).withOptions({ + centered: true, + defaultTitle: (t) => t("common.userAvatar.menu.logoutModal.title"), +}); + +export const ManageNavigationFooter = () => { + const t = useScopedI18n("common.userAvatar.menu"); + const { colorScheme, toggleColorScheme } = useMantineColorScheme(); + + const ColorSchemeIcon = colorScheme === "dark" ? IconSun : IconMoon; + const colorSchemeText = colorScheme === "dark" ? t("switchToLightMode") : t("switchToDarkMode"); + + const session = useSession(); + const router = useRouter(); + + const { logoutUrl } = useAuthContext(); + const { openModal } = useModalAction(LogoutModal); + + const handleSignout = useCallback(async () => { + await signOut({ + redirect: false, + }); + openModal({ + onTimeout: () => { + if (logoutUrl) { + window.location.assign(logoutUrl); + return; + } + router.refresh(); + }, + }); + }, [logoutUrl, openModal, router]); + + return ( + + + + {session.status === "authenticated" && ( + + )} + + ); +};