diff --git a/apps/nextjs/src/app/[locale]/(home)/(board)/layout.tsx b/apps/nextjs/src/app/[locale]/(home)/(board)/layout.tsx new file mode 100644 index 000000000..3467dc941 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/(home)/(board)/layout.tsx @@ -0,0 +1,5 @@ +import definition from "../../boards/(content)/(home)/_definition"; + +const { layout } = definition; + +export default layout; diff --git a/apps/nextjs/src/app/[locale]/(home-board)/page.tsx b/apps/nextjs/src/app/[locale]/(home)/(board)/page.tsx similarity index 64% rename from apps/nextjs/src/app/[locale]/(home-board)/page.tsx rename to apps/nextjs/src/app/[locale]/(home)/(board)/page.tsx index 7554444e5..79d1685df 100644 --- a/apps/nextjs/src/app/[locale]/(home-board)/page.tsx +++ b/apps/nextjs/src/app/[locale]/(home)/(board)/page.tsx @@ -1,4 +1,4 @@ -import definition from "../boards/(content)/(home)/_definition"; +import definition from "../../boards/(content)/(home)/_definition"; const { generateMetadataAsync: generateMetadata, page } = definition; diff --git a/apps/nextjs/src/app/[locale]/(home)/not-found.tsx b/apps/nextjs/src/app/[locale]/(home)/not-found.tsx new file mode 100644 index 000000000..b88f6c393 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/(home)/not-found.tsx @@ -0,0 +1,3 @@ +import HomeBoardNotFoundPage from "../boards/(content)/not-found"; + +export default HomeBoardNotFoundPage; diff --git a/apps/nextjs/src/app/[locale]/(home-board)/layout.tsx b/apps/nextjs/src/app/[locale]/(home-board)/layout.tsx deleted file mode 100644 index 9b69474ba..000000000 --- a/apps/nextjs/src/app/[locale]/(home-board)/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import definition from "../boards/(content)/(home)/_definition"; - -const { layout } = definition; - -export default layout; diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/_definition.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/_definition.tsx similarity index 79% rename from apps/nextjs/src/app/[locale]/boards/(content)/[name]/_definition.tsx rename to apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/_definition.tsx index 82e53110d..d3b65a5e9 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/_definition.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/_definition.tsx @@ -1,6 +1,6 @@ import { api } from "@homarr/api/server"; -import { createBoardContentPage } from "../_creator"; +import { createBoardContentPage } from "../../_creator"; export default createBoardContentPage<{ locale: string; name: string }>({ async getInitialBoardAsync({ name }) { diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/layout.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/layout.tsx similarity index 100% rename from apps/nextjs/src/app/[locale]/boards/(content)/[name]/layout.tsx rename to apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/layout.tsx diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/page.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/page.tsx similarity index 100% rename from apps/nextjs/src/app/[locale]/boards/(content)/[name]/page.tsx rename to apps/nextjs/src/app/[locale]/boards/(content)/[name]/(board)/page.tsx diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx new file mode 100644 index 000000000..9c398669b --- /dev/null +++ b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx @@ -0,0 +1,18 @@ +import { IconLayoutOff } from "@tabler/icons-react"; + +import { getScopedI18n } from "@homarr/translation/server"; + +import { BoardNotFound } from "~/components/board/not-found"; + +export default async function BoardNotFoundPage() { + const tNotFound = await getScopedI18n("board.error.notFound"); + return ( + + ); +} diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx new file mode 100644 index 000000000..ba84e0593 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx @@ -0,0 +1,47 @@ +import { IconHomeOff } from "@tabler/icons-react"; + +import { auth } from "@homarr/auth/next"; +import { db } from "@homarr/db"; +import { boards } from "@homarr/db/schema/sqlite"; +import { getI18n } from "@homarr/translation/server"; + +import type { BoardNotFoundProps } from "~/components/board/not-found"; +import { BoardNotFound } from "~/components/board/not-found"; + +export default async function NotFoundBoardHomePage() { + const boardNotFoundProps = await getPropsAsync(); + + return ; +} + +const getPropsAsync = async (): Promise => { + const boardCount = await db.$count(boards); + const t = await getI18n(); + + if (boardCount === 0) { + return { + icon: { src: "/favicon.ico", alt: "Homarr logo" }, + title: t("board.error.noBoard.title"), + description: t("board.error.noBoard.description"), + link: { label: t("board.error.noBoard.link"), href: "/manage/boards" }, + notice: t("board.error.noBoard.notice"), + }; + } + + const session = await auth(); + const isAdmin = session?.user.permissions.includes("admin"); + const type = isAdmin ? "admin" : session !== null ? "user" : "anonymous"; + const href = { + admin: "/manage/settings", + user: `/manage/users/${session?.user.id}/general`, + anonymous: "/manage/boards", + }[type]; + + return { + icon: IconHomeOff, + title: t(`board.error.homeBoard.title`), + description: t(`board.error.homeBoard.${type}.description`), + link: { label: t(`board.error.homeBoard.${type}.link`), href }, + notice: t(`board.error.homeBoard.${type}.notice`), + }; +}; diff --git a/apps/nextjs/src/components/board/not-found.tsx b/apps/nextjs/src/components/board/not-found.tsx new file mode 100644 index 000000000..d597597de --- /dev/null +++ b/apps/nextjs/src/components/board/not-found.tsx @@ -0,0 +1,41 @@ +import { Anchor, AppShellMain, Center, Flex, Group, Image, Text, Title } from "@mantine/core"; + +import type { TablerIcon } from "@homarr/ui"; + +import { fullHeightWithoutHeaderAndFooter } from "~/constants"; +import { MainHeader } from "../layout/header"; +import { HomarrLogoWithTitle } from "../layout/logo/homarr-logo"; +import { ClientShell } from "../layout/shell"; + +export interface BoardNotFoundProps { + icon: TablerIcon | { src: string; alt: string }; + title: string; + description: string; + link: { + label: string; + href: string; + }; + notice: string; +} + +export const BoardNotFound = ({ icon: Icon, title, description, link, notice }: BoardNotFoundProps) => { + return ( + + } hasNavigation={false} /> + +
+ + + {"src" in Icon ? {Icon.alt} : } + + {title} + + {description} + {link.label} + {notice} + +
+
+
+ ); +}; diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 8997a4deb..516d0d5a5 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1978,6 +1978,38 @@ } } } + }, + "error": { + "noBoard": { + "title": "Welcome to Homarr", + "description": "A sleek, modern dashboard that puts all of your apps and services at your fingertips.", + "link": "Create your first board", + "notice": "To make this page disappear, create a board and set it as the home board" + }, + "notFound": { + "title": "Board not found", + "description": "The board specified was either not found or you don't have access to it.", + "link": "View all boards", + "notice": "Check the link or contact an administrator if you think it should be accessible" + }, + "homeBoard": { + "title": "No home board", + "admin": { + "description": "You haven't set a home board for the server yet.", + "link": "Configure server-wide home board", + "notice": "To make this page disappear for all users, set a home board for the server" + }, + "user": { + "description": "You haven't set a home board yet.", + "link": "Configure your home board", + "notice": "To make this page disappear, specify the home board in your preferences" + }, + "anonymous": { + "description": "The server administrator hasn't set a home board yet.", + "link": "View public boards", + "notice": "To make this page disappear, ask the server administrator to set a home board for the server" + } + } } }, "management": {