feat: user preferences (#470)

* wip: improve user preferences

* wip: fix translations and add user danger zone

* feat: add user delete button to danger zone

* fix: test not working

* refactor: add access checks for user edit page, improve not found behaviour, change user preference link in avatar menu to correct link

* fix: remove invalid bg for container

* chore: address pull request feedback
This commit is contained in:
Meier Lukas
2024-05-12 16:27:56 +02:00
committed by GitHub
parent f0da1d81a6
commit db01301845
24 changed files with 961 additions and 414 deletions

View File

@@ -16,7 +16,7 @@ import { useAtomValue } from "jotai";
import { clientApi } from "@homarr/api/client";
import { useConfirmModal, useModalAction } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import {
loadWidgetDynamic,
reduceWidgetOptionsWithDefaultValues,
@@ -115,7 +115,8 @@ const BoardItem = ({ item, ...dimensions }: ItemProps) => {
};
const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
const t = useScopedI18n("item");
const tItem = useScopedI18n("item");
const t = useI18n();
const { openModal } = useModalAction(WidgetEditModal);
const { openConfirmModal } = useConfirmModal();
const isEditMode = useAtomValue(editModeAtom);
@@ -160,8 +161,8 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
const openRemoveModal = () => {
openConfirmModal({
title: t("remove.title"),
children: t("remove.message"),
title: tItem("remove.title"),
children: tItem("remove.message"),
onConfirm: () => {
removeItem({ itemId: item.id });
},
@@ -182,24 +183,24 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
</ActionIcon>
</Menu.Target>
<Menu.Dropdown miw={128}>
<Menu.Label>{t("menu.label.settings")}</Menu.Label>
<Menu.Label>{tItem("menu.label.settings")}</Menu.Label>
<Menu.Item
leftSection={<IconPencil size={16} />}
onClick={openEditModal}
>
{t("action.edit")}
{tItem("action.edit")}
</Menu.Item>
<Menu.Item leftSection={<IconLayoutKanban size={16} />}>
{t("action.move")}
{tItem("action.move")}
</Menu.Item>
<Menu.Divider />
<Menu.Label c="red.6">{t("menu.label.dangerZone")}</Menu.Label>
<Menu.Label c="red.6">{t("common.dangerZone")}</Menu.Label>
<Menu.Item
c="red.6"
leftSection={<IconTrash size={16} />}
onClick={openRemoveModal}
>
{t("action.remove")}
{tItem("action.remove")}
</Menu.Item>
</Menu.Dropdown>
</Menu>

View File

@@ -0,0 +1,70 @@
import { Fragment } from "react";
import {
Card,
CardSection,
Divider,
Group,
Stack,
Text,
Title,
} from "@mantine/core";
import { getI18n } from "@homarr/translation/server";
interface DangerZoneRootProps {
children: React.ReactNode[] | React.ReactNode;
}
export const DangerZoneRoot = async ({ children }: DangerZoneRootProps) => {
const t = await getI18n();
return (
<Stack gap="sm">
<Title c="red.8" order={2}>
{t("common.dangerZone")}
</Title>
<Card withBorder style={{ borderColor: "var(--mantine-color-red-8)" }}>
<Stack gap="sm">
{Array.isArray(children)
? children.map((child, index) => (
<Fragment key={index}>
{child}
{index + 1 !== children.length && (
<CardSection>
<Divider />
</CardSection>
)}
</Fragment>
))
: children}
</Stack>
</Card>
</Stack>
);
};
interface DangerZoneItemProps {
label: string;
description: string;
action: React.ReactNode;
}
export const DangerZoneItem = ({
label,
description,
action,
}: DangerZoneItemProps) => {
return (
<Group justify="space-between" px="md">
<Stack gap={0}>
<Text fw="bold" size="sm">
{label}
</Text>
<Text size="sm">{description}</Text>
</Stack>
<Group justify="end" w={{ base: "100%", xs: "auto" }}>
{action}
</Group>
</Group>
);
};

View File

@@ -18,6 +18,7 @@ import {
IconLogin,
IconLogout,
IconMoon,
IconSettings,
IconSun,
IconTool,
} from "@tabler/icons-react";
@@ -71,6 +72,15 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
>
{t("navigateDefaultBoard")}
</Menu.Item>
{Boolean(session.data) && (
<Menu.Item
component={Link}
href={`/manage/users/${session.data?.user.id}`}
leftSection={<IconSettings size="1rem" />}
>
{t("preferences")}
</Menu.Item>
)}
<Menu.Item
component={Link}
href="/manage"
@@ -111,7 +121,7 @@ const LogoutModal = createModal<{ onTimeout: () => void }>(
useEffect(() => {
start();
}, []);
}, [start]);
return (
<Center h={200 - 2 * 16}>