diff --git a/.github/workflows/deployment-docker-image.yml b/.github/workflows/deployment-docker-image.yml index 74772e8ef..ab7ef7e1b 100644 --- a/.github/workflows/deployment-docker-image.yml +++ b/.github/workflows/deployment-docker-image.yml @@ -94,6 +94,8 @@ jobs: env: GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }} run: | + git config user.name "Releases Homarr" + git config user.email "175486441+homarr-releases[bot]@users.noreply.github.com" git fetch origin dev git checkout dev git pull origin dev diff --git a/.gitignore b/.gitignore index 7e1d14c68..3d0fc5393 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,9 @@ yarn-error.log* *.log apps/tasks/tasks.cjs +apps/tasks/tasks.css apps/websocket/wssServer.cjs +apps/websocket/wssServer.css apps/nextjs/.million/ packages/cli/cli.cjs diff --git a/apps/nextjs/next.config.ts b/apps/nextjs/next.config.ts index 5e36315b1..5a082309c 100644 --- a/apps/nextjs/next.config.ts +++ b/apps/nextjs/next.config.ts @@ -1,13 +1,13 @@ // Importing env files here to validate on build -import "@homarr/auth/env.mjs"; -import "@homarr/db/env.mjs"; -import "@homarr/common/env.mjs"; +import "@homarr/auth/env"; +import "@homarr/db/env"; +import "@homarr/common/env"; import type { NextConfig } from "next"; import MillionLint from "@million/lint"; import createNextIntlPlugin from "next-intl/plugin"; -import "./src/env.mjs"; +import "./src/env.ts"; // Package path does not work... so we need to use relative path const withNextIntl = createNextIntlPlugin("../../packages/translation/src/request.ts"); diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index d43f482d0..fdccb2a78 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -38,18 +38,18 @@ "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0", - "@mantine/colors-generator": "^7.15.3", - "@mantine/core": "^7.15.3", - "@mantine/dropzone": "^7.15.3", - "@mantine/hooks": "^7.15.3", - "@mantine/modals": "^7.15.3", - "@mantine/tiptap": "^7.15.3", + "@mantine/colors-generator": "^7.16.0", + "@mantine/core": "^7.16.0", + "@mantine/dropzone": "^7.16.0", + "@mantine/hooks": "^7.16.0", + "@mantine/modals": "^7.16.0", + "@mantine/tiptap": "^7.16.0", "@million/lint": "1.0.14", "@t3-oss/env-nextjs": "^0.11.1", "@tabler/icons-react": "^3.28.1", - "@tanstack/react-query": "^5.63.0", - "@tanstack/react-query-devtools": "^5.63.0", - "@tanstack/react-query-next-experimental": "5.63.0", + "@tanstack/react-query": "^5.64.1", + "@tanstack/react-query-devtools": "^5.64.1", + "@tanstack/react-query-next-experimental": "5.64.1", "@trpc/client": "next", "@trpc/next": "next", "@trpc/react-query": "next", @@ -72,7 +72,7 @@ "react-dom": "19.0.0", "react-error-boundary": "^5.0.0", "react-simple-code-editor": "^0.14.1", - "sass": "^1.83.1", + "sass": "^1.83.4", "superjson": "2.2.2", "swagger-ui-react": "^5.18.2", "use-deep-compare-effect": "^1.8.1" @@ -82,13 +82,13 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/chroma-js": "3.1.0", - "@types/node": "^22.10.5", + "@types/node": "^22.10.6", "@types/prismjs": "^1.26.5", - "@types/react": "19.0.4", - "@types/react-dom": "19.0.2", + "@types/react": "19.0.7", + "@types/react-dom": "19.0.3", "@types/swagger-ui-react": "^4.18.3", "concurrently": "^9.1.2", - "eslint": "^9.17.0", + "eslint": "^9.18.0", "node-loader": "^2.1.0", "prettier": "^3.4.2", "typescript": "^5.7.3" diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index 52d5c10b7..3d2042030 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -21,7 +21,7 @@ import type { AppRouter } from "@homarr/api"; import { clientApi, getTrpcUrl } from "@homarr/api/client"; import { createHeadersCallbackForSource } from "@homarr/api/shared"; -import { env } from "~/env.mjs"; +import { env } from "~/env"; const getWebSocketProtocol = () => { // window is not defined on server side diff --git a/apps/nextjs/src/app/[locale]/auth/login/page.tsx b/apps/nextjs/src/app/[locale]/auth/login/page.tsx index 9b5dfbd83..c9d913c08 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/page.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/page.tsx @@ -1,7 +1,7 @@ import { redirect } from "next/navigation"; import { Card, Center, Stack, Text, Title } from "@mantine/core"; -import { env } from "@homarr/auth/env.mjs"; +import { env } from "@homarr/auth/env"; import { auth } from "@homarr/auth/next"; import { getScopedI18n } from "@homarr/translation/server"; diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index e951a4318..b0657e1e1 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -28,7 +28,7 @@ import { useCategoryActions } from "~/components/board/sections/category/categor import { CategoryEditModal } from "~/components/board/sections/category/category-edit-modal"; import { useDynamicSectionActions } from "~/components/board/sections/dynamic/dynamic-actions"; import { HeaderButton } from "~/components/layout/header/button"; -import { env } from "~/env.mjs"; +import { env } from "~/env"; import { useEditMode, useRequiredBoard } from "./_context"; export const BoardContentHeaderActions = () => { 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]/layout.tsx b/apps/nextjs/src/app/[locale]/layout.tsx index 6469ddd39..8f145ee3c 100644 --- a/apps/nextjs/src/app/[locale]/layout.tsx +++ b/apps/nextjs/src/app/[locale]/layout.tsx @@ -9,7 +9,7 @@ import "~/styles/scroll-area.scss"; import { notFound } from "next/navigation"; import { NextIntlClientProvider } from "next-intl"; -import { env } from "@homarr/auth/env.mjs"; +import { env } from "@homarr/auth/env"; import { auth } from "@homarr/auth/next"; import { ModalProvider } from "@homarr/modals"; import { Notifications } from "@homarr/notifications"; 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 (
- +