Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
639cbbd032 |
No files matched your search
@@ -36,6 +36,7 @@ import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
|
|||||||
import type { NavigationLink } from "~/components/layout/navigation";
|
import type { NavigationLink } from "~/components/layout/navigation";
|
||||||
import { MainNavigation } from "~/components/layout/navigation";
|
import { MainNavigation } from "~/components/layout/navigation";
|
||||||
import { ClientShell } from "~/components/layout/shell";
|
import { ClientShell } from "~/components/layout/shell";
|
||||||
|
import { ManageNavigationFooter } from "~/components/manage/manage-navigation-footer";
|
||||||
|
|
||||||
export default async function ManageLayout({ children }: PropsWithChildren) {
|
export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||||
const t = await getScopedI18n("management.navbar");
|
const t = await getScopedI18n("management.navbar");
|
||||||
@@ -194,7 +195,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
|||||||
return (
|
return (
|
||||||
<ClientShell hasNavigation>
|
<ClientShell hasNavigation>
|
||||||
<MainHeader></MainHeader>
|
<MainHeader></MainHeader>
|
||||||
<MainNavigation links={navigationLinks}></MainNavigation>
|
<MainNavigation links={navigationLinks} footerSection={<ManageNavigationFooter />}></MainNavigation>
|
||||||
<AppShellMain>{children}</AppShellMain>
|
<AppShellMain>{children}</AppShellMain>
|
||||||
</ClientShell>
|
</ClientShell>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,11 +6,20 @@ import { LanguageCombobox } from "./language-combobox";
|
|||||||
|
|
||||||
interface CurrentLanguageComboboxProps {
|
interface CurrentLanguageComboboxProps {
|
||||||
width?: string;
|
width?: string;
|
||||||
|
showTranslatedName?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CurrentLanguageCombobox = ({ width }: CurrentLanguageComboboxProps) => {
|
export const CurrentLanguageCombobox = ({ width, showTranslatedName }: CurrentLanguageComboboxProps) => {
|
||||||
const currentLocale = useCurrentLocale();
|
const currentLocale = useCurrentLocale();
|
||||||
const { changeLocale, isPending } = useChangeLocale();
|
const { changeLocale, isPending } = useChangeLocale();
|
||||||
|
|
||||||
return <LanguageCombobox value={currentLocale} onChange={changeLocale} isPending={isPending} width={width} />;
|
return (
|
||||||
|
<LanguageCombobox
|
||||||
|
showTranslatedName={showTranslatedName}
|
||||||
|
value={currentLocale}
|
||||||
|
onChange={changeLocale}
|
||||||
|
isPending={isPending}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
@@ -16,9 +16,17 @@ interface LanguageComboboxProps {
|
|||||||
onChange: (value: SupportedLanguage) => void;
|
onChange: (value: SupportedLanguage) => void;
|
||||||
isPending?: boolean;
|
isPending?: boolean;
|
||||||
width?: string;
|
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({
|
const combobox = useCombobox({
|
||||||
onDropdownClose: () => combobox.resetSelectedOption(),
|
onDropdownClose: () => combobox.resetSelectedOption(),
|
||||||
});
|
});
|
||||||
@@ -53,7 +61,7 @@ export const LanguageCombobox = ({ label, value, onChange, isPending, width }: L
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
w={width}
|
w={width}
|
||||||
>
|
>
|
||||||
<OptionItem currentLocale={value} localeKey={value} />
|
<OptionItem currentLocale={value} localeKey={value} showTranslatedName={showTranslatedName} />
|
||||||
</InputBase>
|
</InputBase>
|
||||||
</Combobox.Target>
|
</Combobox.Target>
|
||||||
<Combobox.Dropdown>
|
<Combobox.Dropdown>
|
||||||
@@ -75,10 +83,12 @@ const OptionItem = ({
|
|||||||
currentLocale,
|
currentLocale,
|
||||||
localeKey,
|
localeKey,
|
||||||
showCheck,
|
showCheck,
|
||||||
|
showTranslatedName = true,
|
||||||
}: {
|
}: {
|
||||||
currentLocale: SupportedLanguage;
|
currentLocale: SupportedLanguage;
|
||||||
localeKey: SupportedLanguage;
|
localeKey: SupportedLanguage;
|
||||||
showCheck?: boolean;
|
showCheck?: boolean;
|
||||||
|
showTranslatedName?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Group wrap="nowrap" justify="space-between">
|
<Group wrap="nowrap" justify="space-between">
|
||||||
@@ -86,9 +96,11 @@ const OptionItem = ({
|
|||||||
<LanguageIcon icon={localeConfigurations[localeKey].icon} />
|
<LanguageIcon icon={localeConfigurations[localeKey].icon} />
|
||||||
<Group wrap="nowrap" gap="xs">
|
<Group wrap="nowrap" gap="xs">
|
||||||
<Text>{localeConfigurations[localeKey].name}</Text>
|
<Text>{localeConfigurations[localeKey].name}</Text>
|
||||||
<Text size="xs" c="dimmed" inherit>
|
{showTranslatedName && (
|
||||||
({localeConfigurations[localeKey].translatedName})
|
<Text size="xs" c="dimmed" inherit>
|
||||||
</Text>
|
({localeConfigurations[localeKey].translatedName})
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
{showCheck && localeKey === currentLocale && <IconCheck color="currentColor" size={16} />}
|
{showCheck && localeKey === currentLocale && <IconCheck color="currentColor" size={16} />}
|
||||||
|
|||||||
@@ -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 }) => (
|
||||||
|
<Stack>
|
||||||
|
<div>{t("description")}</div>
|
||||||
|
<Button onClick={onTimeout} fullWidth>
|
||||||
|
{t("action")}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}).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 (
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
leftSection={<ColorSchemeIcon size="1rem" />}
|
||||||
|
onClick={toggleColorScheme}
|
||||||
|
justify="flex-start"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{colorSchemeText}
|
||||||
|
</Button>
|
||||||
|
<CurrentLanguageCombobox width="100%" showTranslatedName={false} />
|
||||||
|
{session.status === "authenticated" && (
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
leftSection={<IconLogout size="1rem" />}
|
||||||
|
onClick={handleSignout}
|
||||||
|
color="red"
|
||||||
|
justify="flex-start"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{t("logout")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in new issue
Block a user