refactor: move modals to seperate package (#1135)
* refactor: move modals to seperate package * fix: format issue * fix: lint issues * fix: format issue * fix: only used as type
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
"@homarr/integrations": "workspace:^0.1.0",
|
"@homarr/integrations": "workspace:^0.1.0",
|
||||||
"@homarr/log": "workspace:^",
|
"@homarr/log": "workspace:^",
|
||||||
"@homarr/modals": "workspace:^0.1.0",
|
"@homarr/modals": "workspace:^0.1.0",
|
||||||
|
"@homarr/modals-collection": "workspace:^0.1.0",
|
||||||
"@homarr/notifications": "workspace:^0.1.0",
|
"@homarr/notifications": "workspace:^0.1.0",
|
||||||
"@homarr/old-schema": "workspace:^0.1.0",
|
"@homarr/old-schema": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Te
|
|||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
|
||||||
import { signIn } from "@homarr/auth/client";
|
import { signIn } from "@homarr/auth/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import type { useForm } from "@homarr/form";
|
import type { useForm } from "@homarr/form";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
@@ -14,8 +15,6 @@ import { useScopedI18n } from "@homarr/translation/client";
|
|||||||
import type { z } from "@homarr/validation";
|
import type { z } from "@homarr/validation";
|
||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface LoginFormProps {
|
interface LoginFormProps {
|
||||||
providers: string[];
|
providers: string[];
|
||||||
oidcClientName: string;
|
oidcClientName: string;
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ import {
|
|||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useModalAction } from "@homarr/modals";
|
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 { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { ItemSelectModal } from "~/components/board/items/item-select-modal";
|
import { ItemSelectModal } from "~/components/board/items/item-select-modal";
|
||||||
import { useBoardPermissions } from "~/components/board/permissions/client";
|
import { useBoardPermissions } from "~/components/board/permissions/client";
|
||||||
import { useCategoryActions } from "~/components/board/sections/category/category-actions";
|
import { useCategoryActions } from "~/components/board/sections/category/category-actions";
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import { IconTrash } from "@tabler/icons-react";
|
|||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "../../../revalidatePathAction";
|
|
||||||
|
|
||||||
interface AppDeleteButtonProps {
|
interface AppDeleteButtonProps {
|
||||||
app: RouterOutputs["app"]["all"][number];
|
app: RouterOutputs["app"]["all"][number];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { useRouter } from "next/navigation";
|
|||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import type { TranslationFunction } from "@homarr/translation";
|
import type { TranslationFunction } from "@homarr/translation";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { validation, z } from "@homarr/validation";
|
import type { validation, z } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { AppForm } from "../../_form";
|
import { AppForm } from "../../_form";
|
||||||
|
|
||||||
interface AppEditFormProps {
|
interface AppEditFormProps {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { useCallback } from "react";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import type { TranslationFunction } from "@homarr/translation";
|
import type { TranslationFunction } from "@homarr/translation";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { validation, z } from "@homarr/validation";
|
import type { validation, z } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { AppForm } from "../_form";
|
import { AppForm } from "../_form";
|
||||||
|
|
||||||
export const AppNewForm = () => {
|
export const AppNewForm = () => {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { IconHome, IconSettings, IconTrash } from "@tabler/icons-react";
|
|||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { useBoardPermissions } from "~/components/board/permissions/client";
|
import { useBoardPermissions } from "~/components/board/permissions/client";
|
||||||
|
|
||||||
const iconProps = {
|
const iconProps = {
|
||||||
|
|||||||
@@ -4,15 +4,12 @@ import { useCallback } from "react";
|
|||||||
import { Affix, Button, Group, Menu } from "@mantine/core";
|
import { Affix, Button, Group, Menu } from "@mantine/core";
|
||||||
import { IconCategoryPlus, IconChevronDown, IconFileImport } from "@tabler/icons-react";
|
import { IconCategoryPlus, IconChevronDown, IconFileImport } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useModalAction } from "@homarr/modals";
|
import { useModalAction } from "@homarr/modals";
|
||||||
|
import { AddBoardModal, ImportBoardModal } from "@homarr/modals-collection";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { BetaBadge } from "@homarr/ui";
|
import { BetaBadge } from "@homarr/ui";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { AddBoardModal } from "~/components/manage/boards/add-board-modal";
|
|
||||||
import { ImportBoardModal } from "~/components/manage/boards/import-board-modal";
|
|
||||||
|
|
||||||
interface CreateBoardButtonProps {
|
interface CreateBoardButtonProps {
|
||||||
boardNames: string[];
|
boardNames: string[];
|
||||||
}
|
}
|
||||||
@@ -22,24 +19,13 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
|
|||||||
const { openModal: openAddModal } = useModalAction(AddBoardModal);
|
const { openModal: openAddModal } = useModalAction(AddBoardModal);
|
||||||
const { openModal: openImportModal } = useModalAction(ImportBoardModal);
|
const { openModal: openImportModal } = useModalAction(ImportBoardModal);
|
||||||
|
|
||||||
const { mutateAsync, isPending } = clientApi.board.createBoard.useMutation({
|
|
||||||
onSettled: async () => {
|
|
||||||
await revalidatePathActionAsync("/manage/boards");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onCreateClick = useCallback(() => {
|
const onCreateClick = useCallback(() => {
|
||||||
openAddModal({
|
openAddModal({
|
||||||
onSuccess: async (values) => {
|
onSettled: async () => {
|
||||||
await mutateAsync({
|
await revalidatePathActionAsync("/manage/boards");
|
||||||
name: values.name,
|
|
||||||
columnCount: values.columnCount,
|
|
||||||
isPublic: values.isPublic,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
boardNames,
|
|
||||||
});
|
});
|
||||||
}, [mutateAsync, boardNames, openAddModal]);
|
}, [openAddModal]);
|
||||||
|
|
||||||
const onImportClick = useCallback(() => {
|
const onImportClick = useCallback(() => {
|
||||||
openImportModal({ boardNames });
|
openImportModal({ boardNames });
|
||||||
@@ -47,7 +33,7 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
|
|||||||
|
|
||||||
const buttonGroupContent = (
|
const buttonGroupContent = (
|
||||||
<>
|
<>
|
||||||
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={onCreateClick} loading={isPending}>
|
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={onCreateClick}>
|
||||||
{t("management.page.board.action.new.label")}
|
{t("management.page.board.action.new.label")}
|
||||||
</Button>
|
</Button>
|
||||||
<Menu position="bottom-end">
|
<Menu position="bottom-end">
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import { ActionIcon } from "@mantine/core";
|
|||||||
import { IconTrash } from "@tabler/icons-react";
|
import { IconTrash } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "../../../revalidatePathAction";
|
|
||||||
|
|
||||||
interface DeleteIntegrationActionButtonProps {
|
interface DeleteIntegrationActionButtonProps {
|
||||||
count: number;
|
count: number;
|
||||||
integration: { id: string; name: string };
|
integration: { id: string; name: string };
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Button, Fieldset, Group, Stack, TextInput } from "@mantine/core";
|
|||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { getAllSecretKindOptions, getDefaultSecretKinds } from "@homarr/definitions";
|
import { getAllSecretKindOptions, getDefaultSecretKinds } from "@homarr/definitions";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
|
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
|
||||||
@@ -15,7 +16,6 @@ import { useI18n } from "@homarr/translation/client";
|
|||||||
import type { z } from "@homarr/validation";
|
import type { z } from "@homarr/validation";
|
||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { SecretCard } from "../../_components/secrets/integration-secret-card";
|
import { SecretCard } from "../../_components/secrets/integration-secret-card";
|
||||||
import { IntegrationSecretInput } from "../../_components/secrets/integration-secret-inputs";
|
import { IntegrationSecretInput } from "../../_components/secrets/integration-secret-inputs";
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Alert, Button, Fieldset, Group, SegmentedControl, Stack, Text, TextInpu
|
|||||||
import { IconInfoCircle } from "@tabler/icons-react";
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
|
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
|
||||||
import { getAllSecretKindOptions } from "@homarr/definitions";
|
import { getAllSecretKindOptions } from "@homarr/definitions";
|
||||||
import type { UseFormReturnType } from "@homarr/form";
|
import type { UseFormReturnType } from "@homarr/form";
|
||||||
@@ -18,7 +19,6 @@ import type { z } from "@homarr/validation";
|
|||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { IntegrationSecretInput } from "../_components/secrets/integration-secret-inputs";
|
import { IntegrationSecretInput } from "../_components/secrets/integration-secret-inputs";
|
||||||
import { revalidatePathActionAsync } from "../../../../revalidatePathAction";
|
|
||||||
|
|
||||||
interface NewIntegrationFormProps {
|
interface NewIntegrationFormProps {
|
||||||
searchParams: Partial<z.infer<typeof validation.integration.create>> & {
|
searchParams: Partial<z.infer<typeof validation.integration.create>> & {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import React from "react";
|
|||||||
import { Card, LoadingOverlay, Stack, Title } from "@mantine/core";
|
import { Card, LoadingOverlay, Stack, Title } from "@mantine/core";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useForm } from "@homarr/form";
|
import { useForm } from "@homarr/form";
|
||||||
import type { defaultServerSettings } from "@homarr/server-settings";
|
import type { defaultServerSettings } from "@homarr/server-settings";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { SwitchSetting } from "~/app/[locale]/manage/settings/_components/setting-switch";
|
import { SwitchSetting } from "~/app/[locale]/manage/settings/_components/setting-switch";
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface AnalyticsSettingsProps {
|
interface AnalyticsSettingsProps {
|
||||||
initialData: typeof defaultServerSettings.analytics;
|
initialData: typeof defaultServerSettings.analytics;
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import React from "react";
|
|||||||
import { Card, LoadingOverlay, Stack, Text, Title } from "@mantine/core";
|
import { Card, LoadingOverlay, Stack, Text, Title } from "@mantine/core";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useForm } from "@homarr/form";
|
import { useForm } from "@homarr/form";
|
||||||
import type { defaultServerSettings } from "@homarr/server-settings";
|
import type { defaultServerSettings } from "@homarr/server-settings";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { SwitchSetting } from "~/app/[locale]/manage/settings/_components/setting-switch";
|
import { SwitchSetting } from "~/app/[locale]/manage/settings/_components/setting-switch";
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface CrawlingAndIndexingSettingsProps {
|
interface CrawlingAndIndexingSettingsProps {
|
||||||
initialData: typeof defaultServerSettings.crawlingAndIndexing;
|
initialData: typeof defaultServerSettings.crawlingAndIndexing;
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ import { Button, Group, Select, Stack } from "@mantine/core";
|
|||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import type { z } from "@homarr/validation";
|
import type { z } from "@homarr/validation";
|
||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface ChangeHomeBoardFormProps {
|
interface ChangeHomeBoardFormProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
boardsData: { value: string; label: string }[];
|
boardsData: { value: string; label: string }[];
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ import { Button } from "@mantine/core";
|
|||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface DeleteUserButtonProps {
|
interface DeleteUserButtonProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ import { IconPencil, IconPhotoEdit, IconPhotoX } from "@tabler/icons-react";
|
|||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import { UserAvatar } from "@homarr/ui";
|
import { UserAvatar } from "@homarr/ui";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface UserProfileAvatarForm {
|
interface UserProfileAvatarForm {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import { Button, Group, Stack, TextInput } from "@mantine/core";
|
|||||||
|
|
||||||
import type { RouterInputs, RouterOutputs } from "@homarr/api";
|
import type { RouterInputs, RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface UserProfileFormProps {
|
interface UserProfileFormProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ import { Button, Fieldset, Group, PasswordInput, Stack } from "@mantine/core";
|
|||||||
import type { RouterInputs, RouterOutputs } from "@homarr/api";
|
import type { RouterInputs, RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useSession } from "@homarr/auth/client";
|
import { useSession } from "@homarr/auth/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { CustomPasswordInput } from "@homarr/ui";
|
import { CustomPasswordInput } from "@homarr/ui";
|
||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface ChangePasswordFormProps {
|
interface ChangePasswordFormProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import { useRouter } from "next/navigation";
|
|||||||
import { Button } from "@mantine/core";
|
import { Button } from "@mantine/core";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface DeleteGroupProps {
|
interface DeleteGroupProps {
|
||||||
group: {
|
group: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import { useCallback } from "react";
|
|||||||
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface RenameGroupFormProps {
|
interface RenameGroupFormProps {
|
||||||
group: {
|
group: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useModalAction } from "@homarr/modals";
|
import { useModalAction } from "@homarr/modals";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { UserSelectModal } from "~/components/access/user-select-modal";
|
import { UserSelectModal } from "~/components/access/user-select-modal";
|
||||||
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import { useCallback } from "react";
|
|||||||
import { Button } from "@mantine/core";
|
import { Button } from "@mantine/core";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
|
|
||||||
interface RemoveGroupMemberProps {
|
interface RemoveGroupMemberProps {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
user: { id: string; name: string | null };
|
user: { id: string; name: string | null };
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { useCallback } from "react";
|
|||||||
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { createModal, useModalAction } from "@homarr/modals";
|
import { createModal, useModalAction } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
|
||||||
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
||||||
|
|
||||||
export const AddGroup = () => {
|
export const AddGroup = () => {
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import { MantineReactTable } from "mantine-react-table";
|
|||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||||
|
import { InviteCreateModal } from "@homarr/modals-collection";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
||||||
|
|
||||||
import { InviteCreateModal } from "./invite-create-modal";
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
interface InviteListComponentProps {
|
interface InviteListComponentProps {
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { Button, Group, InputWrapper, Slider, Stack, Switch, TextInput } from "@mantine/core";
|
|
||||||
|
|
||||||
import { useZodForm } from "@homarr/form";
|
|
||||||
import { createModal } from "@homarr/modals";
|
|
||||||
import { useI18n } from "@homarr/translation/client";
|
|
||||||
import { validation } from "@homarr/validation";
|
|
||||||
import { createCustomErrorParams } from "@homarr/validation/form";
|
|
||||||
|
|
||||||
interface InnerProps {
|
|
||||||
boardNames: string[];
|
|
||||||
onSuccess: (props: { name: string; columnCount: number; isPublic: boolean }) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
|
||||||
const t = useI18n();
|
|
||||||
const form = useZodForm(
|
|
||||||
validation.board.create.refine((value) => !innerProps.boardNames.includes(value.name), {
|
|
||||||
params: createCustomErrorParams("boardAlreadyExists"),
|
|
||||||
path: ["name"],
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
initialValues: {
|
|
||||||
name: "",
|
|
||||||
columnCount: 10,
|
|
||||||
isPublic: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const columnCountChecks = validation.board.create.shape.columnCount._def.checks;
|
|
||||||
const minColumnCount = columnCountChecks.find((check) => check.kind === "min")?.value;
|
|
||||||
const maxColumnCount = columnCountChecks.find((check) => check.kind === "max")?.value;
|
|
||||||
|
|
||||||
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")} />
|
|
||||||
<InputWrapper label={t("board.field.columnCount.label")} {...form.getInputProps("columnCount")}>
|
|
||||||
<Slider min={minColumnCount} max={maxColumnCount} step={1} {...form.getInputProps("columnCount")} />
|
|
||||||
</InputWrapper>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
label={t("board.field.isPublic.label")}
|
|
||||||
description={t("board.field.isPublic.description")}
|
|
||||||
{...form.getInputProps("isPublic")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Group justify="right">
|
|
||||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
|
||||||
{t("common.action.cancel")}
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" color="teal">
|
|
||||||
{t("common.action.create")}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}).withOptions({
|
|
||||||
defaultTitle: (t) => t("management.page.board.action.new.label"),
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,25 @@
|
|||||||
import { createTRPCReact } from "@trpc/react-query";
|
import { createTRPCClient, createTRPCReact, httpLink } from "@trpc/react-query";
|
||||||
|
import SuperJSON from "superjson";
|
||||||
|
|
||||||
import type { AppRouter } from ".";
|
import type { AppRouter } from ".";
|
||||||
|
|
||||||
export const clientApi = createTRPCReact<AppRouter>();
|
export const clientApi = createTRPCReact<AppRouter>();
|
||||||
|
export const fetchApi = createTRPCClient<AppRouter>({
|
||||||
|
links: [
|
||||||
|
httpLink({
|
||||||
|
url: `${getBaseUrl()}/api/trpc`,
|
||||||
|
transformer: SuperJSON,
|
||||||
|
headers() {
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set("x-trpc-source", "fetch");
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
function getBaseUrl() {
|
||||||
|
if (typeof window !== "undefined") return window.location.origin;
|
||||||
|
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||||
|
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,20 @@ import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publ
|
|||||||
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
||||||
|
|
||||||
export const boardRouter = createTRPCRouter({
|
export const boardRouter = createTRPCRouter({
|
||||||
|
exists: permissionRequiredProcedure
|
||||||
|
.requiresPermission("board-create")
|
||||||
|
.input(z.string())
|
||||||
|
.query(async ({ ctx, input: name }) => {
|
||||||
|
try {
|
||||||
|
await noBoardWithSimilarNameAsync(ctx.db, name);
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof TRPCError && error.code === "CONFLICT") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}),
|
||||||
getAllBoards: publicProcedure.query(async ({ ctx }) => {
|
getAllBoards: publicProcedure.query(async ({ ctx }) => {
|
||||||
const userId = ctx.session?.user.id;
|
const userId = ctx.session?.user.id;
|
||||||
const permissionsOfCurrentUserWhenPresent = await ctx.db.query.boardUserPermissions.findMany({
|
const permissionsOfCurrentUserWhenPresent = await ctx.db.query.boardUserPermissions.findMany({
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from "./app-url/client";
|
export * from "./app-url/client";
|
||||||
|
export * from "./revalidate-path-action";
|
||||||
|
|||||||
9
packages/modals-collection/eslint.config.js
Normal file
9
packages/modals-collection/eslint.config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import baseConfig from "@homarr/eslint-config/base";
|
||||||
|
|
||||||
|
/** @type {import('typescript-eslint').Config} */
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [],
|
||||||
|
},
|
||||||
|
...baseConfig,
|
||||||
|
];
|
||||||
1
packages/modals-collection/index.ts
Normal file
1
packages/modals-collection/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./src";
|
||||||
47
packages/modals-collection/package.json
Normal file
47
packages/modals-collection/package.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "@homarr/modals-collection",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.ts"
|
||||||
|
},
|
||||||
|
"typesVersions": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rm -rf .turbo node_modules",
|
||||||
|
"lint": "eslint",
|
||||||
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@homarr/api": "workspace:^0.1.0",
|
||||||
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
|
"@homarr/form": "workspace:^0.1.0",
|
||||||
|
"@homarr/modals": "workspace:^0.1.0",
|
||||||
|
"@homarr/notifications": "workspace:^0.1.0",
|
||||||
|
"@homarr/old-schema": "workspace:^0.1.0",
|
||||||
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
|
"@mantine/core": "^7.12.2",
|
||||||
|
"@tabler/icons-react": "^3.17.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"next": "^14.2.11",
|
||||||
|
"react": "^18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
|
"eslint": "^9.10.0",
|
||||||
|
"typescript": "^5.6.2"
|
||||||
|
},
|
||||||
|
"prettier": "@homarr/prettier-config"
|
||||||
|
}
|
||||||
128
packages/modals-collection/src/boards/add-board-modal.tsx
Normal file
128
packages/modals-collection/src/boards/add-board-modal.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { Button, Group, InputWrapper, Slider, Stack, Switch, TextInput } from "@mantine/core";
|
||||||
|
import { useDebouncedValue } from "@mantine/hooks";
|
||||||
|
import { IconAlertTriangle, IconCircleCheck } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import type { MaybePromise } from "@homarr/common/types";
|
||||||
|
import { useZodForm } from "@homarr/form";
|
||||||
|
import { createModal } from "@homarr/modals";
|
||||||
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
|
interface InnerProps {
|
||||||
|
onSettled: () => MaybePromise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const form = useZodForm(validation.board.create, {
|
||||||
|
mode: "controlled",
|
||||||
|
initialValues: {
|
||||||
|
name: "",
|
||||||
|
columnCount: 10,
|
||||||
|
isPublic: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { mutate, isPending } = clientApi.board.createBoard.useMutation({
|
||||||
|
onSettled: innerProps.onSettled,
|
||||||
|
});
|
||||||
|
|
||||||
|
const boardNameStatus = useBoardNameStatus(form.values.name);
|
||||||
|
|
||||||
|
const columnCountChecks = validation.board.create.shape.columnCount._def.checks;
|
||||||
|
const minColumnCount = columnCountChecks.find((check) => check.kind === "min")?.value;
|
||||||
|
const maxColumnCount = columnCountChecks.find((check) => check.kind === "max")?.value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={form.onSubmit((values) => {
|
||||||
|
// Prevent submit before name availability check
|
||||||
|
if (!boardNameStatus.canSubmit) return;
|
||||||
|
mutate(values, {
|
||||||
|
onSuccess: () => {
|
||||||
|
actions.closeModal();
|
||||||
|
showSuccessNotification({
|
||||||
|
title: "Board created",
|
||||||
|
message: `Board ${values.name} has been created`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
showErrorNotification({
|
||||||
|
title: "Failed to create board",
|
||||||
|
message: `Board ${values.name} could not be created`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
label={t("board.field.name.label")}
|
||||||
|
data-autofocus
|
||||||
|
{...form.getInputProps("name")}
|
||||||
|
description={
|
||||||
|
boardNameStatus.description ? (
|
||||||
|
<Group c={boardNameStatus.description.color} gap="xs" align="center">
|
||||||
|
{boardNameStatus.description.icon ? <boardNameStatus.description.icon size={16} /> : null}
|
||||||
|
<span>{boardNameStatus.description.label}</span>
|
||||||
|
</Group>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputWrapper label={t("board.field.columnCount.label")} {...form.getInputProps("columnCount")}>
|
||||||
|
<Slider min={minColumnCount} max={maxColumnCount} step={1} {...form.getInputProps("columnCount")} />
|
||||||
|
</InputWrapper>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
label={t("board.field.isPublic.label")}
|
||||||
|
description={t("board.field.isPublic.description")}
|
||||||
|
{...form.getInputProps("isPublic")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||||
|
{t("common.action.cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" color="teal" loading={isPending}>
|
||||||
|
{t("common.action.create")}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}).withOptions({
|
||||||
|
defaultTitle: (t) => t("management.page.board.action.new.label"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useBoardNameStatus = (name: string) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const [debouncedName] = useDebouncedValue(name, 250);
|
||||||
|
const { data: boardExists, isLoading } = clientApi.board.exists.useQuery(debouncedName, {
|
||||||
|
enabled: validation.board.create.shape.name.safeParse(debouncedName).success,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
canSubmit: !boardExists && !isLoading,
|
||||||
|
description:
|
||||||
|
debouncedName.trim() === ""
|
||||||
|
? undefined
|
||||||
|
: isLoading
|
||||||
|
? {
|
||||||
|
label: "Checking availability...",
|
||||||
|
}
|
||||||
|
: boardExists === undefined
|
||||||
|
? undefined
|
||||||
|
: boardExists
|
||||||
|
? {
|
||||||
|
icon: IconAlertTriangle,
|
||||||
|
label: t("common.zod.errors.custom.boardAlreadyExists"), // The board ${debouncedName} already exists
|
||||||
|
color: "red",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
icon: IconCircleCheck,
|
||||||
|
label: `${debouncedName} is available`,
|
||||||
|
color: "green",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { Button, Fieldset, FileInput, Grid, Group, Radio, Stack, Switch, TextInp
|
|||||||
import { IconFileUpload } from "@tabler/icons-react";
|
import { IconFileUpload } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
@@ -10,24 +11,21 @@ import { oldmarrConfigSchema } from "@homarr/old-schema";
|
|||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { SelectWithDescription } from "@homarr/ui";
|
import { SelectWithDescription } from "@homarr/ui";
|
||||||
import type { OldmarrImportConfiguration } from "@homarr/validation";
|
import type { OldmarrImportConfiguration } from "@homarr/validation";
|
||||||
import { createOldmarrImportConfigurationSchema, superRefineJsonImportFile, z } from "@homarr/validation";
|
import { oldmarrImportConfigurationSchema, superRefineJsonImportFile, z } from "@homarr/validation";
|
||||||
|
|
||||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
import { useBoardNameStatus } from "./add-board-modal";
|
||||||
|
|
||||||
interface InnerProps {
|
export const ImportBoardModal = createModal(({ actions }) => {
|
||||||
boardNames: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ImportBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
|
||||||
const tOldImport = useScopedI18n("board.action.oldImport");
|
const tOldImport = useScopedI18n("board.action.oldImport");
|
||||||
const tCommon = useScopedI18n("common");
|
const tCommon = useScopedI18n("common");
|
||||||
const [fileValid, setFileValid] = useState(true);
|
const [fileValid, setFileValid] = useState(true);
|
||||||
const form = useZodForm(
|
const form = useZodForm(
|
||||||
z.object({
|
z.object({
|
||||||
file: z.instanceof(File).nullable().superRefine(superRefineJsonImportFile),
|
file: z.instanceof(File).nullable().superRefine(superRefineJsonImportFile),
|
||||||
configuration: createOldmarrImportConfigurationSchema(innerProps.boardNames),
|
configuration: oldmarrImportConfigurationSchema,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
mode: "controlled",
|
||||||
initialValues: {
|
initialValues: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
file: null!,
|
file: null!,
|
||||||
@@ -67,6 +65,7 @@ export const ImportBoardModal = createModal<InnerProps>(({ actions, innerProps }
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { mutateAsync, isPending } = clientApi.board.importOldmarrConfig.useMutation();
|
const { mutateAsync, isPending } = clientApi.board.importOldmarrConfig.useMutation();
|
||||||
|
const boardNameStatus = useBoardNameStatus(form.values.configuration.name);
|
||||||
|
|
||||||
const handleSubmitAsync = async (values: { file: File; configuration: OldmarrImportConfiguration }) => {
|
const handleSubmitAsync = async (values: { file: File; configuration: OldmarrImportConfiguration }) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -94,7 +93,7 @@ export const ImportBoardModal = createModal<InnerProps>(({ actions, innerProps }
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={form.onSubmit((values) => {
|
onSubmit={form.onSubmit((values) => {
|
||||||
if (!fileValid) {
|
if (!fileValid || !boardNameStatus.canSubmit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +138,19 @@ export const ImportBoardModal = createModal<InnerProps>(({ actions, innerProps }
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
|
||||||
<TextInput withAsterisk label={tOldImport("form.name.label")} {...form.getInputProps("configuration.name")} />
|
<TextInput
|
||||||
|
withAsterisk
|
||||||
|
label={tOldImport("form.name.label")}
|
||||||
|
description={
|
||||||
|
boardNameStatus.description ? (
|
||||||
|
<Group c={boardNameStatus.description.color} gap="xs" align="center">
|
||||||
|
{boardNameStatus.description.icon ? <boardNameStatus.description.icon size={16} /> : null}
|
||||||
|
<span>{boardNameStatus.description.label}</span>
|
||||||
|
</Group>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
{...form.getInputProps("configuration.name")}
|
||||||
|
/>
|
||||||
|
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
withAsterisk
|
withAsterisk
|
||||||
2
packages/modals-collection/src/boards/index.ts
Normal file
2
packages/modals-collection/src/boards/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { AddBoardModal } from "./add-board-modal";
|
||||||
|
export { ImportBoardModal } from "./import-board-modal";
|
||||||
2
packages/modals-collection/src/index.ts
Normal file
2
packages/modals-collection/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./boards";
|
||||||
|
export * from "./invites";
|
||||||
2
packages/modals-collection/src/invites/index.ts
Normal file
2
packages/modals-collection/src/invites/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { InviteCopyModal } from "./invite-copy-modal";
|
||||||
|
export { InviteCreateModal } from "./invite-create-modal";
|
||||||
8
packages/modals-collection/tsconfig.json
Normal file
8
packages/modals-collection/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@homarr/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||||
|
},
|
||||||
|
"include": ["*.ts", "src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -69,23 +69,15 @@ const permissionsSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createOldmarrImportConfigurationSchema = (existingBoardNames: string[]) =>
|
export const oldmarrImportConfigurationSchema = z.object({
|
||||||
z.object({
|
name: boardNameSchema,
|
||||||
name: boardNameSchema.refine(
|
onlyImportApps: z.boolean().default(false),
|
||||||
(value) => {
|
distinctAppsByHref: z.boolean().default(true),
|
||||||
return existingBoardNames.every((name) => name.toLowerCase().trim() !== value.toLowerCase().trim());
|
screenSize: z.enum(["lg", "md", "sm"]).default("lg"),
|
||||||
},
|
sidebarBehaviour: z.enum(["remove-items", "last-section"]).default("last-section"),
|
||||||
{
|
});
|
||||||
params: createCustomErrorParams("boardAlreadyExists"),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onlyImportApps: z.boolean().default(false),
|
|
||||||
distinctAppsByHref: z.boolean().default(true),
|
|
||||||
screenSize: z.enum(["lg", "md", "sm"]).default("lg"),
|
|
||||||
sidebarBehaviour: z.enum(["remove-items", "last-section"]).default("last-section"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type OldmarrImportConfiguration = z.infer<ReturnType<typeof createOldmarrImportConfigurationSchema>>;
|
export type OldmarrImportConfiguration = z.infer<typeof oldmarrImportConfigurationSchema>;
|
||||||
|
|
||||||
export const superRefineJsonImportFile = (value: File | null, context: z.RefinementCtx) => {
|
export const superRefineJsonImportFile = (value: File | null, context: z.RefinementCtx) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -121,7 +113,7 @@ export const superRefineJsonImportFile = (value: File | null, context: z.Refinem
|
|||||||
|
|
||||||
const importJsonFileSchema = zfd.formData({
|
const importJsonFileSchema = zfd.formData({
|
||||||
file: zfd.file().superRefine(superRefineJsonImportFile),
|
file: zfd.file().superRefine(superRefineJsonImportFile),
|
||||||
configuration: zfd.json(createOldmarrImportConfigurationSchema([])),
|
configuration: zfd.json(oldmarrImportConfigurationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const savePermissionsSchema = createSavePermissionsSchema(zodEnumFromArray(boardPermissions));
|
const savePermissionsSchema = createSavePermissionsSchema(zodEnumFromArray(boardPermissions));
|
||||||
|
|||||||
@@ -26,5 +26,5 @@ export {
|
|||||||
type BoardItemAdvancedOptions,
|
type BoardItemAdvancedOptions,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
export { passwordRequirements } from "./user";
|
export { passwordRequirements } from "./user";
|
||||||
export { createOldmarrImportConfigurationSchema, superRefineJsonImportFile } from "./board";
|
export { oldmarrImportConfigurationSchema, superRefineJsonImportFile } from "./board";
|
||||||
export type { OldmarrImportConfiguration } from "./board";
|
export type { OldmarrImportConfiguration } from "./board";
|
||||||
|
|||||||
2633
pnpm-lock.yaml
generated
2633
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -24,8 +24,8 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^9.10.0",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.6.2"
|
||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config"
|
"prettier": "@homarr/prettier-config"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user