Compare commits

...
Author SHA1 Message Date
ajnart 5b41d169c0 fix: simplify settings layout 2026-08-20 12:21:35 +02:00
ajnart 62b3c0aa5e fix: rework settings section navigation 2026-08-20 12:18:55 +02:00
ajnart a07e6a2188 fix: align settings responsive breakpoint 2026-08-20 11:59:10 +02:00
ajnart c09f3e7501 feat: rework management settings layout 2026-08-20 11:48:13 +02:00
7 changed files with 30 additions and 15 deletions

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ tags:
The settings page applies options to the whole Homarr application. The settings page applies options to the whole Homarr application.
Some changes need a full page reload before they take effect. Some changes need a full page reload before they take effect.
All changes are collected in a single form. Click the **Save changes** button in the save bar at the bottom of the page to apply them. The save bar only appears when you have unsaved changes, and the **Discard** button reverts your changes. All settings sections remain visible in a single form. Click the **Save changes** button in the save bar at the bottom of the page to apply them. The save bar only appears when you have unsaved changes, and the **Discard** button reverts your changes.
## Analytics ## Analytics
@@ -29,6 +29,7 @@ export const AppearanceSettingsForm = ({ form }: AppearanceSettingsFormProps) =>
}))} }))}
{...form.getInputProps("defaultColorScheme")} {...form.getInputProps("defaultColorScheme")}
SelectOption={AppearanceCustomOption} SelectOption={AppearanceCustomOption}
withinPortal
/> />
</SectionCard> </SectionCard>
); );
@@ -2,7 +2,7 @@
import { Switch, Text } from "@mantine/core"; import { Switch, Text } from "@mantine/core";
import { clientApi } from "@homarr/api/client"; import type { RouterOutputs } from "@homarr/api";
import type { UseFormReturnType } from "@homarr/form"; import type { UseFormReturnType } from "@homarr/form";
import { useScopedI18n } from "@homarr/translation/client"; import { useScopedI18n } from "@homarr/translation/client";
@@ -12,11 +12,11 @@ import type { FormValues } from "./settings-form";
interface BoardSettingsFormProps { interface BoardSettingsFormProps {
form: UseFormReturnType<FormValues>; form: UseFormReturnType<FormValues>;
selectableBoards: RouterOutputs["board"]["getPublicBoards"];
} }
export const BoardSettingsForm = ({ form }: BoardSettingsFormProps) => { export const BoardSettingsForm = ({ form, selectableBoards }: BoardSettingsFormProps) => {
const tBoard = useScopedI18n("management.page.settings.section.board"); const tBoard = useScopedI18n("management.page.settings.section.board");
const [selectableBoards] = clientApi.board.getPublicBoards.useSuspenseQuery();
return ( return (
<SectionCard title={tBoard("title")}> <SectionCard title={tBoard("title")}>
@@ -26,6 +26,7 @@ export const BoardSettingsForm = ({ form }: BoardSettingsFormProps) => {
clearable clearable
boards={selectableBoards} boards={selectableBoards}
{...form.getInputProps("homeBoardId")} {...form.getInputProps("homeBoardId")}
withinPortal
/> />
<BoardSelect <BoardSelect
@@ -34,6 +35,7 @@ export const BoardSettingsForm = ({ form }: BoardSettingsFormProps) => {
clearable clearable
boards={selectableBoards} boards={selectableBoards}
{...form.getInputProps("mobileHomeBoardId")} {...form.getInputProps("mobileHomeBoardId")}
withinPortal
/> />
<Text fw={500}>{tBoard("status.title")}</Text> <Text fw={500}>{tBoard("status.title")}</Text>
@@ -22,6 +22,7 @@ export const CultureSettingsForm = ({ form }: CultureSettingsFormProps) => {
label={tCulture("defaultLocale.label")} label={tCulture("defaultLocale.label")}
{...localeInputProps} {...localeInputProps}
value={localeInputProps.value as SupportedLanguage} value={localeInputProps.value as SupportedLanguage}
withinPortal
/> />
</SectionCard> </SectionCard>
); );
@@ -2,7 +2,7 @@
import { Select } from "@mantine/core"; import { Select } from "@mantine/core";
import { clientApi } from "@homarr/api/client"; import type { RouterOutputs } from "@homarr/api";
import type { UseFormReturnType } from "@homarr/form"; import type { UseFormReturnType } from "@homarr/form";
import { useScopedI18n } from "@homarr/translation/client"; import { useScopedI18n } from "@homarr/translation/client";
@@ -11,11 +11,11 @@ import type { FormValues } from "./settings-form";
interface SearchSettingsFormProps { interface SearchSettingsFormProps {
form: UseFormReturnType<FormValues>; form: UseFormReturnType<FormValues>;
selectableSearchEngines: RouterOutputs["searchEngine"]["getSelectable"];
} }
export const SearchSettingsForm = ({ form }: SearchSettingsFormProps) => { export const SearchSettingsForm = ({ form, selectableSearchEngines }: SearchSettingsFormProps) => {
const tSearch = useScopedI18n("management.page.settings.section.search"); const tSearch = useScopedI18n("management.page.settings.section.search");
const [selectableSearchEngines] = clientApi.searchEngine.getSelectable.useSuspenseQuery({ withIntegrations: false });
return ( return (
<SectionCard title={tSearch("title")}> <SectionCard title={tSearch("title")}>
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react";
import { Button, Stack } from "@mantine/core"; import { Button, Stack } from "@mantine/core";
import { z } from "zod/v4"; import { z } from "zod/v4";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client"; import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client"; import { revalidatePathActionAsync } from "@homarr/common/client";
import { env } from "@homarr/common/env"; import { env } from "@homarr/common/env";
@@ -58,9 +59,11 @@ const buildInitialValues = (initialData: ServerSettings): FormValues => ({
interface SettingsFormProps { interface SettingsFormProps {
initialData: ServerSettings; initialData: ServerSettings;
selectableBoards: RouterOutputs["board"]["getPublicBoards"];
selectableSearchEngines: RouterOutputs["searchEngine"]["getSelectable"];
} }
export const SettingsForm = ({ initialData }: SettingsFormProps) => { export const SettingsForm = ({ initialData, selectableBoards, selectableSearchEngines }: SettingsFormProps) => {
const t = useI18n(); const t = useI18n();
const tSettings = useScopedI18n("management.page.settings"); const tSettings = useScopedI18n("management.page.settings");
@@ -169,13 +172,13 @@ export const SettingsForm = ({ initialData }: SettingsFormProps) => {
return ( return (
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}> <form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
<Stack gap="xl"> <Stack gap="xl">
<AnalyticsSettings form={form} /> <BoardSettingsForm form={form} selectableBoards={selectableBoards} />
<CrawlingAndIndexingSettings form={form} />
<BoardSettingsForm form={form} />
<UserSettingsForm form={form} /> <UserSettingsForm form={form} />
<SearchSettingsForm form={form} /> <SearchSettingsForm form={form} selectableSearchEngines={selectableSearchEngines} />
<AppearanceSettingsForm form={form} /> <AppearanceSettingsForm form={form} />
<CultureSettingsForm form={form} /> <CultureSettingsForm form={form} />
<AnalyticsSettings form={form} />
<CrawlingAndIndexingSettings form={form} />
{form.isDirty() && ( {form.isDirty() && (
<UnsavedChangesBar> <UnsavedChangesBar>
@@ -23,12 +23,20 @@ export default async function SettingsPage() {
notFound(); notFound();
} }
const serverSettings = await api.serverSettings.getAll(); const [serverSettings, selectableBoards, selectableSearchEngines, tSettings] = await Promise.all([
const tSettings = await getScopedI18n("management.page.settings"); api.serverSettings.getAll(),
api.board.getPublicBoards(),
api.searchEngine.getSelectable({ withIntegrations: false }),
getScopedI18n("management.page.settings"),
]);
return ( return (
<ManagePageLayout title={tSettings("title")}> <ManagePageLayout title={tSettings("title")}>
<SettingsForm initialData={serverSettings} /> <SettingsForm
initialData={serverSettings}
selectableBoards={selectableBoards}
selectableSearchEngines={selectableSearchEngines}
/>
</ManagePageLayout> </ManagePageLayout>
); );
} }