From e01d74f4f89aee9a122b660e6303c356871c6ebb Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Tue, 14 Jan 2025 19:54:55 +0100 Subject: [PATCH] feat(board): add mobile home board (#1910) * feat(board): add mobile home board * fix: add missing translations * fix: mysql key reference with other datatype * fix: format issue * fix: missing trpc context arguments in tests * fix: missing trpc context arguments in tests --- .../[locale]/boards/[name]/settings/page.tsx | 6 +- .../_components/board-card-menu-dropdown.tsx | 15 +- .../src/app/[locale]/manage/boards/page.tsx | 10 +- .../_components/board-settings-form.tsx | 19 + .../_components/_change-home-board.tsx | 21 +- .../manage/users/[userId]/general/page.tsx | 2 +- packages/api/src/router/board.ts | 55 +- packages/api/src/router/test/app.spec.ts | 10 + packages/api/src/router/test/board.spec.ts | 61 +- .../router/test/docker/docker-router.spec.ts | 3 + packages/api/src/router/test/group.spec.ts | 66 +- .../integration/integration-router.spec.ts | 13 + packages/api/src/router/test/invite.spec.ts | 5 + .../src/router/test/serverSettings.spec.ts | 3 + packages/api/src/router/test/user.spec.ts | 8 + .../api/src/router/test/widgets/app.spec.ts | 2 + packages/api/src/router/user.ts | 12 +- packages/api/src/trpc.ts | 2 + packages/common/src/server.ts | 1 + packages/common/src/user-agent.ts | 11 + .../migrations/mysql/0020_salty_doorman.sql | 2 + .../migrations/mysql/meta/0020_snapshot.json | 1700 +++++++++++++++++ .../db/migrations/mysql/meta/_journal.json | 7 + .../sqlite/0020_empty_hellfire_club.sql | 1 + .../migrations/sqlite/meta/0020_snapshot.json | 1625 ++++++++++++++++ .../db/migrations/sqlite/meta/_journal.json | 7 + packages/db/schema/mysql.ts | 3 + packages/db/schema/sqlite.ts | 3 + packages/server-settings/src/index.ts | 1 + .../boards-search-group.tsx | 26 +- packages/translation/src/lang/en.json | 19 +- packages/validation/src/user.ts | 5 +- 32 files changed, 3634 insertions(+), 90 deletions(-) create mode 100644 packages/common/src/user-agent.ts create mode 100644 packages/db/migrations/mysql/0020_salty_doorman.sql create mode 100644 packages/db/migrations/mysql/meta/0020_snapshot.json create mode 100644 packages/db/migrations/sqlite/0020_empty_hellfire_club.sql create mode 100644 packages/db/migrations/sqlite/meta/0020_snapshot.json diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx index 36818573a..1ffb892d1 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx @@ -101,7 +101,11 @@ export default async function BoardSettingsPage(props: Props) { - + )} diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx index eef7dedb2..68ecf3a57 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx @@ -3,7 +3,7 @@ import { useCallback } from "react"; import Link from "next/link"; import { Menu } from "@mantine/core"; -import { IconCopy, IconHome, IconSettings, IconTrash } from "@tabler/icons-react"; +import { IconCopy, IconDeviceMobile, IconHome, IconSettings, IconTrash } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; @@ -43,6 +43,12 @@ export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => await revalidatePathActionAsync("/"); }, }); + const setMobileHomeBoardMutation = clientApi.board.setMobileHomeBoard.useMutation({ + onSettled: async () => { + // Revalidate all as it's part of the user settings, /boards page and board manage page + await revalidatePathActionAsync("/"); + }, + }); const deleteBoardMutation = clientApi.board.deleteBoard.useMutation({ onSettled: async () => { await revalidatePathActionAsync("/manage/boards"); @@ -68,6 +74,10 @@ export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => await setHomeBoardMutation.mutateAsync({ id: board.id }); }, [board.id, setHomeBoardMutation]); + const handleSetMobileHomeBoard = useCallback(async () => { + await setMobileHomeBoardMutation.mutateAsync({ id: board.id }); + }, [board.id, setMobileHomeBoardMutation]); + const handleDuplicateBoard = useCallback(() => { openDuplicateModal({ board: { @@ -85,6 +95,9 @@ export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => }> {t("setHomeBoard.label")} + }> + {t("setMobileHomeBoard.label")} + {session?.user.permissions.includes("board-create") && ( }> {t("duplicate.label")} diff --git a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx index 2b1529894..4974b6a86 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx @@ -15,7 +15,7 @@ import { Title, Tooltip, } from "@mantine/core"; -import { IconDotsVertical, IconHomeFilled, IconLock, IconWorld } from "@tabler/icons-react"; +import { IconDeviceMobile, IconDotsVertical, IconHomeFilled, IconLock, IconWorld } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; @@ -88,6 +88,14 @@ const BoardCard = async ({ board }: BoardCardProps) => { )} + {board.isMobileHome && ( + + }> + {t("action.setMobileHomeBoard.badge.label")} + + + )} + {board.creator && ( diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx index 8890cbce0..69f2329b0 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx @@ -37,6 +37,25 @@ export const BoardSettingsForm = ({ defaultValues }: { defaultValues: ServerSett )} {...form.getInputProps("homeBoardId")} /> + ({ + value: board.id, + label: board.name, + image: board.logoImageUrl, + }))} + SelectOption={({ label, image }: { value: string; label: string; image: string | null }) => ( + + {/* eslint-disable-next-line @next/next/no-img-element */} + {image ? {label} : } + + {label} + + + )} + {...form.getInputProps("mobileHomeBoardId")} + /> )} diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx index b6fe54770..1666e15b2 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx @@ -18,13 +18,14 @@ interface ChangeHomeBoardFormProps { export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormProps) => { const t = useI18n(); - const { mutate, isPending } = clientApi.user.changeHomeBoardId.useMutation({ + const { mutate, isPending } = clientApi.user.changeHomeBoards.useMutation({ async onSettled() { await revalidatePathActionAsync(`/manage/users/${user.id}`); }, onSuccess(_, variables) { form.setInitialValues({ homeBoardId: variables.homeBoardId, + mobileHomeBoardId: variables.mobileHomeBoardId, }); showSuccessNotification({ message: t("user.action.changeHomeBoard.notification.success.message"), @@ -36,9 +37,10 @@ export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormPro }); }, }); - const form = useZodForm(validation.user.changeHomeBoard, { + const form = useZodForm(validation.user.changeHomeBoards, { initialValues: { homeBoardId: user.homeBoardId ?? "", + mobileHomeBoardId: user.mobileHomeBoardId ?? "", }, }); @@ -52,7 +54,18 @@ export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormPro return (
- +