CHanged Event admin layout
This commit is contained in:
@@ -1,2 +1,6 @@
|
|||||||
MOODLE_TOKEN=
|
MOODLE_TOKEN=
|
||||||
MOODLE_URL=
|
MOODLE_URL=
|
||||||
|
MAIL_SERVER=
|
||||||
|
MAIL_USER=
|
||||||
|
MAIL_PASSWORD=
|
||||||
|
MAIL_PORT=
|
||||||
@@ -2,6 +2,8 @@ import { getMoodleCourseCompletionStatus, getMoodleUserById } from "./moodle";
|
|||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
import { prisma } from "@repo/db";
|
import { prisma } from "@repo/db";
|
||||||
import { eventCompleted } from "@repo/ui/helper";
|
import { eventCompleted } from "@repo/ui/helper";
|
||||||
|
import { sendCourseCompletedEmail } from "modules/mail";
|
||||||
|
import { handleParticipantFinished } from "modules/event";
|
||||||
|
|
||||||
const syncMoodleIds = async () => {
|
const syncMoodleIds = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -57,7 +59,7 @@ const updateParticipantMoodleResults = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (quizzResult?.completionstatus?.completed === true) {
|
if (quizzResult?.completionstatus?.completed === true) {
|
||||||
await prisma.participant.update({
|
return prisma.participant.update({
|
||||||
where: {
|
where: {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
},
|
},
|
||||||
@@ -65,7 +67,7 @@ const updateParticipantMoodleResults = async () => {
|
|||||||
finisherMoodleCurseCompleted: true,
|
finisherMoodleCurseCompleted: true,
|
||||||
statusLog: {
|
statusLog: {
|
||||||
push: {
|
push: {
|
||||||
event: "Finisher course completed",
|
event: "Moodle-Kurs abgeschlossen",
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
user: "system",
|
user: "system",
|
||||||
},
|
},
|
||||||
@@ -77,33 +79,40 @@ const updateParticipantMoodleResults = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkedFinishedParticipants = async () => {
|
export const checkFinishedParticipants = async () => {
|
||||||
|
console.log("Checking finished participants");
|
||||||
const participantsPending = await prisma.participant.findMany({
|
const participantsPending = await prisma.participant.findMany({
|
||||||
where: {
|
where: {
|
||||||
finished: false,
|
completetionWorkflowFinished: false,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
Event: true,
|
Event: true,
|
||||||
User: true,
|
User: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
participantsPending.forEach(async (p) => {
|
participantsPending.forEach(async (p) => {
|
||||||
if (!p.User) return;
|
if (!p.User) return;
|
||||||
if (!p.User.moodleId) return;
|
|
||||||
|
|
||||||
const completed = eventCompleted(p.Event, p);
|
const completed = eventCompleted(p.Event, p);
|
||||||
|
|
||||||
|
if (!completed) return;
|
||||||
|
handleParticipantFinished(p.Event, p, p.User);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
|
CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
|
||||||
CronJob.from({
|
CronJob.from({
|
||||||
cronTime: "*/5 * * * *",
|
cronTime: "*/1 * * * *",
|
||||||
onTick: async () => {
|
onTick: async () => {
|
||||||
console.log("Updating participant moodle results");
|
console.log("Updating participant moodle results");
|
||||||
await updateParticipantMoodleResults();
|
await updateParticipantMoodleResults();
|
||||||
|
await checkFinishedParticipants();
|
||||||
},
|
},
|
||||||
start: true,
|
start: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateParticipantMoodleResults();
|
const debug = async () => {
|
||||||
|
await updateParticipantMoodleResults();
|
||||||
|
await checkFinishedParticipants();
|
||||||
|
};
|
||||||
|
|
||||||
|
debug();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Event, Participant, prisma, User } from "@repo/db";
|
import { Event, Participant, prisma, User } from "@repo/db";
|
||||||
|
import { sendCourseCompletedEmail } from "modules/mail";
|
||||||
|
|
||||||
export const handleParticipantFinished = async (
|
export const handleParticipantFinished = async (
|
||||||
event: Event,
|
event: Event,
|
||||||
@@ -11,9 +12,6 @@ export const handleParticipantFinished = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Send Discord Message
|
|
||||||
//TODO: Send Email
|
|
||||||
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
@@ -22,19 +20,24 @@ export const handleParticipantFinished = async (
|
|||||||
badges: {
|
badges: {
|
||||||
push: event.finishedBadges,
|
push: event.finishedBadges,
|
||||||
},
|
},
|
||||||
permissions: event.finishedPermissions,
|
permissions: {
|
||||||
|
push: event.finishedPermissions,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//TODO: Send Discord Message
|
||||||
|
await sendCourseCompletedEmail(user.email, user, event);
|
||||||
|
|
||||||
await prisma.participant.update({
|
await prisma.participant.update({
|
||||||
where: {
|
where: {
|
||||||
id: participant.id,
|
id: participant.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
finished: true,
|
completetionWorkflowFinished: true,
|
||||||
statusLog: {
|
statusLog: {
|
||||||
push: {
|
push: {
|
||||||
event: "Event finished",
|
event: "Berechtigungen und Badges vergeben",
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
user: "system",
|
user: "system",
|
||||||
},
|
},
|
||||||
|
|||||||
21
apps/hub-server/modules/mail-templates/CourseCompleted.tsx
Normal file
21
apps/hub-server/modules/mail-templates/CourseCompleted.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Event, User } from "@repo/db";
|
||||||
|
|
||||||
|
import { Html, Button, render } from "@react-email/components";
|
||||||
|
|
||||||
|
const Template = ({ event, user }: { user: User; event: Event }) => (
|
||||||
|
<Html lang="en">
|
||||||
|
<p>You completed the Course {event.name}</p>
|
||||||
|
<p>Congratulation</p>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
|
||||||
|
export function renderCourseCompleted({
|
||||||
|
user,
|
||||||
|
event,
|
||||||
|
}: {
|
||||||
|
user: User;
|
||||||
|
event: Event;
|
||||||
|
}) {
|
||||||
|
return render(<Template event={event} user={user} />);
|
||||||
|
}
|
||||||
59
apps/hub-server/modules/mail.ts
Normal file
59
apps/hub-server/modules/mail.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Event, User } from "@repo/db";
|
||||||
|
import nodemailer from "nodemailer";
|
||||||
|
import { renderCourseCompleted } from "./mail-templates/CourseCompleted";
|
||||||
|
|
||||||
|
let transporter: nodemailer.Transporter | null = null;
|
||||||
|
|
||||||
|
const initTransporter = () => {
|
||||||
|
if (!process.env.MAIL_SERVER)
|
||||||
|
return console.error("MAIL_SERVER is not defined");
|
||||||
|
if (!process.env.MAIL_PORT) return console.error("MAIL_PORT is not defined");
|
||||||
|
if (!process.env.MAIL_USER) return console.error("MAIL_USER is not defined");
|
||||||
|
if (!process.env.MAIL_PASSWORD)
|
||||||
|
return console.error("MAIL_PASSWORD is not defined");
|
||||||
|
|
||||||
|
transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.MAIL_SERVER,
|
||||||
|
port: parseInt(process.env.MAIL_PORT),
|
||||||
|
secure: true, // true for 465, false for other ports
|
||||||
|
auth: {
|
||||||
|
user: process.env.MAIL_USER,
|
||||||
|
pass: process.env.MAIL_PASSWORD,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
transporter.on("error", (err) => {
|
||||||
|
console.error("Mail occurred:", err);
|
||||||
|
});
|
||||||
|
transporter.on("idle", () => {
|
||||||
|
console.log("Mail Idle");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initTransporter();
|
||||||
|
|
||||||
|
export const sendCourseCompletedEmail = async (
|
||||||
|
to: string,
|
||||||
|
user: User,
|
||||||
|
event: Event,
|
||||||
|
) => {
|
||||||
|
const emailHtml = await renderCourseCompleted({ user, event });
|
||||||
|
|
||||||
|
if (!transporter) {
|
||||||
|
console.error("Transporter is not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
|
from: process.env.MAIL_USER,
|
||||||
|
to,
|
||||||
|
subject: `Congratulations ${user.firstname} on completing ${event.name}`,
|
||||||
|
html: emailHtml,
|
||||||
|
},
|
||||||
|
(error, info) => {
|
||||||
|
if (error) {
|
||||||
|
console.error("Error sending email:", error);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,12 +11,16 @@
|
|||||||
"@repo/db": "*",
|
"@repo/db": "*",
|
||||||
"@repo/typescript-config": "*",
|
"@repo/typescript-config": "*",
|
||||||
"@types/node": "^22.13.5",
|
"@types/node": "^22.13.5",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"typescript": "latest"
|
"typescript": "latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-email/components": "^0.0.33",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"cron": "^4.1.0",
|
"cron": "^4.1.0",
|
||||||
"dotenv": "^16.4.7"
|
"dotenv": "^16.4.7",
|
||||||
|
"nodemailer": "^6.10.0",
|
||||||
|
"react": "^19.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "@repo/typescript-config/base.json",
|
"extends": "@repo/typescript-config/base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"baseUrl": "."
|
"baseUrl": ".",
|
||||||
},
|
"jsx": "react"
|
||||||
"include": ["**/*.ts", "./index.ts"],
|
},
|
||||||
"exclude": ["node_modules", "dist"]
|
"include": ["**/*.ts", "./index.ts"],
|
||||||
}
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,33 +22,37 @@ import { Switch } from "../../../../_components/ui/Switch";
|
|||||||
interface AppointmentModalProps {
|
interface AppointmentModalProps {
|
||||||
event?: Event;
|
event?: Event;
|
||||||
ref: RefObject<HTMLDialogElement | null>;
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
|
participantModal: RefObject<HTMLDialogElement | null>;
|
||||||
appointmentsTableRef: React.RefObject<PaginatedTableRef>;
|
appointmentsTableRef: React.RefObject<PaginatedTableRef>;
|
||||||
appointmentForm: UseFormReturn<
|
appointmentForm: UseFormReturn<
|
||||||
EventAppointmentOptionalDefaults,
|
EventAppointmentOptionalDefaults,
|
||||||
any,
|
any,
|
||||||
undefined
|
undefined
|
||||||
>;
|
>;
|
||||||
|
participantForm: UseFormReturn<Participant, any, undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppointmentModal = ({
|
export const AppointmentModal = ({
|
||||||
event,
|
event,
|
||||||
ref,
|
ref,
|
||||||
|
participantModal,
|
||||||
appointmentsTableRef,
|
appointmentsTableRef,
|
||||||
appointmentForm,
|
appointmentForm,
|
||||||
|
participantForm,
|
||||||
}: AppointmentModalProps) => {
|
}: AppointmentModalProps) => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const participantTableRef = useRef<PaginatedTableRef>(null);
|
const participantTableRef = useRef<PaginatedTableRef>(null);
|
||||||
const participantForm = useForm<Participant>({
|
|
||||||
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog ref={ref} className="modal">
|
<dialog ref={ref} className="modal">
|
||||||
<div className="modal-box">
|
<div className="modal-box">
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
{/* if there is a button in form, it will close the modal */}
|
{/* if there is a button in form, it will close the modal */}
|
||||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
<button
|
||||||
|
className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
|
onClick={() => ref.current?.close()}
|
||||||
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -57,6 +61,7 @@ export const AppointmentModal = ({
|
|||||||
</h3>
|
</h3>
|
||||||
<form
|
<form
|
||||||
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
||||||
|
console.log(values);
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
const createdAppointment = await upsertAppointment(values);
|
const createdAppointment = await upsertAppointment(values);
|
||||||
ref.current?.close();
|
ref.current?.close();
|
||||||
@@ -75,131 +80,83 @@ export const AppointmentModal = ({
|
|||||||
}}
|
}}
|
||||||
/> */}
|
/> */}
|
||||||
<div>
|
<div>
|
||||||
{appointmentForm.watch("id") && (
|
<PaginatedTable
|
||||||
<PaginatedTable
|
hide={appointmentForm.watch("id") === undefined}
|
||||||
ref={participantTableRef}
|
ref={participantTableRef}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
accessorKey: "User.firstname",
|
accessorKey: "User.firstname",
|
||||||
header: "Vorname",
|
header: "Vorname",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "User.lastname",
|
accessorKey: "User.lastname",
|
||||||
header: "Nachname",
|
header: "Nachname",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Aktion",
|
header: "Aktion",
|
||||||
cell: ({ row }: CellContext<Participant, any>) => {
|
cell: ({ row }: CellContext<Participant, any>) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
participantForm.reset(row.original);
|
||||||
|
participantModal.current?.showModal();
|
||||||
|
}}
|
||||||
|
className="btn btn-outline btn-sm"
|
||||||
|
>
|
||||||
|
anzeigen
|
||||||
|
</button>
|
||||||
|
{!row.original.attended && event?.hasPresenceEvents && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
type="button"
|
||||||
participantForm.reset(row.original);
|
onSubmit={() => {}}
|
||||||
|
onClick={async () => {
|
||||||
|
await upsertParticipant({
|
||||||
|
eventId: event!.id,
|
||||||
|
userId: participantForm.watch("userId"),
|
||||||
|
attended: true,
|
||||||
|
});
|
||||||
|
participantTableRef.current?.refresh();
|
||||||
}}
|
}}
|
||||||
className="btn btn-outline btn-sm"
|
className="btn btn-outline btn-info btn-sm"
|
||||||
>
|
>
|
||||||
anzeigen
|
Anwesend
|
||||||
</button>
|
</button>
|
||||||
{!row.original.attended &&
|
)}
|
||||||
event?.hasPresenceEvents && (
|
</>
|
||||||
<button
|
);
|
||||||
type="button"
|
|
||||||
onSubmit={() => {}}
|
|
||||||
onClick={async () => {
|
|
||||||
await upsertParticipant({
|
|
||||||
eventId: event!.id,
|
|
||||||
userId: participantForm.watch("userId"),
|
|
||||||
attended: true,
|
|
||||||
});
|
|
||||||
participantTableRef.current?.refresh();
|
|
||||||
}}
|
|
||||||
className="btn btn-outline btn-info btn-sm"
|
|
||||||
>
|
|
||||||
Anwesend
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
},
|
||||||
prismaModel={"participant"}
|
]}
|
||||||
filter={{
|
prismaModel={"participant"}
|
||||||
eventAppointmentId: appointmentForm.watch("id"),
|
filter={{
|
||||||
}}
|
eventAppointmentId: appointmentForm.watch("id"),
|
||||||
include={{ User: true }}
|
}}
|
||||||
leftOfPagination={
|
include={{ User: true }}
|
||||||
<div className="flex gap-2">
|
leftOfPagination={
|
||||||
<Button type="submit" className="btn btn-primary">
|
<div className="flex gap-2">
|
||||||
Speichern
|
<Button type="submit" className="btn btn-primary">
|
||||||
|
Speichern
|
||||||
|
</Button>
|
||||||
|
{appointmentForm.watch("id") && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onSubmit={() => {}}
|
||||||
|
onClick={async () => {
|
||||||
|
await deleteAppoinement(appointmentForm.watch("id")!);
|
||||||
|
ref.current?.close();
|
||||||
|
appointmentsTableRef.current?.refresh();
|
||||||
|
}}
|
||||||
|
className="btn btn-error btn-outline"
|
||||||
|
>
|
||||||
|
Löschen
|
||||||
</Button>
|
</Button>
|
||||||
{appointmentForm.watch("id") && (
|
)}
|
||||||
<Button
|
</div>
|
||||||
type="button"
|
}
|
||||||
onSubmit={() => {}}
|
/>
|
||||||
onClick={async () => {
|
|
||||||
await deleteAppoinement(appointmentForm.watch("id")!);
|
|
||||||
ref.current?.close();
|
|
||||||
appointmentsTableRef.current?.refresh();
|
|
||||||
}}
|
|
||||||
className="btn btn-error btn-outline"
|
|
||||||
>
|
|
||||||
Löschen
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-action"></div>
|
|
||||||
</form>
|
</form>
|
||||||
{participantForm.watch("id") && (
|
|
||||||
<form
|
|
||||||
onSubmit={participantForm.handleSubmit(async (data) => {
|
|
||||||
await upsertParticipant({
|
|
||||||
...data,
|
|
||||||
statusLog: data.statusLog as Prisma.InputJsonValue[],
|
|
||||||
});
|
|
||||||
participantTableRef.current?.refresh();
|
|
||||||
})}
|
|
||||||
className="space-y-1"
|
|
||||||
>
|
|
||||||
<h1 className="text-2xl">Teilnehmer bearbeiten</h1>
|
|
||||||
<Switch
|
|
||||||
form={participantForm}
|
|
||||||
name="appointmentCancelled"
|
|
||||||
label="Termin abgesagt"
|
|
||||||
/>
|
|
||||||
<Switch
|
|
||||||
form={participantForm}
|
|
||||||
name="finisherMoodleCurseCompleted"
|
|
||||||
label="Abschluss-Moodle kurs abgeschlossen"
|
|
||||||
/>
|
|
||||||
{event?.hasPresenceEvents && (
|
|
||||||
<Switch
|
|
||||||
form={participantForm}
|
|
||||||
name="attended"
|
|
||||||
label="An Presenstermin teilgenommen"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<h3 className="text-xl">Verlauf</h3>
|
|
||||||
{participantForm.watch("statusLog").map((s) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="flex justify-between"
|
|
||||||
key={(s as any).timestamp}
|
|
||||||
>
|
|
||||||
<p>{(s as any).event}</p>
|
|
||||||
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<Button>Speichern</Button>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
prisma,
|
prisma,
|
||||||
Prisma,
|
Prisma,
|
||||||
} from "@repo/db";
|
} from "@repo/db";
|
||||||
import { Bot, Calendar, FileText, UserIcon } from "lucide-react";
|
import { Bot, Calendar, FileText, User, UserIcon } from "lucide-react";
|
||||||
import { Input } from "../../../../_components/ui/Input";
|
import { Input } from "../../../../_components/ui/Input";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -39,6 +39,7 @@ import { Select } from "../../../../_components/ui/Select";
|
|||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { MarkdownEditor } from "../../../../_components/ui/MDEditor";
|
import { MarkdownEditor } from "../../../../_components/ui/MDEditor";
|
||||||
import { AppointmentModal } from "./AppointmentModal";
|
import { AppointmentModal } from "./AppointmentModal";
|
||||||
|
import { ParticipantModal } from "./ParticipantModal";
|
||||||
|
|
||||||
export const Form = ({ event }: { event?: Event }) => {
|
export const Form = ({ event }: { event?: Event }) => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@@ -53,17 +54,28 @@ export const Form = ({ event }: { event?: Event }) => {
|
|||||||
presenterId: session?.user?.id,
|
presenterId: session?.user?.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const participantForm = useForm<Participant>({
|
||||||
|
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
|
||||||
|
});
|
||||||
const appointmentsTableRef = useRef<PaginatedTableRef>(null);
|
const appointmentsTableRef = useRef<PaginatedTableRef>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
const appointmentModal = useRef<HTMLDialogElement>(null);
|
const appointmentModal = useRef<HTMLDialogElement>(null);
|
||||||
|
const participantModal = useRef<HTMLDialogElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppointmentModal
|
<AppointmentModal
|
||||||
|
participantModal={participantModal}
|
||||||
|
participantForm={participantForm}
|
||||||
appointmentForm={appointmentForm}
|
appointmentForm={appointmentForm}
|
||||||
ref={appointmentModal}
|
ref={appointmentModal}
|
||||||
appointmentsTableRef={appointmentsTableRef}
|
appointmentsTableRef={appointmentsTableRef}
|
||||||
|
event={event}
|
||||||
|
/>
|
||||||
|
<ParticipantModal
|
||||||
|
participantForm={participantForm}
|
||||||
|
ref={participantModal}
|
||||||
/>
|
/>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(async (values) => {
|
onSubmit={form.handleSubmit(async (values) => {
|
||||||
@@ -164,6 +176,8 @@ export const Form = ({ event }: { event?: Event }) => {
|
|||||||
appointmentModal.current?.showModal();
|
appointmentModal.current?.showModal();
|
||||||
appointmentForm.reset({
|
appointmentForm.reset({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
eventId: event.id,
|
||||||
|
presenterId: session?.user?.id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -238,6 +252,62 @@ export const Form = ({ event }: { event?: Event }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{!form.watch("hasPresenceEvents") ? (
|
||||||
|
<div className="card bg-base-200 shadow-xl col-span-6">
|
||||||
|
<div className="card-body">
|
||||||
|
<PaginatedTable
|
||||||
|
leftOfSearch={
|
||||||
|
<h2 className="card-title">
|
||||||
|
<Calendar className="w-5 h-5" /> Teilnehmer
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
searchFields={["User.firstname", "User.lastname"]}
|
||||||
|
ref={appointmentsTableRef}
|
||||||
|
prismaModel={"participant"}
|
||||||
|
filter={{
|
||||||
|
eventId: event?.id,
|
||||||
|
}}
|
||||||
|
include={{
|
||||||
|
User: true,
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: "Vorname",
|
||||||
|
accessorKey: "User.firstname",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Nachname",
|
||||||
|
accessorKey: "User.lastname",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Moodle Kurs abgeschlossen",
|
||||||
|
accessorKey: "finisherMoodleCurseCompleted",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Aktionen",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onSubmit={() => false}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
participantForm.reset(row.original);
|
||||||
|
participantModal.current?.showModal();
|
||||||
|
}}
|
||||||
|
className="btn btn-sm btn-outline"
|
||||||
|
>
|
||||||
|
Bearbeiten
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<div className="card bg-base-200 shadow-xl col-span-6">
|
<div className="card bg-base-200 shadow-xl col-span-6">
|
||||||
<div className="card-body ">
|
<div className="card-body ">
|
||||||
<div className="flex w-full gap-4">
|
<div className="flex w-full gap-4">
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Switch } from "../../../../_components/ui/Switch";
|
||||||
|
import { Button } from "../../../../_components/ui/Button";
|
||||||
|
import { Participant, Prisma } from "@repo/db";
|
||||||
|
import { UseFormReturn } from "react-hook-form";
|
||||||
|
import { upsertParticipant } from "../../../events/actions";
|
||||||
|
import { RefObject } from "react";
|
||||||
|
|
||||||
|
interface ParticipantModalProps {
|
||||||
|
participantForm: UseFormReturn<Participant>;
|
||||||
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ParticipantModal = ({
|
||||||
|
participantForm,
|
||||||
|
ref,
|
||||||
|
}: ParticipantModalProps) => {
|
||||||
|
return (
|
||||||
|
<dialog className="modal" ref={ref}>
|
||||||
|
<div className="modal-box">
|
||||||
|
<form method="dialog">
|
||||||
|
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<h1 className="text-2xl">Teilnehmer bearbeiten</h1>
|
||||||
|
<form
|
||||||
|
onSubmit={participantForm.handleSubmit(async (data) => {
|
||||||
|
await upsertParticipant({
|
||||||
|
...data,
|
||||||
|
statusLog: data.statusLog as Prisma.InputJsonValue[],
|
||||||
|
});
|
||||||
|
participantForm.reset();
|
||||||
|
ref.current?.close();
|
||||||
|
})}
|
||||||
|
className="space-y-1"
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
form={participantForm}
|
||||||
|
name="appointmentCancelled"
|
||||||
|
label="Termin abgesagt"
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
form={participantForm}
|
||||||
|
name="finisherMoodleCurseCompleted"
|
||||||
|
label="Abschluss-Moodle-Kurs abgeschlossen"
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
form={participantForm}
|
||||||
|
name="completetionWorkflowFinished"
|
||||||
|
label="Abgeschlossen (E-Mail-Benachrichtigung senden)"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<h3 className="text-xl">Verlauf</h3>
|
||||||
|
{participantForm.watch("statusLog")?.map((s) => (
|
||||||
|
<div className="flex justify-between" key={(s as any).timestamp}>
|
||||||
|
<p>{(s as any).event}</p>
|
||||||
|
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Button type="submit">Speichern</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -120,6 +120,7 @@ const ModalBtn = ({
|
|||||||
<div className="flex items-center gap-2 justify-center">
|
<div className="flex items-center gap-2 justify-center">
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
{!!dates.length && (
|
{!!dates.length && (
|
||||||
|
// TODO: Prevent users from selecting an appointment that is full
|
||||||
<Select
|
<Select
|
||||||
form={selectAppointmentForm as any}
|
form={selectAppointmentForm as any}
|
||||||
options={dates.map((date) => ({
|
options={dates.map((date) => ({
|
||||||
@@ -180,10 +181,10 @@ const ModalBtn = ({
|
|||||||
>
|
>
|
||||||
absagen
|
absagen
|
||||||
</button>
|
</button>
|
||||||
<p className="mt-3 text-center">
|
|
||||||
Bitte finde dich an diesem Termin in unserem Discord ein.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="mt-3 text-center">
|
||||||
|
Bitte finde dich an diesem Termin in unserem Discord ein.
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -251,7 +252,7 @@ const MoodleCourseIndicator = ({
|
|||||||
event: Event;
|
event: Event;
|
||||||
}) => {
|
}) => {
|
||||||
const courseUrl = `${process.env.NEXT_PUBLIC_MOODLE_URL}/course/view.php?id=${moodleCourseId}`;
|
const courseUrl = `${process.env.NEXT_PUBLIC_MOODLE_URL}/course/view.php?id=${moodleCourseId}`;
|
||||||
if (!participant || (event.hasPresenceEvents && !participant?.attended))
|
if (event.hasPresenceEvents && !participant?.attended)
|
||||||
return (
|
return (
|
||||||
<p className="py-4 flex items-center gap-2 justify-center">
|
<p className="py-4 flex items-center gap-2 justify-center">
|
||||||
<Clock10Icon className="text-error" />
|
<Clock10Icon className="text-error" />
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface PaginatedTableProps<TData>
|
|||||||
leftOfSearch?: React.ReactNode;
|
leftOfSearch?: React.ReactNode;
|
||||||
rightOfSearch?: React.ReactNode;
|
rightOfSearch?: React.ReactNode;
|
||||||
leftOfPagination?: React.ReactNode;
|
leftOfPagination?: React.ReactNode;
|
||||||
|
hide?: boolean;
|
||||||
ref?: Ref<PaginatedTableRef>;
|
ref?: Ref<PaginatedTableRef>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ export function PaginatedTable<TData>({
|
|||||||
leftOfSearch,
|
leftOfSearch,
|
||||||
rightOfSearch,
|
rightOfSearch,
|
||||||
leftOfPagination,
|
leftOfPagination,
|
||||||
|
hide,
|
||||||
...restProps
|
...restProps
|
||||||
}: PaginatedTableProps<TData>) {
|
}: PaginatedTableProps<TData>) {
|
||||||
const [data, setData] = useState<TData[]>([]);
|
const [data, setData] = useState<TData[]>([]);
|
||||||
@@ -111,19 +113,23 @@ export function PaginatedTable<TData>({
|
|||||||
)}
|
)}
|
||||||
<div className="flex justify-center">{rightOfSearch}</div>
|
<div className="flex justify-center">{rightOfSearch}</div>
|
||||||
</div>
|
</div>
|
||||||
<SortableTable
|
{!hide && (
|
||||||
data={data}
|
<SortableTable
|
||||||
prismaModel={prismaModel}
|
data={data}
|
||||||
showEditButton={showEditButton}
|
prismaModel={prismaModel}
|
||||||
{...restProps}
|
showEditButton={showEditButton}
|
||||||
/>
|
{...restProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex items-between">
|
<div className="flex items-between">
|
||||||
{leftOfPagination}
|
{leftOfPagination}
|
||||||
<Pagination
|
{!hide && (
|
||||||
totalPages={Math.ceil(total / rowsPerPage)}
|
<Pagination
|
||||||
page={page}
|
totalPages={Math.ceil(total / rowsPerPage)}
|
||||||
setPage={setPage}
|
page={page}
|
||||||
/>
|
setPage={setPage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ export const GET = async (req: NextRequest) => {
|
|||||||
});
|
});
|
||||||
if (!user)
|
if (!user)
|
||||||
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
console.log("getting moodle ID");
|
||||||
const moodleUser = await getMoodleUserById(user.id);
|
const moodleUser = await getMoodleUserById(user.id);
|
||||||
prisma.user.update({
|
console.log("got moodle ID", moodleUser.id);
|
||||||
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
},
|
},
|
||||||
@@ -35,8 +36,6 @@ export const GET = async (req: NextRequest) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user.moodleId) return;
|
|
||||||
|
|
||||||
const participatingEvents = await prisma.participant.findMany({
|
const participatingEvents = await prisma.participant.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@next-auth/prisma-adapter": "^1.0.7",
|
"@next-auth/prisma-adapter": "^1.0.7",
|
||||||
"@repo/db": "*",
|
"@repo/db": "*",
|
||||||
"@repo/ui": "*",
|
"@repo/ui": "*",
|
||||||
|
"@tanstack/react-query": "^5.67.2",
|
||||||
"@tanstack/react-table": "^8.20.6",
|
"@tanstack/react-table": "^8.20.6",
|
||||||
"@uiw/react-md-editor": "^4.0.5",
|
"@uiw/react-md-editor": "^4.0.5",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ services:
|
|||||||
- "8080:80"
|
- "8080:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
redis:
|
||||||
|
container_name: redis
|
||||||
|
image: docker.io/bitnami/redis:7.4
|
||||||
|
environment:
|
||||||
|
# ALLOW_EMPTY_PASSWORD is recommended only for development.
|
||||||
|
- ALLOW_EMPTY_PASSWORD=yes
|
||||||
|
- REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- "redis_data:/bitnami/redis/data"
|
||||||
|
|
||||||
moodle_database:
|
moodle_database:
|
||||||
container_name: moodle_database
|
container_name: moodle_database
|
||||||
@@ -62,3 +73,5 @@ volumes:
|
|||||||
moodle_data:
|
moodle_data:
|
||||||
moodle_database:
|
moodle_database:
|
||||||
moodle_moodledata:
|
moodle_moodledata:
|
||||||
|
redis_data:
|
||||||
|
driver: local
|
||||||
|
|||||||
538
package-lock.json
generated
538
package-lock.json
generated
@@ -95,6 +95,7 @@
|
|||||||
"@next-auth/prisma-adapter": "^1.0.7",
|
"@next-auth/prisma-adapter": "^1.0.7",
|
||||||
"@repo/db": "*",
|
"@repo/db": "*",
|
||||||
"@repo/ui": "*",
|
"@repo/ui": "*",
|
||||||
|
"@tanstack/react-query": "^5.67.2",
|
||||||
"@tanstack/react-table": "^8.20.6",
|
"@tanstack/react-table": "^8.20.6",
|
||||||
"@uiw/react-md-editor": "^4.0.5",
|
"@uiw/react-md-editor": "^4.0.5",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
@@ -137,14 +138,18 @@
|
|||||||
},
|
},
|
||||||
"apps/hub-server": {
|
"apps/hub-server": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-email/components": "^0.0.33",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"cron": "^4.1.0",
|
"cron": "^4.1.0",
|
||||||
"dotenv": "^16.4.7"
|
"dotenv": "^16.4.7",
|
||||||
|
"nodemailer": "^6.10.0",
|
||||||
|
"react": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/db": "*",
|
"@repo/db": "*",
|
||||||
"@repo/typescript-config": "*",
|
"@repo/typescript-config": "*",
|
||||||
"@types/node": "^22.13.5",
|
"@types/node": "^22.13.5",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"typescript": "latest"
|
"typescript": "latest"
|
||||||
}
|
}
|
||||||
@@ -2096,6 +2101,286 @@
|
|||||||
"react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
|
"react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-email/body": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/button": {
|
||||||
|
"version": "0.0.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.0.19.tgz",
|
||||||
|
"integrity": "sha512-HYHrhyVGt7rdM/ls6FuuD6XE7fa7bjZTJqB2byn6/oGsfiEZaogY77OtoLL/mrQHjHjZiJadtAMSik9XLcm7+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/code-block": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-4D43p+LIMjDzm66gTDrZch0Flkip5je91mAT7iGs6+SbPyalHgIA+lFQoQwhz/VzHHLxuD0LV6gwmU/WUQ2WEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prismjs": "1.29.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/code-inline": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/code-inline/-/code-inline-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/column": {
|
||||||
|
"version": "0.0.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/column/-/column-0.0.13.tgz",
|
||||||
|
"integrity": "sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/components": {
|
||||||
|
"version": "0.0.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.33.tgz",
|
||||||
|
"integrity": "sha512-/GKdT3YijT1iEWPAXF644jr12w5xVgzUr0zlbZGt2KOkGeFHNZUCL5UtRopmnjrH/Fayf8Gjv6q/4E2cZgDtdQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-email/body": "0.0.11",
|
||||||
|
"@react-email/button": "0.0.19",
|
||||||
|
"@react-email/code-block": "0.0.11",
|
||||||
|
"@react-email/code-inline": "0.0.5",
|
||||||
|
"@react-email/column": "0.0.13",
|
||||||
|
"@react-email/container": "0.0.15",
|
||||||
|
"@react-email/font": "0.0.9",
|
||||||
|
"@react-email/head": "0.0.12",
|
||||||
|
"@react-email/heading": "0.0.15",
|
||||||
|
"@react-email/hr": "0.0.11",
|
||||||
|
"@react-email/html": "0.0.11",
|
||||||
|
"@react-email/img": "0.0.11",
|
||||||
|
"@react-email/link": "0.0.12",
|
||||||
|
"@react-email/markdown": "0.0.14",
|
||||||
|
"@react-email/preview": "0.0.12",
|
||||||
|
"@react-email/render": "1.0.5",
|
||||||
|
"@react-email/row": "0.0.12",
|
||||||
|
"@react-email/section": "0.0.16",
|
||||||
|
"@react-email/tailwind": "1.0.4",
|
||||||
|
"@react-email/text": "0.0.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/container": {
|
||||||
|
"version": "0.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.15.tgz",
|
||||||
|
"integrity": "sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/font": {
|
||||||
|
"version": "0.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/font/-/font-0.0.9.tgz",
|
||||||
|
"integrity": "sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/head": {
|
||||||
|
"version": "0.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/head/-/head-0.0.12.tgz",
|
||||||
|
"integrity": "sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/heading": {
|
||||||
|
"version": "0.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.15.tgz",
|
||||||
|
"integrity": "sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/hr": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/html": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/html/-/html-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/img": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/link": {
|
||||||
|
"version": "0.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.12.tgz",
|
||||||
|
"integrity": "sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/markdown": {
|
||||||
|
"version": "0.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/markdown/-/markdown-0.0.14.tgz",
|
||||||
|
"integrity": "sha512-5IsobCyPkb4XwnQO8uFfGcNOxnsg3311GRXhJ3uKv51P7Jxme4ycC/MITnwIZ10w2zx7HIyTiqVzTj4XbuIHbg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"md-to-react-email": "5.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/preview": {
|
||||||
|
"version": "0.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.12.tgz",
|
||||||
|
"integrity": "sha512-g/H5fa9PQPDK6WUEG7iTlC19sAktI23qyoiJtMLqQiXFCfWeQMhqjLGKeLSKkfzszqmfJCjZtpSiKtBoOdxp3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/render": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-CA69HYXPk21HhtAXATIr+9JJwpDNmAFCvdMUjWmeoD1+KhJ9NAxusMRxKNeibdZdslmq3edaeOKGbdQ9qjK8LQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"html-to-text": "9.0.5",
|
||||||
|
"prettier": "3.4.2",
|
||||||
|
"react-promise-suspense": "0.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/row": {
|
||||||
|
"version": "0.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.12.tgz",
|
||||||
|
"integrity": "sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/section": {
|
||||||
|
"version": "0.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.16.tgz",
|
||||||
|
"integrity": "sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/tailwind": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-tJdcusncdqgvTUYZIuhNC6LYTfL9vNTSQpwWdTCQhQ1lsrNCEE4OKCSdzSV3S9F32pi0i0xQ+YPJHKIzGjdTSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-email/text": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-a7nl/2KLpRHOYx75YbYZpWspUbX1DFY7JIZbOv5x0QU8SvwDbJt+Hm01vG34PffFyYvHEXrc6Qnip2RTjljNjg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@repo/db": {
|
"node_modules/@repo/db": {
|
||||||
"resolved": "packages/database",
|
"resolved": "packages/database",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -2124,6 +2409,19 @@
|
|||||||
"integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==",
|
"integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@selderee/plugin-htmlparser2": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"selderee": "^0.11.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
@@ -2381,6 +2679,32 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/query-core": {
|
||||||
|
"version": "5.67.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.67.2.tgz",
|
||||||
|
"integrity": "sha512-+iaFJ/pt8TaApCk6LuZ0WHS/ECVfTzrxDOEL9HH9Dayyb5OVuomLzDXeSaI2GlGT/8HN7bDGiRXDts3LV+u6ww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/react-query": {
|
||||||
|
"version": "5.67.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.67.2.tgz",
|
||||||
|
"integrity": "sha512-6Sa+BVNJWhAV4QHvIqM73norNeGRWGC3ftN0Ix87cmMvI215I1wyJ44KUTt/9a0V9YimfGcg25AITaYVel71Og==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/query-core": "5.67.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/react-table": {
|
"node_modules/@tanstack/react-table": {
|
||||||
"version": "8.20.6",
|
"version": "8.20.6",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz",
|
||||||
@@ -2655,6 +2979,16 @@
|
|||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/nodemailer": {
|
||||||
|
"version": "6.4.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
|
||||||
|
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||||
@@ -4570,6 +4904,15 @@
|
|||||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/defaults": {
|
"node_modules/defaults": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||||
@@ -4751,6 +5094,61 @@
|
|||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dot-case": {
|
"node_modules/dot-case": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz",
|
||||||
@@ -6886,6 +7284,22 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-to-text": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@selderee/plugin-htmlparser2": "^0.11.0",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"htmlparser2": "^8.0.2",
|
||||||
|
"selderee": "^0.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-url-attributes": {
|
"node_modules/html-url-attributes": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||||
@@ -6906,6 +7320,25 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "8.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||||
|
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"entities": "^4.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-proxy-agent": {
|
"node_modules/http-proxy-agent": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||||
@@ -7987,6 +8420,15 @@
|
|||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/leac": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/leaflet": {
|
"node_modules/leaflet": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
@@ -8405,6 +8847,18 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -8413,6 +8867,18 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/md-to-react-email": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/md-to-react-email/-/md-to-react-email-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"marked": "7.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0 || ^19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdast-util-find-and-replace": {
|
"node_modules/mdast-util-find-and-replace": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
||||||
@@ -9666,6 +10132,15 @@
|
|||||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nodemailer": {
|
||||||
|
"version": "6.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
|
||||||
|
"integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==",
|
||||||
|
"license": "MIT-0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/npm": {
|
"node_modules/npm": {
|
||||||
"version": "11.1.0",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/npm/-/npm-11.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/npm/-/npm-11.1.0.tgz",
|
||||||
@@ -12615,6 +13090,19 @@
|
|||||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parseley": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"leac": "^0.6.0",
|
||||||
|
"peberminta": "^0.9.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pascal-case": {
|
"node_modules/pascal-case": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz",
|
||||||
@@ -12674,6 +13162,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/peberminta": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -12857,7 +13354,6 @@
|
|||||||
"version": "3.4.2",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
||||||
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -12902,6 +13398,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prismjs": {
|
||||||
|
"version": "1.29.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||||
|
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@@ -13013,6 +13518,7 @@
|
|||||||
"version": "19.0.0",
|
"version": "19.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -13126,6 +13632,21 @@
|
|||||||
"react": ">=18"
|
"react": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-promise-suspense": {
|
||||||
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-promise-suspense/node_modules/fast-deep-equal": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-select": {
|
"node_modules/react-select": {
|
||||||
"version": "5.10.0",
|
"version": "5.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.0.tgz",
|
||||||
@@ -13784,6 +14305,18 @@
|
|||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/selderee": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"parseley": "^0.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/killymxi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.6.3",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
@@ -15625,6 +16158,7 @@
|
|||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@repo/db": "*",
|
||||||
"@repo/eslint-config": "*",
|
"@repo/eslint-config": "*",
|
||||||
"@repo/typescript-config": "*",
|
"@repo/typescript-config": "*",
|
||||||
"@turbo/gen": "^1.12.4",
|
"@turbo/gen": "^1.12.4",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ model Participant {
|
|||||||
finisherMoodleCurseCompleted Boolean @default(false)
|
finisherMoodleCurseCompleted Boolean @default(false)
|
||||||
attended Boolean @default(false)
|
attended Boolean @default(false)
|
||||||
appointmentCancelled Boolean @default(false)
|
appointmentCancelled Boolean @default(false)
|
||||||
finished Boolean @default(false)
|
completetionWorkflowFinished Boolean @default(false)
|
||||||
eventAppointmentId Int?
|
eventAppointmentId Int?
|
||||||
statusLog Json[] @default([])
|
statusLog Json[] @default([])
|
||||||
eventId Int
|
eventId Int
|
||||||
|
|||||||
Reference in New Issue
Block a user