diff --git a/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.module.css b/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.module.css index cfadc375d..ae85132dc 100644 --- a/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.module.css +++ b/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.module.css @@ -1,5 +1,4 @@ .bannerContainer { - padding: 3rem; border-radius: 8px; overflow: hidden; background: linear-gradient( diff --git a/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx b/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx index 35943bcf9..cb1a3094a 100644 --- a/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx +++ b/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx @@ -38,17 +38,17 @@ export const HeroBanner = () => { const gridSpan = 12 / countIconGroups; return ( - + - + <Title fz={{ base: "h4", md: "h2" }} c="dimmed"> Welcome back to your - - - Homarr Dashboard + + + Homarr Board - + {Array(countIconGroups) .fill(0) diff --git a/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx b/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx index 0f81cde48..dd20241d3 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx @@ -49,7 +49,7 @@ export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => { }, [app, mutate, t, openConfirmModal]); return ( - + ); diff --git a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx index bdac7a8a9..30344ccab 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx @@ -1,23 +1,13 @@ import Link from "next/link"; -import { - ActionIcon, - ActionIconGroup, - Anchor, - Avatar, - Button, - Card, - Container, - Group, - Stack, - Text, - Title, -} from "@mantine/core"; +import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core"; import { IconApps, IconPencil } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; import { getI18n, getScopedI18n } from "@homarr/translation/server"; +import { ManageContainer } from "~/components/manage/manage-container"; +import { MobileAffixButton } from "~/components/manage/mobile-affix-button"; import { AppDeleteButton } from "./_app-delete-button"; export default async function AppsPage() { @@ -25,13 +15,13 @@ export default async function AppsPage() { const t = await getScopedI18n("app"); return ( - + {t("page.list.title")} - + {apps.length === 0 && } {apps.length > 0 && ( @@ -42,7 +32,7 @@ export default async function AppsPage() { )} - + ); } @@ -50,10 +40,12 @@ interface AppCardProps { app: RouterOutputs["app"]["all"][number]; } -const AppCard = ({ app }: AppCardProps) => { +const AppCard = async ({ app }: AppCardProps) => { + const t = await getScopedI18n("app"); + return ( - + { }} /> - {app.name} + + {app.name} + {app.description && ( - + {app.description} )} {app.href && ( - + {app.href} )} @@ -86,7 +80,7 @@ const AppCard = ({ app }: AppCardProps) => { href={`/manage/apps/edit/${app.id}`} variant="subtle" color="gray" - aria-label="Edit app" + aria-label={t("page.edit.title")} > diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx index 4bd24a346..1c59db412 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx @@ -1,7 +1,6 @@ "use client"; import { useCallback } from "react"; -import { Button } from "@mantine/core"; import { IconCategoryPlus } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; @@ -10,6 +9,7 @@ import { useI18n } from "@homarr/translation/client"; import { revalidatePathActionAsync } from "~/app/revalidatePathAction"; import { AddBoardModal } from "~/components/manage/boards/add-board-modal"; +import { MobileAffixButton } from "~/components/manage/mobile-affix-button"; interface CreateBoardButtonProps { boardNames: string[]; @@ -37,8 +37,8 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => { }, [mutateAsync, boardNames, openModal]); return ( - + ); }; diff --git a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx index 906262eb4..b117a6d82 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx @@ -10,6 +10,7 @@ import { Group, Menu, MenuTarget, + Stack, Text, Title, Tooltip, @@ -22,6 +23,7 @@ import { getScopedI18n } from "@homarr/translation/server"; import { UserAvatar } from "@homarr/ui"; import { getBoardPermissionsAsync } from "~/components/board/permissions/server"; +import { ManageContainer } from "~/components/manage/manage-container"; import { BoardCardMenuDropdown } from "./_components/board-card-menu-dropdown"; import { CreateBoardButton } from "./_components/create-board-button"; @@ -31,20 +33,22 @@ export default async function ManageBoardsPage() { const boards = await api.board.getAllBoards(); return ( - <> - - {t("title")} - board.name)} /> - + + + + {t("title")} + board.name)} /> + - - {boards.map((board) => ( - - - - ))} - - + + {boards.map((board) => ( + + + + ))} + + + ); } diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/_integration-buttons.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/_integration-buttons.tsx index 2f31cb169..3640dc790 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/_integration-buttons.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/_integration-buttons.tsx @@ -56,7 +56,7 @@ export const DeleteIntegrationActionButton = ({ count, integration }: DeleteInte }, }); }} - aria-label="Delete integration" + aria-label={t("title")} > diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx index 8f03a39ff..b2700ff70 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx @@ -3,7 +3,7 @@ import type { ChangeEvent } from "react"; import React, { useMemo, useState } from "react"; import Link from "next/link"; -import { Group, Menu, ScrollArea, Stack, Text, TextInput } from "@mantine/core"; +import { Flex, Group, Menu, ScrollArea, Text, TextInput } from "@mantine/core"; import { IconSearch } from "@tabler/icons-react"; import { getIntegrationName, integrationKinds } from "@homarr/definitions"; @@ -25,7 +25,7 @@ export const IntegrationCreateDropdownContent = () => { ); return ( - + } placeholder={t("integration.page.list.search")} @@ -47,6 +47,6 @@ export const IntegrationCreateDropdownContent = () => { ) : ( {t("common.noResults")} )} - + ); }; diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx index 15ff2ef83..8bdcd0329 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx @@ -1,3 +1,5 @@ +import { Fragment } from "react"; +import type { PropsWithChildren } from "react"; import Link from "next/link"; import { AccordionControl, @@ -5,9 +7,11 @@ import { AccordionPanel, ActionIcon, ActionIconGroup, + Affix, Anchor, + Box, Button, - Container, + Divider, Group, Menu, MenuDropdown, @@ -22,7 +26,7 @@ import { Text, Title, } from "@mantine/core"; -import { IconChevronDown, IconPencil } from "@tabler/icons-react"; +import { IconChevronDown, IconChevronUp, IconPencil } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; @@ -32,6 +36,7 @@ import { getIntegrationName } from "@homarr/definitions"; import { getScopedI18n } from "@homarr/translation/server"; import { CountBadge } from "@homarr/ui"; +import { ManageContainer } from "~/components/manage/manage-container"; import { ActiveTabAccordion } from "../../../../components/active-tab-accordion"; import { IntegrationAvatar } from "./_integration-avatar"; import { DeleteIntegrationActionButton } from "./_integration-buttons"; @@ -48,26 +53,47 @@ export default async function IntegrationsPage({ searchParams }: IntegrationsPag const t = await getScopedI18n("integration"); return ( - + {t("page.list.title")} - - - - - - - - + + + + + + + + + + + + + + + + + + - + ); } +const IntegrationSelectMenu = ({ children }: PropsWithChildren) => { + return ( + + {children} + + + + + ); +}; + interface IntegrationListProps { integrations: RouterOutputs["integration"]["all"]; activeTab?: IntegrationKind; @@ -105,7 +131,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps - +
{t("field.name.label")} @@ -130,7 +156,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps href={`/manage/integrations/edit/${integration.id}`} variant="subtle" color="gray" - aria-label="Edit integration" + aria-label={t("page.edit.title", { name: getIntegrationName(integration.kind) })} > @@ -142,6 +168,34 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps ))}
+ + + {integrations.map((integration, index) => ( + + {index !== 0 && } + + + {integration.name} + + + + + + + + + {integration.url} + + + + ))} +
))} diff --git a/apps/nextjs/src/app/[locale]/manage/page.tsx b/apps/nextjs/src/app/[locale]/manage/page.tsx index 711a2e3a3..3e45269e9 100644 --- a/apps/nextjs/src/app/[locale]/manage/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/page.tsx @@ -72,8 +72,8 @@ export default async function ManagementPage() { {links.map((link, index) => ( - - + + {link.count} diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/analytics.settings.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/analytics.settings.tsx index 7ac93b917..fe10331c9 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/analytics.settings.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/analytics.settings.tsx @@ -109,7 +109,9 @@ const SwitchSetting = ({ {title} - {text} + + {text} + diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/layout.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/layout.tsx index be6c0f71a..b74e6d780 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/layout.tsx @@ -1,7 +1,7 @@ import type { PropsWithChildren } from "react"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { Button, Container, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core"; +import { Button, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core"; import { IconSettings, IconShieldLock } from "@tabler/icons-react"; import { api } from "@homarr/api/server"; @@ -9,6 +9,7 @@ import { auth } from "@homarr/auth/next"; import { getI18n, getScopedI18n } from "@homarr/translation/server"; import { UserAvatar } from "@homarr/ui"; +import { ManageContainer } from "~/components/manage/manage-container"; import { catchTrpcNotFound } from "~/errors/trpc-not-found"; import { NavigationLink } from "../groups/[id]/_navigation"; import { canAccessUserEditPage } from "./access"; @@ -28,7 +29,7 @@ export default async function Layout({ children, params }: PropsWithChildren + @@ -64,6 +65,6 @@ export default async function Layout({ children, params }: PropsWithChildren {children} - + ); } diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx index e512c13b5..42d9acbc6 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/layout.tsx @@ -1,11 +1,12 @@ import type { PropsWithChildren } from "react"; import Link from "next/link"; -import { Button, Container, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core"; +import { Button, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core"; import { IconLock, IconSettings, IconUsersGroup } from "@tabler/icons-react"; import { api } from "@homarr/api/server"; import { getI18n, getScopedI18n } from "@homarr/translation/server"; +import { ManageContainer } from "~/components/manage/manage-container"; import { NavigationLink } from "./_navigation"; interface LayoutProps { @@ -18,7 +19,7 @@ export default async function Layout({ children, params }: PropsWithChildren + @@ -54,6 +55,6 @@ export default async function Layout({ children, params }: PropsWithChildren {children} - + ); } diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/_add-group-member.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/_add-group-member.tsx index 2d7d2941d..5bac56612 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/_add-group-member.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/_add-group-member.tsx @@ -1,7 +1,6 @@ "use client"; import { useCallback } from "react"; -import { Button } from "@mantine/core"; import { clientApi } from "@homarr/api/client"; import { useModalAction } from "@homarr/modals"; @@ -9,6 +8,7 @@ import { useScopedI18n } from "@homarr/translation/client"; import { UserSelectModal } from "~/app/[locale]/boards/[name]/settings/_access/user-select-modal"; import { revalidatePathActionAsync } from "~/app/revalidatePathAction"; +import { MobileAffixButton } from "~/components/manage/mobile-affix-button"; interface AddGroupMemberProps { groupId: string; @@ -40,8 +40,8 @@ export const AddGroupMember = ({ groupId, presentUserIds }: AddGroupMemberProps) }, [openModal, presentUserIds, groupId, mutateAsync, tMembersAdd]); return ( - + ); }; diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx index 21f89c5e9..303d7b22d 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx @@ -101,7 +101,7 @@ interface PermissionRowProps { const PermissionRow = ({ name, label, description }: PermissionRowProps) => { return ( - + {label} {description} diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/_add-group.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/_add-group.tsx index b28ddccd0..ed6e6dd3d 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/_add-group.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/_add-group.tsx @@ -11,6 +11,7 @@ import { useI18n } from "@homarr/translation/client"; import { validation } from "@homarr/validation"; import { revalidatePathActionAsync } from "~/app/revalidatePathAction"; +import { MobileAffixButton } from "~/components/manage/mobile-affix-button"; export const AddGroup = () => { const t = useI18n(); @@ -21,9 +22,9 @@ export const AddGroup = () => { }, [openModal]); return ( - + ); }; diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx index 410fe57ed..ec46596bc 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx @@ -1,17 +1,5 @@ import Link from "next/link"; -import { - Anchor, - Container, - Group, - Stack, - Table, - TableTbody, - TableTd, - TableTh, - TableThead, - TableTr, - Title, -} from "@mantine/core"; +import { Anchor, Group, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from "@mantine/core"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; @@ -19,6 +7,7 @@ import { getI18n } from "@homarr/translation/server"; import { SearchInput, TablePagination, UserAvatarGroup } from "@homarr/ui"; import { z } from "@homarr/validation"; +import { ManageContainer } from "~/components/manage/manage-container"; import { AddGroup } from "./_add-group"; const searchParamsSchema = z.object({ @@ -41,7 +30,7 @@ export default async function GroupsListPage(props: GroupsListPageProps) { const { items: groups, totalCount } = await api.group.getPaginated(searchParams); return ( - + {t("group.title")} @@ -72,7 +61,7 @@ export default async function GroupsListPage(props: GroupsListPageProps) { - + ); } diff --git a/apps/nextjs/src/components/manage/manage-container.tsx b/apps/nextjs/src/components/manage/manage-container.tsx new file mode 100644 index 000000000..3d31b3e2f --- /dev/null +++ b/apps/nextjs/src/components/manage/manage-container.tsx @@ -0,0 +1,11 @@ +import type { PropsWithChildren } from "react"; +import type { MantineSize } from "@mantine/core"; +import { Container } from "@mantine/core"; + +export const ManageContainer = ({ children, size }: PropsWithChildren<{ size?: MantineSize }>) => { + return ( + + {children} + + ); +}; diff --git a/apps/nextjs/src/components/manage/mobile-affix-button.tsx b/apps/nextjs/src/components/manage/mobile-affix-button.tsx new file mode 100644 index 000000000..abe4f017c --- /dev/null +++ b/apps/nextjs/src/components/manage/mobile-affix-button.tsx @@ -0,0 +1,16 @@ +import { forwardRef } from "react"; +import type { ButtonProps } from "@mantine/core"; +import { Affix, Button, createPolymorphicComponent } from "@mantine/core"; + +type MobileAffixButtonProps = Omit; + +export const MobileAffixButton = createPolymorphicComponent<"button", MobileAffixButtonProps>( + forwardRef((props, ref) => ( + <> +