chore: update prettier configuration for print width (#519)
* feat: update prettier configuration for print width * chore: apply code formatting to entire repository * fix: remove build files * fix: format issue --------- Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -7,28 +7,16 @@ import type { AccordionProps } from "@mantine/core";
|
||||
import { Accordion } from "@mantine/core";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
|
||||
type ActiveTabAccordionProps = PropsWithChildren<
|
||||
Omit<AccordionProps<false>, "onChange">
|
||||
>;
|
||||
type ActiveTabAccordionProps = PropsWithChildren<Omit<AccordionProps<false>, "onChange">>;
|
||||
|
||||
// Replace state without fetchign new data
|
||||
const replace = (newUrl: string) => {
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, as: newUrl, url: newUrl },
|
||||
"",
|
||||
newUrl,
|
||||
);
|
||||
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
|
||||
};
|
||||
|
||||
export const ActiveTabAccordion = ({
|
||||
children,
|
||||
...props
|
||||
}: ActiveTabAccordionProps) => {
|
||||
export const ActiveTabAccordion = ({ children, ...props }: ActiveTabAccordionProps) => {
|
||||
const pathname = usePathname();
|
||||
const onChange = useCallback(
|
||||
(tab: string | null) => (tab ? replace(`?tab=${tab}`) : replace(pathname)),
|
||||
[pathname],
|
||||
);
|
||||
const onChange = useCallback((tab: string | null) => (tab ? replace(`?tab=${tab}`) : replace(pathname)), [pathname]);
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (props.defaultValue) {
|
||||
|
||||
@@ -47,12 +47,8 @@ export const useItemActions = () => {
|
||||
({ kind }: CreateItem) => {
|
||||
updateBoard((previous) => {
|
||||
const lastSection = previous.sections
|
||||
.filter(
|
||||
(section): section is EmptySection => section.kind === "empty",
|
||||
)
|
||||
.sort(
|
||||
(sectionA, sectionB) => sectionB.position - sectionA.position,
|
||||
)[0];
|
||||
.filter((section): section is EmptySection => section.kind === "empty")
|
||||
.sort((sectionA, sectionB) => sectionB.position - sectionA.position)[0];
|
||||
|
||||
if (!lastSection) return previous;
|
||||
|
||||
@@ -91,8 +87,7 @@ export const useItemActions = () => {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
// Return same section if item is not in it
|
||||
if (!section.items.some((item) => item.id === itemId))
|
||||
return section;
|
||||
if (!section.items.some((item) => item.id === itemId)) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.map((item) => {
|
||||
@@ -119,8 +114,7 @@ export const useItemActions = () => {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
// Return same section if item is not in it
|
||||
if (!section.items.some((item) => item.id === itemId))
|
||||
return section;
|
||||
if (!section.items.some((item) => item.id === itemId)) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.map((item) => {
|
||||
@@ -128,9 +122,7 @@ export const useItemActions = () => {
|
||||
if (item.id !== itemId) return item;
|
||||
return {
|
||||
...item,
|
||||
...("integrations" in item
|
||||
? { integrations: newIntegrations }
|
||||
: {}),
|
||||
...("integrations" in item ? { integrations: newIntegrations } : {}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
@@ -168,18 +160,14 @@ export const useItemActions = () => {
|
||||
const moveItemToSection = useCallback(
|
||||
({ itemId, sectionId, ...positionProps }: MoveItemToSection) => {
|
||||
updateBoard((previous) => {
|
||||
const currentSection = previous.sections.find((section) =>
|
||||
section.items.some((item) => item.id === itemId),
|
||||
);
|
||||
const currentSection = previous.sections.find((section) => section.items.some((item) => item.id === itemId));
|
||||
|
||||
// If item is in the same section (on initial loading) don't do anything
|
||||
if (!currentSection) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
const currentItem = currentSection.items.find(
|
||||
(item) => item.id === itemId,
|
||||
);
|
||||
const currentItem = currentSection.items.find((item) => item.id === itemId);
|
||||
if (!currentItem) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
@@ -13,14 +13,7 @@ export const ItemSelectModal = createModal<void>(({ actions }) => {
|
||||
return (
|
||||
<Grid>
|
||||
{objectEntries(widgetImports).map(([key, value]) => {
|
||||
return (
|
||||
<WidgetItem
|
||||
key={key}
|
||||
kind={key}
|
||||
definition={value.definition}
|
||||
closeModal={actions.closeModal}
|
||||
/>
|
||||
);
|
||||
return <WidgetItem key={key} kind={key} definition={value.definition} closeModal={actions.closeModal} />;
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
@@ -56,13 +49,7 @@ const WidgetItem = ({
|
||||
<Text lh={1.2} style={{ whiteSpace: "normal" }} ta="center">
|
||||
{t(`widget.${kind}.name`)}
|
||||
</Text>
|
||||
<Text
|
||||
lh={1.2}
|
||||
style={{ whiteSpace: "normal" }}
|
||||
size="xs"
|
||||
ta="center"
|
||||
c="dimmed"
|
||||
>
|
||||
<Text lh={1.2} style={{ whiteSpace: "normal" }} size="xs" ta="center" c="dimmed">
|
||||
{t(`widget.${kind}.description`)}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -15,62 +15,55 @@ interface InnerProps {
|
||||
onSuccess?: (name: string) => void;
|
||||
}
|
||||
|
||||
export const BoardRenameModal = createModal<InnerProps>(
|
||||
({ actions, innerProps }) => {
|
||||
const utils = clientApi.useUtils();
|
||||
const t = useI18n();
|
||||
const { mutate, isPending } = clientApi.board.renameBoard.useMutation({
|
||||
onSettled() {
|
||||
void utils.board.getBoardByName.invalidate({
|
||||
name: innerProps.previousName,
|
||||
});
|
||||
void utils.board.getHomeBoard.invalidate();
|
||||
},
|
||||
});
|
||||
const form = useZodForm(validation.board.rename.omit({ id: true }), {
|
||||
initialValues: {
|
||||
export const BoardRenameModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
||||
const utils = clientApi.useUtils();
|
||||
const t = useI18n();
|
||||
const { mutate, isPending } = clientApi.board.renameBoard.useMutation({
|
||||
onSettled() {
|
||||
void utils.board.getBoardByName.invalidate({
|
||||
name: innerProps.previousName,
|
||||
});
|
||||
void utils.board.getHomeBoard.invalidate();
|
||||
},
|
||||
});
|
||||
const form = useZodForm(validation.board.rename.omit({ id: true }), {
|
||||
initialValues: {
|
||||
name: innerProps.previousName,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: FormType) => {
|
||||
mutate(
|
||||
{
|
||||
id: innerProps.id,
|
||||
name: values.name,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: FormType) => {
|
||||
mutate(
|
||||
{
|
||||
id: innerProps.id,
|
||||
name: values.name,
|
||||
{
|
||||
onSuccess: () => {
|
||||
actions.closeModal();
|
||||
innerProps.onSuccess?.(values.name);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
actions.closeModal();
|
||||
innerProps.onSuccess?.(values.name);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label={t("board.field.name.label")}
|
||||
{...form.getInputProps("name")}
|
||||
data-autofocus
|
||||
/>
|
||||
<Group justify="end">
|
||||
<Button variant="subtle" color="gray" onClick={actions.closeModal}>
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.confirm")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
},
|
||||
);
|
||||
},
|
||||
).withOptions({
|
||||
defaultTitle: (t) =>
|
||||
t("board.setting.section.dangerZone.action.rename.modal.title"),
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<TextInput label={t("board.field.name.label")} {...form.getInputProps("name")} data-autofocus />
|
||||
<Group justify="end">
|
||||
<Button variant="subtle" color="gray" onClick={actions.closeModal}>
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.confirm")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle: (t) => t("board.setting.section.dangerZone.action.rename.modal.title"),
|
||||
});
|
||||
|
||||
type FormType = Omit<z.infer<(typeof validation)["board"]["rename"]>, "id">;
|
||||
|
||||
@@ -2,9 +2,7 @@ import { auth } from "@homarr/auth/next";
|
||||
import type { BoardPermissionsProps } from "@homarr/auth/shared";
|
||||
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||
|
||||
export const getBoardPermissionsAsync = async (
|
||||
board: BoardPermissionsProps,
|
||||
) => {
|
||||
export const getBoardPermissionsAsync = async (board: BoardPermissionsProps) => {
|
||||
const session = await auth();
|
||||
return constructBoardPermissions(board, session);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import type { RefObject } from "react";
|
||||
import {
|
||||
Card,
|
||||
Collapse,
|
||||
Group,
|
||||
Stack,
|
||||
Title,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import { Card, Collapse, Group, Stack, Title, UnstyledButton } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
|
||||
@@ -30,23 +23,14 @@ export const BoardCategorySection = ({ section, mainRef }: Props) => {
|
||||
<Group wrap="nowrap" gap="sm">
|
||||
<UnstyledButton w="100%" p="sm" onClick={toggle}>
|
||||
<Group wrap="nowrap">
|
||||
{opened ? (
|
||||
<IconChevronUp size={20} />
|
||||
) : (
|
||||
<IconChevronDown size={20} />
|
||||
)}
|
||||
{opened ? <IconChevronUp size={20} /> : <IconChevronDown size={20} />}
|
||||
<Title order={3}>{section.name}</Title>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
<CategoryMenu category={section} />
|
||||
</Group>
|
||||
<Collapse in={opened} p="sm" pt={0}>
|
||||
<div
|
||||
className="grid-stack grid-stack-category"
|
||||
data-category
|
||||
data-section-id={section.id}
|
||||
ref={refs.wrapper}
|
||||
>
|
||||
<div className="grid-stack grid-stack-category" data-category data-section-id={section.id} ref={refs.wrapper}>
|
||||
<SectionContent items={section.items} refs={refs} />
|
||||
</div>
|
||||
</Collapse>
|
||||
|
||||
@@ -2,11 +2,7 @@ import { useCallback } from "react";
|
||||
|
||||
import { createId } from "@homarr/db/client";
|
||||
|
||||
import type {
|
||||
CategorySection,
|
||||
EmptySection,
|
||||
Section,
|
||||
} from "~/app/[locale]/boards/_types";
|
||||
import type { CategorySection, EmptySection, Section } from "~/app/[locale]/boards/_types";
|
||||
import { useUpdateBoard } from "~/app/[locale]/boards/(content)/_client";
|
||||
|
||||
interface AddCategory {
|
||||
@@ -41,9 +37,7 @@ export const useCategoryActions = () => {
|
||||
sections: [
|
||||
// Place sections before the new category
|
||||
...previous.sections.filter(
|
||||
(section) =>
|
||||
(section.kind === "category" || section.kind === "empty") &&
|
||||
section.position < position,
|
||||
(section) => (section.kind === "category" || section.kind === "empty") && section.position < position,
|
||||
),
|
||||
{
|
||||
id: createId(),
|
||||
@@ -62,8 +56,7 @@ export const useCategoryActions = () => {
|
||||
...previous.sections
|
||||
.filter(
|
||||
(section): section is CategorySection | EmptySection =>
|
||||
(section.kind === "category" || section.kind === "empty") &&
|
||||
section.position >= position,
|
||||
(section.kind === "category" || section.kind === "empty") && section.position >= position,
|
||||
)
|
||||
.map((section) => ({
|
||||
...section,
|
||||
@@ -134,29 +127,19 @@ export const useCategoryActions = () => {
|
||||
({ id, direction }: MoveCategory) => {
|
||||
updateBoard((previous) => {
|
||||
const currentCategory = previous.sections.find(
|
||||
(section): section is CategorySection =>
|
||||
section.kind === "category" && section.id === id,
|
||||
(section): section is CategorySection => section.kind === "category" && section.id === id,
|
||||
);
|
||||
if (!currentCategory) return previous;
|
||||
if (currentCategory?.position === 1 && direction === "up")
|
||||
return previous;
|
||||
if (
|
||||
currentCategory?.position === previous.sections.length - 2 &&
|
||||
direction === "down"
|
||||
)
|
||||
return previous;
|
||||
if (currentCategory?.position === 1 && direction === "up") return previous;
|
||||
if (currentCategory?.position === previous.sections.length - 2 && direction === "down") return previous;
|
||||
|
||||
return {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
if (section.kind !== "category" && section.kind !== "empty")
|
||||
return section;
|
||||
if (section.kind !== "category" && section.kind !== "empty") return section;
|
||||
const offset = direction === "up" ? -2 : 2;
|
||||
// Move category and empty section
|
||||
if (
|
||||
section.position === currentCategory.position ||
|
||||
section.position - 1 === currentCategory.position
|
||||
) {
|
||||
if (section.position === currentCategory.position || section.position - 1 === currentCategory.position) {
|
||||
return {
|
||||
...section,
|
||||
position: section.position + offset,
|
||||
@@ -165,8 +148,7 @@ export const useCategoryActions = () => {
|
||||
|
||||
if (
|
||||
direction === "up" &&
|
||||
(section.position === currentCategory.position - 2 ||
|
||||
section.position === currentCategory.position - 1)
|
||||
(section.position === currentCategory.position - 2 || section.position === currentCategory.position - 1)
|
||||
) {
|
||||
return {
|
||||
...section,
|
||||
@@ -176,8 +158,7 @@ export const useCategoryActions = () => {
|
||||
|
||||
if (
|
||||
direction === "down" &&
|
||||
(section.position === currentCategory.position + 2 ||
|
||||
section.position === currentCategory.position + 3)
|
||||
(section.position === currentCategory.position + 2 || section.position === currentCategory.position + 3)
|
||||
) {
|
||||
return {
|
||||
...section,
|
||||
@@ -197,21 +178,18 @@ export const useCategoryActions = () => {
|
||||
({ id: categoryId }: RemoveCategory) => {
|
||||
updateBoard((previous) => {
|
||||
const currentCategory = previous.sections.find(
|
||||
(section): section is CategorySection =>
|
||||
section.kind === "category" && section.id === categoryId,
|
||||
(section): section is CategorySection => section.kind === "category" && section.id === categoryId,
|
||||
);
|
||||
if (!currentCategory) return previous;
|
||||
|
||||
const aboveWrapper = previous.sections.find(
|
||||
(section): section is EmptySection =>
|
||||
section.kind === "empty" &&
|
||||
section.position === currentCategory.position - 1,
|
||||
section.kind === "empty" && section.position === currentCategory.position - 1,
|
||||
);
|
||||
|
||||
const removedWrapper = previous.sections.find(
|
||||
(section): section is EmptySection =>
|
||||
section.kind === "empty" &&
|
||||
section.position === currentCategory.position + 1,
|
||||
section.kind === "empty" && section.position === currentCategory.position + 1,
|
||||
);
|
||||
|
||||
if (!aboveWrapper || !removedWrapper) return previous;
|
||||
@@ -232,16 +210,10 @@ export const useCategoryActions = () => {
|
||||
return {
|
||||
...previous,
|
||||
sections: [
|
||||
...previous.sections.filter(
|
||||
(section) => section.position < currentCategory.position - 1,
|
||||
),
|
||||
...previous.sections.filter((section) => section.position < currentCategory.position - 1),
|
||||
{
|
||||
...aboveWrapper,
|
||||
items: [
|
||||
...aboveWrapper.items,
|
||||
...previousCategoryItems,
|
||||
...previousBelowWrapperItems,
|
||||
],
|
||||
items: [...aboveWrapper.items, ...previousCategoryItems, ...previousBelowWrapperItems],
|
||||
},
|
||||
...previous.sections
|
||||
.filter(
|
||||
|
||||
@@ -16,41 +16,35 @@ interface InnerProps {
|
||||
onSuccess: (category: Category) => void;
|
||||
}
|
||||
|
||||
export const CategoryEditModal = createModal<InnerProps>(
|
||||
({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const form = useZodForm(z.object({ name: z.string().min(1) }), {
|
||||
initialValues: {
|
||||
name: innerProps.category.name,
|
||||
},
|
||||
});
|
||||
export const CategoryEditModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const form = useZodForm(z.object({ name: z.string().min(1) }), {
|
||||
initialValues: {
|
||||
name: innerProps.category.name,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
void innerProps.onSuccess({
|
||||
...innerProps.category,
|
||||
name: values.name,
|
||||
});
|
||||
actions.closeModal();
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label={t("section.category.field.name.label")}
|
||||
data-autofocus
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<Group justify="right">
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" color="teal">
|
||||
{innerProps.submitLabel}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
},
|
||||
).withOptions({});
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
void innerProps.onSuccess({
|
||||
...innerProps.category,
|
||||
name: values.name,
|
||||
});
|
||||
actions.closeModal();
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput label={t("section.category.field.name.label")} data-autofocus {...form.getInputProps("name")} />
|
||||
<Group justify="right">
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" color="teal">
|
||||
{innerProps.submitLabel}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({});
|
||||
|
||||
@@ -11,8 +11,7 @@ import { CategoryEditModal } from "./category-edit-modal";
|
||||
export const useCategoryMenuActions = (category: CategorySection) => {
|
||||
const { openModal } = useModalAction(CategoryEditModal);
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const { addCategory, moveCategory, removeCategory, renameCategory } =
|
||||
useCategoryActions();
|
||||
const { addCategory, moveCategory, removeCategory, renameCategory } = useCategoryActions();
|
||||
const t = useI18n();
|
||||
|
||||
const createCategoryAtPosition = useCallback(
|
||||
|
||||
@@ -67,14 +67,8 @@ const useActions = (category: CategorySection) => {
|
||||
};
|
||||
|
||||
const useEditModeActions = (category: CategorySection) => {
|
||||
const {
|
||||
addCategoryAbove,
|
||||
addCategoryBelow,
|
||||
moveCategoryUp,
|
||||
moveCategoryDown,
|
||||
edit,
|
||||
remove,
|
||||
} = useCategoryMenuActions(category);
|
||||
const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } =
|
||||
useCategoryMenuActions(category);
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -5,12 +5,7 @@ import { useMemo } from "react";
|
||||
import type { RefObject } from "react";
|
||||
import { ActionIcon, Card, Menu } from "@mantine/core";
|
||||
import { useElementSize } from "@mantine/hooks";
|
||||
import {
|
||||
IconDotsVertical,
|
||||
IconLayoutKanban,
|
||||
IconPencil,
|
||||
IconTrash,
|
||||
} from "@tabler/icons-react";
|
||||
import { IconDotsVertical, IconLayoutKanban, IconPencil, IconTrash } from "@tabler/icons-react";
|
||||
import combineClasses from "clsx";
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
@@ -43,12 +38,7 @@ export const SectionContent = ({ items, refs }: Props) => {
|
||||
return (
|
||||
<>
|
||||
{items.map((item) => (
|
||||
<BoardItem
|
||||
key={item.id}
|
||||
refs={refs}
|
||||
item={item}
|
||||
opacity={board.opacity}
|
||||
/>
|
||||
<BoardItem key={item.id} refs={refs} item={item} opacity={board.opacity} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
@@ -133,14 +123,9 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
|
||||
const { openModal } = useModalAction(WidgetEditModal);
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const isEditMode = useAtomValue(editModeAtom);
|
||||
const { updateItemOptions, updateItemIntegrations, removeItem } =
|
||||
useItemActions();
|
||||
const { data: integrationData, isPending } =
|
||||
clientApi.integration.all.useQuery();
|
||||
const currentDefinition = useMemo(
|
||||
() => widgetImports[item.kind].definition,
|
||||
[item.kind],
|
||||
);
|
||||
const { updateItemOptions, updateItemIntegrations, removeItem } = useItemActions();
|
||||
const { data: integrationData, isPending } = clientApi.integration.all.useQuery();
|
||||
const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]);
|
||||
|
||||
if (!isEditMode || isPending) return null;
|
||||
|
||||
@@ -164,9 +149,7 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
|
||||
integrationData: (integrationData ?? []).filter(
|
||||
(integration) =>
|
||||
"supportedIntegrations" in currentDefinition &&
|
||||
(currentDefinition.supportedIntegrations as string[]).some(
|
||||
(kind) => kind === integration.kind,
|
||||
),
|
||||
(currentDefinition.supportedIntegrations as string[]).some((kind) => kind === integration.kind),
|
||||
),
|
||||
integrationSupport: "supportedIntegrations" in currentDefinition,
|
||||
});
|
||||
@@ -185,34 +168,19 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
|
||||
return (
|
||||
<Menu withinPortal withArrow position="right-start" arrowPosition="center">
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
variant="transparent"
|
||||
pos="absolute"
|
||||
top={offset}
|
||||
right={offset}
|
||||
style={{ zIndex: 1 }}
|
||||
>
|
||||
<ActionIcon variant="transparent" pos="absolute" top={offset} right={offset} style={{ zIndex: 1 }}>
|
||||
<IconDotsVertical />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown miw={128}>
|
||||
<Menu.Label>{tItem("menu.label.settings")}</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<IconPencil size={16} />}
|
||||
onClick={openEditModal}
|
||||
>
|
||||
<Menu.Item leftSection={<IconPencil size={16} />} onClick={openEditModal}>
|
||||
{tItem("action.edit")}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<IconLayoutKanban size={16} />}>
|
||||
{tItem("action.move")}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<IconLayoutKanban size={16} />}>{tItem("action.move")}</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Label c="red.6">{t("common.dangerZone")}</Menu.Label>
|
||||
<Menu.Item
|
||||
c="red.6"
|
||||
leftSection={<IconTrash size={16} />}
|
||||
onClick={openRemoveModal}
|
||||
>
|
||||
<Menu.Item c="red.6" leftSection={<IconTrash size={16} />} onClick={openRemoveModal}>
|
||||
{tItem("action.remove")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
|
||||
@@ -19,11 +19,7 @@ export const BoardEmptySection = ({ section, mainRef }: Props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
section.items.length > 0 || isEditMode
|
||||
? defaultClasses
|
||||
: `${defaultClasses} gridstack-empty-wrapper`
|
||||
}
|
||||
className={section.items.length > 0 || isEditMode ? defaultClasses : `${defaultClasses} gridstack-empty-wrapper`}
|
||||
style={{ transitionDuration: "0s" }}
|
||||
data-empty
|
||||
data-section-id={section.id}
|
||||
|
||||
@@ -15,20 +15,14 @@ interface InitializeGridstackProps {
|
||||
sectionColumnCount: number;
|
||||
}
|
||||
|
||||
export const initializeGridstack = ({
|
||||
section,
|
||||
refs,
|
||||
sectionColumnCount,
|
||||
}: InitializeGridstackProps) => {
|
||||
export const initializeGridstack = ({ section, refs, sectionColumnCount }: InitializeGridstackProps) => {
|
||||
if (!refs.wrapper.current) return false;
|
||||
// initialize gridstack
|
||||
const newGrid = refs.gridstack;
|
||||
newGrid.current = GridStack.init(
|
||||
{
|
||||
column: sectionColumnCount,
|
||||
margin: Math.round(
|
||||
Math.max(Math.min(refs.wrapper.current.offsetWidth / 100, 10), 1),
|
||||
),
|
||||
margin: Math.round(Math.max(Math.min(refs.wrapper.current.offsetWidth / 100, 10), 1)),
|
||||
cellHeight: 128,
|
||||
float: true,
|
||||
alwaysShowResizeHandle: true,
|
||||
|
||||
@@ -2,17 +2,10 @@ import type { MutableRefObject, RefObject } from "react";
|
||||
import { createRef, useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
import type {
|
||||
GridItemHTMLElement,
|
||||
GridStack,
|
||||
GridStackNode,
|
||||
} from "@homarr/gridstack";
|
||||
import type { GridItemHTMLElement, GridStack, GridStackNode } from "@homarr/gridstack";
|
||||
|
||||
import type { Section } from "~/app/[locale]/boards/_types";
|
||||
import {
|
||||
useMarkSectionAsReady,
|
||||
useRequiredBoard,
|
||||
} from "~/app/[locale]/boards/(content)/_context";
|
||||
import { useMarkSectionAsReady, useRequiredBoard } from "~/app/[locale]/boards/(content)/_context";
|
||||
import { editModeAtom } from "../../editMode";
|
||||
import { useItemActions } from "../../items/item-actions";
|
||||
import { initializeGridstack } from "./init-gridstack";
|
||||
@@ -32,10 +25,7 @@ interface UseGridstackProps {
|
||||
mainRef?: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const useGridstack = ({
|
||||
section,
|
||||
mainRef,
|
||||
}: UseGridstackProps): UseGristackReturnType => {
|
||||
export const useGridstack = ({ section, mainRef }: UseGridstackProps): UseGristackReturnType => {
|
||||
const isEditMode = useAtomValue(editModeAtom);
|
||||
const markAsReady = useMarkSectionAsReady();
|
||||
const { moveAndResizeItem, moveItemToSection } = useItemActions();
|
||||
@@ -157,10 +147,7 @@ interface UseCssVariableConfiguration {
|
||||
* @param mainRef reference to the main div wrapping all sections
|
||||
* @param gridRef reference to the gridstack object
|
||||
*/
|
||||
const useCssVariableConfiguration = ({
|
||||
mainRef,
|
||||
gridRef,
|
||||
}: UseCssVariableConfiguration) => {
|
||||
const useCssVariableConfiguration = ({ mainRef, gridRef }: UseCssVariableConfiguration) => {
|
||||
const board = useRequiredBoard();
|
||||
|
||||
// Get reference to the :root element
|
||||
@@ -177,10 +164,7 @@ const useCssVariableConfiguration = ({
|
||||
if (!mainRef?.current) return;
|
||||
const widgetWidth = mainRef.current.clientWidth / board.columnCount;
|
||||
// widget width is used to define sizes of gridstack items within global.scss
|
||||
root?.style.setProperty(
|
||||
"--gridstack-widget-width",
|
||||
widgetWidth.toString(),
|
||||
);
|
||||
root?.style.setProperty("--gridstack-widget-width", widgetWidth.toString());
|
||||
gridRef.current?.cellHeight(widgetWidth);
|
||||
};
|
||||
onResize();
|
||||
@@ -194,9 +178,6 @@ const useCssVariableConfiguration = ({
|
||||
|
||||
// Define column count by using the sectionColumnCount
|
||||
useEffect(() => {
|
||||
root?.style.setProperty(
|
||||
"--gridstack-column-count",
|
||||
board.columnCount.toString(),
|
||||
);
|
||||
root?.style.setProperty("--gridstack-column-count", board.columnCount.toString());
|
||||
}, [board.columnCount, root]);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import type { FocusEventHandler } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Combobox,
|
||||
Group,
|
||||
Image,
|
||||
InputBase,
|
||||
Skeleton,
|
||||
Text,
|
||||
useCombobox,
|
||||
} from "@mantine/core";
|
||||
import { Combobox, Group, Image, InputBase, Skeleton, Text, useCombobox } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
@@ -21,13 +13,7 @@ interface IconPickerProps {
|
||||
onBlur?: FocusEventHandler;
|
||||
}
|
||||
|
||||
export const IconPicker = ({
|
||||
initialValue,
|
||||
onChange,
|
||||
error,
|
||||
onFocus,
|
||||
onBlur,
|
||||
}: IconPickerProps) => {
|
||||
export const IconPicker = ({ initialValue, onChange, error, onFocus, onBlur }: IconPickerProps) => {
|
||||
const [value, setValue] = useState<string>(initialValue ?? "");
|
||||
const [search, setSearch] = useState(initialValue ?? "");
|
||||
|
||||
@@ -43,10 +29,7 @@ export const IconPicker = ({
|
||||
|
||||
const notNullableData = data?.icons ?? [];
|
||||
|
||||
const totalOptions = notNullableData.reduce(
|
||||
(acc, group) => acc + group.icons.length,
|
||||
0,
|
||||
);
|
||||
const totalOptions = notNullableData.reduce((acc, group) => acc + group.icons.length, 0);
|
||||
|
||||
const groups = notNullableData.map((group) => {
|
||||
const options = group.icons.map((item) => (
|
||||
@@ -104,9 +87,7 @@ export const IconPicker = ({
|
||||
|
||||
<Combobox.Dropdown>
|
||||
<Combobox.Header>
|
||||
<Text c="dimmed">
|
||||
{t("iconPicker.header", { countIcons: data?.countIcons })}
|
||||
</Text>
|
||||
<Text c="dimmed">{t("iconPicker.header", { countIcons: data?.countIcons })}</Text>
|
||||
</Combobox.Header>
|
||||
<Combobox.Options mah={350} style={{ overflowY: "auto" }}>
|
||||
{totalOptions > 0 ? (
|
||||
@@ -117,11 +98,7 @@ export const IconPicker = ({
|
||||
Array(15)
|
||||
.fill(0)
|
||||
.map((_, index: number) => (
|
||||
<Combobox.Option
|
||||
value={`skeleton-${index}`}
|
||||
key={index}
|
||||
disabled
|
||||
>
|
||||
<Combobox.Option value={`skeleton-${index}`} key={index} disabled>
|
||||
<Skeleton height={25} visible />
|
||||
</Combobox.Option>
|
||||
))
|
||||
|
||||
@@ -51,11 +51,7 @@ export const LanguageCombobox = () => {
|
||||
<Combobox.Options>
|
||||
{supportedLanguages.map((languageKey) => (
|
||||
<Combobox.Option value={languageKey} key={languageKey}>
|
||||
<OptionItem
|
||||
currentLocale={currentLocale}
|
||||
localeKey={languageKey}
|
||||
showCheck
|
||||
/>
|
||||
<OptionItem currentLocale={currentLocale} localeKey={languageKey} showCheck />
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
@@ -76,9 +72,7 @@ const OptionItem = ({
|
||||
return (
|
||||
<Group wrap="nowrap" justify="space-between">
|
||||
<Group wrap="nowrap">
|
||||
<span
|
||||
className={`fi fi-${localeAttributes[localeKey].flagIcon} ${classes.flagIcon}`}
|
||||
></span>
|
||||
<span className={`fi fi-${localeAttributes[localeKey].flagIcon} ${classes.flagIcon}`}></span>
|
||||
<Group wrap="nowrap" gap="xs">
|
||||
<Text>{localeAttributes[localeKey].name}</Text>
|
||||
<Text size="xs" c="dimmed" inherit>
|
||||
@@ -86,9 +80,7 @@ const OptionItem = ({
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
{showCheck && localeKey === currentLocale && (
|
||||
<IconCheck color="currentColor" size={16} />
|
||||
)}
|
||||
{showCheck && localeKey === currentLocale && <IconCheck color="currentColor" size={16} />}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,10 +4,7 @@ import type { AppShellProps } from "@mantine/core";
|
||||
import { useOptionalBoard } from "~/app/[locale]/boards/(content)/_context";
|
||||
|
||||
const supportedVideoFormats = ["mp4", "webm", "ogg"];
|
||||
const isVideo = (url: string) =>
|
||||
supportedVideoFormats.some((format) =>
|
||||
url.toLowerCase().endsWith(`.${format}`),
|
||||
);
|
||||
const isVideo = (url: string) => supportedVideoFormats.some((format) => url.toLowerCase().endsWith(`.${format}`));
|
||||
|
||||
export const useOptionalBackgroundProps = (): Partial<AppShellProps> => {
|
||||
const board = useOptionalBoard();
|
||||
|
||||
@@ -26,13 +26,7 @@ export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => {
|
||||
</UnstyledButton>
|
||||
</Group>
|
||||
<DesktopSearchInput />
|
||||
<Group
|
||||
h="100%"
|
||||
align="center"
|
||||
justify="end"
|
||||
style={{ flex: 1 }}
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group h="100%" align="center" justify="end" style={{ flex: 1 }} wrap="nowrap">
|
||||
{actions}
|
||||
<MobileSearchButton />
|
||||
<UserButton />
|
||||
|
||||
@@ -9,12 +9,7 @@ export const navigationCollapsedAtom = atom(true);
|
||||
export const ClientBurger = () => {
|
||||
const [collapsed, setCollapsed] = useAtom(navigationCollapsedAtom);
|
||||
|
||||
const toggle = useCallback(
|
||||
() => setCollapsed((collapsed) => !collapsed),
|
||||
[setCollapsed],
|
||||
);
|
||||
const toggle = useCallback(() => setCollapsed((collapsed) => !collapsed), [setCollapsed]);
|
||||
|
||||
return (
|
||||
<Burger opened={!collapsed} onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||
);
|
||||
return <Burger opened={!collapsed} onClick={toggle} hiddenFrom="sm" size="sm" />;
|
||||
};
|
||||
|
||||
@@ -23,24 +23,22 @@ const headerButtonActionIconProps: ActionIconProps = {
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const HeaderButton = forwardRef<HTMLButtonElement, HeaderButtonProps>(
|
||||
(props, ref) => {
|
||||
if ("href" in props) {
|
||||
return (
|
||||
<ActionIcon
|
||||
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||
component={Link}
|
||||
{...props}
|
||||
{...headerButtonActionIconProps}
|
||||
>
|
||||
{props.children}
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
||||
export const HeaderButton = forwardRef<HTMLButtonElement, HeaderButtonProps>((props, ref) => {
|
||||
if ("href" in props) {
|
||||
return (
|
||||
<ActionIcon ref={ref} {...props} {...headerButtonActionIconProps}>
|
||||
<ActionIcon
|
||||
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||
component={Link}
|
||||
{...props}
|
||||
{...headerButtonActionIconProps}
|
||||
>
|
||||
{props.children}
|
||||
</ActionIcon>
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ActionIcon ref={ref} {...props} {...headerButtonActionIconProps}>
|
||||
{props.children}
|
||||
</ActionIcon>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -28,10 +28,7 @@ interface CommonLogoWithTitleProps {
|
||||
hideTitleOnMobile?: boolean;
|
||||
}
|
||||
|
||||
export const BoardLogoWithTitle = ({
|
||||
size,
|
||||
hideTitleOnMobile,
|
||||
}: CommonLogoWithTitleProps) => {
|
||||
export const BoardLogoWithTitle = ({ size, hideTitleOnMobile }: CommonLogoWithTitleProps) => {
|
||||
const board = useRequiredBoard();
|
||||
const imageOptions = useImageOptions();
|
||||
return (
|
||||
|
||||
@@ -14,16 +14,12 @@ const imageOptions = {
|
||||
shouldUseNextImage: true,
|
||||
};
|
||||
|
||||
export const HomarrLogo = ({ size }: LogoProps) => (
|
||||
<Logo size={size} {...imageOptions} />
|
||||
);
|
||||
export const HomarrLogo = ({ size }: LogoProps) => <Logo size={size} {...imageOptions} />;
|
||||
|
||||
interface CommonLogoWithTitleProps {
|
||||
size: LogoWithTitleProps["size"];
|
||||
}
|
||||
|
||||
export const HomarrLogoWithTitle = ({ size }: CommonLogoWithTitleProps) => {
|
||||
return (
|
||||
<LogoWithTitle size={size} title={homarrPageTitle} image={imageOptions} />
|
||||
);
|
||||
return <LogoWithTitle size={size} title={homarrPageTitle} image={imageOptions} />;
|
||||
};
|
||||
|
||||
@@ -9,12 +9,7 @@ interface LogoProps {
|
||||
shouldUseNextImage?: boolean;
|
||||
}
|
||||
|
||||
export const Logo = ({
|
||||
size = 60,
|
||||
shouldUseNextImage = false,
|
||||
src,
|
||||
alt,
|
||||
}: LogoProps) =>
|
||||
export const Logo = ({ size = 60, shouldUseNextImage = false, src, alt }: LogoProps) =>
|
||||
shouldUseNextImage ? (
|
||||
<Image src={src} alt={alt} width={size} height={size} />
|
||||
) : (
|
||||
@@ -36,22 +31,13 @@ export interface LogoWithTitleProps {
|
||||
hideTitleOnMobile?: boolean;
|
||||
}
|
||||
|
||||
export const LogoWithTitle = ({
|
||||
size,
|
||||
title,
|
||||
image,
|
||||
hideTitleOnMobile,
|
||||
}: LogoWithTitleProps) => {
|
||||
export const LogoWithTitle = ({ size, title, image, hideTitleOnMobile }: LogoWithTitleProps) => {
|
||||
const { logoSize, titleOrder } = logoWithTitleSizes[size];
|
||||
|
||||
return (
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Logo {...image} size={logoSize} />
|
||||
<Title
|
||||
order={titleOrder}
|
||||
visibleFrom={hideTitleOnMobile ? "sm" : undefined}
|
||||
textWrap="nowrap"
|
||||
>
|
||||
<Title order={titleOrder} visibleFrom={hideTitleOnMobile ? "sm" : undefined} textWrap="nowrap">
|
||||
{title}
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
@@ -6,11 +6,7 @@ import { usePathname } from "next/navigation";
|
||||
import { NavLink } from "@mantine/core";
|
||||
|
||||
export const CommonNavLink = (props: ClientNavigationLink) =>
|
||||
"href" in props ? (
|
||||
<NavLinkHref {...props} />
|
||||
) : (
|
||||
<NavLinkWithItems {...props} />
|
||||
);
|
||||
"href" in props ? <NavLinkHref {...props} /> : <NavLinkWithItems {...props} />;
|
||||
|
||||
const NavLinkHref = (props: NavigationLinkHref) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -11,11 +11,7 @@ interface MainNavigationProps {
|
||||
links: NavigationLink[];
|
||||
}
|
||||
|
||||
export const MainNavigation = ({
|
||||
headerSection,
|
||||
footerSection,
|
||||
links,
|
||||
}: MainNavigationProps) => {
|
||||
export const MainNavigation = ({ headerSection, footerSection, links }: MainNavigationProps) => {
|
||||
return (
|
||||
<AppShellNavbar p="md">
|
||||
{headerSection && <AppShellSection>{headerSection}</AppShellSection>}
|
||||
|
||||
@@ -12,50 +12,41 @@ interface InnerProps {
|
||||
onSuccess: ({ name }: { name: string }) => Promise<void>;
|
||||
}
|
||||
|
||||
export const AddBoardModal = createModal<InnerProps>(
|
||||
({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const form = useZodForm(
|
||||
z.object({
|
||||
name: boardSchemas.byName.shape.name.refine(
|
||||
(value) => !innerProps.boardNames.includes(value),
|
||||
{
|
||||
params: createCustomErrorParams("boardAlreadyExists"),
|
||||
},
|
||||
),
|
||||
export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const form = useZodForm(
|
||||
z.object({
|
||||
name: boardSchemas.byName.shape.name.refine((value) => !innerProps.boardNames.includes(value), {
|
||||
params: createCustomErrorParams("boardAlreadyExists"),
|
||||
}),
|
||||
{
|
||||
initialValues: {
|
||||
name: "",
|
||||
},
|
||||
}),
|
||||
{
|
||||
initialValues: {
|
||||
name: "",
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
void innerProps.onSuccess(values);
|
||||
actions.closeModal();
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label={t("board.field.name.label")}
|
||||
data-autofocus
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<Group justify="right">
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button disabled={!form.isValid()} type="submit" color="teal">
|
||||
{t("common.action.create")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
},
|
||||
).withOptions({
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
void innerProps.onSuccess(values);
|
||||
actions.closeModal();
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput label={t("board.field.name.label")} data-autofocus {...form.getInputProps("name")} />
|
||||
<Group justify="right">
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button disabled={!form.isValid()} type="submit" color="teal">
|
||||
{t("common.action.create")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle: (t) => t("management.page.board.action.new.label"),
|
||||
});
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { Fragment } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardSection,
|
||||
Divider,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { Card, CardSection, Divider, Group, Stack, Text, Title } from "@mantine/core";
|
||||
|
||||
import { getI18n } from "@homarr/translation/server";
|
||||
|
||||
@@ -49,11 +41,7 @@ interface DangerZoneItemProps {
|
||||
action: React.ReactNode;
|
||||
}
|
||||
|
||||
export const DangerZoneItem = ({
|
||||
label,
|
||||
description,
|
||||
action,
|
||||
}: DangerZoneItemProps) => {
|
||||
export const DangerZoneItem = ({ label, description, action }: DangerZoneItemProps) => {
|
||||
return (
|
||||
<Group justify="space-between" px="md">
|
||||
<Stack gap={0}>
|
||||
|
||||
@@ -4,13 +4,7 @@ import type { ReactNode } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Center,
|
||||
Menu,
|
||||
Stack,
|
||||
Text,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core";
|
||||
import { useTimeout } from "@mantine/hooks";
|
||||
import {
|
||||
IconCheck,
|
||||
@@ -41,8 +35,7 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
|
||||
|
||||
const ColorSchemeIcon = colorScheme === "dark" ? IconSun : IconMoon;
|
||||
|
||||
const colorSchemeText =
|
||||
colorScheme === "dark" ? t("switchToLightMode") : t("switchToDarkMode");
|
||||
const colorSchemeText = colorScheme === "dark" ? t("switchToLightMode") : t("switchToDarkMode");
|
||||
|
||||
const session = useSession();
|
||||
const router = useRouter();
|
||||
@@ -63,17 +56,10 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
|
||||
return (
|
||||
<Menu width={300} withArrow withinPortal>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
onClick={toggleColorScheme}
|
||||
leftSection={<ColorSchemeIcon size="1rem" />}
|
||||
>
|
||||
<Menu.Item onClick={toggleColorScheme} leftSection={<ColorSchemeIcon size="1rem" />}>
|
||||
{colorSchemeText}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
component={Link}
|
||||
href="/boards"
|
||||
leftSection={<IconHome size="1rem" />}
|
||||
>
|
||||
<Menu.Item component={Link} href="/boards" leftSection={<IconHome size="1rem" />}>
|
||||
{t("homeBoard")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
@@ -92,29 +78,18 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
|
||||
{t("preferences")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item
|
||||
component={Link}
|
||||
href="/manage"
|
||||
leftSection={<IconTool size="1rem" />}
|
||||
>
|
||||
<Menu.Item component={Link} href="/manage" leftSection={<IconTool size="1rem" />}>
|
||||
{t("management")}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
<Menu.Divider />
|
||||
{session.status === "authenticated" ? (
|
||||
<Menu.Item
|
||||
onClick={handleSignout}
|
||||
leftSection={<IconLogout size="1rem" />}
|
||||
color="red"
|
||||
>
|
||||
<Menu.Item onClick={handleSignout} leftSection={<IconLogout size="1rem" />} color="red">
|
||||
{t("logout")}
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<Menu.Item
|
||||
onClick={() => router.push("/auth/login")}
|
||||
leftSection={<IconLogin size="1rem" />}
|
||||
>
|
||||
<Menu.Item onClick={() => router.push("/auth/login")} leftSection={<IconLogin size="1rem" />}>
|
||||
{t("login")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
@@ -124,30 +99,28 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const LogoutModal = createModal<{ onTimeout: () => void }>(
|
||||
({ actions, innerProps }) => {
|
||||
const t = useScopedI18n("common.userAvatar.menu");
|
||||
const { start } = useTimeout(() => {
|
||||
actions.closeModal();
|
||||
innerProps.onTimeout();
|
||||
}, 1500);
|
||||
const LogoutModal = createModal<{ onTimeout: () => void }>(({ actions, innerProps }) => {
|
||||
const t = useScopedI18n("common.userAvatar.menu");
|
||||
const { start } = useTimeout(() => {
|
||||
actions.closeModal();
|
||||
innerProps.onTimeout();
|
||||
}, 1500);
|
||||
|
||||
useEffect(() => {
|
||||
start();
|
||||
}, [start]);
|
||||
useEffect(() => {
|
||||
start();
|
||||
}, [start]);
|
||||
|
||||
return (
|
||||
<Center h={200 - 2 * 16}>
|
||||
<Stack align="center" c="green">
|
||||
<IconCheck size={50} />
|
||||
<Text ta="center" fw="bold">
|
||||
{t("loggedOut")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
},
|
||||
).withOptions({
|
||||
return (
|
||||
<Center h={200 - 2 * 16}>
|
||||
<Stack align="center" c="green">
|
||||
<IconCheck size={50} />
|
||||
<Text ta="center" fw="bold">
|
||||
{t("loggedOut")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}).withOptions({
|
||||
centered: true,
|
||||
withCloseButton: false,
|
||||
transitionProps: {
|
||||
|
||||
@@ -17,17 +17,7 @@ export const UserAvatar = async ({ size }: UserAvatarProps) => {
|
||||
|
||||
if (!currentSession?.user) return <Avatar {...commonProps} />;
|
||||
if (currentSession.user.image)
|
||||
return (
|
||||
<Avatar
|
||||
{...commonProps}
|
||||
src={currentSession.user.image}
|
||||
alt={currentSession.user.name!}
|
||||
/>
|
||||
);
|
||||
return <Avatar {...commonProps} src={currentSession.user.image} alt={currentSession.user.name!} />;
|
||||
|
||||
return (
|
||||
<Avatar {...commonProps}>
|
||||
{currentSession.user.name!.substring(0, 2).toUpperCase()}
|
||||
</Avatar>
|
||||
);
|
||||
return <Avatar {...commonProps}>{currentSession.user.name!.substring(0, 2).toUpperCase()}</Avatar>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user