feat: quick add app modal (#2248)

This commit is contained in:
Manuel
2025-02-18 22:53:44 +01:00
committed by GitHub
parent 63e96230e0
commit 6420feee72
24 changed files with 359 additions and 136 deletions

View File

@@ -30,6 +30,7 @@
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/docker": "workspace:^0.1.0",
"@homarr/form": "workspace:^0.1.0",
"@homarr/forms-collection": "workspace:^0.1.0",
"@homarr/gridstack": "^1.12.0",
"@homarr/icons": "workspace:^0.1.0",
"@homarr/integrations": "workspace:^0.1.0",

View File

@@ -7,12 +7,11 @@ import type { z } from "zod";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { AppForm } from "@homarr/forms-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import type { validation } from "@homarr/validation";
import { AppForm } from "../../_form";
interface AppEditFormProps {
app: RouterOutputs["app"]["byId"];
}
@@ -58,6 +57,7 @@ export const AppEditForm = ({ app }: AppEditFormProps) => {
initialValues={app}
handleSubmit={handleSubmit}
isPending={isPending}
showBackToOverview
/>
);
};

View File

@@ -2,10 +2,10 @@ import { notFound } from "next/navigation";
import { Container, Stack, Title } from "@mantine/core";
import { auth } from "@homarr/auth/next";
import { AppNewForm } from "@homarr/forms-collection";
import { getI18n } from "@homarr/translation/server";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { AppNewForm } from "./_app-new-form";
export default async function AppNewPage() {
const session = await auth();
@@ -22,7 +22,7 @@ export default async function AppNewPage() {
<Container>
<Stack>
<Title>{t("app.page.create.title")}</Title>
<AppNewForm />
<AppNewForm showBackToOverview showCreateAnother />
</Stack>
</Container>
</>

View File

@@ -1,15 +1,11 @@
"use client";
import type { JSX } from "react";
import { Button, FileButton } from "@mantine/core";
import { Button } from "@mantine/core";
import { IconUpload } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import type { MaybePromise } from "@homarr/common/types";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { UploadMedia } from "@homarr/forms-collection";
import { useI18n } from "@homarr/translation/client";
import { supportedMediaUploadFormats } from "@homarr/validation";
export const UploadMediaButton = () => {
const t = useI18n();
@@ -27,45 +23,3 @@ export const UploadMediaButton = () => {
</UploadMedia>
);
};
interface UploadMediaProps {
children: (props: { onClick: () => void; loading: boolean }) => JSX.Element;
onSettled?: () => MaybePromise<void>;
onSuccess?: (media: { id: string; url: string }) => MaybePromise<void>;
}
export const UploadMedia = ({ children, onSettled, onSuccess }: UploadMediaProps) => {
const t = useI18n();
const { mutateAsync, isPending } = clientApi.media.uploadMedia.useMutation();
const handleFileUploadAsync = async (file: File | null) => {
if (!file) return;
const formData = new FormData();
formData.append("file", file);
await mutateAsync(formData, {
async onSuccess(mediaId) {
showSuccessNotification({
message: t("media.action.upload.notification.success.message"),
});
await onSuccess?.({
id: mediaId,
url: `/api/user-medias/${mediaId}`,
});
},
onError() {
showErrorNotification({
message: t("media.action.upload.notification.error.message"),
});
},
async onSettled() {
await onSettled?.();
},
});
};
return (
<FileButton onChange={handleFileUploadAsync} accept={supportedMediaUploadFormats.join(",")}>
{({ onClick }) => children({ onClick, loading: isPending })}
</FileButton>
);
};

View File

@@ -9,12 +9,11 @@ import type { z } from "zod";
import { clientApi } from "@homarr/api/client";
import { searchEngineTypes } from "@homarr/definitions";
import { useZodForm } from "@homarr/form";
import { IconPicker } from "@homarr/forms-collection";
import type { TranslationFunction } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
import { validation } from "@homarr/validation";
import { IconPicker } from "~/components/icons/picker/icon-picker";
type FormType = z.infer<typeof validation.searchEngine.manage>;
interface SearchEngineFormProps {

View File

@@ -111,16 +111,19 @@ export const appRouter = createTRPCRouter({
create: permissionRequiredProcedure
.requiresPermission("app-create")
.input(validation.app.manage)
.output(z.void())
.output(z.object({ appId: z.string() }))
.meta({ openapi: { method: "POST", path: "/api/apps", tags: ["apps"], protect: true } })
.mutation(async ({ ctx, input }) => {
const id = createId();
await ctx.db.insert(apps).values({
id: createId(),
id,
name: input.name,
description: input.description,
iconUrl: input.iconUrl,
href: input.href,
});
return { appId: id };
}),
createMany: permissionRequiredProcedure
.requiresPermission("app-create")

View File

@@ -0,0 +1,4 @@
import baseConfig from "@homarr/eslint-config/base";
/** @type {import('typescript-eslint').Config} */
export default [...baseConfig];

View File

@@ -0,0 +1 @@
export * from "./src";

View File

@@ -0,0 +1,43 @@
{
"name": "@homarr/forms-collection",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"exports": {
".": "./index.ts"
},
"typesVersions": {
"*": {
"*": [
"src/*"
]
}
},
"scripts": {
"clean": "rm -rf .turbo node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit"
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/api": "workspace:^0.1.0",
"@homarr/auth": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",
"@homarr/form": "workspace:^0.1.0",
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^7.17.0",
"react": "19.0.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.20.1",
"typescript": "^5.7.3"
}
}

View File

@@ -24,7 +24,7 @@ import { clientApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { useScopedI18n } from "@homarr/translation/client";
import { UploadMedia } from "~/app/[locale]/manage/medias/_actions/upload-media";
import { UploadMedia } from "../upload-media/upload-media";
import classes from "./icon-picker.module.css";
interface IconPickerProps {
@@ -124,12 +124,7 @@ export const IconPicker = ({ value: propsValue, onChange, error, onFocus, onBlur
<InputBase
flex={1}
rightSection={<Combobox.Chevron />}
leftSection={
previewUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={previewUrl} alt="" style={{ width: 20, height: 20 }} />
) : null
}
leftSection={previewUrl ? <img src={previewUrl} alt="" style={{ width: 20, height: 20 }} /> : null}
value={search}
onChange={(event) => {
combobox.openDropdown();

View File

@@ -0,0 +1,6 @@
export * from "./new-app/_app-new-form";
export * from "./new-app/_form";
export * from "./icon-picker/icon-picker";
export * from "./upload-media/upload-media";

View File

@@ -10,9 +10,15 @@ import { showErrorNotification, showSuccessNotification } from "@homarr/notifica
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import type { validation } from "@homarr/validation";
import { AppForm } from "../_form";
import { AppForm } from "./_form";
export const AppNewForm = () => {
export const AppNewForm = ({
showCreateAnother,
showBackToOverview,
}: {
showCreateAnother: boolean;
showBackToOverview: boolean;
}) => {
const tScoped = useScopedI18n("app.page.create.notification");
const t = useI18n();
const router = useRouter();
@@ -52,8 +58,9 @@ export const AppNewForm = () => {
<AppForm
buttonLabels={{
submit: t("common.action.create"),
submitAndCreateAnother: t("common.action.createAnother"),
submitAndCreateAnother: showCreateAnother ? t("common.action.createAnother") : undefined,
}}
showBackToOverview={showBackToOverview}
handleSubmit={handleSubmit}
isPending={isPending}
/>

View File

@@ -9,11 +9,12 @@ import { useZodForm } from "@homarr/form";
import { useI18n } from "@homarr/translation/client";
import { validation } from "@homarr/validation";
import { IconPicker } from "~/components/icons/picker/icon-picker";
import { IconPicker } from "../icon-picker/icon-picker";
type FormType = z.infer<typeof validation.app.manage>;
interface AppFormProps {
showBackToOverview: boolean;
buttonLabels: {
submit: string;
submitAndCreateAnother?: string;
@@ -25,6 +26,7 @@ interface AppFormProps {
export const AppForm = ({
buttonLabels,
showBackToOverview,
handleSubmit: originalHandleSubmit,
initialValues,
isPending,
@@ -61,9 +63,11 @@ export const AppForm = ({
<TextInput {...form.getInputProps("href")} label={t("app.field.url.label")} />
<Group justify="end">
<Button variant="default" component={Link} href="/manage/apps">
{t("common.action.backToOverview")}
</Button>
{showBackToOverview && (
<Button variant="default" component={Link} href="/manage/apps">
{t("common.action.backToOverview")}
</Button>
)}
{buttonLabels.submitAndCreateAnother && (
<Button
type="submit"

View File

@@ -0,0 +1,50 @@
import type { JSX } from "react";
import { FileButton } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import type { MaybePromise } from "@homarr/common/types";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client";
import { supportedMediaUploadFormats } from "@homarr/validation";
interface UploadMediaProps {
children: (props: { onClick: () => void; loading: boolean }) => JSX.Element;
onSettled?: () => MaybePromise<void>;
onSuccess?: (media: { id: string; url: string }) => MaybePromise<void>;
}
export const UploadMedia = ({ children, onSettled, onSuccess }: UploadMediaProps) => {
const t = useI18n();
const { mutateAsync, isPending } = clientApi.media.uploadMedia.useMutation();
const handleFileUploadAsync = async (file: File | null) => {
if (!file) return;
const formData = new FormData();
formData.append("file", file);
await mutateAsync(formData, {
async onSuccess(mediaId) {
showSuccessNotification({
message: t("media.action.upload.notification.success.message"),
});
await onSuccess?.({
id: mediaId,
url: `/api/user-medias/${mediaId}`,
});
},
onError() {
showErrorNotification({
message: t("media.action.upload.notification.error.message"),
});
},
async onSettled() {
await onSettled?.();
},
});
};
return (
<FileButton onChange={handleFileUploadAsync} accept={supportedMediaUploadFormats.join(",")}>
{({ onClick }) => children({ onClick, loading: isPending })}
</FileButton>
);
};

View File

@@ -0,0 +1,8 @@
{
"extends": "@homarr/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src", "index.tsx"],
"exclude": ["node_modules"]
}

View File

@@ -25,6 +25,7 @@
"@homarr/api": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",
"@homarr/form": "workspace:^0.1.0",
"@homarr/forms-collection": "workspace:^0.1.0",
"@homarr/modals": "workspace:^0.1.0",
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/old-import": "workspace:^0.1.0",

View File

@@ -0,0 +1 @@
export { QuickAddAppModal } from "./quick-add-app/quick-add-app-modal";

View File

@@ -0,0 +1,56 @@
import type { z } from "zod";
import { clientApi } from "@homarr/api/client";
import { AppForm } from "@homarr/forms-collection";
import { createModal } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import type { validation } from "@homarr/validation";
interface QuickAddAppModalProps {
onClose: (createdAppId: string) => Promise<void>;
}
export const QuickAddAppModal = createModal<QuickAddAppModalProps>(({ actions, innerProps }) => {
const tScoped = useScopedI18n("app.page.create.notification");
const t = useI18n();
const { mutate, isPending } = clientApi.app.create.useMutation({
onError: () => {
showErrorNotification({
title: tScoped("error.title"),
message: tScoped("error.message"),
});
},
});
const handleSubmit = (values: z.infer<typeof validation.app.manage>) => {
mutate(values, {
async onSuccess({ appId }) {
showSuccessNotification({
title: tScoped("success.title"),
message: tScoped("success.message"),
});
await innerProps.onClose(appId);
actions.closeModal();
},
});
};
return (
<AppForm
buttonLabels={{
submit: t("board.action.quickCreateApp.modal.createAndUse"),
submitAndCreateAnother: undefined,
}}
showBackToOverview={false}
handleSubmit={handleSubmit}
isPending={isPending}
/>
);
}).withOptions({
defaultTitle(t) {
return t("board.action.quickCreateApp.modal.title");
},
});

View File

@@ -3,3 +3,4 @@ export * from "./invites";
export * from "./groups";
export * from "./search-engines";
export * from "./docker";
export * from "./apps";

View File

@@ -1608,7 +1608,8 @@
},
"app": {
"noData": "No app found",
"description": "Click <here></here> to create a new app"
"description": "Click <here></here> to create a new app",
"quickCreate": "Create app on the fly"
},
"error": {
"noIntegration": "No integration selected",
@@ -2007,6 +2008,12 @@
}
}
}
},
"quickCreateApp": {
"modal": {
"title": "Create app on the fly",
"createAndUse": "Create and use"
}
}
},
"field": {

View File

@@ -35,6 +35,7 @@
"@homarr/form": "workspace:^0.1.0",
"@homarr/integrations": "workspace:^0.1.0",
"@homarr/modals": "workspace:^0.1.0",
"@homarr/modals-collection": "workspace:^0.1.0",
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/redis": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",

View File

@@ -3,11 +3,13 @@
import { memo, useMemo } from "react";
import Link from "next/link";
import type { SelectProps } from "@mantine/core";
import { Anchor, Group, Loader, Select, Text } from "@mantine/core";
import { IconCheck } from "@tabler/icons-react";
import { Anchor, Button, Group, Loader, Select, SimpleGrid, Text } from "@mantine/core";
import { IconCheck, IconRocket } from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useModalAction } from "@homarr/modals";
import { QuickAddAppModal } from "@homarr/modals-collection";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
@@ -18,7 +20,9 @@ export const WidgetAppInput = ({ property, kind }: CommonWidgetInputProps<"app">
const t = useI18n();
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();
const { data: apps, isPending } = clientApi.app.selectable.useQuery();
const { data: apps, isPending, refetch } = clientApi.app.selectable.useQuery();
const { openModal } = useModalAction(QuickAddAppModal);
const currentApp = useMemo(
() => apps?.find((app) => app.id === form.values.options.appId),
@@ -26,34 +30,53 @@ export const WidgetAppInput = ({ property, kind }: CommonWidgetInputProps<"app">
);
return (
<Select
label={tInput("label")}
searchable
limit={10}
leftSection={<MemoizedLeftSection isPending={isPending} currentApp={currentApp} />}
nothingFoundMessage={t("widget.common.app.noData")}
renderOption={renderSelectOption}
data={
apps?.map((app) => ({
label: app.name,
value: app.id,
iconUrl: app.iconUrl,
})) ?? []
}
inputWrapperOrder={["label", "input", "description", "error"]}
description={
<Text size="xs">
{t.rich("widget.common.app.description", {
here: () => (
<Anchor size="xs" component={Link} target="_blank" href="/manage/apps/new">
{t("common.here")}
</Anchor>
),
})}
</Text>
}
{...form.getInputProps(`options.${property}`)}
/>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing={{ base: "md" }} style={{ alignItems: "center" }}>
<Select
label={tInput("label")}
searchable
limit={10}
leftSection={<MemoizedLeftSection isPending={isPending} currentApp={currentApp} />}
nothingFoundMessage={t("widget.common.app.noData")}
renderOption={renderSelectOption}
data={
apps?.map((app) => ({
label: app.name,
value: app.id,
iconUrl: app.iconUrl,
})) ?? []
}
inputWrapperOrder={["label", "input", "description", "error"]}
description={
<Text size="xs">
{t.rich("widget.common.app.description", {
here: () => (
<Anchor size="xs" component={Link} target="_blank" href="/manage/apps/new">
{t("common.here")}
</Anchor>
),
})}
</Text>
}
styles={{ root: { flex: "1" } }}
{...form.getInputProps(`options.${property}`)}
/>
<Button
mt={3}
rightSection={<IconRocket size="1.5rem" />}
variant="default"
onClick={() =>
openModal({
// eslint-disable-next-line no-restricted-syntax
async onClose(createdAppId) {
await refetch();
form.setFieldValue(`options.${property}`, createdAppId);
},
})
}
>
{t("widget.common.app.quickCreate")}
</Button>
</SimpleGrid>
);
};

126
pnpm-lock.yaml generated
View File

@@ -127,6 +127,9 @@ importers:
'@homarr/form':
specifier: workspace:^0.1.0
version: link:../../packages/form
'@homarr/forms-collection':
specifier: workspace:^0.1.0
version: link:../../packages/forms-collection
'@homarr/gridstack':
specifier: ^1.12.0
version: 1.12.0
@@ -216,16 +219,16 @@ importers:
version: 5.66.7(@tanstack/react-query@5.66.7(react@19.0.0))(next@15.1.7(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.85.0))(react@19.0.0)
'@trpc/client':
specifier: next
version: 11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3)
version: 11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3)
'@trpc/next':
specifier: next
version: 11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/react-query@11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(next@15.1.7(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.85.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
version: 11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/react-query@11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(next@15.1.7(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.85.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
'@trpc/react-query':
specifier: next
version: 11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
version: 11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
'@trpc/server':
specifier: next
version: 11.0.0-rc.781(typescript@5.7.3)
version: 11.0.0-rc.788(typescript@5.7.3)
'@xterm/addon-canvas':
specifier: ^0.7.0
version: 0.7.0(@xterm/xterm@5.5.0)
@@ -580,13 +583,13 @@ importers:
version: link:../validation
'@trpc/client':
specifier: next
version: 11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3)
version: 11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3)
'@trpc/react-query':
specifier: next
version: 11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
version: 11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
'@trpc/server':
specifier: next
version: 11.0.0-rc.781(typescript@5.7.3)
version: 11.0.0-rc.788(typescript@5.7.3)
lodash.clonedeep:
specifier: ^4.5.0
version: 4.5.0
@@ -607,7 +610,7 @@ importers:
version: 2.2.2
trpc-to-openapi:
specifier: ^2.1.3
version: 2.1.3(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(zod-openapi@2.19.0(zod@3.24.2))(zod@3.24.2)
version: 2.1.3(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(zod-openapi@2.19.0(zod@3.24.2))(zod@3.24.2)
zod:
specifier: ^3.24.2
version: 3.24.2
@@ -1148,6 +1151,55 @@ importers:
specifier: ^5.7.3
version: 5.7.3
packages/forms-collection:
dependencies:
'@homarr/api':
specifier: workspace:^0.1.0
version: link:../api
'@homarr/auth':
specifier: workspace:^0.1.0
version: link:../auth
'@homarr/common':
specifier: workspace:^0.1.0
version: link:../common
'@homarr/form':
specifier: workspace:^0.1.0
version: link:../form
'@homarr/notifications':
specifier: workspace:^0.1.0
version: link:../notifications
'@homarr/translation':
specifier: workspace:^0.1.0
version: link:../translation
'@homarr/validation':
specifier: workspace:^0.1.0
version: link:../validation
'@mantine/core':
specifier: ^7.17.0
version: 7.17.0(@mantine/hooks@7.17.0(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react:
specifier: 19.0.0
version: 19.0.0
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@homarr/eslint-config':
specifier: workspace:^0.2.0
version: link:../../tooling/eslint
'@homarr/prettier-config':
specifier: workspace:^0.1.0
version: link:../../tooling/prettier
'@homarr/tsconfig':
specifier: workspace:^0.1.0
version: link:../../tooling/typescript
eslint:
specifier: ^9.20.1
version: 9.20.1
typescript:
specifier: ^5.7.3
version: 5.7.3
packages/icons:
dependencies:
'@homarr/common':
@@ -1319,6 +1371,9 @@ importers:
'@homarr/form':
specifier: workspace:^0.1.0
version: link:../form
'@homarr/forms-collection':
specifier: workspace:^0.1.0
version: link:../forms-collection
'@homarr/modals':
specifier: workspace:^0.1.0
version: link:../modals
@@ -1947,6 +2002,9 @@ importers:
'@homarr/modals':
specifier: workspace:^0.1.0
version: link:../modals
'@homarr/modals-collection':
specifier: workspace:^0.1.0
version: link:../modals-collection
'@homarr/notifications':
specifier: workspace:^0.1.0
version: link:../notifications
@@ -4398,19 +4456,19 @@ packages:
tree-sitter:
optional: true
'@trpc/client@11.0.0-rc.781':
resolution: {integrity: sha512-zAXsTPzk4RnvxSg6oOs4NSjorHufxIZ9rU6jGCHW4JrrIQP34RwruufUViC+xwrNLz17wIjKNvraQDT+cjowmw==}
'@trpc/client@11.0.0-rc.788':
resolution: {integrity: sha512-88+jCRr7RtoAbJPOKQ2jxE+KSuPfenohPf7wddFBGWQGTYFUifPpJgk9ihWPGMxEboFBqtfgXqZMrvmgUiZicw==}
peerDependencies:
'@trpc/server': 11.0.0-rc.781+df4d4ede3
'@trpc/server': 11.0.0-rc.788+a8e9f72c0
typescript: '>=5.7.2'
'@trpc/next@11.0.0-rc.781':
resolution: {integrity: sha512-rDV5Ult/GA3g7rNR4JRes8zReTsL/96K3sQH+79uY3G3HASaKb1U3nFjiB0/toI/4EoHW0jFSx/WDsTrKIqrsw==}
'@trpc/next@11.0.0-rc.788':
resolution: {integrity: sha512-cNjsv87VeWhqg2qUM7EdGJxixcVRcPrurYcnZY7kJOGf+Iyv+ybxIZhNA7smLyvih0i1sqUt8bFyWw1veTEzWA==}
peerDependencies:
'@tanstack/react-query': ^5.59.15
'@trpc/client': 11.0.0-rc.781+df4d4ede3
'@trpc/react-query': 11.0.0-rc.781+df4d4ede3
'@trpc/server': 11.0.0-rc.781+df4d4ede3
'@trpc/client': 11.0.0-rc.788+a8e9f72c0
'@trpc/react-query': 11.0.0-rc.788+a8e9f72c0
'@trpc/server': 11.0.0-rc.788+a8e9f72c0
next: '*'
react: '>=16.8.0'
react-dom: '>=16.8.0'
@@ -4421,18 +4479,18 @@ packages:
'@trpc/react-query':
optional: true
'@trpc/react-query@11.0.0-rc.781':
resolution: {integrity: sha512-BLd7JfFCJ1fNigpNek0fMARm4fbk7BbheMY8/jNggnE9CkcEqnsOzq+wP6ji8kX8c4NVbxdF5L4PQmEA+3uaNA==}
'@trpc/react-query@11.0.0-rc.788':
resolution: {integrity: sha512-0cU3O6hqPH9FwllH5K2b6yZcYdv5aAoZ3gh4j57sDqyI115XjS+a1fBd9SgZun61b9YCR4OsbaEiFJZKmsBMDQ==}
peerDependencies:
'@tanstack/react-query': ^5.62.8
'@trpc/client': 11.0.0-rc.781+df4d4ede3
'@trpc/server': 11.0.0-rc.781+df4d4ede3
'@trpc/client': 11.0.0-rc.788+a8e9f72c0
'@trpc/server': 11.0.0-rc.788+a8e9f72c0
react: '>=18.2.0'
react-dom: '>=18.2.0'
typescript: '>=5.7.2'
'@trpc/server@11.0.0-rc.781':
resolution: {integrity: sha512-KmZDE2Qa+zfKTIbvsNNZESUK0Zoeo0k8/ZPPMzTh0+Tm7L6yeFNk+i4npC1i8Nxr8pxaJUIG5Jab7Rv00hsoHQ==}
'@trpc/server@11.0.0-rc.788':
resolution: {integrity: sha512-ifsIgRSUXJFSqS7v+XUvt4pcIG2PDw1cKmvL40MhrgPRxG+VBlizxDLk3xXnXrkYVrw6jlDCik5oi+ZMXkHiDA==}
peerDependencies:
typescript: '>=5.7.2'
@@ -12383,33 +12441,33 @@ snapshots:
tree-sitter: 0.22.1
optional: true
'@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3)':
'@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3)':
dependencies:
'@trpc/server': 11.0.0-rc.781(typescript@5.7.3)
'@trpc/server': 11.0.0-rc.788(typescript@5.7.3)
typescript: 5.7.3
'@trpc/next@11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/react-query@11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(next@15.1.7(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.85.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)':
'@trpc/next@11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/react-query@11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(next@15.1.7(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.85.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)':
dependencies:
'@trpc/client': 11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3)
'@trpc/server': 11.0.0-rc.781(typescript@5.7.3)
'@trpc/client': 11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3)
'@trpc/server': 11.0.0-rc.788(typescript@5.7.3)
next: 15.1.7(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.85.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
typescript: 5.7.3
optionalDependencies:
'@tanstack/react-query': 5.66.7(react@19.0.0)
'@trpc/react-query': 11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
'@trpc/react-query': 11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
'@trpc/react-query@11.0.0-rc.781(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)':
'@trpc/react-query@11.0.0-rc.788(@tanstack/react-query@5.66.7(react@19.0.0))(@trpc/client@11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3))(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)':
dependencies:
'@tanstack/react-query': 5.66.7(react@19.0.0)
'@trpc/client': 11.0.0-rc.781(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(typescript@5.7.3)
'@trpc/server': 11.0.0-rc.781(typescript@5.7.3)
'@trpc/client': 11.0.0-rc.788(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(typescript@5.7.3)
'@trpc/server': 11.0.0-rc.788(typescript@5.7.3)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
typescript: 5.7.3
'@trpc/server@11.0.0-rc.781(typescript@5.7.3)':
'@trpc/server@11.0.0-rc.788(typescript@5.7.3)':
dependencies:
typescript: 5.7.3
@@ -18054,9 +18112,9 @@ snapshots:
triple-beam@1.4.1: {}
trpc-to-openapi@2.1.3(@trpc/server@11.0.0-rc.781(typescript@5.7.3))(zod-openapi@2.19.0(zod@3.24.2))(zod@3.24.2):
trpc-to-openapi@2.1.3(@trpc/server@11.0.0-rc.788(typescript@5.7.3))(zod-openapi@2.19.0(zod@3.24.2))(zod@3.24.2):
dependencies:
'@trpc/server': 11.0.0-rc.781(typescript@5.7.3)
'@trpc/server': 11.0.0-rc.788(typescript@5.7.3)
co-body: 6.2.0
h3: 1.13.0
openapi3-ts: 4.4.0