From 9bbce7a20fdce734767c749a4f2f6ba40b8bdee1 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Wed, 20 May 2026 14:24:26 +0200 Subject: [PATCH] Fix Mantine v9 CI failures for typecheck, lint, and e2e. Align form generics with v9 TransformedValues typing, merge element-size refs for gridstack, and tighten selectors and lint fixes from the migration. --- .../app/[locale]/auth/login/_login-form.tsx | 4 +- .../integration-secret-segmented-control.tsx | 2 +- .../settings/_components/setting-switch.tsx | 2 +- .../manage/tools/logs/level-selection.tsx | 11 +++- .../_components/_first-day-of-week.tsx | 2 +- .../board/sections/gridstack/use-gridstack.ts | 17 ++++--- e2e/lldap.spec.ts | 2 +- .../_inputs/widget-dynamic-select-input.tsx | 50 ++++++++++++------- .../src/_inputs/widget-select-input.tsx | 2 +- .../src/media-transcoding/component.tsx | 9 +++- 10 files changed, 64 insertions(+), 37 deletions(-) diff --git a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx index 10a2f7c58..8d36d1e79 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx @@ -9,7 +9,7 @@ import { z } from "zod/v4"; import { signIn } from "@homarr/auth/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; -import type { useForm } from "@homarr/form"; +import type { UseFormReturnType } from "@homarr/form"; import { useZodForm } from "@homarr/form"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useScopedI18n } from "@homarr/translation/client"; @@ -162,7 +162,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c interface SubmitButtonProps { isPending: boolean; - form: ReturnType FormType>>; + form: UseFormReturnType; provider: "credentials" | "ldap"; } diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/_components/secrets/integration-secret-segmented-control.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/_components/secrets/integration-secret-segmented-control.tsx index e524b3870..9272e47c2 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/_components/secrets/integration-secret-segmented-control.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/_components/secrets/integration-secret-segmented-control.tsx @@ -12,7 +12,7 @@ interface FormType { interface SecretKindsSegmentedControlProps { defaultKinds?: IntegrationSecretKind[]; secretKinds: IntegrationSecretKind[][]; - form: UseFormReturnType TFormType>; + form: UseFormReturnType; } export const SecretKindsSegmentedControl = ({ diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/setting-switch.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/setting-switch.tsx index 293a8fa96..adec086ad 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/setting-switch.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/setting-switch.tsx @@ -15,7 +15,7 @@ export const SwitchSetting = >({ disabled, }: { form: Modify< - UseFormReturnType TFormValue>, + UseFormReturnType, { setFieldValue: (key: keyof TFormValue, value: (previous: boolean) => boolean) => void; } diff --git a/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx b/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx index 71846ca02..243521471 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/logs/level-selection.tsx @@ -2,12 +2,14 @@ import { Select } from "@mantine/core"; -import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; import { logLevelConfiguration, logLevels } from "@homarr/core/infrastructure/logs/constants"; +import type { LogLevel } from "@homarr/core/infrastructure/logs/constants"; import { useI18n } from "@homarr/translation/client"; import { useLogContext } from "./log-context"; +const levelByValue = Object.fromEntries(logLevels.map((logLevel) => [logLevel, logLevel])) as Record; + export const LogLevelSelection = () => { const { level, setLevel } = useLogContext(); const t = useI18n(); @@ -19,7 +21,12 @@ export const LogLevelSelection = () => { label: `${logLevelConfiguration[level].prefix} ${t(`log.level.option.${level}`)}`, }))} value={level} - onChange={(value) => setLevel(value as LogLevel)} + onChange={(value) => { + const nextLevel = value && levelByValue[value]; + if (nextLevel) { + setLevel(nextLevel); + } + }} checkIconPosition="right" /> ); diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx index 71bef0737..2bf43a3bc 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx @@ -62,7 +62,7 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => { return (
- onChange(parseInt(value))}> + onChange(parseInt(nextValue, 10))}> diff --git a/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts b/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts index eda89a8e6..b5e468de6 100644 --- a/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts +++ b/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts @@ -1,6 +1,6 @@ -import type { RefObject } from "react"; +import type { Ref, RefObject } from "react"; import { createRef, useCallback, useEffect, useRef } from "react"; -import { useElementSize } from "@mantine/hooks"; +import { useElementSize, useMergedRef } from "@mantine/hooks"; import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context"; import { useEditMode } from "@homarr/boards/edit-mode"; @@ -13,7 +13,7 @@ import { useSectionActions } from "../section-actions"; import { initializeGridstack } from "./init-gridstack"; export interface UseGridstackRefs { - wrapper: RefObject; + wrapper: Ref; items: RefObject>>; gridstack: RefObject; } @@ -59,8 +59,9 @@ export const useGridstack = (section: Omit, itemIds: string[]) const { moveAndResizeItem, moveItemToSection } = useItemActions(); const { moveAndResizeInnerSection, moveInnerSectionToSection } = useSectionActions(); - // define reference for wrapper - is used to calculate the width of the wrapper - const { ref: wrapperRef, width, height } = useElementSize(); + const wrapperElementRef = useRef(null); + const { ref: measureWrapperRef, width, height } = useElementSize(); + const wrapperRef = useMergedRef(measureWrapperRef, wrapperElementRef); // references to the diffrent items contained in the gridstack const itemRefs = useRef>>({}); // reference of the gridstack object for modifications after initialization @@ -193,7 +194,7 @@ export const useGridstack = (section: Omit, itemIds: string[]) itemIds, refs: { items: itemRefs, - wrapper: wrapperRef, + wrapper: wrapperElementRef, gridstack: gridRef, }, sectionColumnCount: columnCount, @@ -276,7 +277,7 @@ export const useGridstack = (section: Omit, itemIds: string[]) useCssVariableConfiguration({ columnCount, gridRef, - wrapperRef, + wrapperRef: wrapperElementRef, width, height, isDynamic: section.kind === "dynamic", @@ -293,7 +294,7 @@ export const useGridstack = (section: Omit, itemIds: string[]) interface UseCssVariableConfiguration { gridRef: UseGridstackRefs["gridstack"]; - wrapperRef: UseGridstackRefs["wrapper"]; + wrapperRef: RefObject; width: number; height: number; columnCount: number; diff --git a/e2e/lldap.spec.ts b/e2e/lldap.spec.ts index a7fb2a7ef..f1f07790d 100644 --- a/e2e/lldap.spec.ts +++ b/e2e/lldap.spec.ts @@ -45,7 +45,7 @@ describe("LLDAP authorization", () => { // Act await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}/auth/login`); await page.getByLabel("Username").fill(defaultCredentials.username); - await page.getByLabel("Password").fill(defaultCredentials.password); + await page.locator("#password").fill(defaultCredentials.password); await page.locator("css=button[type='submit']").click(); // Assert diff --git a/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx b/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx index 39b70b325..14bcc8a67 100644 --- a/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx +++ b/packages/widgets/src/_inputs/widget-dynamic-select-input.tsx @@ -25,9 +25,22 @@ export const WidgetDynamicSelectInput = ({ property, kind, options }: CommonWidg const [search, setSearch] = useState(""); const [debouncedSearch] = useDebouncedValue(search, 300); const { isPending, options: selectOptions } = options.useOptions(debouncedSearch, form.values.integrationIds); - const value = inputProps.value as DynamicSelectOption | null; + const currentOption = inputProps.value as DynamicSelectOption | null; const onChange = inputProps.onChange as (value: DynamicSelectOption | null) => void; + const getLabelForValue = (selectedValue: string) => { + const matched = selectOptions.find((option) => + typeof option === "string" ? option === selectedValue : option.value === selectedValue, + ); + if (!matched) { + return selectedValue; + } + if (typeof matched === "string") { + return matched; + } + return translateIfNecessary(t, matched.label) ?? selectedValue; + }; + return (