completed user admin page

This commit is contained in:
PxlLoewe
2025-03-15 11:09:55 -07:00
parent abf3475c7c
commit 37d02ea0bc
23 changed files with 567 additions and 106 deletions

View File

@@ -62,7 +62,6 @@ export default async () => {
});
const filteredEvents = events.filter((event) => {
console.log;
if (eventCompleted(event, event.participants[0])) return false;
if (
event.type === "OBLIGATED_COURSE" &&

View File

@@ -61,9 +61,8 @@ export const AppointmentModal = ({
</h3>
<form
onSubmit={appointmentForm.handleSubmit(async (values) => {
console.log(values);
if (!event) return;
const createdAppointment = await upsertAppointment(values);
await upsertAppointment(values);
ref.current?.close();
appointmentsTableRef.current?.refresh();
})}

View File

@@ -1,10 +1,10 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { BADGES, User } from "@repo/db";
import { BADGES, PERMISSION, User } from "@repo/db";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { updateUser } from "../../../../settings/actions";
import { editUser, resetPassword } from "../../action";
import { toast } from "react-hot-toast";
import {
PersonIcon,
@@ -18,36 +18,25 @@ import {
} from "@radix-ui/react-icons";
import { Button } from "../../../../../_components/ui/Button";
import { Select } from "../../../../../_components/ui/Select";
import { UserSchema } from "@repo/db/zod";
import { useRouter } from "next/navigation";
interface ProfileFormProps {
user: User | null;
user: User;
}
export const ProfileForm: React.FC<ProfileFormProps> = ({ user }) => {
const schema = z.object({
firstname: z.string().min(2).max(30),
lastname: z.string().min(2).max(30),
email: z.string().email({
message: "Bitte gebe eine gültige E-Mail Adresse ein",
}),
});
const [isLoading, setIsLoading] = useState(false);
type IFormInput = z.infer<typeof schema>;
const form = useForm<IFormInput>({
defaultValues: {
firstname: user?.firstname,
lastname: user?.lastname,
email: user?.email,
},
resolver: zodResolver(schema),
const form = useForm<User>({
defaultValues: user,
resolver: zodResolver(UserSchema),
});
return (
<form
className="card-body"
onSubmit={form.handleSubmit(async (values) => {
setIsLoading(true);
await updateUser(values);
await editUser(values.id, values);
form.reset(values);
setIsLoading(false);
toast.success("Deine Änderungen wurden gespeichert!", {
@@ -114,13 +103,23 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }) => {
<Select
isMulti
form={form}
name="finishedBadges"
name="badges"
label="Badges"
options={Object.entries(BADGES).map(([key, value]) => ({
label: value,
value: key,
}))}
/>
<Select
isMulti
form={form}
name="permissions"
label="Permissions"
options={Object.entries(PERMISSION).map(([key, value]) => ({
label: value,
value: key,
}))}
/>
<div className="card-actions justify-center pt-6">
<Button
role="submit"
@@ -137,6 +136,8 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }) => {
};
export const AdminForm: React.FC<ProfileFormProps> = ({ user }) => {
const router = useRouter();
return (
<div className="card-body">
<h2 className="card-title">
@@ -145,23 +146,59 @@ export const AdminForm: React.FC<ProfileFormProps> = ({ user }) => {
<div className="text-left">
<div className="card-actions pt-6">
<Button
role="submit"
onClick={async () => {
const { password } = await resetPassword(user.id);
toast.success(
`Neues Passwort
${password}, es wurde dem Nutzer an die E-Mail gesendet!`,
{
style: {
background: "var(--color-base-100)",
color: "var(--color-base-content)",
},
},
);
}}
className="btn-sm btn-wide btn-outline btn-success"
>
<LockOpen1Icon /> Passwort zurücksetzen
</Button>
<Button
role="submit"
className="btn-sm btn-wide btn-outline btn-error"
>
<HobbyKnifeIcon /> User Sperren
</Button>
<Button
role="submit"
className="btn-sm btn-wide btn-outline btn-warning"
>
<HeartIcon /> User Entperren
</Button>
{!user.isBanned && (
<Button
onClick={async () => {
await editUser(user.id, { isBanned: true });
toast.success("Nutzer wurde gesperrt!", {
style: {
background: "var(--color-base-100)",
color: "var(--color-base-content)",
},
});
router.refresh();
}}
role="submit"
className="btn-sm btn-wide btn-outline btn-error"
>
<HobbyKnifeIcon /> User Sperren
</Button>
)}
{user.isBanned && (
<Button
onClick={async () => {
await editUser(user.id, { isBanned: false });
toast.success("Nutzer wurde entsperrt!", {
style: {
background: "var(--color-base-100)",
color: "var(--color-base-content)",
},
});
router.refresh();
}}
role="submit"
className="btn-sm btn-wide btn-outline btn-warning"
>
<HobbyKnifeIcon /> User Entperren
</Button>
)}
</div>
</div>
</div>

View File

@@ -1,19 +1,18 @@
import { PersonIcon } from "@radix-ui/react-icons";
import { PrismaClient, User } from "@repo/db";
import { AdminForm, ProfileForm } from "./_components/forms";
import { Error } from "../../../../_components/Error";
export default async ({ params }: { params: { id: string } }) => {
const prisma = new PrismaClient();
const { id } = params;
const { id } = await params;
const user: User | null = await prisma.user.findUnique({
where: {
id: id,
},
});
console.log(user);
if (!user) return <Error statusCode={404} title="User not found" />;
return (
<div className="grid grid-cols-6 gap-4">
<div className="col-span-full">

View File

@@ -0,0 +1,35 @@
"use server";
import { PrismaClient } from "@prisma/client";
import { prisma, Prisma } from "@repo/db";
import bcrypt from "bcryptjs";
import { sendMailByTemplate } from "../../../../helper/mail";
export const editUser = async (id: string, data: Prisma.UserUpdateInput) => {
return await prisma.user.update({
where: {
id: id,
},
data,
});
};
export const resetPassword = async (id: string) => {
const password = Math.random().toString(36).slice(-8);
const hashedPassword = await bcrypt.hash(password, 15);
const user = await prisma.user.update({
where: {
id: id,
},
data: {
password: hashedPassword,
},
});
await sendMailByTemplate(user.email, "password-change", {
user: user,
password: password,
});
return { password };
};

View File

@@ -103,13 +103,6 @@ const ModalBtn = ({
? (selectedDate as any)?.Participants?.length + 1
: ownIndexInParticipantList + 1;
console.log({
selectedDate,
ownPlaceInParticipantList,
ownIndexInParticipantList,
maxParticipants: event.maxParticipants,
});
return (
<>
<button

View File

@@ -43,7 +43,7 @@ export const Register = () => {
passwordConfirm: "",
},
});
console.log(form.formState.errors);
return (
<form
className="card-body"

View File

@@ -0,0 +1,11 @@
"use client";
export const Error = ({
statusCode,
title,
}: {
statusCode: number;
title: string;
}) => {
return <Error statusCode={404} title="User not found" />;
};

View File

@@ -1,25 +1,43 @@
import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';
import { cn } from '../../../helper/cn';
import {
ButtonHTMLAttributes,
DetailedHTMLProps,
useEffect,
useState,
} from "react";
import { cn } from "../../../helper/cn";
export const Button = ({
isLoading,
...props
isLoading,
...props
}: DetailedHTMLProps<
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> & {
isLoading?: boolean;
isLoading?: boolean;
}) => {
return (
<button
{...(props as any)}
className={cn('btn', props.className)}
disabled={isLoading || props.disabled}
>
{isLoading && (
<span className="loading loading-spinner loading-sm"></span>
)}
{props.children as any}
</button>
);
const [isLoadingState, setIsLoadingState] = useState(isLoading);
useEffect(() => {
setIsLoadingState(isLoading);
}, [isLoading]);
return (
<button
{...(props as any)}
className={cn("btn", props.className)}
disabled={isLoadingState || props.disabled}
onClick={async (e) => {
if (props.onClick) {
setIsLoadingState(true);
await props.onClick(e);
setIsLoadingState(false);
}
}}
>
{isLoadingState && (
<span className="loading loading-spinner loading-sm"></span>
)}
{props.children as any}
</button>
);
};

View File

@@ -76,11 +76,16 @@ const SelectCom = <T extends FieldValues>({
<SelectTemplate
onChange={(newValue: any) => {
if (Array.isArray(newValue)) {
form.setValue(name, newValue.map((v: any) => v.value) as any);
form.setValue(name, newValue.map((v: any) => v.value) as any, {
shouldDirty: true,
});
} else {
form.setValue(name, newValue.value);
form.setValue(name, newValue.value, {
shouldDirty: true,
});
}
form.trigger(name);
form.Dirty;
}}
value={
(inputProps as any)?.isMulti

View File

@@ -24,9 +24,7 @@ export const GET = async (req: NextRequest) => {
if (!user)
return NextResponse.json({ error: "User not found" }, { status: 404 });
setTimeout(async () => {
console.log("getting moodle ID");
const moodleUser = await getMoodleUserById(user.id);
console.log("got moodle ID", moodleUser.id);
await prisma.user.update({
where: {
id: user.id,