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:
@@ -18,9 +18,7 @@ export const updateBoardName = (name: string | null) => {
|
||||
boardName = name;
|
||||
};
|
||||
|
||||
type UpdateCallback = (
|
||||
prev: RouterOutputs["board"]["getHomeBoard"],
|
||||
) => RouterOutputs["board"]["getHomeBoard"];
|
||||
type UpdateCallback = (prev: RouterOutputs["board"]["getHomeBoard"]) => RouterOutputs["board"]["getHomeBoard"];
|
||||
|
||||
export const useUpdateBoard = () => {
|
||||
const utils = clientApi.useUtils();
|
||||
@@ -46,9 +44,7 @@ export const ClientBoard = () => {
|
||||
const board = useRequiredBoard();
|
||||
const isReady = useIsBoardReady();
|
||||
|
||||
const sortedSections = board.sections.sort(
|
||||
(sectionA, sectionB) => sectionA.position - sectionB.position,
|
||||
);
|
||||
const sortedSections = board.sections.sort((sectionA, sectionB) => sectionA.position - sectionB.position);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -61,24 +57,12 @@ export const ClientBoard = () => {
|
||||
loaderProps={{ size: "lg" }}
|
||||
h={fullHeightWithoutHeaderAndFooter}
|
||||
/>
|
||||
<Stack
|
||||
ref={ref}
|
||||
h="100%"
|
||||
style={{ visibility: isReady ? "visible" : "hidden" }}
|
||||
>
|
||||
<Stack ref={ref} h="100%" style={{ visibility: isReady ? "visible" : "hidden" }}>
|
||||
{sortedSections.map((section) =>
|
||||
section.kind === "empty" ? (
|
||||
<BoardEmptySection
|
||||
key={section.id}
|
||||
section={section}
|
||||
mainRef={ref}
|
||||
/>
|
||||
<BoardEmptySection key={section.id} section={section} mainRef={ref} />
|
||||
) : (
|
||||
<BoardCategorySection
|
||||
key={section.id}
|
||||
section={section}
|
||||
mainRef={ref}
|
||||
/>
|
||||
<BoardCategorySection key={section.id} section={section} mainRef={ref} />
|
||||
),
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
@@ -52,18 +46,12 @@ export const BoardProvider = ({
|
||||
}, [pathname, utils, initialBoard.name]);
|
||||
|
||||
useEffect(() => {
|
||||
setReadySections((previous) =>
|
||||
previous.filter((id) =>
|
||||
data.sections.some((section) => section.id === id),
|
||||
),
|
||||
);
|
||||
setReadySections((previous) => previous.filter((id) => data.sections.some((section) => section.id === id)));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data.sections.length, setReadySections]);
|
||||
|
||||
const markAsReady = useCallback((id: string) => {
|
||||
setReadySections((previous) =>
|
||||
previous.includes(id) ? previous : [...previous, id],
|
||||
);
|
||||
setReadySections((previous) => (previous.includes(id) ? previous : [...previous, id]));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,9 +18,7 @@ interface Props<TParams extends Params> {
|
||||
getInitialBoardAsync: (params: TParams) => Promise<Board>;
|
||||
}
|
||||
|
||||
export const createBoardContentPage = <
|
||||
TParams extends Record<string, unknown>,
|
||||
>({
|
||||
export const createBoardContentPage = <TParams extends Record<string, unknown>>({
|
||||
getInitialBoardAsync: getInitialBoard,
|
||||
}: Props<TParams>) => {
|
||||
return {
|
||||
@@ -32,21 +30,13 @@ export const createBoardContentPage = <
|
||||
page: () => {
|
||||
return <ClientBoard />;
|
||||
},
|
||||
generateMetadataAsync: async ({
|
||||
params,
|
||||
}: {
|
||||
params: TParams;
|
||||
}): Promise<Metadata> => {
|
||||
generateMetadataAsync: async ({ params }: { params: TParams }): Promise<Metadata> => {
|
||||
try {
|
||||
const board = await getInitialBoard(params);
|
||||
const t = await getI18n();
|
||||
|
||||
return {
|
||||
title:
|
||||
board.metaTitle ??
|
||||
createMetaTitle(
|
||||
t("board.content.metaTitle", { boardName: board.name }),
|
||||
),
|
||||
title: board.metaTitle ?? createMetaTitle(t("board.content.metaTitle", { boardName: board.name })),
|
||||
icons: {
|
||||
icon: board.faviconImageUrl ? board.faviconImageUrl : undefined,
|
||||
},
|
||||
|
||||
@@ -16,10 +16,7 @@ import { useAtom, useAtomValue } from "jotai";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import {
|
||||
showErrorNotification,
|
||||
showSuccessNotification,
|
||||
} from "@homarr/notifications";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
||||
@@ -54,8 +51,7 @@ export const BoardContentHeaderActions = () => {
|
||||
};
|
||||
|
||||
const AddMenu = () => {
|
||||
const { openModal: openCategoryEditModal } =
|
||||
useModalAction(CategoryEditModal);
|
||||
const { openModal: openCategoryEditModal } = useModalAction(CategoryEditModal);
|
||||
const { openModal: openItemSelectModal } = useModalAction(ItemSelectModal);
|
||||
const { addCategoryToEnd } = useCategoryActions();
|
||||
const t = useI18n();
|
||||
@@ -95,22 +91,14 @@ const AddMenu = () => {
|
||||
</HeaderButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown style={{ transform: "translate(-3px, 0)" }}>
|
||||
<Menu.Item
|
||||
leftSection={<IconBox size={20} />}
|
||||
onClick={handleSelectItem}
|
||||
>
|
||||
<Menu.Item leftSection={<IconBox size={20} />} onClick={handleSelectItem}>
|
||||
{t("item.action.create")}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<IconPackageImport size={20} />}>
|
||||
{t("item.action.import")}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<IconPackageImport size={20} />}>{t("item.action.import")}</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
<Menu.Item
|
||||
leftSection={<IconBoxAlignTop size={20} />}
|
||||
onClick={handleAddCategory}
|
||||
>
|
||||
<Menu.Item leftSection={<IconBoxAlignTop size={20} />} onClick={handleAddCategory}>
|
||||
{t("section.category.action.create")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
@@ -123,24 +111,23 @@ const EditModeMenu = () => {
|
||||
const board = useRequiredBoard();
|
||||
const utils = clientApi.useUtils();
|
||||
const t = useScopedI18n("board.action.edit");
|
||||
const { mutate: saveBoard, isPending } =
|
||||
clientApi.board.saveBoard.useMutation({
|
||||
onSuccess() {
|
||||
showSuccessNotification({
|
||||
title: t("notification.success.title"),
|
||||
message: t("notification.success.message"),
|
||||
});
|
||||
void utils.board.getBoardByName.invalidate({ name: board.name });
|
||||
void revalidatePathActionAsync(`/boards/${board.name}`);
|
||||
setEditMode(false);
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: t("notification.error.title"),
|
||||
message: t("notification.error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
const { mutate: saveBoard, isPending } = clientApi.board.saveBoard.useMutation({
|
||||
onSuccess() {
|
||||
showSuccessNotification({
|
||||
title: t("notification.success.title"),
|
||||
message: t("notification.success.message"),
|
||||
});
|
||||
void utils.board.getBoardByName.invalidate({ name: board.name });
|
||||
void revalidatePathActionAsync(`/boards/${board.name}`);
|
||||
setEditMode(false);
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: t("notification.error.title"),
|
||||
message: t("notification.error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
if (isEditMode) return saveBoard(board);
|
||||
@@ -149,11 +136,7 @@ const EditModeMenu = () => {
|
||||
|
||||
return (
|
||||
<HeaderButton onClick={toggle} loading={isPending}>
|
||||
{isEditMode ? (
|
||||
<IconPencilOff stroke={1.5} />
|
||||
) : (
|
||||
<IconPencil stroke={1.5} />
|
||||
)}
|
||||
{isEditMode ? <IconPencilOff stroke={1.5} /> : <IconPencil stroke={1.5} />}
|
||||
</HeaderButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,9 +22,7 @@ export const BoardMantineProvider = ({ children }: PropsWithChildren) => {
|
||||
};
|
||||
|
||||
export const generateColors = (hex: string) => {
|
||||
const lightnessForColors = [
|
||||
-0.25, -0.2, -0.15, -0.1, -0.05, 0, 0.05, 0.1, 0.15, 0.2,
|
||||
] as const;
|
||||
const lightnessForColors = [-0.25, -0.2, -0.15, -0.1, -0.05, 0, 0.05, 0.1, 0.15, 0.2] as const;
|
||||
const rgbaColors = lightnessForColors.map((lightness) => {
|
||||
if (lightness < 0) {
|
||||
return lighten(hex, -lightness);
|
||||
|
||||
@@ -44,11 +44,7 @@ export const AccessSettingsContent = ({ board, initialPermissions }: Props) => {
|
||||
<Tabs.List grow>
|
||||
<TabItem value="user" count={counts.user} icon={IconUser} />
|
||||
<TabItem value="group" count={counts.group} icon={IconUsersGroup} />
|
||||
<TabItem
|
||||
value="inherited"
|
||||
count={initialPermissions.inherited.length}
|
||||
icon={IconUserDown}
|
||||
/>
|
||||
<TabItem value="inherited" count={initialPermissions.inherited.length} icon={IconUserDown} />
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="user">
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
import { useCallback } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import type { SelectProps } from "@mantine/core";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Group,
|
||||
Select,
|
||||
TableTd,
|
||||
TableTr,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconCheck,
|
||||
IconEye,
|
||||
IconPencil,
|
||||
IconSettings,
|
||||
} from "@tabler/icons-react";
|
||||
import { Button, Flex, Group, Select, TableTd, TableTr, Text } from "@mantine/core";
|
||||
import { IconCheck, IconEye, IconPencil, IconSettings } from "@tabler/icons-react";
|
||||
|
||||
import type { BoardPermission } from "@homarr/definitions";
|
||||
import { boardPermissions } from "@homarr/definitions";
|
||||
@@ -38,12 +25,7 @@ interface BoardAccessSelectRowProps {
|
||||
onCountChange: OnCountChange;
|
||||
}
|
||||
|
||||
export const BoardAccessSelectRow = ({
|
||||
itemContent,
|
||||
permission,
|
||||
index,
|
||||
onCountChange,
|
||||
}: BoardAccessSelectRowProps) => {
|
||||
export const BoardAccessSelectRow = ({ itemContent, permission, index, onCountChange }: BoardAccessSelectRowProps) => {
|
||||
const tRoot = useI18n();
|
||||
const tPermissions = useScopedI18n("board.setting.section.access.permission");
|
||||
const form = useFormContext();
|
||||
@@ -61,11 +43,7 @@ export const BoardAccessSelectRow = ({
|
||||
<TableTr>
|
||||
<TableTd w={{ sm: 128, lg: 256 }}>{itemContent}</TableTd>
|
||||
<TableTd>
|
||||
<Flex
|
||||
direction={{ base: "column", xs: "row" }}
|
||||
align={{ base: "end", xs: "center" }}
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Flex direction={{ base: "column", xs: "row" }} align={{ base: "end", xs: "center" }} wrap="nowrap">
|
||||
<Select
|
||||
allowDeselect={false}
|
||||
flex="1"
|
||||
@@ -93,10 +71,7 @@ interface BoardAccessDisplayRowProps {
|
||||
permission: BoardPermission | "board-full";
|
||||
}
|
||||
|
||||
export const BoardAccessDisplayRow = ({
|
||||
itemContent,
|
||||
permission,
|
||||
}: BoardAccessDisplayRowProps) => {
|
||||
export const BoardAccessDisplayRow = ({ itemContent, permission }: BoardAccessDisplayRowProps) => {
|
||||
const tPermissions = useScopedI18n("board.setting.section.access.permission");
|
||||
const Icon = icons[permission];
|
||||
|
||||
@@ -106,10 +81,7 @@ export const BoardAccessDisplayRow = ({
|
||||
<TableTd>
|
||||
<Group gap={0}>
|
||||
<Flex w={34} h={34} align="center" justify="center">
|
||||
<Icon
|
||||
size="1rem"
|
||||
color="var(--input-section-color, var(--mantine-color-dimmed))"
|
||||
/>
|
||||
<Icon size="1rem" color="var(--input-section-color, var(--mantine-color-dimmed))" />
|
||||
</Flex>
|
||||
<Text size="sm">{tPermissions(`item.${permission}.label`)}</Text>
|
||||
</Group>
|
||||
@@ -131,9 +103,7 @@ const RenderOption: SelectProps["renderOption"] = ({ option, checked }) => {
|
||||
<Group flex="1" gap="xs" wrap="nowrap">
|
||||
<Icon {...iconProps} />
|
||||
{option.label}
|
||||
{checked && (
|
||||
<IconCheck style={{ marginInlineStart: "auto" }} {...iconProps} />
|
||||
)}
|
||||
{checked && <IconCheck style={{ marginInlineStart: "auto" }} {...iconProps} />}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ export interface BoardAccessFormType {
|
||||
}[];
|
||||
}
|
||||
|
||||
export const [FormProvider, useFormContext, useForm] =
|
||||
createFormContext<BoardAccessFormType>();
|
||||
export const [FormProvider, useFormContext, useForm] = createFormContext<BoardAccessFormType>();
|
||||
|
||||
export type OnCountChange = (callback: (prev: number) => number) => void;
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Anchor,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
} from "@mantine/core";
|
||||
import { Anchor, Button, Group, Stack, Table, TableTbody, TableTh, TableThead, TableTr } from "@mantine/core";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
@@ -24,30 +14,21 @@ import { FormProvider, useForm } from "./form";
|
||||
import { GroupSelectModal } from "./group-select-modal";
|
||||
import type { FormProps } from "./user-access";
|
||||
|
||||
export const GroupsForm = ({
|
||||
board,
|
||||
initialPermissions,
|
||||
onCountChange,
|
||||
}: FormProps) => {
|
||||
const { mutate, isPending } =
|
||||
clientApi.board.saveGroupBoardPermissions.useMutation();
|
||||
export const GroupsForm = ({ board, initialPermissions, onCountChange }: FormProps) => {
|
||||
const { mutate, isPending } = clientApi.board.saveGroupBoardPermissions.useMutation();
|
||||
const utils = clientApi.useUtils();
|
||||
const [groups, setGroups] = useState<Map<string, Group>>(
|
||||
new Map(
|
||||
initialPermissions.groupPermissions.map(({ group }) => [group.id, group]),
|
||||
),
|
||||
new Map(initialPermissions.groupPermissions.map(({ group }) => [group.id, group])),
|
||||
);
|
||||
const { openModal } = useModalAction(GroupSelectModal);
|
||||
const t = useI18n();
|
||||
const tPermissions = useScopedI18n("board.setting.section.access.permission");
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
items: initialPermissions.groupPermissions.map(
|
||||
({ group, permission }) => ({
|
||||
itemId: group.id,
|
||||
permission,
|
||||
}),
|
||||
),
|
||||
items: initialPermissions.groupPermissions.map(({ group, permission }) => ({
|
||||
itemId: group.id,
|
||||
permission,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -92,9 +73,7 @@ export const GroupsForm = ({
|
||||
<Table>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ whiteSpace: "nowrap" }}>
|
||||
{tPermissions("field.group.label")}
|
||||
</TableTh>
|
||||
<TableTh style={{ whiteSpace: "nowrap" }}>{tPermissions("field.group.label")}</TableTh>
|
||||
<TableTh>{tPermissions("field.permission.label")}</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
@@ -102,9 +81,7 @@ export const GroupsForm = ({
|
||||
{form.values.items.map((row, index) => (
|
||||
<BoardAccessSelectRow
|
||||
key={row.itemId}
|
||||
itemContent={
|
||||
<GroupItemContent group={groups.get(row.itemId)!} />
|
||||
}
|
||||
itemContent={<GroupItemContent group={groups.get(row.itemId)!} />}
|
||||
permission={row.permission}
|
||||
index={index}
|
||||
onCountChange={onCountChange}
|
||||
@@ -114,11 +91,7 @@ export const GroupsForm = ({
|
||||
</Table>
|
||||
|
||||
<Group justify="space-between">
|
||||
<Button
|
||||
rightSection={<IconPlus size="1rem" />}
|
||||
variant="light"
|
||||
onClick={handleAddUser}
|
||||
>
|
||||
<Button rightSection={<IconPlus size="1rem" />} variant="light" onClick={handleAddUser}>
|
||||
{t("common.action.add")}
|
||||
</Button>
|
||||
<Button type="submit" loading={isPending} color="teal">
|
||||
@@ -133,16 +106,10 @@ export const GroupsForm = ({
|
||||
|
||||
export const GroupItemContent = ({ group }: { group: Group }) => {
|
||||
return (
|
||||
<Anchor
|
||||
component={Link}
|
||||
href={`/manage/users/groups/${group.id}`}
|
||||
size="sm"
|
||||
style={{ whiteSpace: "nowrap" }}
|
||||
>
|
||||
<Anchor component={Link} href={`/manage/users/groups/${group.id}`} size="sm" style={{ whiteSpace: "nowrap" }}>
|
||||
{group.name}
|
||||
</Anchor>
|
||||
);
|
||||
};
|
||||
|
||||
type Group =
|
||||
RouterOutputs["board"]["getBoardPermissions"]["groupPermissions"][0]["group"];
|
||||
type Group = RouterOutputs["board"]["getBoardPermissions"]["groupPermissions"][0]["group"];
|
||||
|
||||
@@ -16,59 +16,52 @@ interface GroupSelectFormType {
|
||||
groupId: string;
|
||||
}
|
||||
|
||||
export const GroupSelectModal = createModal<InnerProps>(
|
||||
({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const { data: groups, isPending } = clientApi.group.selectable.useQuery();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const form = useForm<GroupSelectFormType>();
|
||||
const handleSubmitAsync = async (values: GroupSelectFormType) => {
|
||||
const currentGroup = groups?.find((group) => group.id === values.groupId);
|
||||
if (!currentGroup) return;
|
||||
setLoading(true);
|
||||
await innerProps.onSelect({
|
||||
id: currentGroup.id,
|
||||
name: currentGroup.name,
|
||||
});
|
||||
export const GroupSelectModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const { data: groups, isPending } = clientApi.group.selectable.useQuery();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const form = useForm<GroupSelectFormType>();
|
||||
const handleSubmitAsync = async (values: GroupSelectFormType) => {
|
||||
const currentGroup = groups?.find((group) => group.id === values.groupId);
|
||||
if (!currentGroup) return;
|
||||
setLoading(true);
|
||||
await innerProps.onSelect({
|
||||
id: currentGroup.id,
|
||||
name: currentGroup.name,
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
actions.closeModal();
|
||||
};
|
||||
setLoading(false);
|
||||
actions.closeModal();
|
||||
};
|
||||
|
||||
const confirmLabel = innerProps.confirmLabel ?? t("common.action.add");
|
||||
const confirmLabel = innerProps.confirmLabel ?? t("common.action.add");
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}
|
||||
>
|
||||
<Stack>
|
||||
<Select
|
||||
{...form.getInputProps("groupId")}
|
||||
label={t("group.action.select.label")}
|
||||
clearable
|
||||
searchable
|
||||
leftSection={isPending ? <Loader size="xs" /> : undefined}
|
||||
nothingFoundMessage={t("group.action.select.notFound")}
|
||||
limit={5}
|
||||
data={groups
|
||||
?.filter(
|
||||
(group) => !innerProps.presentGroupIds.includes(group.id),
|
||||
)
|
||||
.map((group) => ({ value: group.id, label: group.name }))}
|
||||
/>
|
||||
<Group justify="end">
|
||||
<Button variant="default" onClick={actions.closeModal}>
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={loading}>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
},
|
||||
).withOptions({
|
||||
defaultTitle: (t) =>
|
||||
t("board.setting.section.access.permission.groupSelect.title"),
|
||||
return (
|
||||
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
|
||||
<Stack>
|
||||
<Select
|
||||
{...form.getInputProps("groupId")}
|
||||
label={t("group.action.select.label")}
|
||||
clearable
|
||||
searchable
|
||||
leftSection={isPending ? <Loader size="xs" /> : undefined}
|
||||
nothingFoundMessage={t("group.action.select.notFound")}
|
||||
limit={5}
|
||||
data={groups
|
||||
?.filter((group) => !innerProps.presentGroupIds.includes(group.id))
|
||||
.map((group) => ({ value: group.id, label: group.name }))}
|
||||
/>
|
||||
<Group justify="end">
|
||||
<Button variant="default" onClick={actions.closeModal}>
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={loading}>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle: (t) => t("board.setting.section.access.permission.groupSelect.title"),
|
||||
});
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
} from "@mantine/core";
|
||||
import { Stack, Table, TableTbody, TableTh, TableThead, TableTr } from "@mantine/core";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { getPermissionsWithChildren } from "@homarr/definitions";
|
||||
@@ -41,9 +34,7 @@ export const InheritTable = ({ initialPermissions }: InheritTableProps) => {
|
||||
const boardPermission =
|
||||
permission in mapPermissions
|
||||
? mapPermissions[permission as keyof typeof mapPermissions]
|
||||
: getPermissionsWithChildren([permission]).includes(
|
||||
"board-full-access",
|
||||
)
|
||||
: getPermissionsWithChildren([permission]).includes("board-full-access")
|
||||
? "board-full"
|
||||
: null;
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Anchor,
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
} from "@mantine/core";
|
||||
import { Anchor, Box, Button, Group, Stack, Table, TableTbody, TableTh, TableThead, TableTr } from "@mantine/core";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
@@ -21,10 +10,7 @@ import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
import { UserAvatar } from "@homarr/ui";
|
||||
|
||||
import type { Board } from "../../../_types";
|
||||
import {
|
||||
BoardAccessDisplayRow,
|
||||
BoardAccessSelectRow,
|
||||
} from "./board-access-table-rows";
|
||||
import { BoardAccessDisplayRow, BoardAccessSelectRow } from "./board-access-table-rows";
|
||||
import type { BoardAccessFormType, OnCountChange } from "./form";
|
||||
import { FormProvider, useForm } from "./form";
|
||||
import { UserSelectModal } from "./user-select-modal";
|
||||
@@ -35,18 +21,11 @@ export interface FormProps {
|
||||
onCountChange: OnCountChange;
|
||||
}
|
||||
|
||||
export const UsersForm = ({
|
||||
board,
|
||||
initialPermissions,
|
||||
onCountChange,
|
||||
}: FormProps) => {
|
||||
const { mutate, isPending } =
|
||||
clientApi.board.saveUserBoardPermissions.useMutation();
|
||||
export const UsersForm = ({ board, initialPermissions, onCountChange }: FormProps) => {
|
||||
const { mutate, isPending } = clientApi.board.saveUserBoardPermissions.useMutation();
|
||||
const utils = clientApi.useUtils();
|
||||
const [users, setUsers] = useState<Map<string, User>>(
|
||||
new Map(
|
||||
initialPermissions.userPermissions.map(({ user }) => [user.id, user]),
|
||||
),
|
||||
new Map(initialPermissions.userPermissions.map(({ user }) => [user.id, user])),
|
||||
);
|
||||
const { openModal } = useModalAction(UserSelectModal);
|
||||
const t = useI18n();
|
||||
@@ -81,9 +60,7 @@ export const UsersForm = ({
|
||||
const presentUserIds = form.values.items.map(({ itemId: id }) => id);
|
||||
|
||||
openModal({
|
||||
presentUserIds: board.creatorId
|
||||
? presentUserIds.concat(board.creatorId)
|
||||
: presentUserIds,
|
||||
presentUserIds: board.creatorId ? presentUserIds.concat(board.creatorId) : presentUserIds,
|
||||
onSelect: (user) => {
|
||||
setUsers((prev) => new Map(prev).set(user.id, user));
|
||||
form.setFieldValue("items", [
|
||||
@@ -111,17 +88,12 @@ export const UsersForm = ({
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{board.creator && (
|
||||
<BoardAccessDisplayRow
|
||||
itemContent={<UserItemContent user={board.creator} />}
|
||||
permission="board-full"
|
||||
/>
|
||||
<BoardAccessDisplayRow itemContent={<UserItemContent user={board.creator} />} permission="board-full" />
|
||||
)}
|
||||
{form.values.items.map((row, index) => (
|
||||
<BoardAccessSelectRow
|
||||
key={row.itemId}
|
||||
itemContent={
|
||||
<UserItemContent user={users.get(row.itemId)!} />
|
||||
}
|
||||
itemContent={<UserItemContent user={users.get(row.itemId)!} />}
|
||||
permission={row.permission}
|
||||
index={index}
|
||||
onCountChange={onCountChange}
|
||||
@@ -131,11 +103,7 @@ export const UsersForm = ({
|
||||
</Table>
|
||||
|
||||
<Group justify="space-between">
|
||||
<Button
|
||||
rightSection={<IconPlus size="1rem" />}
|
||||
variant="light"
|
||||
onClick={handleAddUser}
|
||||
>
|
||||
<Button rightSection={<IconPlus size="1rem" />} variant="light" onClick={handleAddUser}>
|
||||
{t("common.action.add")}
|
||||
</Button>
|
||||
<Button type="submit" loading={isPending} color="teal">
|
||||
@@ -154,12 +122,7 @@ const UserItemContent = ({ user }: { user: User }) => {
|
||||
<Box visibleFrom="xs">
|
||||
<UserAvatar user={user} size="sm" />
|
||||
</Box>
|
||||
<Anchor
|
||||
component={Link}
|
||||
href={`/manage/users/${user.id}`}
|
||||
size="sm"
|
||||
style={{ whiteSpace: "nowrap" }}
|
||||
>
|
||||
<Anchor component={Link} href={`/manage/users/${user.id}`} size="sm" style={{ whiteSpace: "nowrap" }}>
|
||||
{user.name}
|
||||
</Anchor>
|
||||
</Group>
|
||||
|
||||
@@ -12,11 +12,7 @@ import { UserAvatar } from "@homarr/ui";
|
||||
|
||||
interface InnerProps {
|
||||
presentUserIds: string[];
|
||||
onSelect: (props: {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
}) => void | Promise<void>;
|
||||
onSelect: (props: { id: string; name: string; image: string }) => void | Promise<void>;
|
||||
confirmLabel?: string;
|
||||
}
|
||||
|
||||
@@ -24,68 +20,59 @@ interface UserSelectFormType {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export const UserSelectModal = createModal<InnerProps>(
|
||||
({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const { data: users, isPending } = clientApi.user.selectable.useQuery();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const form = useForm<UserSelectFormType>();
|
||||
const handleSubmitAsync = async (values: UserSelectFormType) => {
|
||||
const currentUser = users?.find((user) => user.id === values.userId);
|
||||
if (!currentUser) return;
|
||||
setLoading(true);
|
||||
await innerProps.onSelect({
|
||||
id: currentUser.id,
|
||||
name: currentUser.name ?? "",
|
||||
image: currentUser.image ?? "",
|
||||
});
|
||||
export const UserSelectModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const { data: users, isPending } = clientApi.user.selectable.useQuery();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const form = useForm<UserSelectFormType>();
|
||||
const handleSubmitAsync = async (values: UserSelectFormType) => {
|
||||
const currentUser = users?.find((user) => user.id === values.userId);
|
||||
if (!currentUser) return;
|
||||
setLoading(true);
|
||||
await innerProps.onSelect({
|
||||
id: currentUser.id,
|
||||
name: currentUser.name ?? "",
|
||||
image: currentUser.image ?? "",
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
actions.closeModal();
|
||||
};
|
||||
setLoading(false);
|
||||
actions.closeModal();
|
||||
};
|
||||
|
||||
const confirmLabel = innerProps.confirmLabel ?? t("common.action.add");
|
||||
const currentUser = users?.find((user) => user.id === form.values.userId);
|
||||
const confirmLabel = innerProps.confirmLabel ?? t("common.action.add");
|
||||
const currentUser = users?.find((user) => user.id === form.values.userId);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}
|
||||
>
|
||||
<Stack>
|
||||
<Select
|
||||
{...form.getInputProps("userId")}
|
||||
label={t("user.action.select.label")}
|
||||
searchable
|
||||
clearable
|
||||
leftSection={
|
||||
isPending ? (
|
||||
<Loader size="xs" />
|
||||
) : currentUser ? (
|
||||
<UserAvatar user={currentUser} size="xs" />
|
||||
) : undefined
|
||||
}
|
||||
nothingFoundMessage={t("user.action.select.notFound")}
|
||||
renderOption={createRenderOption(users ?? [])}
|
||||
limit={5}
|
||||
data={users
|
||||
?.filter((user) => !innerProps.presentUserIds.includes(user.id))
|
||||
.map((user) => ({ value: user.id, label: user.name ?? "" }))}
|
||||
/>
|
||||
<Group justify="end">
|
||||
<Button variant="default" onClick={actions.closeModal}>
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={loading}>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
},
|
||||
).withOptions({
|
||||
defaultTitle: (t) =>
|
||||
t("board.setting.section.access.permission.userSelect.title"),
|
||||
return (
|
||||
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
|
||||
<Stack>
|
||||
<Select
|
||||
{...form.getInputProps("userId")}
|
||||
label={t("user.action.select.label")}
|
||||
searchable
|
||||
clearable
|
||||
leftSection={
|
||||
isPending ? <Loader size="xs" /> : currentUser ? <UserAvatar user={currentUser} size="xs" /> : undefined
|
||||
}
|
||||
nothingFoundMessage={t("user.action.select.notFound")}
|
||||
renderOption={createRenderOption(users ?? [])}
|
||||
limit={5}
|
||||
data={users
|
||||
?.filter((user) => !innerProps.presentUserIds.includes(user.id))
|
||||
.map((user) => ({ value: user.id, label: user.name ?? "" }))}
|
||||
/>
|
||||
<Group justify="end">
|
||||
<Button variant="default" onClick={actions.closeModal}>
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={loading}>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle: (t) => t("board.setting.section.access.permission.userSelect.title"),
|
||||
});
|
||||
|
||||
const iconProps = {
|
||||
@@ -95,9 +82,7 @@ const iconProps = {
|
||||
size: "1rem",
|
||||
};
|
||||
|
||||
const createRenderOption = (
|
||||
users: RouterOutputs["user"]["selectable"],
|
||||
): SelectProps["renderOption"] =>
|
||||
const createRenderOption = (users: RouterOutputs["user"]["selectable"]): SelectProps["renderOption"] =>
|
||||
function InnerRenderRoot({ option, checked }) {
|
||||
const user = users.find((user) => user.id === option.value);
|
||||
if (!user) return null;
|
||||
@@ -106,9 +91,7 @@ const createRenderOption = (
|
||||
<Group flex="1" gap="xs">
|
||||
<UserAvatar user={user} size="xs" />
|
||||
{option.label}
|
||||
{checked && (
|
||||
<IconCheck style={{ marginInlineStart: "auto" }} {...iconProps} />
|
||||
)}
|
||||
{checked && <IconCheck style={{ marginInlineStart: "auto" }} {...iconProps} />}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
import { Button, Grid, Group, Stack, TextInput } from "@mantine/core";
|
||||
|
||||
import {
|
||||
backgroundImageAttachments,
|
||||
backgroundImageRepeats,
|
||||
backgroundImageSizes,
|
||||
} from "@homarr/definitions";
|
||||
import { backgroundImageAttachments, backgroundImageRepeats, backgroundImageSizes } from "@homarr/definitions";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
@@ -22,8 +18,7 @@ interface Props {
|
||||
}
|
||||
export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
const t = useI18n();
|
||||
const { mutate: savePartialSettings, isPending } =
|
||||
useSavePartialSettingsMutation(board);
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
const form = useZodForm(validation.board.savePartialSettings, {
|
||||
initialValues: {
|
||||
backgroundImageUrl: board.backgroundImageUrl ?? "",
|
||||
@@ -37,14 +32,8 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
"backgroundImageAttachment",
|
||||
backgroundImageAttachments,
|
||||
);
|
||||
const backgroundImageSizeData = useBackgroundOptionData(
|
||||
"backgroundImageSize",
|
||||
backgroundImageSizes,
|
||||
);
|
||||
const backgroundImageRepeatData = useBackgroundOptionData(
|
||||
"backgroundImageRepeat",
|
||||
backgroundImageRepeats,
|
||||
);
|
||||
const backgroundImageSizeData = useBackgroundOptionData("backgroundImageSize", backgroundImageSizes);
|
||||
const backgroundImageRepeatData = useBackgroundOptionData("backgroundImageRepeat", backgroundImageRepeats);
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -96,13 +85,9 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
type BackgroundImageKey =
|
||||
| "backgroundImageAttachment"
|
||||
| "backgroundImageSize"
|
||||
| "backgroundImageRepeat";
|
||||
type BackgroundImageKey = "backgroundImageAttachment" | "backgroundImageSize" | "backgroundImageRepeat";
|
||||
|
||||
type inferOptions<TKey extends BackgroundImageKey> =
|
||||
TranslationObject["board"]["field"][TKey]["option"];
|
||||
type inferOptions<TKey extends BackgroundImageKey> = TranslationObject["board"]["field"][TKey]["option"];
|
||||
|
||||
const useBackgroundOptionData = <
|
||||
TKey extends BackgroundImageKey,
|
||||
@@ -120,9 +105,7 @@ const useBackgroundOptionData = <
|
||||
(value) =>
|
||||
({
|
||||
label: t(`board.field.${key}.option.${value as string}.label` as never),
|
||||
description: t(
|
||||
`board.field.${key}.option.${value as string}.description` as never,
|
||||
),
|
||||
description: t(`board.field.${key}.option.${value as string}.description` as never),
|
||||
value: value as string,
|
||||
badge:
|
||||
data.defaultValue === value
|
||||
|
||||
@@ -44,8 +44,7 @@ export const ColorSettingsContent = ({ board }: Props) => {
|
||||
const [showPreview, { toggle }] = useDisclosure(false);
|
||||
const t = useI18n();
|
||||
const theme = useMantineTheme();
|
||||
const { mutate: savePartialSettings, isPending } =
|
||||
useSavePartialSettingsMutation(board);
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -77,11 +76,7 @@ export const ColorSettingsContent = ({ board }: Props) => {
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<Anchor onClick={toggle}>
|
||||
{showPreview
|
||||
? t("common.preview.hide")
|
||||
: t("common.preview.show")}
|
||||
</Anchor>
|
||||
<Anchor onClick={toggle}>{showPreview ? t("common.preview.hide") : t("common.preview.show")}</Anchor>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<Collapse in={showPreview}>
|
||||
@@ -121,10 +116,7 @@ interface ColorsPreviewProps {
|
||||
const ColorsPreview = ({ previewColor }: ColorsPreviewProps) => {
|
||||
const theme = useMantineTheme();
|
||||
|
||||
const colors =
|
||||
previewColor && hexRegex.test(previewColor)
|
||||
? generateColors(previewColor)
|
||||
: generateColors("#000000");
|
||||
const colors = previewColor && hexRegex.test(previewColor) ? generateColors(previewColor) : generateColors("#000000");
|
||||
|
||||
return (
|
||||
<Group gap={0} wrap="nowrap">
|
||||
|
||||
@@ -20,8 +20,7 @@ export const DangerZoneSettingsContent = () => {
|
||||
const { openModal } = useModalAction(BoardRenameModal);
|
||||
const { mutate: changeVisibility, isPending: isChangeVisibilityPending } =
|
||||
clientApi.board.changeBoardVisibility.useMutation();
|
||||
const { mutate: deleteBoard, isPending: isDeletePending } =
|
||||
clientApi.board.deleteBoard.useMutation();
|
||||
const { mutate: deleteBoard, isPending: isDeletePending } = clientApi.board.deleteBoard.useMutation();
|
||||
const utils = clientApi.useUtils();
|
||||
const visibility = board.isPublic ? "public" : "private";
|
||||
|
||||
@@ -37,12 +36,8 @@ export const DangerZoneSettingsContent = () => {
|
||||
|
||||
const onVisibilityClick = useCallback(() => {
|
||||
openConfirmModal({
|
||||
title: t(
|
||||
`section.dangerZone.action.visibility.confirm.${visibility}.title`,
|
||||
),
|
||||
children: t(
|
||||
`section.dangerZone.action.visibility.confirm.${visibility}.description`,
|
||||
),
|
||||
title: t(`section.dangerZone.action.visibility.confirm.${visibility}.title`),
|
||||
children: t(`section.dangerZone.action.visibility.confirm.${visibility}.description`),
|
||||
onConfirm: () => {
|
||||
changeVisibility(
|
||||
{
|
||||
@@ -98,12 +93,8 @@ export const DangerZoneSettingsContent = () => {
|
||||
<Divider />
|
||||
<DangerZoneRow
|
||||
label={t("section.dangerZone.action.visibility.label")}
|
||||
description={t(
|
||||
`section.dangerZone.action.visibility.description.${visibility}`,
|
||||
)}
|
||||
buttonText={t(
|
||||
`section.dangerZone.action.visibility.button.${visibility}`,
|
||||
)}
|
||||
description={t(`section.dangerZone.action.visibility.description.${visibility}`)}
|
||||
buttonText={t(`section.dangerZone.action.visibility.button.${visibility}`)}
|
||||
onClick={onVisibilityClick}
|
||||
isPending={isChangeVisibilityPending}
|
||||
/>
|
||||
@@ -127,13 +118,7 @@ interface DangerZoneRowProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const DangerZoneRow = ({
|
||||
label,
|
||||
description,
|
||||
buttonText,
|
||||
onClick,
|
||||
isPending,
|
||||
}: DangerZoneRowProps) => {
|
||||
const DangerZoneRow = ({ label, description, buttonText, onClick, isPending }: DangerZoneRowProps) => {
|
||||
return (
|
||||
<Group justify="space-between" px="md" className={classes.dangerZoneGroup}>
|
||||
<Stack gap={0}>
|
||||
@@ -143,12 +128,7 @@ const DangerZoneRow = ({
|
||||
<Text size="sm">{description}</Text>
|
||||
</Stack>
|
||||
<Group justify="end" w={{ base: "100%", xs: "auto" }}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="red"
|
||||
loading={isPending}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Button variant="subtle" color="red" loading={isPending} onClick={onClick}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
Button,
|
||||
Grid,
|
||||
Group,
|
||||
Loader,
|
||||
Stack,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
useDebouncedValue,
|
||||
useDocumentTitle,
|
||||
useFavicon,
|
||||
} from "@mantine/hooks";
|
||||
import { Button, Grid, Group, Loader, Stack, TextInput, Tooltip } from "@mantine/core";
|
||||
import { useDebouncedValue, useDocumentTitle, useFavicon } from "@mantine/hooks";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
|
||||
import { useZodForm } from "@homarr/form";
|
||||
@@ -38,8 +26,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
||||
});
|
||||
const { updateBoard } = useUpdateBoard();
|
||||
|
||||
const { mutate: savePartialSettings, isPending } =
|
||||
useSavePartialSettingsMutation(board);
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
const form = useZodForm(
|
||||
validation.board.savePartialSettings
|
||||
.pick({
|
||||
@@ -106,9 +93,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
||||
<Grid.Col span={{ xs: 12, md: 6 }}>
|
||||
<TextInput
|
||||
label={t("board.field.metaTitle.label")}
|
||||
placeholder={createMetaTitle(
|
||||
t("board.content.metaTitle", { boardName: board.name }),
|
||||
)}
|
||||
placeholder={createMetaTitle(t("board.content.metaTitle", { boardName: board.name }))}
|
||||
rightSection={<PendingOrInvalidIndicator {...metaTitleStatus} />}
|
||||
{...form.getInputProps("metaTitle")}
|
||||
/>
|
||||
@@ -140,22 +125,12 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PendingOrInvalidIndicator = ({
|
||||
isPending,
|
||||
isInvalid,
|
||||
}: {
|
||||
isPending: boolean;
|
||||
isInvalid?: boolean;
|
||||
}) => {
|
||||
const PendingOrInvalidIndicator = ({ isPending, isInvalid }: { isPending: boolean; isInvalid?: boolean }) => {
|
||||
const t = useI18n();
|
||||
|
||||
if (isInvalid) {
|
||||
return (
|
||||
<Tooltip
|
||||
multiline
|
||||
w={220}
|
||||
label={t("board.setting.section.general.unrecognizedLink")}
|
||||
>
|
||||
<Tooltip multiline w={220} label={t("board.setting.section.general.unrecognizedLink")}>
|
||||
<IconAlertTriangle size="1rem" color="red" />
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -197,8 +172,7 @@ const useMetaTitlePreview = (title: string | null) => {
|
||||
|
||||
const validFaviconExtensions = ["ico", "png", "svg", "gif"];
|
||||
const isValidUrl = (url: string) =>
|
||||
url.includes("/") &&
|
||||
validFaviconExtensions.some((extension) => url.endsWith(`.${extension}`));
|
||||
url.includes("/") && validFaviconExtensions.some((extension) => url.endsWith(`.${extension}`));
|
||||
|
||||
const useFaviconPreview = (url: string | null) => {
|
||||
const [faviconDebounced] = useDebouncedValue(url ?? "", 500);
|
||||
|
||||
@@ -14,16 +14,12 @@ interface Props {
|
||||
}
|
||||
export const LayoutSettingsContent = ({ board }: Props) => {
|
||||
const t = useI18n();
|
||||
const { mutate: savePartialSettings, isPending } =
|
||||
useSavePartialSettingsMutation(board);
|
||||
const form = useZodForm(
|
||||
validation.board.savePartialSettings.pick({ columnCount: true }).required(),
|
||||
{
|
||||
initialValues: {
|
||||
columnCount: board.columnCount,
|
||||
},
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
const form = useZodForm(validation.board.savePartialSettings.pick({ columnCount: true }).required(), {
|
||||
initialValues: {
|
||||
columnCount: board.columnCount,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -38,13 +34,7 @@ export const LayoutSettingsContent = ({ board }: Props) => {
|
||||
<Grid>
|
||||
<Grid.Col span={{ sm: 12, md: 6 }}>
|
||||
<Input.Wrapper label={t("board.field.columnCount.label")}>
|
||||
<Slider
|
||||
mt="xs"
|
||||
min={1}
|
||||
max={24}
|
||||
step={1}
|
||||
{...form.getInputProps("columnCount")}
|
||||
/>
|
||||
<Slider mt="xs" min={1} max={24} step={1} {...form.getInputProps("columnCount")} />
|
||||
</Input.Wrapper>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
AccordionControl,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Container,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { AccordionControl, AccordionItem, AccordionPanel, Container, Stack, Text, Title } from "@mantine/core";
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
IconBrush,
|
||||
@@ -69,10 +61,7 @@ const getBoardAndPermissionsAsync = async (params: Props["params"]) => {
|
||||
}
|
||||
};
|
||||
|
||||
export default async function BoardSettingsPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: Props) {
|
||||
export default async function BoardSettingsPage({ params, searchParams }: Props) {
|
||||
const { board, permissions } = await getBoardAndPermissionsAsync(params);
|
||||
const { hasFullAccess } = await getBoardPermissionsAsync(board);
|
||||
const t = await getScopedI18n("board.setting");
|
||||
@@ -81,10 +70,7 @@ export default async function BoardSettingsPage({
|
||||
<Container>
|
||||
<Stack>
|
||||
<Title>{t("title", { boardName: capitalize(board.name) })}</Title>
|
||||
<ActiveTabAccordion
|
||||
variant="separated"
|
||||
defaultValue={searchParams.tab ?? "general"}
|
||||
>
|
||||
<ActiveTabAccordion variant="separated" defaultValue={searchParams.tab ?? "general"}>
|
||||
<AccordionItemFor value="general" icon={IconSettings}>
|
||||
<GeneralSettingsContent board={board} />
|
||||
</AccordionItemFor>
|
||||
@@ -103,17 +89,9 @@ export default async function BoardSettingsPage({
|
||||
{hasFullAccess && (
|
||||
<>
|
||||
<AccordionItemFor value="access" icon={IconUser}>
|
||||
<AccessSettingsContent
|
||||
board={board}
|
||||
initialPermissions={permissions}
|
||||
/>
|
||||
<AccessSettingsContent board={board} initialPermissions={permissions} />
|
||||
</AccordionItemFor>
|
||||
<AccordionItemFor
|
||||
value="dangerZone"
|
||||
icon={IconAlertTriangle}
|
||||
danger
|
||||
noPadding
|
||||
>
|
||||
<AccordionItemFor value="dangerZone" icon={IconAlertTriangle} danger noPadding>
|
||||
<DangerZoneSettingsContent />
|
||||
</AccordionItemFor>
|
||||
</>
|
||||
@@ -131,13 +109,7 @@ type AccordionItemForProps = PropsWithChildren<{
|
||||
noPadding?: boolean;
|
||||
}>;
|
||||
|
||||
const AccordionItemFor = async ({
|
||||
value,
|
||||
children,
|
||||
icon: Icon,
|
||||
danger,
|
||||
noPadding,
|
||||
}: AccordionItemForProps) => {
|
||||
const AccordionItemFor = async ({ value, children, icon: Icon, danger, noPadding }: AccordionItemForProps) => {
|
||||
const t = await getScopedI18n("board.setting.section");
|
||||
return (
|
||||
<AccordionItem
|
||||
@@ -158,13 +130,7 @@ const AccordionItemFor = async ({
|
||||
{t(`${value}.title`)}
|
||||
</Text>
|
||||
</AccordionControl>
|
||||
<AccordionPanel
|
||||
styles={
|
||||
noPadding
|
||||
? { content: { paddingRight: 0, paddingLeft: 0 } }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<AccordionPanel styles={noPadding ? { content: { paddingRight: 0, paddingLeft: 0 } } : undefined}>
|
||||
{children}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
|
||||
@@ -41,10 +41,7 @@ export const createBoardLayout = <TParams extends Params>({
|
||||
});
|
||||
|
||||
return (
|
||||
<GlobalItemServerDataRunner
|
||||
board={initialBoard}
|
||||
shouldRun={isBoardContentPage}
|
||||
>
|
||||
<GlobalItemServerDataRunner board={initialBoard} shouldRun={isBoardContentPage}>
|
||||
<BoardProvider initialBoard={initialBoard}>
|
||||
<BoardMantineProvider>
|
||||
<ClientShell hasNavigation={false}>
|
||||
|
||||
@@ -8,7 +8,4 @@ export type Item = Section["items"][number];
|
||||
export type CategorySection = Extract<Section, { kind: "category" }>;
|
||||
export type EmptySection = Extract<Section, { kind: "empty" }>;
|
||||
|
||||
export type ItemOfKind<TKind extends WidgetKind> = Extract<
|
||||
Item,
|
||||
{ kind: TKind }
|
||||
>;
|
||||
export type ItemOfKind<TKind extends WidgetKind> = Extract<Item, { kind: TKind }>;
|
||||
|
||||
Reference in New Issue
Block a user