CHanged Event admin layout

This commit is contained in:
PxlLoewe
2025-03-10 18:11:28 -07:00
parent 77b266e0bf
commit c01e618a56
17 changed files with 918 additions and 170 deletions

View File

@@ -1,2 +1,6 @@
MOODLE_TOKEN=
MOODLE_URL=
MAIL_SERVER=
MAIL_USER=
MAIL_PASSWORD=
MAIL_PORT=

View File

@@ -2,6 +2,8 @@ import { getMoodleCourseCompletionStatus, getMoodleUserById } from "./moodle";
import { CronJob } from "cron";
import { prisma } from "@repo/db";
import { eventCompleted } from "@repo/ui/helper";
import { sendCourseCompletedEmail } from "modules/mail";
import { handleParticipantFinished } from "modules/event";
const syncMoodleIds = async () => {
try {
@@ -57,7 +59,7 @@ const updateParticipantMoodleResults = async () => {
);
if (quizzResult?.completionstatus?.completed === true) {
await prisma.participant.update({
return prisma.participant.update({
where: {
id: p.id,
},
@@ -65,7 +67,7 @@ const updateParticipantMoodleResults = async () => {
finisherMoodleCurseCompleted: true,
statusLog: {
push: {
event: "Finisher course completed",
event: "Moodle-Kurs abgeschlossen",
timestamp: new Date(),
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({
where: {
finished: false,
completetionWorkflowFinished: false,
},
include: {
Event: true,
User: true,
},
});
participantsPending.forEach(async (p) => {
if (!p.User) return;
if (!p.User.moodleId) return;
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: "*/5 * * * *",
cronTime: "*/1 * * * *",
onTick: async () => {
console.log("Updating participant moodle results");
await updateParticipantMoodleResults();
await checkFinishedParticipants();
},
start: true,
});
updateParticipantMoodleResults();
const debug = async () => {
await updateParticipantMoodleResults();
await checkFinishedParticipants();
};
debug();

View File

@@ -1,4 +1,5 @@
import { Event, Participant, prisma, User } from "@repo/db";
import { sendCourseCompletedEmail } from "modules/mail";
export const handleParticipantFinished = async (
event: Event,
@@ -11,9 +12,6 @@ export const handleParticipantFinished = async (
},
});
//TODO: Send Discord Message
//TODO: Send Email
await prisma.user.update({
where: {
id: user.id,
@@ -22,19 +20,24 @@ export const handleParticipantFinished = async (
badges: {
push: event.finishedBadges,
},
permissions: event.finishedPermissions,
permissions: {
push: event.finishedPermissions,
},
},
});
//TODO: Send Discord Message
await sendCourseCompletedEmail(user.email, user, event);
await prisma.participant.update({
where: {
id: participant.id,
},
data: {
finished: true,
completetionWorkflowFinished: true,
statusLog: {
push: {
event: "Event finished",
event: "Berechtigungen und Badges vergeben",
timestamp: new Date(),
user: "system",
},

View 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} />);
}

View 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 {
}
},
);
};

View File

@@ -11,12 +11,16 @@
"@repo/db": "*",
"@repo/typescript-config": "*",
"@types/node": "^22.13.5",
"@types/nodemailer": "^6.4.17",
"concurrently": "^9.1.2",
"typescript": "latest"
},
"dependencies": {
"@react-email/components": "^0.0.33",
"axios": "^1.7.9",
"cron": "^4.1.0",
"dotenv": "^16.4.7"
"dotenv": "^16.4.7",
"nodemailer": "^6.10.0",
"react": "^19.0.0"
}
}

View File

@@ -3,8 +3,9 @@
"compilerOptions": {
"outDir": "dist",
"allowImportingTsExtensions": false,
"baseUrl": "."
"baseUrl": ".",
"jsx": "react"
},
"include": ["**/*.ts", "./index.ts"],
"exclude": ["node_modules", "dist"]
}
}

View File

@@ -22,33 +22,37 @@ import { Switch } from "../../../../_components/ui/Switch";
interface AppointmentModalProps {
event?: Event;
ref: RefObject<HTMLDialogElement | null>;
participantModal: RefObject<HTMLDialogElement | null>;
appointmentsTableRef: React.RefObject<PaginatedTableRef>;
appointmentForm: UseFormReturn<
EventAppointmentOptionalDefaults,
any,
undefined
>;
participantForm: UseFormReturn<Participant, any, undefined>;
}
export const AppointmentModal = ({
event,
ref,
participantModal,
appointmentsTableRef,
appointmentForm,
participantForm,
}: AppointmentModalProps) => {
const { data: session } = useSession();
const participantTableRef = useRef<PaginatedTableRef>(null);
const participantForm = useForm<Participant>({
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
});
return (
<dialog ref={ref} className="modal">
<div className="modal-box">
<form method="dialog">
{/* 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>
</form>
@@ -57,6 +61,7 @@ export const AppointmentModal = ({
</h3>
<form
onSubmit={appointmentForm.handleSubmit(async (values) => {
console.log(values);
if (!event) return;
const createdAppointment = await upsertAppointment(values);
ref.current?.close();
@@ -75,8 +80,8 @@ export const AppointmentModal = ({
}}
/> */}
<div>
{appointmentForm.watch("id") && (
<PaginatedTable
hide={appointmentForm.watch("id") === undefined}
ref={participantTableRef}
columns={[
{
@@ -95,13 +100,13 @@ export const AppointmentModal = ({
<button
onClick={() => {
participantForm.reset(row.original);
participantModal.current?.showModal();
}}
className="btn btn-outline btn-sm"
>
anzeigen
</button>
{!row.original.attended &&
event?.hasPresenceEvents && (
{!row.original.attended && event?.hasPresenceEvents && (
<button
type="button"
onSubmit={() => {}}
@@ -150,56 +155,8 @@ export const AppointmentModal = ({
</div>
}
/>
)}
</div>
<div className="modal-action"></div>
</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>
</dialog>
);

View File

@@ -17,7 +17,7 @@ import {
prisma,
Prisma,
} 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 { useRef, useState } from "react";
import {
@@ -39,6 +39,7 @@ import { Select } from "../../../../_components/ui/Select";
import { useSession } from "next-auth/react";
import { MarkdownEditor } from "../../../../_components/ui/MDEditor";
import { AppointmentModal } from "./AppointmentModal";
import { ParticipantModal } from "./ParticipantModal";
export const Form = ({ event }: { event?: Event }) => {
const { data: session } = useSession();
@@ -53,17 +54,28 @@ export const Form = ({ event }: { event?: Event }) => {
presenterId: session?.user?.id,
},
});
const participantForm = useForm<Participant>({
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
});
const appointmentsTableRef = useRef<PaginatedTableRef>(null);
const [loading, setLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
const appointmentModal = useRef<HTMLDialogElement>(null);
const participantModal = useRef<HTMLDialogElement>(null);
return (
<>
<AppointmentModal
participantModal={participantModal}
participantForm={participantForm}
appointmentForm={appointmentForm}
ref={appointmentModal}
appointmentsTableRef={appointmentsTableRef}
event={event}
/>
<ParticipantModal
participantForm={participantForm}
ref={participantModal}
/>
<form
onSubmit={form.handleSubmit(async (values) => {
@@ -164,6 +176,8 @@ export const Form = ({ event }: { event?: Event }) => {
appointmentModal.current?.showModal();
appointmentForm.reset({
id: undefined,
eventId: event.id,
presenterId: session?.user?.id,
});
}}
>
@@ -238,6 +252,62 @@ export const Form = ({ event }: { event?: Event }) => {
</div>
</div>
) : 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-body ">
<div className="flex w-full gap-4">

View File

@@ -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>
);
};

View File

@@ -120,6 +120,7 @@ const ModalBtn = ({
<div className="flex items-center gap-2 justify-center">
<CalendarIcon />
{!!dates.length && (
// TODO: Prevent users from selecting an appointment that is full
<Select
form={selectAppointmentForm as any}
options={dates.map((date) => ({
@@ -180,10 +181,10 @@ const ModalBtn = ({
>
absagen
</button>
</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;
}) => {
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 (
<p className="py-4 flex items-center gap-2 justify-center">
<Clock10Icon className="text-error" />

View File

@@ -25,6 +25,7 @@ interface PaginatedTableProps<TData>
leftOfSearch?: React.ReactNode;
rightOfSearch?: React.ReactNode;
leftOfPagination?: React.ReactNode;
hide?: boolean;
ref?: Ref<PaginatedTableRef>;
}
@@ -39,6 +40,7 @@ export function PaginatedTable<TData>({
leftOfSearch,
rightOfSearch,
leftOfPagination,
hide,
...restProps
}: PaginatedTableProps<TData>) {
const [data, setData] = useState<TData[]>([]);
@@ -111,19 +113,23 @@ export function PaginatedTable<TData>({
)}
<div className="flex justify-center">{rightOfSearch}</div>
</div>
{!hide && (
<SortableTable
data={data}
prismaModel={prismaModel}
showEditButton={showEditButton}
{...restProps}
/>
)}
<div className="flex items-between">
{leftOfPagination}
{!hide && (
<Pagination
totalPages={Math.ceil(total / rowsPerPage)}
page={page}
setPage={setPage}
/>
)}
</div>
</div>
);

View File

@@ -23,10 +23,11 @@ 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);
prisma.user.update({
console.log("got moodle ID", moodleUser.id);
await prisma.user.update({
where: {
id: user.id,
},
@@ -35,8 +36,6 @@ export const GET = async (req: NextRequest) => {
},
});
if (user.moodleId) return;
const participatingEvents = await prisma.participant.findMany({
where: {
userId: user.id,

View File

@@ -13,6 +13,7 @@
"@next-auth/prisma-adapter": "^1.0.7",
"@repo/db": "*",
"@repo/ui": "*",
"@tanstack/react-query": "^5.67.2",
"@tanstack/react-table": "^8.20.6",
"@uiw/react-md-editor": "^4.0.5",
"axios": "^1.7.9",

View File

@@ -21,6 +21,17 @@ services:
- "8080:80"
depends_on:
- 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:
container_name: moodle_database
@@ -62,3 +73,5 @@ volumes:
moodle_data:
moodle_database:
moodle_moodledata:
redis_data:
driver: local

538
package-lock.json generated
View File

@@ -95,6 +95,7 @@
"@next-auth/prisma-adapter": "^1.0.7",
"@repo/db": "*",
"@repo/ui": "*",
"@tanstack/react-query": "^5.67.2",
"@tanstack/react-table": "^8.20.6",
"@uiw/react-md-editor": "^4.0.5",
"axios": "^1.7.9",
@@ -137,14 +138,18 @@
},
"apps/hub-server": {
"dependencies": {
"@react-email/components": "^0.0.33",
"axios": "^1.7.9",
"cron": "^4.1.0",
"dotenv": "^16.4.7"
"dotenv": "^16.4.7",
"nodemailer": "^6.10.0",
"react": "^19.0.0"
},
"devDependencies": {
"@repo/db": "*",
"@repo/typescript-config": "*",
"@types/node": "^22.13.5",
"@types/nodemailer": "^6.4.17",
"concurrently": "^9.1.2",
"typescript": "latest"
}
@@ -2096,6 +2101,286 @@
"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": {
"resolved": "packages/database",
"link": true
@@ -2124,6 +2409,19 @@
"integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==",
"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": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -2381,6 +2679,32 @@
"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": {
"version": "8.20.6",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz",
@@ -2655,6 +2979,16 @@
"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": {
"version": "4.0.2",
"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==",
"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": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -4751,6 +5094,61 @@
"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": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz",
@@ -6886,6 +7284,22 @@
"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": {
"version": "3.0.1",
"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"
}
},
"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": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -7987,6 +8420,15 @@
"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": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
@@ -8405,6 +8847,18 @@
"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": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -8413,6 +8867,18 @@
"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": {
"version": "3.0.2",
"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==",
"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": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/npm/-/npm-11.1.0.tgz",
@@ -12615,6 +13090,19 @@
"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": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz",
@@ -12674,6 +13162,15 @@
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -12857,7 +13354,6 @@
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"bin": {
"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": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -13013,6 +13518,7 @@
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -13126,6 +13632,21 @@
"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": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.0.tgz",
@@ -13784,6 +14305,18 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"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": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@@ -15625,6 +16158,7 @@
"react-dom": "^19.0.0"
},
"devDependencies": {
"@repo/db": "*",
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"@turbo/gen": "^1.12.4",

View File

@@ -22,7 +22,7 @@ model Participant {
finisherMoodleCurseCompleted Boolean @default(false)
attended Boolean @default(false)
appointmentCancelled Boolean @default(false)
finished Boolean @default(false)
completetionWorkflowFinished Boolean @default(false)
eventAppointmentId Int?
statusLog Json[] @default([])
eventId Int