feat: quick add app modal (#2248)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
4
packages/forms-collection/eslint.config.js
Normal file
4
packages/forms-collection/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [...baseConfig];
|
||||
1
packages/forms-collection/index.ts
Normal file
1
packages/forms-collection/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
43
packages/forms-collection/package.json
Normal file
43
packages/forms-collection/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
6
packages/forms-collection/src/index.tsx
Normal file
6
packages/forms-collection/src/index.tsx
Normal 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";
|
||||
@@ -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}
|
||||
/>
|
||||
@@ -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"
|
||||
50
packages/forms-collection/src/upload-media/upload-media.tsx
Normal file
50
packages/forms-collection/src/upload-media/upload-media.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
8
packages/forms-collection/tsconfig.json
Normal file
8
packages/forms-collection/tsconfig.json
Normal 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"]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
1
packages/modals-collection/src/apps/index.ts
Normal file
1
packages/modals-collection/src/apps/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { QuickAddAppModal } from "./quick-add-app/quick-add-app-modal";
|
||||
@@ -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");
|
||||
},
|
||||
});
|
||||
@@ -3,3 +3,4 @@ export * from "./invites";
|
||||
export * from "./groups";
|
||||
export * from "./search-engines";
|
||||
export * from "./docker";
|
||||
export * from "./apps";
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
126
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user