diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json
index 73250ed51..6543052c0 100644
--- a/apps/nextjs/package.json
+++ b/apps/nextjs/package.json
@@ -22,6 +22,7 @@
"@homarr/form": "workspace:^0.1.0",
"@homarr/gridstack": "^1.0.0",
"@homarr/log": "workspace:^",
+ "@homarr/modals": "workspace:^0.1.0",
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/spotlight": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
@@ -46,7 +47,6 @@
"chroma-js": "^2.4.2",
"dayjs": "^1.11.10",
"jotai": "^2.7.1",
- "mantine-modal-manager": "^7.6.2",
"next": "^14.1.3",
"postcss-preset-mantine": "^1.13.0",
"react": "18.2.0",
diff --git a/apps/nextjs/src/app/[locale]/(main)/apps/_app-delete-button.tsx b/apps/nextjs/src/app/[locale]/(main)/apps/_app-delete-button.tsx
index eb34d6729..5494e7386 100644
--- a/apps/nextjs/src/app/[locale]/(main)/apps/_app-delete-button.tsx
+++ b/apps/nextjs/src/app/[locale]/(main)/apps/_app-delete-button.tsx
@@ -4,6 +4,7 @@ import { useCallback } from "react";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
+import { useConfirmModal } from "@homarr/modals";
import {
showErrorNotification,
showSuccessNotification,
@@ -12,7 +13,6 @@ import { useScopedI18n } from "@homarr/translation/client";
import { ActionIcon, IconTrash } from "@homarr/ui";
import { revalidatePathAction } from "../../../revalidatePathAction";
-import { modalEvents } from "../../modals";
interface AppDeleteButtonProps {
app: RouterOutputs["app"]["all"][number];
@@ -20,10 +20,11 @@ interface AppDeleteButtonProps {
export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => {
const t = useScopedI18n("app.page.delete");
+ const { openConfirmModal } = useConfirmModal();
const { mutate, isPending } = clientApi.app.delete.useMutation();
const onClick = useCallback(() => {
- modalEvents.openConfirmModal({
+ openConfirmModal({
title: t("title"),
children: t("message", app),
onConfirm: () => {
@@ -47,7 +48,7 @@ export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => {
);
},
});
- }, [app, mutate, t]);
+ }, [app, mutate, t, openConfirmModal]);
return (
{
const t = useScopedI18n("integration.page.delete");
const router = useRouter();
+ const { openConfirmModal } = useConfirmModal();
const { mutateAsync, isPending } = clientApi.integration.delete.useMutation();
return (
@@ -32,7 +33,7 @@ export const DeleteIntegrationActionButton = ({
variant="subtle"
color="red"
onClick={() => {
- modalEvents.openConfirmModal({
+ openConfirmModal({
title: t("title"),
children: t("message", integration),
onConfirm: () => {
diff --git a/apps/nextjs/src/app/[locale]/(main)/integrations/edit/[id]/_integration-edit-form.tsx b/apps/nextjs/src/app/[locale]/(main)/integrations/edit/[id]/_integration-edit-form.tsx
index 50670d878..bd804bc91 100644
--- a/apps/nextjs/src/app/[locale]/(main)/integrations/edit/[id]/_integration-edit-form.tsx
+++ b/apps/nextjs/src/app/[locale]/(main)/integrations/edit/[id]/_integration-edit-form.tsx
@@ -10,6 +10,7 @@ import {
getDefaultSecretKinds,
} from "@homarr/definitions";
import { useForm, zodResolver } from "@homarr/form";
+import { useConfirmModal } from "@homarr/modals";
import {
showErrorNotification,
showSuccessNotification,
@@ -19,7 +20,6 @@ import { Button, Fieldset, Group, Stack, TextInput } from "@homarr/ui";
import type { z } from "@homarr/validation";
import { validation } from "@homarr/validation";
-import { modalEvents } from "~/app/[locale]/modals";
import { SecretCard } from "../../_integration-secret-card";
import { IntegrationSecretInput } from "../../_integration-secret-inputs";
import {
@@ -35,9 +35,10 @@ interface EditIntegrationForm {
export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
const t = useI18n();
+ const { openConfirmModal } = useConfirmModal();
const secretsKinds =
- getAllSecretKindOptions(integration.kind).find((x) =>
- integration.secrets.every((y) => x.includes(y.kind)),
+ getAllSecretKindOptions(integration.kind).find((secretKinds) =>
+ integration.secrets.every((secret) => secretKinds.includes(secret.kind)),
) ?? getDefaultSecretKinds(integration.kind);
const initialFormValues = {
name: integration.name,
@@ -99,7 +100,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
};
return (
-
diff --git a/apps/nextjs/src/app/[locale]/widgets/[kind]/_dimension-modal.tsx b/apps/nextjs/src/app/[locale]/widgets/[kind]/_dimension-modal.tsx
index b5223e7b0..17bd28fe7 100644
--- a/apps/nextjs/src/app/[locale]/widgets/[kind]/_dimension-modal.tsx
+++ b/apps/nextjs/src/app/[locale]/widgets/[kind]/_dimension-modal.tsx
@@ -1,8 +1,7 @@
"use client";
-import type { ManagedModal } from "mantine-modal-manager";
-
import { useForm } from "@homarr/form";
+import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { Button, Group, InputWrapper, Slider, Stack } from "@homarr/ui";
@@ -11,49 +10,50 @@ interface InnerProps {
setDimensions: (dimensions: Dimensions) => void;
}
-export const PreviewDimensionsModal: ManagedModal = ({
- actions,
- innerProps,
-}) => {
- const t = useI18n();
- const form = useForm({
- initialValues: innerProps.dimensions,
- });
+export const PreviewDimensionsModal = createModal(
+ ({ actions, innerProps }) => {
+ const t = useI18n();
+ const form = useForm({
+ initialValues: innerProps.dimensions,
+ });
- const handleSubmit = (values: Dimensions) => {
- innerProps.setDimensions(values);
- actions.closeModal();
- };
+ const handleSubmit = (values: Dimensions) => {
+ innerProps.setDimensions(values);
+ actions.closeModal();
+ };
- return (
-
- );
-};
+ return (
+
+ );
+ },
+).withOptions({
+ defaultTitle: (t) => t("widgetPreview.dimensions.title"),
+});
export interface Dimensions {
width: number;
diff --git a/apps/nextjs/src/components/board/items/item-actions.tsx b/apps/nextjs/src/components/board/items/item-actions.tsx
index 7ad175449..c1a0a0553 100644
--- a/apps/nextjs/src/components/board/items/item-actions.tsx
+++ b/apps/nextjs/src/components/board/items/item-actions.tsx
@@ -41,8 +41,12 @@ export const useItemActions = () => {
({ kind }: CreateItem) => {
updateBoard((previous) => {
const lastSection = previous.sections
- .filter((s): s is EmptySection => s.kind === "empty")
- .sort((a, b) => b.position - a.position)[0];
+ .filter(
+ (section): section is EmptySection => section.kind === "empty",
+ )
+ .sort(
+ (sectionA, sectionB) => sectionB.position - sectionA.position,
+ )[0];
if (!lastSection) return previous;
diff --git a/apps/nextjs/src/components/board/items/item-select-modal.tsx b/apps/nextjs/src/components/board/items/item-select-modal.tsx
index 087c6bea8..1d5c86b7b 100644
--- a/apps/nextjs/src/components/board/items/item-select-modal.tsx
+++ b/apps/nextjs/src/components/board/items/item-select-modal.tsx
@@ -1,6 +1,5 @@
-import type { ManagedModal } from "mantine-modal-manager";
-
import type { WidgetKind } from "@homarr/definitions";
+import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { Button, Card, Center, Grid, Stack, Text } from "@homarr/ui";
@@ -9,9 +8,7 @@ import { widgetImports } from "../../../../../../packages/widgets/src";
import type { WidgetDefinition } from "../../../../../../packages/widgets/src/definition";
import { useItemActions } from "./item-actions";
-export const ItemSelectModal: ManagedModal> = ({
- actions,
-}) => {
+export const ItemSelectModal = createModal(({ actions }) => {
return (
{objectEntries(widgetImports).map(([key, value]) => {
@@ -26,7 +23,10 @@ export const ItemSelectModal: ManagedModal> = ({
})}
);
-};
+}).withOptions({
+ defaultTitle: (t) => t("item.create.title"),
+ size: "xl",
+});
const WidgetItem = ({
kind,
diff --git a/apps/nextjs/src/components/board/modals/board-rename-modal.tsx b/apps/nextjs/src/components/board/modals/board-rename-modal.tsx
index 4bb594e35..e22ab8333 100644
--- a/apps/nextjs/src/components/board/modals/board-rename-modal.tsx
+++ b/apps/nextjs/src/components/board/modals/board-rename-modal.tsx
@@ -1,9 +1,8 @@
"use client";
-import type { ManagedModal } from "mantine-modal-manager";
-
import { clientApi } from "@homarr/api/client";
import { useForm } from "@homarr/form";
+import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { Button, Group, Stack, TextInput } from "@homarr/ui";
import type { validation, z } from "@homarr/validation";
@@ -14,58 +13,60 @@ interface InnerProps {
onSuccess?: (name: string) => void;
}
-export const BoardRenameModal: ManagedModal = ({
- actions,
- innerProps,
-}) => {
- const utils = clientApi.useUtils();
- const t = useI18n();
- const { mutate, isPending } = clientApi.board.rename.useMutation({
- onSettled() {
- void utils.board.byName.invalidate({ name: innerProps.previousName });
- void utils.board.default.invalidate();
- },
- });
- const form = useForm({
- initialValues: {
- name: innerProps.previousName,
- },
- });
-
- const handleSubmit = (values: FormType) => {
- mutate(
- {
- id: innerProps.id,
- name: values.name,
+export const BoardRenameModal = createModal(
+ ({ actions, innerProps }) => {
+ const utils = clientApi.useUtils();
+ const t = useI18n();
+ const { mutate, isPending } = clientApi.board.rename.useMutation({
+ onSettled() {
+ void utils.board.byName.invalidate({ name: innerProps.previousName });
+ void utils.board.default.invalidate();
},
- {
- onSuccess: () => {
- actions.closeModal();
- innerProps.onSuccess?.(values.name);
+ });
+ const form = useForm({
+ initialValues: {
+ name: innerProps.previousName,
+ },
+ });
+
+ const handleSubmit = (values: FormType) => {
+ mutate(
+ {
+ id: innerProps.id,
+ name: values.name,
},
- },
- );
- };
+ {
+ onSuccess: () => {
+ actions.closeModal();
+ innerProps.onSuccess?.(values.name);
+ },
+ },
+ );
+ };
- return (
-
- );
-};
+ return (
+
+ );
+ },
+).withOptions({
+ defaultTitle: (t) =>
+ t("board.setting.section.dangerZone.action.rename.modal.title"),
+});
type FormType = Omit, "id">;
diff --git a/apps/nextjs/src/components/board/sections/category/category-actions.ts b/apps/nextjs/src/components/board/sections/category/category-actions.ts
index bef680493..72935a077 100644
--- a/apps/nextjs/src/components/board/sections/category/category-actions.ts
+++ b/apps/nextjs/src/components/board/sections/category/category-actions.ts
@@ -80,10 +80,10 @@ export const useCategoryActions = () => {
updateBoard((previous) => {
const lastSection = previous.sections
.filter(
- (x): x is CategorySection | EmptySection =>
- x.kind === "empty" || x.kind === "category",
+ (section): section is CategorySection | EmptySection =>
+ section.kind === "empty" || section.kind === "category",
)
- .sort((a, b) => b.position - a.position)
+ .sort((sectionA, sectionB) => sectionB.position - sectionA.position)
.at(0);
if (!lastSection) return previous;
diff --git a/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx b/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx
index 35f975028..119a95903 100644
--- a/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx
+++ b/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx
@@ -1,6 +1,5 @@
-import type { ManagedModal } from "mantine-modal-manager";
-
import { useForm } from "@homarr/form";
+import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { Button, Group, Stack, TextInput } from "@homarr/ui";
@@ -15,42 +14,41 @@ interface InnerProps {
onSuccess: (category: Category) => void;
}
-export const CategoryEditModal: ManagedModal = ({
- actions,
- innerProps,
-}) => {
- const t = useI18n();
- const form = useForm({
- initialValues: {
- name: innerProps.category.name,
- },
- });
+export const CategoryEditModal = createModal(
+ ({ actions, innerProps }) => {
+ const t = useI18n();
+ const form = useForm({
+ initialValues: {
+ name: innerProps.category.name,
+ },
+ });
- return (
-
- );
-};
+ return (
+
+ );
+ },
+).withOptions({});
diff --git a/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx b/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx
index 697e99233..8106334f7 100644
--- a/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx
+++ b/apps/nextjs/src/components/board/sections/category/category-menu-actions.tsx
@@ -1,23 +1,24 @@
import { useCallback } from "react";
import { createId } from "@homarr/db/client";
+import { useConfirmModal, useModalAction } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import type { CategorySection } from "~/app/[locale]/boards/_types";
-import { modalEvents } from "~/app/[locale]/modals";
import { useCategoryActions } from "./category-actions";
+import { CategoryEditModal } from "./category-edit-modal";
export const useCategoryMenuActions = (category: CategorySection) => {
+ const { openModal } = useModalAction(CategoryEditModal);
+ const { openConfirmModal } = useConfirmModal();
const { addCategory, moveCategory, removeCategory, renameCategory } =
useCategoryActions();
const t = useI18n();
const createCategoryAtPosition = useCallback(
(position: number) => {
- modalEvents.openManagedModal({
- title: t("section.category.create.title"),
- modal: "categoryEditModal",
- innerProps: {
+ openModal(
+ {
category: {
id: createId(),
name: t("section.category.create.title"),
@@ -30,9 +31,12 @@ export const useCategoryMenuActions = (category: CategorySection) => {
},
submitLabel: t("section.category.create.submit"),
},
- });
+ {
+ title: (t) => t("section.category.create.title"),
+ },
+ );
},
- [addCategory, t],
+ [addCategory, t, openModal],
);
// creates a new category above the current
@@ -63,7 +67,7 @@ export const useCategoryMenuActions = (category: CategorySection) => {
// Removes the current category
const remove = useCallback(() => {
- modalEvents.openConfirmModal({
+ openConfirmModal({
title: t("section.category.remove.title"),
children: t("section.category.remove.message", {
name: category.name,
@@ -73,17 +77,12 @@ export const useCategoryMenuActions = (category: CategorySection) => {
id: category.id,
});
},
- confirmProps: {
- color: "red",
- },
});
- }, [category.id, category.name, removeCategory, t]);
+ }, [category.id, category.name, removeCategory, t, openConfirmModal]);
- const edit = () => {
- modalEvents.openManagedModal({
- modal: "categoryEditModal",
- title: t("section.category.edit.title"),
- innerProps: {
+ const edit = useCallback(() => {
+ openModal(
+ {
category,
submitLabel: t("section.category.edit.submit"),
onSuccess: (category) => {
@@ -93,8 +92,11 @@ export const useCategoryMenuActions = (category: CategorySection) => {
});
},
},
- });
- };
+ {
+ title: (t) => t("section.category.edit.title"),
+ },
+ );
+ }, [category, openModal, renameCategory, t]);
return {
addCategoryAbove,
diff --git a/apps/nextjs/src/components/board/sections/content.tsx b/apps/nextjs/src/components/board/sections/content.tsx
index a2efbb741..9aafe8e7a 100644
--- a/apps/nextjs/src/components/board/sections/content.tsx
+++ b/apps/nextjs/src/components/board/sections/content.tsx
@@ -6,6 +6,7 @@ import { useElementSize } from "@mantine/hooks";
import cx from "clsx";
import { useAtomValue } from "jotai";
+import { useConfirmModal, useModalAction } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client";
import {
ActionIcon,
@@ -20,11 +21,11 @@ import {
loadWidgetDynamic,
reduceWidgetOptionsWithDefaultValues,
useServerDataFor,
+ WidgetEditModal,
} from "@homarr/widgets";
import { useRequiredBoard } from "~/app/[locale]/boards/_context";
import type { Item } from "~/app/[locale]/boards/_types";
-import { modalEvents } from "~/app/[locale]/modals";
import { editModeAtom } from "../editMode";
import { useItemActions } from "../items/item-actions";
import type { UseGridstackRefs } from "./gridstack/use-gridstack";
@@ -108,43 +109,38 @@ const BoardItem = ({ item, ...dimensions }: ItemProps) => {
const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
const t = useScopedI18n("item");
+ const { openModal } = useModalAction(WidgetEditModal);
+ const { openConfirmModal } = useConfirmModal();
const isEditMode = useAtomValue(editModeAtom);
const { updateItemOptions, removeItem } = useItemActions();
if (!isEditMode) return null;
const openEditModal = () => {
- modalEvents.openManagedModal({
- title: t("edit.title"),
- modal: "widgetEditModal",
- innerProps: {
- kind: item.kind,
- value: {
- options: item.options,
- integrations: item.integrations.map(({ id }) => id),
- },
- onSuccessfulEdit: ({ options, integrations: _ }) => {
- updateItemOptions({
- itemId: item.id,
- newOptions: options,
- });
- },
- integrationData: [],
- integrationSupport: false,
+ openModal({
+ kind: item.kind,
+ value: {
+ options: item.options,
+ integrations: item.integrations.map(({ id }) => id),
},
+ onSuccessfulEdit: ({ options, integrations: _ }) => {
+ updateItemOptions({
+ itemId: item.id,
+ newOptions: options,
+ });
+ },
+ integrationData: [],
+ integrationSupport: false,
});
};
const openRemoveModal = () => {
- modalEvents.openConfirmModal({
+ openConfirmModal({
title: t("remove.title"),
children: t("remove.message"),
onConfirm: () => {
removeItem({ itemId: item.id });
},
- confirmProps: {
- color: "red",
- },
});
};
diff --git a/apps/nextjs/src/components/layout/header/burger.tsx b/apps/nextjs/src/components/layout/header/burger.tsx
index 02f3def9d..dd270a2be 100644
--- a/apps/nextjs/src/components/layout/header/burger.tsx
+++ b/apps/nextjs/src/components/layout/header/burger.tsx
@@ -10,7 +10,10 @@ export const navigationCollapsedAtom = atom(true);
export const ClientBurger = () => {
const [collapsed, setCollapsed] = useAtom(navigationCollapsedAtom);
- const toggle = useCallback(() => setCollapsed((c) => !c), [setCollapsed]);
+ const toggle = useCallback(
+ () => setCollapsed((collapsed) => !collapsed),
+ [setCollapsed],
+ );
return (
diff --git a/apps/nextjs/src/components/manage/boards/add-board-modal.tsx b/apps/nextjs/src/components/manage/boards/add-board-modal.tsx
index 5420e6515..31ff38cad 100644
--- a/apps/nextjs/src/components/manage/boards/add-board-modal.tsx
+++ b/apps/nextjs/src/components/manage/boards/add-board-modal.tsx
@@ -1,7 +1,7 @@
-import type { ManagedModal } from "mantine-modal-manager";
import { boardSchemas } from "node_modules/@homarr/validation/src/board";
import { useForm, zodResolver } from "@homarr/form";
+import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { Button, Group, Stack, TextInput } from "@homarr/ui";
import { z } from "@homarr/validation";
@@ -11,48 +11,51 @@ interface InnerProps {
onSuccess: ({ name }: { name: string }) => Promise;
}
-export const AddBoardModal: ManagedModal = ({
- actions,
- innerProps,
-}) => {
- const t = useI18n();
- const form = useForm({
- initialValues: {
- name: "",
- },
- validate: zodResolver(
- z.object({
- name: boardSchemas.byName.shape.name.refine(
- (value) => !innerProps.boardNames.includes(value),
- ),
- }),
- ),
- validateInputOnBlur: true,
- validateInputOnChange: true,
- });
+export const AddBoardModal = createModal(
+ ({ actions, innerProps }) => {
+ const t = useI18n();
+ const form = useForm({
+ initialValues: {
+ name: "",
+ },
+ validate: zodResolver(
+ z.object({
+ name: boardSchemas.byName.shape.name.refine(
+ (value) => !innerProps.boardNames.includes(value),
+ ),
+ }),
+ ),
+ validateInputOnBlur: true,
+ validateInputOnChange: true,
+ });
- return (
-
- );
-};
+ return (
+
+ );
+ },
+).withOptions({
+ defaultTitle: (t) => t("management.page.board.button.create"),
+});
diff --git a/apps/nextjs/src/env.mjs b/apps/nextjs/src/env.mjs
index 7442d5130..3663323fa 100644
--- a/apps/nextjs/src/env.mjs
+++ b/apps/nextjs/src/env.mjs
@@ -10,7 +10,7 @@ export const env = createEnv({
VERCEL_URL: z
.string()
.optional()
- .transform((v) => (v ? `https://${v}` : undefined)),
+ .transform((url) => (url ? `https://${url}` : undefined)),
PORT: z.coerce.number().default(3000),
},
/**
diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts
index cf30edb93..871d2660f 100644
--- a/packages/api/src/router/board.ts
+++ b/packages/api/src/router/board.ts
@@ -4,6 +4,7 @@ import superjson from "superjson";
import type { Database, SQL } from "@homarr/db";
import { and, createId, eq, inArray } from "@homarr/db";
import {
+ boardPermissions,
boards,
integrationItems,
items,
@@ -309,6 +310,52 @@ export const boardRouter = createTRPCRouter({
}
});
}),
+
+ permissions: publicProcedure
+ .input(validation.board.permissions)
+ .query(async ({ input, ctx }) => {
+ const permissions = await ctx.db.query.boardPermissions.findMany({
+ where: eq(boardPermissions.boardId, input.id),
+ with: {
+ user: {
+ columns: {
+ id: true,
+ name: true,
+ },
+ },
+ },
+ });
+ return permissions
+ .map((permission) => ({
+ user: {
+ id: permission.userId,
+ name: permission.user.name ?? "",
+ },
+ permission: permission.permission,
+ }))
+ .sort((permissionA, permissionB) => {
+ return permissionA.user.name.localeCompare(permissionB.user.name);
+ });
+ }),
+ savePermissions: publicProcedure
+ .input(validation.board.savePermissions)
+ .mutation(async ({ input, ctx }) => {
+ await ctx.db.transaction(async (tx) => {
+ await tx
+ .delete(boardPermissions)
+ .where(eq(boardPermissions.boardId, input.id));
+ if (input.permissions.length === 0) {
+ return;
+ }
+ await tx.insert(boardPermissions).values(
+ input.permissions.map((permission) => ({
+ userId: permission.user.id,
+ permission: permission.permission,
+ boardId: input.id,
+ })),
+ );
+ });
+ }),
});
const noBoardWithSimilarName = async (
@@ -341,6 +388,12 @@ const getFullBoardWithWhere = async (db: Database, where: SQL) => {
const board = await db.query.boards.findFirst({
where,
with: {
+ creator: {
+ columns: {
+ id: true,
+ name: true,
+ },
+ },
sections: {
with: {
items: {
diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts
index 0aa02f5f7..9eddcf0d0 100644
--- a/packages/api/src/router/user.ts
+++ b/packages/api/src/router/user.ts
@@ -44,6 +44,14 @@ export const userRouter = createTRPCRouter({
},
});
}),
+ selectable: publicProcedure.query(async ({ ctx }) => {
+ return await ctx.db.query.users.findMany({
+ columns: {
+ id: true,
+ name: true,
+ },
+ });
+ }),
getById: publicProcedure
.input(z.object({ userId: z.string() }))
.query(async ({ input, ctx }) => {
diff --git a/packages/auth/test/callbacks.spec.ts b/packages/auth/test/callbacks.spec.ts
index ab24e2c1b..c175a7aa8 100644
--- a/packages/auth/test/callbacks.spec.ts
+++ b/packages/auth/test/callbacks.spec.ts
@@ -54,12 +54,12 @@ const createAdapter = () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
type SessionExport = typeof import("../session");
-const mockSessionToken = "e9ef3010-6981-4a81-b9d6-8495d09cf3b5" as const;
+const mockSessionToken = "e9ef3010-6981-4a81-b9d6-8495d09cf3b5";
const mockSessionExpiry = new Date("2023-07-01");
vi.mock("../session", async (importOriginal) => {
const mod = await importOriginal();
- const generateSessionToken = () => mockSessionToken;
+ const generateSessionToken = (): typeof mockSessionToken => mockSessionToken;
const expireDateAfter = (_seconds: number) => mockSessionExpiry;
return {
diff --git a/packages/db/migrations/0000_sloppy_bloodstorm.sql b/packages/db/migrations/0000_productive_changeling.sql
similarity index 84%
rename from packages/db/migrations/0000_sloppy_bloodstorm.sql
rename to packages/db/migrations/0000_productive_changeling.sql
index 78217a38a..4b6482a73 100644
--- a/packages/db/migrations/0000_sloppy_bloodstorm.sql
+++ b/packages/db/migrations/0000_productive_changeling.sql
@@ -14,10 +14,28 @@ CREATE TABLE `account` (
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
+CREATE TABLE `app` (
+ `id` text PRIMARY KEY NOT NULL,
+ `name` text NOT NULL,
+ `description` text,
+ `icon_url` text NOT NULL,
+ `href` text
+);
+--> statement-breakpoint
+CREATE TABLE `boardPermission` (
+ `board_id` text NOT NULL,
+ `user_id` text NOT NULL,
+ `permission` text NOT NULL,
+ PRIMARY KEY(`board_id`, `permission`, `user_id`),
+ FOREIGN KEY (`board_id`) REFERENCES `board`(`id`) ON UPDATE no action ON DELETE cascade,
+ FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
CREATE TABLE `board` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`is_public` integer DEFAULT false NOT NULL,
+ `creator_id` text,
`page_title` text,
`meta_title` text,
`logo_image_url` text,
@@ -30,7 +48,8 @@ CREATE TABLE `board` (
`secondary_color` text DEFAULT '#fd7e14' NOT NULL,
`opacity` integer DEFAULT 100 NOT NULL,
`custom_css` text,
- `column_count` integer DEFAULT 10 NOT NULL
+ `column_count` integer DEFAULT 10 NOT NULL,
+ FOREIGN KEY (`creator_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE TABLE `integration_item` (
diff --git a/packages/db/migrations/0001_slim_swarm.sql b/packages/db/migrations/0001_slim_swarm.sql
deleted file mode 100644
index 051cb4b8e..000000000
--- a/packages/db/migrations/0001_slim_swarm.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-CREATE TABLE `app` (
- `id` text PRIMARY KEY NOT NULL,
- `name` text NOT NULL,
- `description` text,
- `icon_url` text NOT NULL,
- `href` text
-);
diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json
index dd9337888..9bad663d4 100644
--- a/packages/db/migrations/meta/0000_snapshot.json
+++ b/packages/db/migrations/meta/0000_snapshot.json
@@ -1,7 +1,7 @@
{
"version": "5",
"dialect": "sqlite",
- "id": "7ba12cbf-d8eb-4469-b7c5-7f31acb7717d",
+ "id": "7c2291ee-febd-4b90-994c-85e6ef27102d",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
@@ -111,6 +111,104 @@
},
"uniqueConstraints": {}
},
+ "app": {
+ "name": "app",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "href": {
+ "name": "href",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "boardPermission": {
+ "name": "boardPermission",
+ "columns": {
+ "board_id": {
+ "name": "board_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission": {
+ "name": "permission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "boardPermission_board_id_board_id_fk": {
+ "name": "boardPermission_board_id_board_id_fk",
+ "tableFrom": "boardPermission",
+ "tableTo": "board",
+ "columnsFrom": ["board_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "boardPermission_user_id_user_id_fk": {
+ "name": "boardPermission_user_id_user_id_fk",
+ "tableFrom": "boardPermission",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "boardPermission_board_id_user_id_permission_pk": {
+ "columns": ["board_id", "permission", "user_id"],
+ "name": "boardPermission_board_id_user_id_permission_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
"board": {
"name": "board",
"columns": {
@@ -136,6 +234,13 @@
"autoincrement": false,
"default": false
},
+ "creator_id": {
+ "name": "creator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
"page_title": {
"name": "page_title",
"type": "text",
@@ -242,7 +347,17 @@
"isUnique": true
}
},
- "foreignKeys": {},
+ "foreignKeys": {
+ "board_creator_id_user_id_fk": {
+ "name": "board_creator_id_user_id_fk",
+ "tableFrom": "board",
+ "tableTo": "user",
+ "columnsFrom": ["creator_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
diff --git a/packages/db/migrations/meta/0001_snapshot.json b/packages/db/migrations/meta/0001_snapshot.json
deleted file mode 100644
index 01cc294ee..000000000
--- a/packages/db/migrations/meta/0001_snapshot.json
+++ /dev/null
@@ -1,722 +0,0 @@
-{
- "version": "5",
- "dialect": "sqlite",
- "id": "f7263224-116a-42ba-8fb1-4574cb637880",
- "prevId": "7ba12cbf-d8eb-4469-b7c5-7f31acb7717d",
- "tables": {
- "account": {
- "name": "account",
- "columns": {
- "userId": {
- "name": "userId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "provider": {
- "name": "provider",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "providerAccountId": {
- "name": "providerAccountId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "refresh_token": {
- "name": "refresh_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "access_token": {
- "name": "access_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "expires_at": {
- "name": "expires_at",
- "type": "integer",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "token_type": {
- "name": "token_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "scope": {
- "name": "scope",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "id_token": {
- "name": "id_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "session_state": {
- "name": "session_state",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "userId_idx": {
- "name": "userId_idx",
- "columns": ["userId"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "account_userId_user_id_fk": {
- "name": "account_userId_user_id_fk",
- "tableFrom": "account",
- "tableTo": "user",
- "columnsFrom": ["userId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "account_provider_providerAccountId_pk": {
- "columns": ["provider", "providerAccountId"],
- "name": "account_provider_providerAccountId_pk"
- }
- },
- "uniqueConstraints": {}
- },
- "app": {
- "name": "app",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "icon_url": {
- "name": "icon_url",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "href": {
- "name": "href",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "board": {
- "name": "board",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "is_public": {
- "name": "is_public",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": false
- },
- "page_title": {
- "name": "page_title",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "meta_title": {
- "name": "meta_title",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "logo_image_url": {
- "name": "logo_image_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "favicon_image_url": {
- "name": "favicon_image_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "background_image_url": {
- "name": "background_image_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "background_image_attachment": {
- "name": "background_image_attachment",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'fixed'"
- },
- "background_image_repeat": {
- "name": "background_image_repeat",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'no-repeat'"
- },
- "background_image_size": {
- "name": "background_image_size",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'cover'"
- },
- "primary_color": {
- "name": "primary_color",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'#fa5252'"
- },
- "secondary_color": {
- "name": "secondary_color",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'#fd7e14'"
- },
- "opacity": {
- "name": "opacity",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 100
- },
- "custom_css": {
- "name": "custom_css",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "column_count": {
- "name": "column_count",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 10
- }
- },
- "indexes": {
- "board_name_unique": {
- "name": "board_name_unique",
- "columns": ["name"],
- "isUnique": true
- }
- },
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "integration_item": {
- "name": "integration_item",
- "columns": {
- "item_id": {
- "name": "item_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "integration_id": {
- "name": "integration_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "integration_item_item_id_item_id_fk": {
- "name": "integration_item_item_id_item_id_fk",
- "tableFrom": "integration_item",
- "tableTo": "item",
- "columnsFrom": ["item_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "integration_item_integration_id_integration_id_fk": {
- "name": "integration_item_integration_id_integration_id_fk",
- "tableFrom": "integration_item",
- "tableTo": "integration",
- "columnsFrom": ["integration_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "integration_item_item_id_integration_id_pk": {
- "columns": ["integration_id", "item_id"],
- "name": "integration_item_item_id_integration_id_pk"
- }
- },
- "uniqueConstraints": {}
- },
- "integrationSecret": {
- "name": "integrationSecret",
- "columns": {
- "kind": {
- "name": "kind",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "value": {
- "name": "value",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "updated_at": {
- "name": "updated_at",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "integration_id": {
- "name": "integration_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "integration_secret__kind_idx": {
- "name": "integration_secret__kind_idx",
- "columns": ["kind"],
- "isUnique": false
- },
- "integration_secret__updated_at_idx": {
- "name": "integration_secret__updated_at_idx",
- "columns": ["updated_at"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "integrationSecret_integration_id_integration_id_fk": {
- "name": "integrationSecret_integration_id_integration_id_fk",
- "tableFrom": "integrationSecret",
- "tableTo": "integration",
- "columnsFrom": ["integration_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "integrationSecret_integration_id_kind_pk": {
- "columns": ["integration_id", "kind"],
- "name": "integrationSecret_integration_id_kind_pk"
- }
- },
- "uniqueConstraints": {}
- },
- "integration": {
- "name": "integration",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "url": {
- "name": "url",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "kind": {
- "name": "kind",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "integration__kind_idx": {
- "name": "integration__kind_idx",
- "columns": ["kind"],
- "isUnique": false
- }
- },
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "item": {
- "name": "item",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "section_id": {
- "name": "section_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "kind": {
- "name": "kind",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "x_offset": {
- "name": "x_offset",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "y_offset": {
- "name": "y_offset",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "width": {
- "name": "width",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "height": {
- "name": "height",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "options": {
- "name": "options",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'{\"json\": {}}'"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "item_section_id_section_id_fk": {
- "name": "item_section_id_section_id_fk",
- "tableFrom": "item",
- "tableTo": "section",
- "columnsFrom": ["section_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "section": {
- "name": "section",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "board_id": {
- "name": "board_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "kind": {
- "name": "kind",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "position": {
- "name": "position",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "section_board_id_board_id_fk": {
- "name": "section_board_id_board_id_fk",
- "tableFrom": "section",
- "tableTo": "board",
- "columnsFrom": ["board_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "session": {
- "name": "session",
- "columns": {
- "sessionToken": {
- "name": "sessionToken",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "userId": {
- "name": "userId",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "expires": {
- "name": "expires",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "user_id_idx": {
- "name": "user_id_idx",
- "columns": ["userId"],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "session_userId_user_id_fk": {
- "name": "session_userId_user_id_fk",
- "tableFrom": "session",
- "tableTo": "user",
- "columnsFrom": ["userId"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "user": {
- "name": "user",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "emailVerified": {
- "name": "emailVerified",
- "type": "integer",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "image": {
- "name": "image",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "password": {
- "name": "password",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "salt": {
- "name": "salt",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {}
- },
- "verificationToken": {
- "name": "verificationToken",
- "columns": {
- "identifier": {
- "name": "identifier",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "token": {
- "name": "token",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "expires": {
- "name": "expires",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {
- "verificationToken_identifier_token_pk": {
- "columns": ["identifier", "token"],
- "name": "verificationToken_identifier_token_pk"
- }
- },
- "uniqueConstraints": {}
- }
- },
- "enums": {},
- "_meta": {
- "schemas": {},
- "tables": {},
- "columns": {}
- }
-}
diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json
index 28dd3c9ac..0ab745f96 100644
--- a/packages/db/migrations/meta/_journal.json
+++ b/packages/db/migrations/meta/_journal.json
@@ -5,15 +5,8 @@
{
"idx": 0,
"version": "5",
- "when": 1709409142712,
- "tag": "0000_sloppy_bloodstorm",
- "breakpoints": true
- },
- {
- "idx": 1,
- "version": "5",
- "when": 1709585624230,
- "tag": "0001_slim_swarm",
+ "when": 1710878250235,
+ "tag": "0000_productive_changeling",
"breakpoints": true
}
]
diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts
index 52da4f0e0..66f75e98e 100644
--- a/packages/db/schema/mysql.ts
+++ b/packages/db/schema/mysql.ts
@@ -15,6 +15,7 @@ import type {
BackgroundImageAttachment,
BackgroundImageRepeat,
BackgroundImageSize,
+ BoardPermission,
IntegrationKind,
IntegrationSecretKind,
SectionKind,
@@ -97,8 +98,8 @@ export const integrations = mysqlTable(
url: text("url").notNull(),
kind: varchar("kind", { length: 128 }).$type().notNull(),
},
- (i) => ({
- kindIdx: index("integration__kind_idx").on(i.kind),
+ (integrations) => ({
+ kindIdx: index("integration__kind_idx").on(integrations.kind),
}),
);
@@ -127,6 +128,9 @@ export const boards = mysqlTable("board", {
id: varchar("id", { length: 256 }).notNull().primaryKey(),
name: varchar("name", { length: 256 }).unique().notNull(),
isPublic: boolean("is_public").default(false).notNull(),
+ creatorId: text("creator_id").references(() => users.id, {
+ onDelete: "set null",
+ }),
pageTitle: text("page_title"),
metaTitle: text("meta_title"),
logoImageUrl: text("logo_image_url"),
@@ -151,6 +155,24 @@ export const boards = mysqlTable("board", {
columnCount: int("column_count").default(10).notNull(),
});
+export const boardPermissions = mysqlTable(
+ "boardPermission",
+ {
+ boardId: text("board_id")
+ .notNull()
+ .references(() => boards.id, { onDelete: "cascade" }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ permission: text("permission").$type().notNull(),
+ },
+ (table) => ({
+ compoundKey: primaryKey({
+ columns: [table.boardId, table.userId, table.permission],
+ }),
+ }),
+);
+
export const sections = mysqlTable("section", {
id: varchar("id", { length: 256 }).notNull().primaryKey(),
boardId: varchar("board_id", { length: 256 })
@@ -208,8 +230,25 @@ export const accountRelations = relations(accounts, ({ one }) => ({
export const userRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
+
+ boards: many(boards),
+ boardPermissions: many(boardPermissions),
}));
+export const boardPermissionRelations = relations(
+ boardPermissions,
+ ({ one }) => ({
+ user: one(users, {
+ fields: [boardPermissions.userId],
+ references: [users.id],
+ }),
+ board: one(boards, {
+ fields: [boardPermissions.boardId],
+ references: [boards.id],
+ }),
+ }),
+);
+
export const integrationRelations = relations(integrations, ({ many }) => ({
secrets: many(integrationSecrets),
items: many(integrationItems),
@@ -225,8 +264,13 @@ export const integrationSecretRelations = relations(
}),
);
-export const boardRelations = relations(boards, ({ many }) => ({
+export const boardRelations = relations(boards, ({ many, one }) => ({
sections: many(sections),
+ creator: one(users, {
+ fields: [boards.creatorId],
+ references: [users.id],
+ }),
+ permissions: many(boardPermissions),
}));
export const sectionRelations = relations(sections, ({ many, one }) => ({
diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts
index 3f159e3e7..cbf21a12e 100644
--- a/packages/db/schema/sqlite.ts
+++ b/packages/db/schema/sqlite.ts
@@ -19,6 +19,7 @@ import type {
BackgroundImageAttachment,
BackgroundImageRepeat,
BackgroundImageSize,
+ BoardPermission,
IntegrationKind,
IntegrationSecretKind,
SectionKind,
@@ -94,8 +95,8 @@ export const integrations = sqliteTable(
url: text("url").notNull(),
kind: text("kind").$type().notNull(),
},
- (i) => ({
- kindIdx: index("integration__kind_idx").on(i.kind),
+ (integrations) => ({
+ kindIdx: index("integration__kind_idx").on(integrations.kind),
}),
);
@@ -122,6 +123,9 @@ export const boards = sqliteTable("board", {
id: text("id").notNull().primaryKey(),
name: text("name").unique().notNull(),
isPublic: int("is_public", { mode: "boolean" }).default(false).notNull(),
+ creatorId: text("creator_id").references(() => users.id, {
+ onDelete: "set null",
+ }),
pageTitle: text("page_title"),
metaTitle: text("meta_title"),
logoImageUrl: text("logo_image_url"),
@@ -146,6 +150,24 @@ export const boards = sqliteTable("board", {
columnCount: int("column_count").default(10).notNull(),
});
+export const boardPermissions = sqliteTable(
+ "boardPermission",
+ {
+ boardId: text("board_id")
+ .notNull()
+ .references(() => boards.id, { onDelete: "cascade" }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ permission: text("permission").$type().notNull(),
+ },
+ (table) => ({
+ compoundKey: primaryKey({
+ columns: [table.boardId, table.userId, table.permission],
+ }),
+ }),
+);
+
export const sections = sqliteTable("section", {
id: text("id").notNull().primaryKey(),
boardId: text("board_id")
@@ -203,8 +225,24 @@ export const accountRelations = relations(accounts, ({ one }) => ({
export const userRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
+ boards: many(boards),
+ boardPermissions: many(boardPermissions),
}));
+export const boardPermissionRelations = relations(
+ boardPermissions,
+ ({ one }) => ({
+ user: one(users, {
+ fields: [boardPermissions.userId],
+ references: [users.id],
+ }),
+ board: one(boards, {
+ fields: [boardPermissions.boardId],
+ references: [boards.id],
+ }),
+ }),
+);
+
export const integrationRelations = relations(integrations, ({ many }) => ({
secrets: many(integrationSecrets),
items: many(integrationItems),
@@ -220,8 +258,13 @@ export const integrationSecretRelations = relations(
}),
);
-export const boardRelations = relations(boards, ({ many }) => ({
+export const boardRelations = relations(boards, ({ many, one }) => ({
sections: many(sections),
+ creator: one(users, {
+ fields: [boards.creatorId],
+ references: [users.id],
+ }),
+ permissions: many(boardPermissions),
}));
export const sectionRelations = relations(sections, ({ many, one }) => ({
diff --git a/packages/db/test/schema.spec.ts b/packages/db/test/schema.spec.ts
index 77074734c..cbce0f745 100644
--- a/packages/db/test/schema.spec.ts
+++ b/packages/db/test/schema.spec.ts
@@ -51,7 +51,7 @@ test("schemas should match", () => {
},
);
- const mysqlTable = mysqlSchema[tableName as keyof typeof mysqlSchema];
+ const mysqlTable = mysqlSchema[tableName];
const sqliteForeignKeys = sqliteTable[
Symbol.for("drizzle:SQLiteInlineForeignKeys") as keyof typeof sqliteTable
] as SqliteForeignKey[] | undefined;
@@ -97,7 +97,9 @@ test("schemas should match", () => {
sqliteForeignKey.reference().foreignColumns.forEach((column) => {
expect(
- mysqlForeignKey!.reference().foreignColumns.map((x) => x.name),
+ mysqlForeignKey!
+ .reference()
+ .foreignColumns.map((column) => column.name),
`expect foreign key (${sqliteForeignKey.getName()}) columns to be the same for both schemas`,
).toContainEqual(column.name);
});
diff --git a/packages/definitions/src/index.ts b/packages/definitions/src/index.ts
index d305465db..d72b9e8d1 100644
--- a/packages/definitions/src/index.ts
+++ b/packages/definitions/src/index.ts
@@ -2,3 +2,4 @@ export * from "./board";
export * from "./integration";
export * from "./section";
export * from "./widget";
+export * from "./permissions";
diff --git a/packages/definitions/src/permissions.ts b/packages/definitions/src/permissions.ts
new file mode 100644
index 000000000..7a65a5641
--- /dev/null
+++ b/packages/definitions/src/permissions.ts
@@ -0,0 +1,3 @@
+export const boardPermissions = ["board-view", "board-change"] as const;
+
+export type BoardPermission = (typeof boardPermissions)[number];
diff --git a/packages/modals/index.ts b/packages/modals/index.ts
new file mode 100644
index 000000000..f7a07beae
--- /dev/null
+++ b/packages/modals/index.ts
@@ -0,0 +1,2 @@
+export { ModalProvider, useModalAction, useConfirmModal } from "./src";
+export { createModal } from "./src/creator";
diff --git a/packages/modals/package.json b/packages/modals/package.json
new file mode 100644
index 000000000..71cf84cdb
--- /dev/null
+++ b/packages/modals/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "@homarr/modals",
+ "private": true,
+ "version": "0.1.0",
+ "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/ui": "workspace:^0.1.0",
+ "@homarr/translation": "workspace:^0.1.0"
+ },
+ "devDependencies": {
+ "@homarr/eslint-config": "workspace:^0.2.0",
+ "@homarr/prettier-config": "workspace:^0.1.0",
+ "@homarr/tsconfig": "workspace:^0.1.0",
+ "eslint": "^8.57.0",
+ "typescript": "^5.4.2"
+ },
+ "eslintConfig": {
+ "extends": [
+ "@homarr/eslint-config/base"
+ ]
+ },
+ "prettier": "@homarr/prettier-config"
+}
diff --git a/packages/modals/src/confirm-modal.tsx b/packages/modals/src/confirm-modal.tsx
new file mode 100644
index 000000000..4b90893af
--- /dev/null
+++ b/packages/modals/src/confirm-modal.tsx
@@ -0,0 +1,92 @@
+import { useCallback } from "react";
+import type { ComponentPropsWithoutRef, ReactNode } from "react";
+
+import type {
+ stringOrTranslation,
+ TranslationFunction,
+} from "@homarr/translation";
+import { translateIfNecessary } from "@homarr/translation";
+import { useI18n } from "@homarr/translation/client";
+import type { ButtonProps, GroupProps } from "@homarr/ui";
+import { Box, Button, Group } from "@homarr/ui";
+
+import { createModal } from "./creator";
+
+type MaybePromise = T | Promise;
+
+export interface ConfirmModalProps {
+ title: string;
+ children: ReactNode;
+ onConfirm?: () => MaybePromise;
+ onCancel?: () => MaybePromise;
+ closeOnConfirm?: boolean;
+ closeOnCancel?: boolean;
+ cancelProps?: ButtonProps & ComponentPropsWithoutRef<"button">;
+ confirmProps?: ButtonProps & ComponentPropsWithoutRef<"button">;
+ groupProps?: GroupProps;
+
+ labels?: {
+ confirm?: stringOrTranslation;
+ cancel?: stringOrTranslation;
+ };
+}
+
+export const ConfirmModal = createModal>(
+ ({ actions, innerProps }) => {
+ const t = useI18n();
+ const {
+ children,
+ onConfirm,
+ onCancel,
+ cancelProps,
+ confirmProps,
+ groupProps,
+ labels,
+ } = innerProps;
+
+ const closeOnConfirm = innerProps.closeOnConfirm ?? true;
+ const closeOnCancel = innerProps.closeOnCancel ?? true;
+
+ const cancelLabel =
+ labels?.cancel ?? ((t: TranslationFunction) => t("common.action.cancel"));
+ const confirmLabel =
+ labels?.confirm ??
+ ((t: TranslationFunction) => t("common.action.confirm"));
+
+ const handleCancel = useCallback(
+ async (event: React.MouseEvent) => {
+ typeof cancelProps?.onClick === "function" &&
+ cancelProps?.onClick(event);
+ typeof onCancel === "function" && (await onCancel());
+ closeOnCancel && actions.closeModal();
+ },
+ [cancelProps?.onClick, onCancel, actions.closeModal],
+ );
+
+ const handleConfirm = useCallback(
+ async (event: React.MouseEvent) => {
+ typeof confirmProps?.onClick === "function" &&
+ confirmProps?.onClick(event);
+ typeof onConfirm === "function" && (await onConfirm());
+ closeOnConfirm && actions.closeModal();
+ },
+ [confirmProps?.onClick, onConfirm, actions.closeModal],
+ );
+
+ return (
+ <>
+ {children && {children}}
+
+
+
+
+
+
+ >
+ );
+ },
+).withOptions({});
diff --git a/packages/modals/src/creator.ts b/packages/modals/src/creator.ts
new file mode 100644
index 000000000..362032033
--- /dev/null
+++ b/packages/modals/src/creator.ts
@@ -0,0 +1,14 @@
+import type { CreateModalOptions, ModalComponent } from "./type";
+
+export const createModal = (
+ component: ModalComponent,
+) => {
+ return {
+ withOptions: (options: Partial) => {
+ return {
+ component,
+ options,
+ };
+ },
+ };
+};
diff --git a/packages/modals/src/index.tsx b/packages/modals/src/index.tsx
new file mode 100644
index 000000000..155af31b5
--- /dev/null
+++ b/packages/modals/src/index.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import type { PropsWithChildren } from "react";
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useReducer,
+ useRef,
+} from "react";
+import { randomId } from "@mantine/hooks";
+
+import type { stringOrTranslation } from "@homarr/translation";
+import { translateIfNecessary } from "@homarr/translation";
+import { useI18n } from "@homarr/translation/client";
+import { getDefaultZIndex, Modal } from "@homarr/ui";
+
+import type { ConfirmModalProps } from "./confirm-modal";
+import { ConfirmModal } from "./confirm-modal";
+import { modalReducer } from "./reducer";
+import type { inferInnerProps, ModalDefinition } from "./type";
+
+interface ModalContextProps {
+ openModalInner: (props: {
+ modal: TModal;
+ innerProps: inferInnerProps;
+ options: OpenModalOptions;
+ }) => void;
+ closeModal: (id: string) => void;
+}
+
+export const ModalContext = createContext(null);
+
+export const ModalProvider = ({ children }: PropsWithChildren) => {
+ const t = useI18n();
+ const [state, dispatch] = useReducer(modalReducer, {
+ modals: [],
+ current: null,
+ });
+ const stateRef = useRef(state);
+ stateRef.current = state;
+
+ const closeModal = useCallback(
+ (id: string, canceled?: boolean) => {
+ dispatch({ type: "CLOSE", modalId: id, canceled });
+ },
+ [stateRef, dispatch],
+ );
+
+ const openModalInner: ModalContextProps["openModalInner"] = useCallback(
+ ({ modal, innerProps, options }) => {
+ const id = randomId();
+ const { title, ...rest } = options;
+ dispatch({
+ type: "OPEN",
+ modal: {
+ id,
+ modal,
+ props: {
+ ...modal.options,
+ ...rest,
+ defaultTitle: title ?? modal.options.defaultTitle,
+ innerProps,
+ },
+ },
+ });
+ return id;
+ },
+ [dispatch],
+ );
+
+ const handleCloseModal = useCallback(
+ () => state.current && closeModal(state.current.id),
+ [closeModal, state.current?.id],
+ );
+
+ const activeModals = state.modals.filter(
+ (modal) => modal.id === state.current?.id || modal.props.keepMounted,
+ );
+
+ return (
+
+ {activeModals.map((modal) => (
+ 0}
+ onClose={handleCloseModal}
+ >
+ {modal.reference.content}
+
+ ))}
+
+ {children}
+
+ );
+};
+
+interface OpenModalOptions {
+ keepMounted?: boolean;
+ title?: stringOrTranslation;
+}
+
+export const useModalAction = (
+ modal: TModal,
+) => {
+ const context = useContext(ModalContext);
+
+ if (!context) throw new Error("ModalContext is not provided");
+
+ return {
+ openModal: (
+ innerProps: inferInnerProps,
+ options: OpenModalOptions | void,
+ ) => {
+ context.openModalInner({ modal, innerProps, options: options ?? {} });
+ },
+ };
+};
+
+export const useConfirmModal = () => {
+ const { openModal } = useModalAction(ConfirmModal);
+
+ return {
+ openConfirmModal: (props: ConfirmModalProps) =>
+ openModal(props, { title: props.title }),
+ };
+};
diff --git a/packages/modals/src/reducer.tsx b/packages/modals/src/reducer.tsx
new file mode 100644
index 000000000..52a6cfcd3
--- /dev/null
+++ b/packages/modals/src/reducer.tsx
@@ -0,0 +1,125 @@
+"use client";
+
+import { useContext } from "react";
+
+import { ModalContext } from ".";
+import type { ModalDefinition, ModalState } from "./type";
+
+type ModalStateWithReference = ModalState & {
+ /**
+ * Reference to modal component instance
+ * Used so the modal can be persisted between navigating in newer modals
+ */
+ reference: ReturnType;
+};
+
+interface ModalsState {
+ modals: ModalStateWithReference[];
+
+ /**
+ * Modal that is currently open or was the last open one.
+ * Keeping the last one is necessary for providing a clean exit transition.
+ */
+ current: ModalStateWithReference | null;
+}
+
+interface OpenAction {
+ type: "OPEN";
+ modal: ModalState;
+}
+
+interface CloseAction {
+ type: "CLOSE";
+ modalId: string;
+ canceled?: boolean;
+}
+
+interface CloseAllAction {
+ type: "CLOSE_ALL";
+ canceled?: boolean;
+}
+
+export const modalReducer = (
+ state: ModalsState,
+ action: OpenAction | CloseAction | CloseAllAction,
+): ModalsState => {
+ switch (action.type) {
+ case "OPEN": {
+ const newModal = {
+ ...action.modal,
+ reference: getModal(action.modal),
+ };
+ return {
+ current: newModal,
+ modals: [...state.modals, newModal],
+ };
+ }
+ case "CLOSE": {
+ const modal = state.modals.find((modal) => modal.id === action.modalId);
+ if (!modal) {
+ return state;
+ }
+
+ modal.props.onClose?.();
+
+ const remainingModals = state.modals.filter(
+ (modal) => modal.id !== action.modalId,
+ );
+
+ return {
+ current: remainingModals[remainingModals.length - 1] || state.current,
+ modals: remainingModals,
+ };
+ }
+ case "CLOSE_ALL": {
+ if (!state.modals.length) {
+ return state;
+ }
+
+ // Resolve modal stack from top to bottom
+ state.modals
+ .concat()
+ .reverse()
+ .forEach((modal) => {
+ modal.props.onClose?.();
+ });
+
+ return {
+ current: state.current,
+ modals: [],
+ };
+ }
+ default: {
+ return state;
+ }
+ }
+};
+
+const getModal = (
+ modal: ModalState,
+) => {
+ const ModalContent = modal.modal.component;
+
+ const { innerProps, ...rest } = modal.props;
+ const FullModal = () => {
+ const context = useContext(ModalContext);
+
+ if (!context) {
+ throw new Error("Modal component used outside of modal context");
+ }
+
+ return (
+ context.closeModal(modal.id),
+ }}
+ />
+ );
+ };
+
+ return {
+ modalProps: rest,
+ content: ,
+ };
+};
diff --git a/packages/modals/src/type.ts b/packages/modals/src/type.ts
new file mode 100644
index 000000000..a97e9a577
--- /dev/null
+++ b/packages/modals/src/type.ts
@@ -0,0 +1,43 @@
+import type { ReactNode } from "react";
+
+import type { stringOrTranslation } from "@homarr/translation";
+import type { ModalProps } from "@homarr/ui";
+
+export type ModalComponent = (props: {
+ actions: { closeModal: () => void };
+ innerProps: TInnerProps;
+}) => ReactNode;
+
+export type CreateModalOptions = Pick<
+ ModalOptions,
+ | "size"
+ | "fullScreen"
+ | "centered"
+ | "keepMounted"
+ | "withCloseButton"
+ | "zIndex"
+ | "scrollAreaComponent"
+ | "yOffset"
+> & {
+ defaultTitle: stringOrTranslation;
+};
+
+export interface ModalDefinition {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ component: ModalComponent;
+ options: Partial;
+}
+
+type ModalOptions = Partial> & {
+ innerProps: TInnerProps;
+ defaultTitle?: stringOrTranslation;
+};
+
+export interface ModalState {
+ id: string;
+ modal: TModal;
+ props: ModalOptions>;
+}
+
+export type inferInnerProps =
+ TModal["component"] extends ModalComponent ? P : never;
diff --git a/packages/modals/tsconfig.json b/packages/modals/tsconfig.json
new file mode 100644
index 000000000..5f7c543a8
--- /dev/null
+++ b/packages/modals/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@homarr/tsconfig/base.json",
+ "compilerOptions": {
+ "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
+ },
+ "include": ["*.ts", "*.tsx", "src"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/translation/src/index.ts b/packages/translation/src/index.ts
index dfc6ea084..e7e7389e3 100644
--- a/packages/translation/src/index.ts
+++ b/packages/translation/src/index.ts
@@ -1,3 +1,5 @@
+import type { stringOrTranslation, TranslationFunction } from "./type";
+
export * from "./type";
export const supportedLanguages = ["en", "de"] as const;
@@ -5,3 +7,13 @@ export type SupportedLanguage = (typeof supportedLanguages)[number];
export const defaultLocale = "en";
export { languageMapping } from "./lang";
+
+export const translateIfNecessary = (
+ t: TranslationFunction,
+ value: stringOrTranslation | undefined,
+) => {
+ if (typeof value === "function") {
+ return value(t);
+ }
+ return value;
+};
diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts
index fd88ada24..9581ab5f3 100644
--- a/packages/translation/src/lang/en.ts
+++ b/packages/translation/src/lang/en.ts
@@ -191,9 +191,11 @@ export default {
},
common: {
action: {
+ add: "Add",
backToOverview: "Back to overview",
create: "Create",
edit: "Edit",
+ remove: "Remove",
save: "Save",
saveChanges: "Save changes",
cancel: "Cancel",
@@ -512,6 +514,35 @@ export default {
customCss: {
title: "Custom css",
},
+ access: {
+ title: "Access control",
+ permission: {
+ userSelect: {
+ title: "Add user permission",
+ label: "Select user",
+ notFound: "No user found",
+ },
+ field: {
+ user: {
+ label: "User",
+ },
+ permission: {
+ label: "Permission",
+ },
+ },
+ item: {
+ "board-view": {
+ label: "View board",
+ },
+ "board-change": {
+ label: "Change board",
+ },
+ "board-full": {
+ label: "Full access",
+ },
+ },
+ },
+ },
dangerZone: {
title: "Danger Zone",
action: {
diff --git a/packages/translation/src/type.ts b/packages/translation/src/type.ts
index d673d652d..cd3c12d8e 100644
--- a/packages/translation/src/type.ts
+++ b/packages/translation/src/type.ts
@@ -3,3 +3,4 @@ import type enTranslation from "./lang/en";
export type TranslationFunction = ReturnType;
export type TranslationObject = typeof enTranslation;
+export type stringOrTranslation = string | ((t: TranslationFunction) => string);
diff --git a/packages/validation/src/board.ts b/packages/validation/src/board.ts
index 36faf2af7..437ceb7f2 100644
--- a/packages/validation/src/board.ts
+++ b/packages/validation/src/board.ts
@@ -4,8 +4,10 @@ import {
backgroundImageAttachments,
backgroundImageRepeats,
backgroundImageSizes,
+ boardPermissions,
} from "@homarr/definitions";
+import { zodEnumFromArray } from "./enums";
import { commonItemSchema, createSectionSchema } from "./shared";
const hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
@@ -65,6 +67,23 @@ const saveSchema = z.object({
const createSchema = z.object({ name: boardNameSchema });
+const permissionsSchema = z.object({
+ id: z.string(),
+});
+
+const savePermissionsSchema = z.object({
+ id: z.string(),
+ permissions: z.array(
+ z.object({
+ user: z.object({
+ id: z.string(),
+ name: z.string(),
+ }),
+ permission: zodEnumFromArray(boardPermissions),
+ }),
+ ),
+});
+
export const boardSchemas = {
byName: byNameSchema,
savePartialSettings: savePartialSettingsSchema,
@@ -72,4 +91,6 @@ export const boardSchemas = {
create: createSchema,
rename: renameSchema,
changeVisibility: changeVisibilitySchema,
+ permissions: permissionsSchema,
+ savePermissions: savePermissionsSchema,
};
diff --git a/packages/widgets/package.json b/packages/widgets/package.json
index 4d904e6c5..d506b729a 100644
--- a/packages/widgets/package.json
+++ b/packages/widgets/package.json
@@ -37,6 +37,7 @@
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/form": "workspace:^0.1.0",
"@homarr/api": "workspace:^0.1.0",
+ "@homarr/modals": "workspace:^0.1.0",
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
diff --git a/packages/widgets/src/modals/widget-edit-modal.tsx b/packages/widgets/src/modals/widget-edit-modal.tsx
index b2a7b4531..74183162b 100644
--- a/packages/widgets/src/modals/widget-edit-modal.tsx
+++ b/packages/widgets/src/modals/widget-edit-modal.tsx
@@ -1,8 +1,7 @@
"use client";
-import type { ManagedModal } from "mantine-modal-manager";
-
import type { WidgetKind } from "@homarr/definitions";
+import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { Button, Group, Stack } from "@homarr/ui";
@@ -26,61 +25,67 @@ interface ModalProps {
integrationSupport: boolean;
}
-export const WidgetEditModal: ManagedModal> = ({
- actions,
- innerProps,
-}) => {
- const t = useI18n();
- const form = useForm({
- initialValues: innerProps.value,
- });
+export const WidgetEditModal = createModal>(
+ ({ actions, innerProps }) => {
+ const t = useI18n();
+ const form = useForm({
+ initialValues: innerProps.value,
+ });
- const { definition } = widgetImports[innerProps.kind];
+ const { definition } = widgetImports[innerProps.kind];
- return (
-
+ );
+ },
+).withOptions({});
diff --git a/packages/widgets/src/widget-integration-select.tsx b/packages/widgets/src/widget-integration-select.tsx
index 2b70ec1f4..5074e83b6 100644
--- a/packages/widgets/src/widget-integration-select.tsx
+++ b/packages/widgets/src/widget-integration-select.tsx
@@ -48,17 +48,17 @@ export const WidgetIntegrationSelect = ({
const handleValueSelect = (selectedValue: string) =>
onChange(
multiSelectValues.includes(selectedValue)
- ? multiSelectValues.filter((v) => v !== selectedValue)
+ ? multiSelectValues.filter((value) => value !== selectedValue)
: [...multiSelectValues, selectedValue],
);
- const handleValueRemove = (val: string) =>
- onChange(multiSelectValues.filter((v) => v !== val));
+ const handleValueRemove = (valueToRemove: string) =>
+ onChange(multiSelectValues.filter((value) => value !== valueToRemove));
const values = multiSelectValues.map((item) => (
i.id === item)!}
+ option={data.find((integration) => integration.id === item)!}
onRemove={() => handleValueRemove(item)}
/>
));
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e616d5412..c8f3e1ff4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -162,6 +162,9 @@ importers:
'@homarr/log':
specifier: workspace:^
version: link:../../packages/log
+ '@homarr/modals':
+ specifier: workspace:^0.1.0
+ version: link:../../packages/modals
'@homarr/notifications':
specifier: workspace:^0.1.0
version: link:../../packages/notifications
@@ -236,10 +239,7 @@ importers:
version: 16.4.5
jotai:
specifier: ^2.7.1
- version: 2.7.1(@types/react@18.2.66)(react@18.2.0)
- mantine-modal-manager:
- specifier: ^7.6.2
- version: 7.6.2(@mantine/core@7.6.2)(@mantine/hooks@7.6.2)(react-dom@18.2.0)(react@18.2.0)
+ version: 2.7.1(@types/react@18.2.67)(react@18.2.0)
next:
specifier: ^14.1.3
version: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.72.0)
@@ -279,7 +279,7 @@ importers:
version: 20.11.30
'@types/react':
specifier: ^18.2.66
- version: 18.2.66
+ version: 18.2.67
'@types/react-dom':
specifier: ^18.2.22
version: 18.2.22
@@ -452,7 +452,7 @@ importers:
version: 0.20.14
drizzle-orm:
specifier: ^0.30.2
- version: 0.30.2(@types/better-sqlite3@7.6.9)(better-sqlite3@9.4.3)(mysql2@3.9.2)(react@17.0.2)
+ version: 0.30.3(@types/better-sqlite3@7.6.9)(better-sqlite3@9.4.3)(mysql2@3.9.2)(react@17.0.2)
mysql2:
specifier: ^3.9.2
version: 3.9.2
@@ -548,6 +548,31 @@ importers:
specifier: ^5.4.2
version: 5.4.2
+ packages/modals:
+ dependencies:
+ '@homarr/translation':
+ specifier: workspace:^0.1.0
+ version: link:../translation
+ '@homarr/ui':
+ specifier: workspace:^0.1.0
+ version: link:../ui
+ 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: ^8.57.0
+ version: 8.57.0
+ typescript:
+ specifier: ^5.4.2
+ version: 5.4.2
+
packages/notifications:
dependencies:
'@homarr/ui':
@@ -627,7 +652,7 @@ importers:
dependencies:
'@mantine/core':
specifier: ^7.6.2
- version: 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
+ version: 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@mantine/dates':
specifier: ^7.6.2
version: 7.6.2(@mantine/core@7.6.2)(@mantine/hooks@7.6.2)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0)
@@ -696,6 +721,9 @@ importers:
'@homarr/form':
specifier: workspace:^0.1.0
version: link:../form
+ '@homarr/modals':
+ specifier: workspace:^0.1.0
+ version: link:../modals
'@homarr/notifications':
specifier: workspace:^0.1.0
version: link:../notifications
@@ -732,10 +760,10 @@ importers:
version: 14.1.3
'@typescript-eslint/eslint-plugin':
specifier: ^7.2.0
- version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2)
+ version: 7.3.1(@typescript-eslint/parser@7.3.1)(eslint@8.57.0)(typescript@5.4.2)
'@typescript-eslint/parser':
specifier: ^7.2.0
- version: 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ version: 7.3.1(eslint@8.57.0)(typescript@5.4.2)
eslint-config-prettier:
specifier: ^9.1.0
version: 9.1.0(eslint@8.57.0)
@@ -744,7 +772,7 @@ importers:
version: 1.12.5(eslint@8.57.0)
eslint-plugin-import:
specifier: ^2.29.1
- version: 2.29.1(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)
+ version: 2.29.1(@typescript-eslint/parser@7.3.1)(eslint@8.57.0)
eslint-plugin-jsx-a11y:
specifier: ^6.8.0
version: 6.8.0(eslint@8.57.0)
@@ -803,8 +831,8 @@ packages:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
dependencies:
- '@jridgewell/gen-mapping': 0.3.5
- '@jridgewell/trace-mapping': 0.3.25
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.22
/@angular-devkit/core@17.1.2(chokidar@3.6.0):
resolution: {integrity: sha512-ku+/W/HMCBacSWFppenr9y6Lx8mDuTuQvn1IkTyBLiJOpWnzgVbx9kHDeaDchGa1PwLlJUBBrv27t3qgJOIDPw==}
@@ -945,9 +973,9 @@ packages:
resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.24.0
- '@jridgewell/gen-mapping': 0.3.5
- '@jridgewell/trace-mapping': 0.3.25
+ '@babel/types': 7.23.9
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.22
jsesc: 2.5.2
/@babel/helper-compilation-targets@7.23.6:
@@ -969,19 +997,19 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.23.9
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
/@babel/helper-hoist-variables@7.22.5:
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
/@babel/helper-module-imports@7.22.15:
resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
/@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9):
resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
@@ -1019,13 +1047,13 @@ packages:
resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
/@babel/helper-split-export-declaration@7.22.6:
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
/@babel/helper-string-parser@7.23.4:
resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
@@ -1045,7 +1073,7 @@ packages:
dependencies:
'@babel/template': 7.23.9
'@babel/traverse': 7.23.9
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
transitivePeerDependencies:
- supports-color
@@ -1073,7 +1101,7 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
/@babel/parser@7.24.0:
resolution: {integrity: sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==}
@@ -1081,6 +1109,7 @@ packages:
hasBin: true
dependencies:
'@babel/types': 7.24.0
+ dev: false
/@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.23.9):
resolution: {integrity: sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==}
@@ -1121,8 +1150,8 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.23.5
- '@babel/parser': 7.24.0
- '@babel/types': 7.24.0
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
/@babel/template@7.24.0:
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
@@ -1143,8 +1172,8 @@ packages:
'@babel/helper-function-name': 7.23.0
'@babel/helper-hoist-variables': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6
- '@babel/parser': 7.24.0
- '@babel/types': 7.24.0
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
@@ -1183,6 +1212,7 @@ packages:
'@babel/helper-string-parser': 7.23.4
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
+ dev: false
/@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -1951,6 +1981,14 @@ packages:
'@sinclair/typebox': 0.27.8
dev: true
+ /@jridgewell/gen-mapping@0.3.3:
+ resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/set-array': 1.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/trace-mapping': 0.3.25
+
/@jridgewell/gen-mapping@0.3.5:
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@@ -1958,14 +1996,20 @@ packages:
'@jridgewell/set-array': 1.2.1
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.25
+ dev: true
/@jridgewell/resolve-uri@3.1.2:
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
+ /@jridgewell/set-array@1.1.2:
+ resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
+ engines: {node: '>=6.0.0'}
+
/@jridgewell/set-array@1.2.1:
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
+ dev: true
/@jridgewell/source-map@0.3.5:
resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
@@ -1977,6 +2021,12 @@ packages:
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+ /@jridgewell/trace-mapping@0.3.22:
+ resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+
/@jridgewell/trace-mapping@0.3.25:
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
dependencies:
@@ -2009,7 +2059,7 @@ packages:
chroma-js: 2.4.2
dev: false
- /@mantine/core@7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0):
+ /@mantine/core@7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-qmZhmQVc7ZZ8EKKhPkGuZbfBnLXR0xE45ikxfx+1E6/8hLY5Ypr4nWqh5Pk6p3b+K71yYnBqlbNXbtHLQH0h3g==}
peerDependencies:
'@mantine/hooks': 7.6.2
@@ -2022,8 +2072,8 @@ packages:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-number-format: 5.3.1(react-dom@18.2.0)(react@18.2.0)
- react-remove-scroll: 2.5.7(@types/react@18.2.66)(react@18.2.0)
- react-textarea-autosize: 8.5.3(@types/react@18.2.66)(react@18.2.0)
+ react-remove-scroll: 2.5.7(@types/react@18.2.67)(react@18.2.0)
+ react-textarea-autosize: 8.5.3(@types/react@18.2.67)(react@18.2.0)
type-fest: 4.12.0
transitivePeerDependencies:
- '@types/react'
@@ -2038,7 +2088,7 @@ packages:
react: ^18.2.0
react-dom: ^18.2.0
dependencies:
- '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
+ '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 7.6.2(react@18.2.0)
clsx: 2.1.0
dayjs: 1.11.10
@@ -2072,7 +2122,7 @@ packages:
react: ^18.2.0
react-dom: ^18.2.0
dependencies:
- '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
+ '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 7.6.2(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -2086,7 +2136,7 @@ packages:
react: ^18.2.0
react-dom: ^18.2.0
dependencies:
- '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
+ '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 7.6.2(react@18.2.0)
'@mantine/store': 7.6.2(react@18.2.0)
react: 18.2.0
@@ -2102,7 +2152,7 @@ packages:
react: ^18.2.0
react-dom: ^18.2.0
dependencies:
- '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
+ '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 7.6.2(react@18.2.0)
'@mantine/store': 7.6.2(react@18.2.0)
react: 18.2.0
@@ -2127,7 +2177,7 @@ packages:
react: ^18.2.0
react-dom: ^18.2.0
dependencies:
- '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
+ '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 7.6.2(react@18.2.0)
'@tiptap/extension-link': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4)
'@tiptap/react': 2.2.4(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4)(react-dom@18.2.0)(react@18.2.0)
@@ -3241,20 +3291,20 @@ packages:
/@types/babel__generator@7.6.8:
resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==}
dependencies:
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
dev: true
/@types/babel__template@7.4.4:
resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
dependencies:
- '@babel/parser': 7.24.0
- '@babel/types': 7.24.0
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
dev: true
/@types/babel__traverse@7.20.5:
resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==}
dependencies:
- '@babel/types': 7.24.0
+ '@babel/types': 7.23.9
dev: true
/@types/bcrypt@5.0.2:
@@ -3417,11 +3467,11 @@ packages:
/@types/react-dom@18.2.22:
resolution: {integrity: sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==}
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
dev: true
- /@types/react@18.2.66:
- resolution: {integrity: sha512-OYTmMI4UigXeFMF/j4uv0lBBEbongSgptPrHBxqME44h9+yNov+oL6Z3ocJKo0WyXR84sQUNeyIp9MRfckvZpg==}
+ /@types/react@18.2.67:
+ resolution: {integrity: sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==}
dependencies:
'@types/prop-types': 15.7.11
'@types/scheduler': 0.16.8
@@ -3488,9 +3538,9 @@ packages:
'@types/node': 20.11.30
dev: true
- /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1)(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==}
+ engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
'@typescript-eslint/parser': ^7.0.0
eslint: ^8.56.0
@@ -3500,11 +3550,11 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
- '@typescript-eslint/scope-manager': 7.2.0
- '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
- '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
- '@typescript-eslint/visitor-keys': 7.2.0
+ '@typescript-eslint/parser': 7.3.1(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/scope-manager': 7.3.1
+ '@typescript-eslint/type-utils': 7.3.1(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.3.1
debug: 4.3.4
eslint: 8.57.0
graphemer: 1.4.0
@@ -3517,9 +3567,9 @@ packages:
- supports-color
dev: false
- /@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==}
+ engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
@@ -3527,10 +3577,10 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/scope-manager': 7.2.0
- '@typescript-eslint/types': 7.2.0
- '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
- '@typescript-eslint/visitor-keys': 7.2.0
+ '@typescript-eslint/scope-manager': 7.3.1
+ '@typescript-eslint/types': 7.3.1
+ '@typescript-eslint/typescript-estree': 7.3.1(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.3.1
debug: 4.3.4
eslint: 8.57.0
typescript: 5.4.2
@@ -3538,17 +3588,17 @@ packages:
- supports-color
dev: false
- /@typescript-eslint/scope-manager@7.2.0:
- resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/scope-manager@7.3.1:
+ resolution: {integrity: sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==}
+ engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
- '@typescript-eslint/types': 7.2.0
- '@typescript-eslint/visitor-keys': 7.2.0
+ '@typescript-eslint/types': 7.3.1
+ '@typescript-eslint/visitor-keys': 7.3.1
dev: false
- /@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/type-utils@7.3.1(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==}
+ engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
@@ -3556,8 +3606,8 @@ packages:
typescript:
optional: true
dependencies:
- '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
- '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/typescript-estree': 7.3.1(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.4.2)
debug: 4.3.4
eslint: 8.57.0
ts-api-utils: 1.2.1(typescript@5.4.2)
@@ -3566,22 +3616,22 @@ packages:
- supports-color
dev: false
- /@typescript-eslint/types@7.2.0:
- resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/types@7.3.1:
+ resolution: {integrity: sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==}
+ engines: {node: ^18.18.0 || >=20.0.0}
dev: false
- /@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.2):
- resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/typescript-estree@7.3.1(typescript@5.4.2):
+ resolution: {integrity: sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==}
+ engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
- '@typescript-eslint/types': 7.2.0
- '@typescript-eslint/visitor-keys': 7.2.0
+ '@typescript-eslint/types': 7.3.1
+ '@typescript-eslint/visitor-keys': 7.3.1
debug: 4.3.4
globby: 11.1.0
is-glob: 4.0.3
@@ -3593,18 +3643,18 @@ packages:
- supports-color
dev: false
- /@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@5.4.2):
- resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/utils@7.3.1(eslint@8.57.0)(typescript@5.4.2):
+ resolution: {integrity: sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==}
+ engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.7
- '@typescript-eslint/scope-manager': 7.2.0
- '@typescript-eslint/types': 7.2.0
- '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ '@typescript-eslint/scope-manager': 7.3.1
+ '@typescript-eslint/types': 7.3.1
+ '@typescript-eslint/typescript-estree': 7.3.1(typescript@5.4.2)
eslint: 8.57.0
semver: 7.6.0
transitivePeerDependencies:
@@ -3612,11 +3662,11 @@ packages:
- typescript
dev: false
- /@typescript-eslint/visitor-keys@7.2.0:
- resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ /@typescript-eslint/visitor-keys@7.3.1:
+ resolution: {integrity: sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==}
+ engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
- '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/types': 7.3.1
eslint-visitor-keys: 3.4.3
dev: false
@@ -5066,8 +5116,8 @@ packages:
- supports-color
dev: false
- /drizzle-orm@0.30.2(@types/better-sqlite3@7.6.9)(better-sqlite3@9.4.3)(mysql2@3.9.2)(react@17.0.2):
- resolution: {integrity: sha512-DNd3djg03o+WxZX3pGD8YD+qrWT8gbrbhaZ2W0PVb6yH4rtM/VTB92cTGvumcRh7SSd2KfV0NWYDB70BHIXQTg==}
+ /drizzle-orm@0.30.3(@types/better-sqlite3@7.6.9)(better-sqlite3@9.4.3)(mysql2@3.9.2)(react@17.0.2):
+ resolution: {integrity: sha512-tmIUPy71Ca7BUD5M7Tn9bvXESDWoj66d6lTdKCdf30V26xDFFjbx7DMamhOiWU+H1fflBk5rCdtGyt2SiFPgRg==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
'@cloudflare/workers-types': '>=3'
@@ -5473,7 +5523,7 @@ packages:
- supports-color
dev: false
- /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.3.1)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
engines: {node: '>=4'}
peerDependencies:
@@ -5494,7 +5544,7 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/parser': 7.3.1(eslint@8.57.0)(typescript@5.4.2)
debug: 3.2.7
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@@ -5502,7 +5552,7 @@ packages:
- supports-color
dev: false
- /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0)(eslint@8.57.0):
+ /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.3.1)(eslint@8.57.0):
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
engines: {node: '>=4'}
peerDependencies:
@@ -5512,7 +5562,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/parser': 7.3.1(eslint@8.57.0)(typescript@5.4.2)
array-includes: 3.1.7
array.prototype.findlastindex: 1.2.4
array.prototype.flat: 1.3.2
@@ -5521,7 +5571,7 @@ packages:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.2.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.3.1)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
hasown: 2.0.1
is-core-module: 2.13.1
is-glob: 4.0.3
@@ -6881,7 +6931,7 @@ packages:
resolution: {integrity: sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==}
dev: false
- /jotai@2.7.1(@types/react@18.2.66)(react@18.2.0):
+ /jotai@2.7.1(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-bsaTPn02nFgWNP6cBtg/htZhCu4s0wxqoklRHePp6l/vlsypR9eLn7diRliwXYWMXDpPvW/LLA2afI8vwgFFaw==}
engines: {node: '>=12.20.0'}
peerDependencies:
@@ -6893,7 +6943,7 @@ packages:
react:
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
react: 18.2.0
dev: false
@@ -7206,8 +7256,8 @@ packages:
/magicast@0.3.3:
resolution: {integrity: sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==}
dependencies:
- '@babel/parser': 7.24.0
- '@babel/types': 7.24.0
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
source-map-js: 1.0.2
dev: true
@@ -7228,20 +7278,6 @@ packages:
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
- /mantine-modal-manager@7.6.2(@mantine/core@7.6.2)(@mantine/hooks@7.6.2)(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-tU6nHe8ImEdpBY9WaY4xtVXYc23BTKwJ+0bJ7m3/KkbDw7SSWOXOMevIrcnunlmvZfb5jAwm3CGGmdcS5gYx5w==}
- peerDependencies:
- '@mantine/core': 7.6.2
- '@mantine/hooks': 7.6.2
- react: ^18.2.0
- react-dom: ^18.2.0
- dependencies:
- '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
- '@mantine/hooks': 7.6.2(react@18.2.0)
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
- dev: false
-
/mantine-react-table@2.0.0-beta.0(@mantine/core@7.6.2)(@mantine/dates@7.6.2)(@mantine/hooks@7.6.2)(@tabler/icons-react@3.1.0)(clsx@2.1.0)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-KZOr7nzoSt4aF0hyWKJJBK9/dxVWB3tdg2fFSNnqns9cbPFlLTGXDKguLSoNE8WkzjWE0ThpYJknIAoraL/7ug==}
engines: {node: '>=16'}
@@ -7255,7 +7291,7 @@ packages:
react: '>=18.0'
react-dom: '>=18.0'
dependencies:
- '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
+ '@mantine/core': 7.6.2(@mantine/hooks@7.6.2)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@mantine/dates': 7.6.2(@mantine/core@7.6.2)(@mantine/hooks@7.6.2)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 7.6.2(react@18.2.0)
'@tabler/icons-react': 3.1.0(react@18.2.0)
@@ -8472,7 +8508,7 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /react-remove-scroll-bar@2.3.4(@types/react@18.2.66)(react@18.2.0):
+ /react-remove-scroll-bar@2.3.4(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
engines: {node: '>=10'}
peerDependencies:
@@ -8482,13 +8518,13 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
react: 18.2.0
- react-style-singleton: 2.2.1(@types/react@18.2.66)(react@18.2.0)
+ react-style-singleton: 2.2.1(@types/react@18.2.67)(react@18.2.0)
tslib: 2.6.2
dev: false
- /react-remove-scroll@2.5.7(@types/react@18.2.66)(react@18.2.0):
+ /react-remove-scroll@2.5.7(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==}
engines: {node: '>=10'}
peerDependencies:
@@ -8498,16 +8534,16 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
react: 18.2.0
- react-remove-scroll-bar: 2.3.4(@types/react@18.2.66)(react@18.2.0)
- react-style-singleton: 2.2.1(@types/react@18.2.66)(react@18.2.0)
+ react-remove-scroll-bar: 2.3.4(@types/react@18.2.67)(react@18.2.0)
+ react-style-singleton: 2.2.1(@types/react@18.2.67)(react@18.2.0)
tslib: 2.6.2
- use-callback-ref: 1.3.1(@types/react@18.2.66)(react@18.2.0)
- use-sidecar: 1.1.2(@types/react@18.2.66)(react@18.2.0)
+ use-callback-ref: 1.3.1(@types/react@18.2.67)(react@18.2.0)
+ use-sidecar: 1.1.2(@types/react@18.2.67)(react@18.2.0)
dev: false
- /react-style-singleton@2.2.1(@types/react@18.2.66)(react@18.2.0):
+ /react-style-singleton@2.2.1(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
peerDependencies:
@@ -8517,14 +8553,14 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
get-nonce: 1.0.1
invariant: 2.2.4
react: 18.2.0
tslib: 2.6.2
dev: false
- /react-textarea-autosize@8.5.3(@types/react@18.2.66)(react@18.2.0):
+ /react-textarea-autosize@8.5.3(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==}
engines: {node: '>=10'}
peerDependencies:
@@ -8533,7 +8569,7 @@ packages:
'@babel/runtime': 7.23.9
react: 18.2.0
use-composed-ref: 1.3.0(react@18.2.0)
- use-latest: 1.2.1(@types/react@18.2.66)(react@18.2.0)
+ use-latest: 1.2.1(@types/react@18.2.67)(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
dev: false
@@ -9889,7 +9925,7 @@ packages:
requires-port: 1.0.0
dev: true
- /use-callback-ref@1.3.1(@types/react@18.2.66)(react@18.2.0):
+ /use-callback-ref@1.3.1(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==}
engines: {node: '>=10'}
peerDependencies:
@@ -9899,7 +9935,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
react: 18.2.0
tslib: 2.6.2
dev: false
@@ -9923,7 +9959,7 @@ packages:
react: 18.2.0
dev: false
- /use-isomorphic-layout-effect@1.1.2(@types/react@18.2.66)(react@18.2.0):
+ /use-isomorphic-layout-effect@1.1.2(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
peerDependencies:
'@types/react': '*'
@@ -9932,11 +9968,11 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
react: 18.2.0
dev: false
- /use-latest@1.2.1(@types/react@18.2.66)(react@18.2.0):
+ /use-latest@1.2.1(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==}
peerDependencies:
'@types/react': '*'
@@ -9945,12 +9981,12 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
react: 18.2.0
- use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.66)(react@18.2.0)
+ use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.67)(react@18.2.0)
dev: false
- /use-sidecar@1.1.2(@types/react@18.2.66)(react@18.2.0):
+ /use-sidecar@1.1.2(@types/react@18.2.67)(react@18.2.0):
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'}
peerDependencies:
@@ -9960,7 +9996,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@types/react': 18.2.66
+ '@types/react': 18.2.67
detect-node-es: 1.1.0
react: 18.2.0
tslib: 2.6.2
diff --git a/turbo/generators/templates/package.json.hbs b/turbo/generators/templates/package.json.hbs
index aaaaf7d8f..8c2ed9ff9 100644
--- a/turbo/generators/templates/package.json.hbs
+++ b/turbo/generators/templates/package.json.hbs
@@ -4,6 +4,6 @@
"format": "prettier --check . --ignore-path ../../.gitignore", "typecheck": "tsc
--noEmit" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig":
-"workspace:^0.1.0", "eslint": "^8.56.0", "typescript": "^5.3.3" },
+"workspace:^0.1.0", "eslint": "^8.57.0", "typescript": "^5.4.2" },
"eslintConfig": { "extends": [ "@homarr/eslint-config/base" ] }, "prettier":
"@homarr/prettier-config" }
\ No newline at end of file