Skip to content

Commit

Permalink
chore: address pull request feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
Meierschlumpf committed Oct 25, 2024
1 parent 979794a commit f038ec2
Show file tree
Hide file tree
Showing 15 changed files with 100 additions and 38 deletions.
6 changes: 3 additions & 3 deletions apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { clientApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { parseCookies, setClientCookie } from "@homarr/common";
import type { ColorScheme } from "@homarr/definitions";
import { colorSchemeCookieKey } from "@homarr/definitions";

export const CustomMantineProvider = ({
children,
Expand All @@ -33,11 +34,10 @@ export const CustomMantineProvider = ({
};

export function useColorSchemeManager(): MantineColorSchemeManager {
const key = "homarr-color-scheme";
const { data: session } = useSession();

const updateCookieValue = (value: Exclude<MantineColorScheme, "auto">) => {
setClientCookie(key, value, { expires: dayjs().add(1, "year").toDate(), path: "/" });
setClientCookie(colorSchemeCookieKey, value, { expires: dayjs().add(1, "year").toDate(), path: "/" });
};

const { mutate: mutateColorScheme } = clientApi.user.changeColorScheme.useMutation({
Expand All @@ -54,7 +54,7 @@ export function useColorSchemeManager(): MantineColorSchemeManager {

try {
const cookies = parseCookies(document.cookie);
return (cookies[key] as MantineColorScheme | undefined) ?? defaultValue;
return (cookies[colorSchemeCookieKey] as MantineColorScheme | undefined) ?? defaultValue;
} catch {
return defaultValue;
}
Expand Down
22 changes: 13 additions & 9 deletions apps/nextjs/src/app/[locale]/boards/[name]/settings/_danger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { BoardRenameModal } from "~/components/board/modals/board-rename-modal";
import { useRequiredBoard } from "../../(content)/_context";
import classes from "./danger.module.css";

export const DangerZoneSettingsContent = () => {
export const DangerZoneSettingsContent = ({ hideVisibility }: { hideVisibility: boolean }) => {
const board = useRequiredBoard();
const t = useScopedI18n("board.setting");
const router = useRouter();
Expand Down Expand Up @@ -90,14 +90,18 @@ export const DangerZoneSettingsContent = () => {
buttonText={t("section.dangerZone.action.rename.button")}
onClick={onRenameClick}
/>
<Divider />
<DangerZoneRow
label={t("section.dangerZone.action.visibility.label")}
description={t(`section.dangerZone.action.visibility.description.${visibility}`)}
buttonText={t(`section.dangerZone.action.visibility.button.${visibility}`)}
onClick={onVisibilityClick}
isPending={isChangeVisibilityPending}
/>
{hideVisibility ? null : (
<>
<Divider />
<DangerZoneRow
label={t("section.dangerZone.action.visibility.label")}
description={t(`section.dangerZone.action.visibility.description.${visibility}`)}
buttonText={t(`section.dangerZone.action.visibility.button.${visibility}`)}
onClick={onVisibilityClick}
isPending={isChangeVisibilityPending}
/>
</>
)}
<Divider />
<DangerZoneRow
label={t("section.dangerZone.action.delete.label")}
Expand Down
5 changes: 4 additions & 1 deletion apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { TRPCError } from "@trpc/server";

import { api } from "@homarr/api/server";
import { capitalize } from "@homarr/common";
import { db } from "@homarr/db";
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
import type { TranslationObject } from "@homarr/translation";
import { getScopedI18n } from "@homarr/translation/server";
import type { TablerIcon } from "@homarr/ui";
Expand Down Expand Up @@ -63,6 +65,7 @@ const getBoardAndPermissionsAsync = async (params: Props["params"]) => {

export default async function BoardSettingsPage({ params, searchParams }: Props) {
const { board, permissions } = await getBoardAndPermissionsAsync(params);
const boardSettings = await getServerSettingByKeyAsync(db, "board");
const { hasFullAccess } = await getBoardPermissionsAsync(board);
const t = await getScopedI18n("board.setting");

Expand Down Expand Up @@ -92,7 +95,7 @@ export default async function BoardSettingsPage({ params, searchParams }: Props)
<BoardAccessSettings board={board} initialPermissions={permissions} />
</AccordionItemFor>
<AccordionItemFor value="dangerZone" icon={IconAlertTriangle} danger noPadding>
<DangerZoneSettingsContent />
<DangerZoneSettingsContent hideVisibility={boardSettings.defaultBoardId === board.id} />
</AccordionItemFor>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import { SelectWithCustomItems } from "node_modules/@homarr/ui/src/components/se
import type { ColorScheme } from "@homarr/definitions";
import { colorSchemes } from "@homarr/definitions";
import type { ServerSettings } from "@homarr/server-settings";
import { useScopedI18n } from "@homarr/translation/client";

import { CommonSettingsForm } from "./common-form";

export const AppearanceSettingsForm = ({ defaultValues }: { defaultValues: ServerSettings["appearance"] }) => {
const tApperance = useScopedI18n("management.page.settings.section.appearance");

return (
<CommonSettingsForm settingKey="appearance" defaultValues={defaultValues}>
{(form) => (
<>
<SelectWithCustomItems
label="Default color scheme"
label={tApperance("defaultColorScheme.label")}
data={colorSchemes.map((scheme) => ({
value: scheme,
label: scheme,
label: tApperance(`defaultColorScheme.options.${scheme}`),
}))}
{...form.getInputProps("defaultColorScheme")}
SelectOption={ApperanceCustomOption}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import { SelectWithCustomItems } from "node_modules/@homarr/ui/src/components/se

import { clientApi } from "@homarr/api/client";
import type { ServerSettings } from "@homarr/server-settings";
import { useScopedI18n } from "@homarr/translation/client";

import { CommonSettingsForm } from "./common-form";

export const BoardSettingsForm = ({ defaultValues }: { defaultValues: ServerSettings["board"] }) => {
const tBoard = useScopedI18n("management.page.settings.section.board");
const [selectableBoards] = clientApi.board.getPublicBoards.useSuspenseQuery();

return (
<CommonSettingsForm settingKey="board" defaultValues={defaultValues}>
{(form) => (
<>
<SelectWithCustomItems
label="Global default board"
description="Only public boards are available for selection"
label={tBoard("defaultBoard.label")}
description={tBoard("defaultBoard.description")}
data={selectableBoards.map((board) => ({
value: board.id,
label: board.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { clientApi } from "@homarr/api/client";
import { useForm } from "@homarr/form";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import type { ServerSettings } from "@homarr/server-settings";
import { useI18n, useScopedI18n } from "@homarr/translation/client";

export const CommonSettingsForm = <TKey extends keyof ServerSettings>({
settingKey,
Expand All @@ -16,15 +17,17 @@ export const CommonSettingsForm = <TKey extends keyof ServerSettings>({
defaultValues: ServerSettings[TKey];
children: (form: ReturnType<typeof useForm<ServerSettings[TKey]>>) => React.ReactNode;
}) => {
const t = useI18n();
const tSettings = useScopedI18n("management.page.settings");
const { mutateAsync, isPending } = clientApi.serverSettings.saveSettings.useMutation({
onSuccess() {
showSuccessNotification({
message: "Settings saved successfully",
message: tSettings("notification.success.message"),
});
},
onError() {
showErrorNotification({
message: "Failed to save settings",
message: tSettings("notification.error.message"),
});
},
});
Expand All @@ -45,7 +48,7 @@ export const CommonSettingsForm = <TKey extends keyof ServerSettings>({
{children(form)}
<Group justify="end">
<Button type="submit" loading={isPending}>
Save
{t("common.action.save")}
</Button>
</Group>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ import { objectEntries } from "@homarr/common";
import type { ServerSettings } from "@homarr/server-settings";
import type { SupportedLanguage } from "@homarr/translation";
import { localeAttributes } from "@homarr/translation";
import { useScopedI18n } from "@homarr/translation/client";

import { CommonSettingsForm } from "./common-form";

export const CultureSettingsForm = ({ defaultValues }: { defaultValues: ServerSettings["culture"] }) => {
const tCulture = useScopedI18n("management.page.settings.section.culture");

return (
<CommonSettingsForm settingKey="culture" defaultValues={defaultValues}>
{(form) => (
<>
<SelectWithCustomItems
label="Default locale"
label={tCulture("defaultLocale.label")}
data={objectEntries(localeAttributes).map(([value, { name }]) => ({
value,
label: name,
Expand Down
10 changes: 5 additions & 5 deletions apps/nextjs/src/app/[locale]/manage/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@ export async function generateMetadata() {

export default async function SettingsPage() {
const serverSettings = await api.serverSettings.getAll();
const t = await getScopedI18n("management.page.settings");
const tSettings = await getScopedI18n("management.page.settings");
return (
<>
<DynamicBreadcrumb />
<Stack>
<Title order={1}>{t("title")}</Title>
<Title order={1}>{tSettings("title")}</Title>
<AnalyticsSettings initialData={serverSettings.analytics} />
<CrawlingAndIndexingSettings initialData={serverSettings.crawlingAndIndexing} />
<Stack>
<Title order={2}>Boards</Title>
<Title order={2}>{tSettings("section.board.title")}</Title>
<BoardSettingsForm defaultValues={serverSettings.board} />
</Stack>
<Stack>
<Title order={2}>Appearance</Title>
<Title order={2}>{tSettings("section.appearance.title")}</Title>
<AppearanceSettingsForm defaultValues={serverSettings.appearance} />
</Stack>
<Stack>
<Title order={2}>Culture</Title>
<Title order={2}>{tSettings("section.culture.title")}</Title>
<CultureSettingsForm defaultValues={serverSettings.culture} />
</Stack>
</Stack>
Expand Down
3 changes: 2 additions & 1 deletion apps/nextjs/src/theme/color-scheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { cookies } from "next/headers";
import { db } from "@homarr/db";
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
import type { ColorScheme } from "@homarr/definitions";
import { colorSchemeCookieKey } from "@homarr/definitions";

export const getCurrentColorSchemeAsync = cache(async () => {
const cookieValue = cookies().get("homarr-color-scheme")?.value;
const cookieValue = cookies().get(colorSchemeCookieKey)?.value;

if (cookieValue) {
return cookieValue as ColorScheme;
Expand Down
22 changes: 16 additions & 6 deletions packages/api/src/router/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ export const boardRouter = createTRPCRouter({
.input(validation.board.changeVisibility)
.mutation(async ({ ctx, input }) => {
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");

if (input.visibility !== "public" && boardSettings.defaultBoardId === input.id) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Cannot make default board private",
});
}

await ctx.db
.update(boards)
Expand All @@ -251,12 +259,14 @@ export const boardRouter = createTRPCRouter({
})
: null;

const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
const boardWhere = user?.homeBoardId
? eq(boards.id, user.homeBoardId)
: boardSettings.defaultBoardId
? eq(boards.id, boardSettings.defaultBoardId)
: null;
// 1. user home board, 2. default board, 3. not found
let boardWhere: SQL<unknown> | null = null;
if (user?.homeBoardId) {
boardWhere = eq(boards.id, user.homeBoardId);
} else {
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
boardWhere = boardSettings.defaultBoardId ? eq(boards.id, boardSettings.defaultBoardId) : null;
}

if (!boardWhere) {
throw new TRPCError({

Check failure on line 272 in packages/api/src/router/board.ts

View workflow job for this annotation

GitHub Actions / test

packages/api/src/router/test/board.spec.ts > getHomeBoard should return home board > should return home board

TRPCError: No home board found ❯ packages/api/src/router/board.ts:272:13 ❯ resolveMiddleware node_modules/@trpc/server/dist/unstable-core-do-not-import/procedureBuilder.mjs:102:30 ❯ callRecursive node_modules/@trpc/server/dist/unstable-core-do-not-import/procedureBuilder.mjs:143:24 ❯ procedure node_modules/@trpc/server/dist/unstable-core-do-not-import/procedureBuilder.mjs:176:24 ❯ node_modules/@trpc/server/dist/unstable-core-do-not-import/router.mjs:124:28 ❯ packages/api/src/router/test/board.spec.ts:485:20 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { code: 'NOT_FOUND' }
Expand Down
4 changes: 2 additions & 2 deletions packages/auth/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { NextAuthConfig } from "next-auth";
import { and, eq, inArray } from "@homarr/db";
import type { Database } from "@homarr/db";
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
import { everyoneGroup } from "@homarr/definitions";
import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
import { logger } from "@homarr/log";

import { env } from "./env.mjs";
Expand Down Expand Up @@ -52,7 +52,7 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
}

// We use a cookie as localStorage is not shared with server (otherwise flickering would occur)
cookies().set("homarr-color-scheme", dbUser.colorScheme, {
cookies().set(colorSchemeCookieKey, dbUser.colorScheme, {
path: "/",
expires: dayjs().add(1, "year").toDate(),
});
Expand Down
6 changes: 3 additions & 3 deletions packages/auth/test/events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { eq } from "@homarr/db";
import type { Database } from "@homarr/db";
import { groupMembers, groups, users } from "@homarr/db/schema/sqlite";
import { createDb } from "@homarr/db/test";
import { everyoneGroup } from "@homarr/definitions";
import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";

import { createSignInEventHandler } from "../events";

Expand Down Expand Up @@ -224,7 +224,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => {
});
expect(dbUser?.name).toBe("test-new");
});
test("signInEventHandler should set homarr-color-scheme cookie", async () => {
test("signInEventHandler should set color-scheme cookie", async () => {
// Arrange
const db = createDb();
await createUserAsync(db);
Expand All @@ -239,7 +239,7 @@ describe("createSignInEventHandler should create signInEventHandler", () => {

// Assert
expect(cookies().set).toHaveBeenCalledWith(
"homarr-color-scheme",
colorSchemeCookieKey,
"dark",
expect.objectContaining({
path: "/",
Expand Down
1 change: 1 addition & 0 deletions packages/definitions/src/cookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const colorSchemeCookieKey = "homarr-color-scheme";
1 change: 1 addition & 0 deletions packages/definitions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from "./auth";
export * from "./user";
export * from "./group";
export * from "./docs";
export * from "./cookie";
31 changes: 31 additions & 0 deletions packages/translation/src/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1847,6 +1847,14 @@ export default {
},
settings: {
title: "Settings",
notification: {
success: {
message: "Settings saved successfully",
},
error: {
message: "Failed to save settings",
},
},
section: {
analytics: {
title: "Analytics",
Expand Down Expand Up @@ -1888,6 +1896,29 @@ export default {
text: "Google will build a search box with the crawled links along with other direct links. Enabling this will ask Google to disable that box.",
},
},
board: {
title: "Boards",
defaultBoard: {
label: "Global default board",
description: "Only public boards are available for selection",
},
},
appearance: {
title: "Appearance",
defaultColorScheme: {
label: "Default color scheme",
options: {
light: "Light",
dark: "Dark",
},
},
},
culture: {
title: "Culture",
defaultLocale: {
label: "Default language",
},
},
},
},
tool: {
Expand Down

0 comments on commit f038ec2

Please sign in to comment.