mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-27 00:40:58 +01:00
refactor: improve board manage page (#323)
* refactor: improve board manage page * chore: address pull request feedback
This commit is contained in:
17
apps/nextjs/src/app/[locale]/_client-providers/session.tsx
Normal file
17
apps/nextjs/src/app/[locale]/_client-providers/session.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { SessionProvider } from "@homarr/auth/client";
|
||||
|
||||
interface AuthProviderProps {
|
||||
session: Session | null;
|
||||
}
|
||||
|
||||
export const AuthProvider = ({
|
||||
children,
|
||||
session,
|
||||
}: PropsWithChildren<AuthProviderProps>) => {
|
||||
return <SessionProvider session={session}>{children}</SessionProvider>;
|
||||
};
|
||||
@@ -5,12 +5,14 @@ import "@homarr/notifications/styles.css";
|
||||
import "@homarr/spotlight/styles.css";
|
||||
import "@homarr/ui/styles.css";
|
||||
|
||||
import { auth } from "@homarr/auth";
|
||||
import { ModalProvider } from "@homarr/modals";
|
||||
import { Notifications } from "@homarr/notifications";
|
||||
import { ColorSchemeScript, createTheme, MantineProvider } from "@homarr/ui";
|
||||
|
||||
import { JotaiProvider } from "./_client-providers/jotai";
|
||||
import { NextInternationalProvider } from "./_client-providers/next-international";
|
||||
import { AuthProvider } from "./_client-providers/session";
|
||||
import { TRPCReactProvider } from "./_client-providers/trpc";
|
||||
import { composeWrappers } from "./compose";
|
||||
|
||||
@@ -52,6 +54,10 @@ export default function Layout(props: {
|
||||
const colorScheme = "dark";
|
||||
|
||||
const StackedProvider = composeWrappers([
|
||||
async (innerProps) => {
|
||||
const session = await auth();
|
||||
return <AuthProvider session={session} {...innerProps} />;
|
||||
},
|
||||
(innerProps) => <JotaiProvider {...innerProps} />,
|
||||
(innerProps) => <TRPCReactProvider {...innerProps} />,
|
||||
(innerProps) => (
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useConfirmModal } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { IconSettings, IconTrash, Menu } from "@homarr/ui";
|
||||
|
||||
import { revalidatePathAction } from "~/app/revalidatePathAction";
|
||||
|
||||
const iconProps = {
|
||||
size: 16,
|
||||
stroke: 1.5,
|
||||
};
|
||||
|
||||
interface BoardCardMenuDropdownProps {
|
||||
board: Pick<RouterOutputs["board"]["getAll"][number], "id" | "name">;
|
||||
}
|
||||
|
||||
export const BoardCardMenuDropdown = ({
|
||||
board,
|
||||
}: BoardCardMenuDropdownProps) => {
|
||||
const t = useScopedI18n("management.page.board.action");
|
||||
const tCommon = useScopedI18n("common");
|
||||
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const { mutateAsync, isPending } = clientApi.board.delete.useMutation({
|
||||
onSettled: async () => {
|
||||
await revalidatePathAction("/manage/boards");
|
||||
},
|
||||
});
|
||||
|
||||
const handleDeletion = useCallback(() => {
|
||||
openConfirmModal({
|
||||
title: t("delete.confirm.title"),
|
||||
children: t("delete.confirm.description", {
|
||||
name: board.name,
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
await mutateAsync({
|
||||
id: board.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
}, [board.id, board.name, mutateAsync, openConfirmModal, t]);
|
||||
|
||||
return (
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
component={Link}
|
||||
href={`/boards/${board.name}/settings`}
|
||||
leftSection={<IconSettings {...iconProps} />}
|
||||
>
|
||||
{t("settings.label")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
<Menu.Label c="red.7">
|
||||
{tCommon("menu.section.dangerZone.title")}
|
||||
</Menu.Label>
|
||||
<Menu.Item
|
||||
c="red.7"
|
||||
leftSection={<IconTrash {...iconProps} />}
|
||||
onClick={handleDeletion}
|
||||
disabled={isPending}
|
||||
>
|
||||
{t("delete.label")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
);
|
||||
};
|
||||
@@ -41,7 +41,7 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
|
||||
onClick={onClick}
|
||||
loading={isPending}
|
||||
>
|
||||
{t("management.page.board.button.create")}
|
||||
{t("management.page.board.action.new.label")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { Button } from "@homarr/ui";
|
||||
|
||||
import { revalidatePathAction } from "~/app/revalidatePathAction";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const DeleteBoardButton = ({ id }: Props) => {
|
||||
const t = useI18n();
|
||||
const { mutateAsync, isPending } = clientApi.board.delete.useMutation({
|
||||
onSettled: async () => {
|
||||
await revalidatePathAction("/manage/boards");
|
||||
},
|
||||
});
|
||||
|
||||
const onClick = React.useCallback(async () => {
|
||||
await mutateAsync({
|
||||
id,
|
||||
});
|
||||
}, [id, mutateAsync]);
|
||||
|
||||
return (
|
||||
<Button onClick={onClick} loading={isPending} color="red">
|
||||
{t("management.page.board.button.delete")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,28 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { Card, Grid, GridCol, Group, Text, Title } from "@homarr/ui";
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Card,
|
||||
CardSection,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
IconDotsVertical,
|
||||
IconLock,
|
||||
IconWorld,
|
||||
Menu,
|
||||
MenuTarget,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@homarr/ui";
|
||||
|
||||
import { BoardCardMenuDropdown } from "./_components/board-card-menu-dropdown";
|
||||
import { CreateBoardButton } from "./_components/create-board-button";
|
||||
import { DeleteBoardButton } from "./_components/delete-board-button";
|
||||
|
||||
export default async function ManageBoardsPage() {
|
||||
const t = await getScopedI18n("management.page.board");
|
||||
@@ -22,25 +39,58 @@ export default async function ManageBoardsPage() {
|
||||
<Grid>
|
||||
{boards.map((board) => (
|
||||
<GridCol span={{ xs: 12, md: 4 }} key={board.id}>
|
||||
<Card>
|
||||
<Text fw="bolder" tt="uppercase">
|
||||
{board.name}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
size="sm"
|
||||
my="md"
|
||||
c="dimmed"
|
||||
style={{ lineBreak: "anywhere" }}
|
||||
>
|
||||
{JSON.stringify(board)}
|
||||
</Text>
|
||||
|
||||
<DeleteBoardButton id={board.id} />
|
||||
</Card>
|
||||
<BoardCard board={board} />
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface BoardCardProps {
|
||||
board: RouterOutputs["board"]["getAll"][number];
|
||||
}
|
||||
|
||||
const BoardCard = async ({ board }: BoardCardProps) => {
|
||||
const t = await getScopedI18n("management.page.board");
|
||||
const visibility = board.isPublic ? "public" : "private";
|
||||
const VisibilityIcon = board.isPublic ? IconWorld : IconLock;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardSection p="sm" withBorder>
|
||||
<Group justify="space-between" align="center">
|
||||
<Group gap="sm">
|
||||
<Tooltip label={t(`visibility.${visibility}`)}>
|
||||
<VisibilityIcon size={20} stroke={1.5} />
|
||||
</Tooltip>
|
||||
<Text fw="bolder" tt="uppercase">
|
||||
{board.name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</CardSection>
|
||||
|
||||
<CardSection p="sm">
|
||||
<Group wrap="nowrap">
|
||||
<Button
|
||||
component={Link}
|
||||
href={`/boards/${board.name}`}
|
||||
variant="default"
|
||||
fullWidth
|
||||
>
|
||||
{t("action.open.label")}
|
||||
</Button>
|
||||
<Menu position="bottom-end">
|
||||
<MenuTarget>
|
||||
<ActionIcon variant="default" size="lg">
|
||||
<IconDotsVertical size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</MenuTarget>
|
||||
<BoardCardMenuDropdown board={board} />
|
||||
</Menu>
|
||||
</Group>
|
||||
</CardSection>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,5 +57,5 @@ export const AddBoardModal = createModal<InnerProps>(
|
||||
);
|
||||
},
|
||||
).withOptions({
|
||||
defaultTitle: (t) => t("management.page.board.button.create"),
|
||||
defaultTitle: (t) => t("management.page.board.action.new.label"),
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@ export const boardRouter = createTRPCRouter({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
isPublic: true,
|
||||
},
|
||||
with: {
|
||||
sections: {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { signIn, signOut } from "next-auth/react";
|
||||
export { signIn, signOut, useSession, SessionProvider } from "next-auth/react";
|
||||
|
||||
@@ -234,6 +234,13 @@ export default {
|
||||
navigateDefaultBoard: "Navigate to default board",
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
section: {
|
||||
dangerZone: {
|
||||
title: "Danger Zone",
|
||||
},
|
||||
},
|
||||
},
|
||||
noResults: "No results found",
|
||||
preview: {
|
||||
show: "Show preview",
|
||||
@@ -816,10 +823,28 @@ export default {
|
||||
},
|
||||
page: {
|
||||
board: {
|
||||
title: "Manage boards",
|
||||
button: {
|
||||
create: "Create board",
|
||||
delete: "Delete board",
|
||||
title: "Your boards",
|
||||
action: {
|
||||
new: {
|
||||
label: "New board",
|
||||
},
|
||||
open: {
|
||||
label: "Open board",
|
||||
},
|
||||
settings: {
|
||||
label: "Settings",
|
||||
},
|
||||
delete: {
|
||||
label: "Delete permanently",
|
||||
confirm: {
|
||||
title: "Delete board",
|
||||
description: "Are you sure you want to delete the {name} board?",
|
||||
},
|
||||
},
|
||||
},
|
||||
visibility: {
|
||||
public: "This board is public",
|
||||
private: "This board is private",
|
||||
},
|
||||
modal: {
|
||||
createBoard: {
|
||||
|
||||
Reference in New Issue
Block a user