feat: add user setting for home board (#956)

This commit is contained in:
Meier Lukas
2024-08-09 19:24:50 +02:00
committed by GitHub
parent 13e09968d9
commit f327837d82
6 changed files with 147 additions and 14 deletions

View File

@@ -0,0 +1,68 @@
"use client";
import { Button, Group, Select, Stack } from "@mantine/core";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useZodForm } from "@homarr/form";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client";
import type { z } from "@homarr/validation";
import { validation } from "@homarr/validation";
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
interface ChangeHomeBoardFormProps {
user: RouterOutputs["user"]["getById"];
boardsData: { value: string; label: string }[];
}
export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormProps) => {
const t = useI18n();
const { mutate, isPending } = clientApi.user.changeHomeBoardId.useMutation({
async onSettled() {
await revalidatePathActionAsync(`/manage/users/${user.id}`);
},
onSuccess(_, variables) {
form.setInitialValues({
homeBoardId: variables.homeBoardId,
});
showSuccessNotification({
message: t("user.action.changeHomeBoard.notification.success.message"),
});
},
onError() {
showErrorNotification({
message: t("user.action.changeHomeBoard.notification.error.message"),
});
},
});
const form = useZodForm(validation.user.changeHomeBoard, {
initialValues: {
homeBoardId: user.homeBoardId ?? "",
},
});
const handleSubmit = (values: FormType) => {
mutate({
userId: user.id,
...values,
});
};
return (
<form onSubmit={form.onSubmit(handleSubmit)}>
<Stack gap="md">
<Select w="100%" data={boardsData} {...form.getInputProps("homeBoardId")} />
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
</Stack>
</form>
);
};
type FormType = z.infer<typeof validation.user.changeHomeBoard>;

View File

@@ -1,12 +0,0 @@
import { Stack, Title } from "@mantine/core";
import { LanguageCombobox } from "~/components/language/language-combobox";
export const ProfileLanguageChange = () => {
return (
<Stack mb="lg">
<Title order={2}>Language & Region</Title>
<LanguageCombobox />
</Stack>
);
};

View File

@@ -6,14 +6,15 @@ import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next";
import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { LanguageCombobox } from "~/components/language/language-combobox";
import { DangerZoneItem, DangerZoneRoot } from "~/components/manage/danger-zone";
import { catchTrpcNotFound } from "~/errors/trpc-not-found";
import { createMetaTitle } from "~/metadata";
import { canAccessUserEditPage } from "../access";
import { ChangeHomeBoardForm } from "./_components/_change-home-board";
import { DeleteUserButton } from "./_components/_delete-user-button";
import { UserProfileAvatarForm } from "./_components/_profile-avatar-form";
import { UserProfileForm } from "./_components/_profile-form";
import { ProfileLanguageChange } from "./_components/_profile-language-change";
interface Props {
params: {
@@ -54,6 +55,8 @@ export default async function EditUserPage({ params }: Props) {
notFound();
}
const boards = await api.board.getAllBoards();
const isCredentialsUser = user.provider === "credentials";
return (
@@ -74,7 +77,21 @@ export default async function EditUserPage({ params }: Props) {
</Box>
</Group>
<ProfileLanguageChange />
<Stack mb="lg">
<Title order={2}>{tGeneral("item.language")}</Title>
<LanguageCombobox />
</Stack>
<Stack mb="lg">
<Title order={2}>{tGeneral("item.board")}</Title>
<ChangeHomeBoardForm
user={user}
boardsData={boards.map((board) => ({
value: board.id,
label: board.name,
}))}
/>
</Stack>
{isCredentialsUser && (
<DangerZoneRoot>

View File

@@ -156,6 +156,7 @@ export const userRouter = createTRPCRouter({
emailVerified: true,
image: true,
provider: true,
homeBoardId: true,
},
where: eq(users.id, input.userId),
});
@@ -266,6 +267,39 @@ export const userRouter = createTRPCRouter({
})
.where(eq(users.id, input.userId));
}),
changeHomeBoardId: protectedProcedure
.input(validation.user.changeHomeBoard.and(z.object({ userId: z.string() })))
.mutation(async ({ input, ctx }) => {
const user = ctx.session.user;
// Only admins can change other users' passwords
if (!user.permissions.includes("admin") && user.id !== input.userId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
const dbUser = await ctx.db.query.users.findFirst({
columns: {
id: true,
},
where: eq(users.id, input.userId),
});
if (!dbUser) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
await ctx.db
.update(users)
.set({
homeBoardId: input.homeBoardId,
})
.where(eq(users.id, input.userId));
}),
});
const createUserAsync = async (db: Database, input: z.infer<typeof validation.user.create>) => {

View File

@@ -37,6 +37,9 @@ export default {
previousPassword: {
label: "Previous password",
},
homeBoard: {
label: "Home board",
},
},
error: {
usernameTaken: "Username already taken",
@@ -81,6 +84,16 @@ export default {
},
},
},
changeHomeBoard: {
notification: {
success: {
message: "Home board changed successfully",
},
error: {
message: "Unable to change home board",
},
},
},
manageAvatar: {
changeImage: {
label: "Change image",
@@ -1404,10 +1417,17 @@ export default {
setting: {
general: {
title: "General",
item: {
language: "Language & Region",
board: "Home board",
},
},
security: {
title: "Security",
},
board: {
title: "Boards",
},
},
list: {
metaTitle: "Manage users",
@@ -1736,6 +1756,7 @@ export default {
},
general: "General",
security: "Security",
board: "Boards",
groups: {
label: "Groups",
},

View File

@@ -68,6 +68,10 @@ const changePasswordSchema = z
const changePasswordApiSchema = changePasswordSchema.and(z.object({ userId: z.string() }));
const changeHomeBoardSchema = z.object({
homeBoardId: z.string().min(1),
});
export const userSchemas = {
signIn: signInSchema,
registration: registrationSchema,
@@ -77,5 +81,6 @@ export const userSchemas = {
password: passwordSchema,
editProfile: editProfileSchema,
changePassword: changePasswordSchema,
changeHomeBoard: changeHomeBoardSchema,
changePasswordApi: changePasswordApiSchema,
};