completed user admin page
This commit is contained in:
@@ -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" &&
|
||||
|
||||
@@ -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();
|
||||
})}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
35
apps/hub/app/(app)/admin/user/action.ts
Normal file
35
apps/hub/app/(app)/admin/user/action.ts
Normal 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 };
|
||||
};
|
||||
@@ -103,13 +103,6 @@ const ModalBtn = ({
|
||||
? (selectedDate as any)?.Participants?.length + 1
|
||||
: ownIndexInParticipantList + 1;
|
||||
|
||||
console.log({
|
||||
selectedDate,
|
||||
ownPlaceInParticipantList,
|
||||
ownIndexInParticipantList,
|
||||
maxParticipants: event.maxParticipants,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
|
||||
@@ -43,7 +43,7 @@ export const Register = () => {
|
||||
passwordConfirm: "",
|
||||
},
|
||||
});
|
||||
console.log(form.formState.errors);
|
||||
|
||||
return (
|
||||
<form
|
||||
className="card-body"
|
||||
|
||||
11
apps/hub/app/_components/Error.tsx
Normal file
11
apps/hub/app/_components/Error.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
export const Error = ({
|
||||
statusCode,
|
||||
title,
|
||||
}: {
|
||||
statusCode: number;
|
||||
title: string;
|
||||
}) => {
|
||||
return <Error statusCode={404} title="User not found" />;
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
40
apps/hub/helper/mail.ts
Normal file
40
apps/hub/helper/mail.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export const sendMail = async (
|
||||
email: string,
|
||||
subject: string,
|
||||
html: string,
|
||||
) => {
|
||||
await fetch(`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/mail/send`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
body: JSON.stringify({
|
||||
to: email,
|
||||
subject,
|
||||
html,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export const sendMailByTemplate = async (
|
||||
email: string,
|
||||
template: "password-change" | "course-completed",
|
||||
data: any,
|
||||
) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/mail/template/${template}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
body: JSON.stringify({
|
||||
to: email,
|
||||
data,
|
||||
}),
|
||||
},
|
||||
);
|
||||
console.log(res);
|
||||
} catch (error) {
|
||||
console.error("Error sending mail:", error);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user