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:
@@ -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>
|
||||
|
||||
70
apps/nextjs/src/components/manage/danger-zone.tsx
Normal file
70
apps/nextjs/src/components/manage/danger-zone.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user