removed event-chronjobs, used Events in hub-app insteand, added admin Btn to set Discord-User and run Event-completed-workflow. Fixed Bug of wrong participants-count in Event-Modal
This commit is contained in:
57
apps/discord-server/routes/helper.ts
Normal file
57
apps/discord-server/routes/helper.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { DISCORD_ROLES, Event, getPublicUser, Participant, prisma } from "@repo/db";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { changeMemberRoles, getMember } from "routes/member";
|
||||||
|
|
||||||
|
const router: Router = Router();
|
||||||
|
|
||||||
|
export const eventCompleted = (event: Event, participant?: Participant) => {
|
||||||
|
if (!participant) return false;
|
||||||
|
if (event.finisherMoodleCourseId && !participant.finisherMoodleCurseCompleted) return false;
|
||||||
|
if (event.hasPresenceEvents && !participant.attended) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
router.post("/set-standard-name", async (req, res) => {
|
||||||
|
const { memberId, userId } = req.body;
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
res.status(404).json({ error: "User not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const participant = await prisma.participant.findMany({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Event: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let eventRoles: string[] = [];
|
||||||
|
|
||||||
|
participant.forEach(async (p) => {
|
||||||
|
if (!p.Event.discordRoleId) return;
|
||||||
|
if (eventCompleted(p.Event, p)) {
|
||||||
|
await changeMemberRoles(memberId, [p.Event.discordRoleId], "remove");
|
||||||
|
} else {
|
||||||
|
await changeMemberRoles(memberId, [p.Event.discordRoleId], "add");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const publicUser = getPublicUser(user);
|
||||||
|
const member = await getMember(memberId);
|
||||||
|
|
||||||
|
await member.setNickname(`${publicUser.fullName} (${user.publicId})`);
|
||||||
|
const isPilot = user.permissions.includes("PILOT");
|
||||||
|
const isDispatcher = user.permissions.includes("DISPO");
|
||||||
|
|
||||||
|
await changeMemberRoles(memberId, [DISCORD_ROLES.PILOT], isPilot ? "add" : "remove");
|
||||||
|
await changeMemberRoles(memberId, [DISCORD_ROLES.DISPATCHER], isDispatcher ? "add" : "remove");
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -8,7 +8,7 @@ if (!GUILD_ID) {
|
|||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
const getMember = async (memberId: string) => {
|
export const getMember = async (memberId: string) => {
|
||||||
const guild = client.guilds.cache.get(GUILD_ID);
|
const guild = client.guilds.cache.get(GUILD_ID);
|
||||||
if (!guild) throw new Error("Guild not found");
|
if (!guild) throw new Error("Guild not found");
|
||||||
try {
|
try {
|
||||||
@@ -36,6 +36,27 @@ router.post("/rename", async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const changeMemberRoles = async (
|
||||||
|
memberId: string,
|
||||||
|
roleIds: string[],
|
||||||
|
action: "add" | "remove",
|
||||||
|
) => {
|
||||||
|
const member = await getMember(memberId);
|
||||||
|
|
||||||
|
const currentRoleIds = member.roles.cache.map((role) => role.id);
|
||||||
|
const filteredRoleIds =
|
||||||
|
action === "add"
|
||||||
|
? roleIds.filter((id: string) => !currentRoleIds.includes(id))
|
||||||
|
: roleIds.filter((id: string) => currentRoleIds.includes(id));
|
||||||
|
|
||||||
|
if (filteredRoleIds.length === 0) {
|
||||||
|
return { message: `No roles to ${action}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
await member.roles[action](filteredRoleIds);
|
||||||
|
return { message: `Roles ${action}ed successfully` };
|
||||||
|
};
|
||||||
|
|
||||||
const handleRoleChange = (action: "add" | "remove") => async (req: Request, res: Response) => {
|
const handleRoleChange = (action: "add" | "remove") => async (req: Request, res: Response) => {
|
||||||
const { roleIds, memberId } = req.body;
|
const { roleIds, memberId } = req.body;
|
||||||
if (!Array.isArray(roleIds) || !memberId) {
|
if (!Array.isArray(roleIds) || !memberId) {
|
||||||
@@ -43,26 +64,8 @@ const handleRoleChange = (action: "add" | "remove") => async (req: Request, res:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const member = await getMember(memberId);
|
const result = await changeMemberRoles(memberId, roleIds, action);
|
||||||
|
res.status(200).json(result);
|
||||||
const currentRoleIds = member.roles.cache.map((role) => role.id);
|
|
||||||
const filteredRoleIds =
|
|
||||||
action === "add"
|
|
||||||
? roleIds.filter((id: string) => !currentRoleIds.includes(id))
|
|
||||||
: roleIds.filter((id: string) => currentRoleIds.includes(id));
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Attempting to ${action} roles: ${filteredRoleIds.join(", ")} to member ${member.nickname || member.user.username}`,
|
|
||||||
);
|
|
||||||
// Option to skip if no roles to add/remove
|
|
||||||
if (filteredRoleIds.length === 0) {
|
|
||||||
console.log(`No roles to ${action}`);
|
|
||||||
res.status(200).json({ message: `No roles to ${action}` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await member.roles[action](roleIds);
|
|
||||||
res.status(200).json({ message: `Roles ${action}ed successfully` });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error ${action}ing roles:`, error);
|
console.error(`Error ${action}ing roles:`, error);
|
||||||
res.status(500).json({ error: `Failed to ${action} roles` });
|
res.status(500).json({ error: `Failed to ${action} roles` });
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import memberRouter from "./member";
|
import memberRouter from "./member";
|
||||||
|
import helperRouter from "./helper";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.use("/member", memberRouter);
|
router.use("/member", memberRouter);
|
||||||
|
router.use("/helper", helperRouter);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { getMoodleCourseCompletionStatus, getMoodleUserById } from "./moodle";
|
import { getMoodleCourseCompletionStatus, getMoodleUserById } from "./moodle";
|
||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
import { DISCORD_ROLES, ParticipantLog, prisma } from "@repo/db";
|
import { prisma } from "@repo/db";
|
||||||
import { sendCourseCompletedEmail } from "modules/mail";
|
|
||||||
import { handleParticipantFinished } from "modules/event";
|
import { handleParticipantFinished } from "modules/event";
|
||||||
import { eventCompleted } from "helper/events";
|
|
||||||
import { addRolesToMember, removeRolesFromMember } from "modules/discord";
|
|
||||||
import { JsonValueType } from "@repo/db/zod";
|
|
||||||
|
|
||||||
const syncMoodleIds = async () => {
|
const syncMoodleIds = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -61,6 +57,8 @@ const updateParticipantMoodleResults = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (quizzResult?.completionstatus?.completed === true) {
|
if (quizzResult?.completionstatus?.completed === true) {
|
||||||
|
await handleParticipantFinished(p.Event, p, p.User);
|
||||||
|
|
||||||
return prisma.participant.update({
|
return prisma.participant.update({
|
||||||
where: {
|
where: {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
@@ -81,127 +79,11 @@ const updateParticipantMoodleResults = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkFinishedParticipants = async () => {
|
|
||||||
const participantsPending = await prisma.participant.findMany({
|
|
||||||
where: {
|
|
||||||
completetionWorkflowFinished: false,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Event: true,
|
|
||||||
User: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
participantsPending.forEach(async (p) => {
|
|
||||||
if (!p.User) return;
|
|
||||||
const completed = eventCompleted(p.Event, p);
|
|
||||||
|
|
||||||
if (!completed) return;
|
|
||||||
console.log(
|
|
||||||
`User ${p.User.firstname} ${p.User.lastname} - ${p.User.publicId} finished event ${p.Event.name}`,
|
|
||||||
);
|
|
||||||
handleParticipantFinished(p.Event, p, p.User);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkUnfinishedParticipants = async () => {
|
|
||||||
const participantsPending = await prisma.participant.findMany({
|
|
||||||
where: {
|
|
||||||
completetionWorkflowFinished: false,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Event: true,
|
|
||||||
User: {
|
|
||||||
include: {
|
|
||||||
discordAccounts: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
participantsPending.forEach(async (p) => {
|
|
||||||
if (!p.User) return;
|
|
||||||
const completed = eventCompleted(p.Event, p);
|
|
||||||
|
|
||||||
if (completed) return;
|
|
||||||
|
|
||||||
if (!p.Event.discordRoleId) {
|
|
||||||
await prisma.participant.update({
|
|
||||||
where: {
|
|
||||||
id: p.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
inscriptionWorkflowCompleted: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
`User ${p.User.firstname} ${p.User.lastname} - ${p.User.publicId} did not finish event ${p.Event.name}`,
|
|
||||||
);
|
|
||||||
if (p.User.discordAccounts[0] && p.Event.discordRoleId) {
|
|
||||||
await addRolesToMember(p.User.discordAccounts[0].discordId, [p.Event.discordRoleId]);
|
|
||||||
await prisma.participant.update({
|
|
||||||
where: {
|
|
||||||
id: p.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
inscriptionWorkflowCompleted: true,
|
|
||||||
statusLog: {
|
|
||||||
push: {
|
|
||||||
event: "Discord-Rolle hinzugefügt",
|
|
||||||
timestamp: new Date(),
|
|
||||||
user: "system",
|
|
||||||
} as ParticipantLog as any,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkDiscordRoles = async () => {
|
|
||||||
const user = await prisma.user.findMany({
|
|
||||||
where: {
|
|
||||||
discordAccounts: {
|
|
||||||
some: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
discordAccounts: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const u of user) {
|
|
||||||
// Here ony member Roles regarding their rights are checked
|
|
||||||
if (!u.discordAccounts[0]) continue;
|
|
||||||
const discordAccount = u.discordAccounts[0];
|
|
||||||
|
|
||||||
// For Pilot
|
|
||||||
if (u.permissions.includes("PILOT")) {
|
|
||||||
await addRolesToMember(discordAccount.discordId, [DISCORD_ROLES.PILOT]); // ONLINE_PILOT
|
|
||||||
} else {
|
|
||||||
await removeRolesFromMember(discordAccount.discordId, [DISCORD_ROLES.PILOT]); // ONLINE_PILOT
|
|
||||||
}
|
|
||||||
// for Dispatcher
|
|
||||||
if (u.permissions.includes("DISPO")) {
|
|
||||||
await addRolesToMember(discordAccount.discordId, [DISCORD_ROLES.DISPATCHER]); // ONLINE_DISPATCHER
|
|
||||||
} else {
|
|
||||||
await removeRolesFromMember(discordAccount.discordId, [DISCORD_ROLES.DISPATCHER]); // ONLINE_DISPATCHER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
|
CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
|
||||||
CronJob.from({
|
CronJob.from({
|
||||||
cronTime: "*/1 * * * *",
|
cronTime: "*/1 * * * *",
|
||||||
onTick: async () => {
|
onTick: async () => {
|
||||||
await updateParticipantMoodleResults();
|
await updateParticipantMoodleResults();
|
||||||
await checkFinishedParticipants();
|
|
||||||
await checkUnfinishedParticipants();
|
|
||||||
},
|
},
|
||||||
start: true,
|
start: true,
|
||||||
});
|
});
|
||||||
CronJob.from({
|
|
||||||
cronTime: "0 * * * *",
|
|
||||||
onTick: checkDiscordRoles,
|
|
||||||
start: true,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -36,3 +36,19 @@ export const removeRolesFromMember = async (memberId: string, roleIds: string[])
|
|||||||
console.error("Error removing roles from member:", error);
|
console.error("Error removing roles from member:", error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
export const setStandardName = async ({
|
||||||
|
memberId,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
memberId: string;
|
||||||
|
userId: string;
|
||||||
|
}) => {
|
||||||
|
discordAxiosClient
|
||||||
|
.post("/helper/set-standard-name", {
|
||||||
|
memberId,
|
||||||
|
userId,
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error removing roles from member:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Event, Participant, prisma, User } from "@repo/db";
|
import { Event, Participant, prisma, User } from "@repo/db";
|
||||||
import { removeRolesFromMember } from "modules/discord";
|
import { addRolesToMember, removeRolesFromMember, setStandardName } from "modules/discord";
|
||||||
import { sendCourseCompletedEmail } from "modules/mail";
|
import { sendCourseCompletedEmail } from "modules/mail";
|
||||||
|
|
||||||
export const handleParticipantFinished = async (
|
export const handleParticipantFinished = async (
|
||||||
@@ -35,7 +35,10 @@ export const handleParticipantFinished = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (event.discordRoleId && discordAccount) {
|
if (event.discordRoleId && discordAccount) {
|
||||||
await removeRolesFromMember(discordAccount.discordId, [event.discordRoleId]);
|
await setStandardName({
|
||||||
|
memberId: discordAccount.discordId,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await sendCourseCompletedEmail(user.email, user, event);
|
await sendCourseCompletedEmail(user.email, user, event);
|
||||||
|
|
||||||
@@ -55,3 +58,18 @@ export const handleParticipantFinished = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleParticipantEnrolled = async (
|
||||||
|
event: Event,
|
||||||
|
participant: Participant,
|
||||||
|
user: User,
|
||||||
|
) => {
|
||||||
|
const discordAccount = await prisma.discordAccount.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (event.discordRoleId && discordAccount) {
|
||||||
|
await addRolesToMember(discordAccount.discordId, [event.discordRoleId]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
72
apps/hub-server/routes/event.ts
Normal file
72
apps/hub-server/routes/event.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { prisma } from "@repo/db";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { eventCompleted } from "helper/events";
|
||||||
|
import { handleParticipantEnrolled, handleParticipantFinished } from "modules/event";
|
||||||
|
|
||||||
|
const router: Router = Router();
|
||||||
|
|
||||||
|
router.post("/handle-participant-finished", async (req, res) => {
|
||||||
|
const { participantId } = req.body;
|
||||||
|
if (!participantId) {
|
||||||
|
res.status(400).json({ error: "Participant ID is required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const participant = await prisma.participant.findUnique({
|
||||||
|
where: {
|
||||||
|
id: typeof participantId == "string" ? parseInt(participantId) : participantId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Event: true,
|
||||||
|
User: {
|
||||||
|
include: {
|
||||||
|
discordAccounts: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!participant) {
|
||||||
|
res.status(404).json({ error: "Participant not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const completed = eventCompleted(participant.Event, participant);
|
||||||
|
if (!completed) {
|
||||||
|
res.status(400).json({ error: "Event not completed" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await handleParticipantFinished(participant.Event, participant, participant.User);
|
||||||
|
res.status(200).json({ message: "Participant finished successfully" });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/handle-participant-enrolled", async (req, res) => {
|
||||||
|
const { participantId } = req.body;
|
||||||
|
if (!participantId) {
|
||||||
|
res.status(400).json({ error: "Participant ID is required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const participant = await prisma.participant.findUnique({
|
||||||
|
where: {
|
||||||
|
id: typeof participantId == "string" ? parseInt(participantId) : participantId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Event: true,
|
||||||
|
User: {
|
||||||
|
include: {
|
||||||
|
discordAccounts: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!participant) {
|
||||||
|
res.status(404).json({ error: "Participant not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const completed = eventCompleted(participant.Event, participant);
|
||||||
|
if (completed) {
|
||||||
|
res.status(400).json({ error: "Event already completed" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await handleParticipantEnrolled(participant.Event, participant, participant.User);
|
||||||
|
res.status(200).json({ message: "Participant enrolled successfully" });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import mailRouter from "./mail";
|
import mailRouter from "./mail";
|
||||||
|
import eventRouter from "./event";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.use("/mail", mailRouter);
|
router.use("/mail", mailRouter);
|
||||||
|
router.use("/event", eventRouter);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -14,53 +14,32 @@ const page = async () => {
|
|||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
const events = await prisma.event.findMany({
|
const events = await prisma.event.findMany({
|
||||||
|
where: {
|
||||||
|
type: "OBLIGATED_COURSE",
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
appointments: {
|
|
||||||
where: {
|
|
||||||
appointmentDate: {
|
|
||||||
gte: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
participants: {
|
participants: {
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
appointments: {
|
||||||
});
|
include: {
|
||||||
const userAppointments = await prisma.eventAppointment.findMany({
|
Participants: {
|
||||||
where: {
|
where: {
|
||||||
Participants: {
|
appointmentCancelled: false,
|
||||||
some: {
|
},
|
||||||
userId: user.id,
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const appointments = await prisma.eventAppointment.findMany({
|
|
||||||
where: {
|
|
||||||
appointmentDate: {
|
|
||||||
gte: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Participants: {
|
|
||||||
where: {
|
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_count: {
|
|
||||||
select: {
|
|
||||||
Participants: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredEvents = events.filter((event) => {
|
const filteredEvents = events.filter((event) => {
|
||||||
if (eventCompleted(event, event.participants[0])) return false;
|
const userParticipant = event.participants.find(
|
||||||
|
(participant) => participant.userId === user.id,
|
||||||
|
);
|
||||||
|
if (eventCompleted(event, userParticipant)) return false;
|
||||||
if (event.type === "OBLIGATED_COURSE" && !eventCompleted(event, event.participants[0]))
|
if (event.type === "OBLIGATED_COURSE" && !eventCompleted(event, event.participants[0]))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
@@ -78,8 +57,10 @@ const page = async () => {
|
|||||||
{filteredEvents.map((event) => {
|
{filteredEvents.map((event) => {
|
||||||
return (
|
return (
|
||||||
<KursItem
|
<KursItem
|
||||||
appointments={appointments}
|
appointments={event.appointments}
|
||||||
selectedAppointments={userAppointments}
|
selectedAppointments={event.appointments.filter((a) =>
|
||||||
|
a.Participants.find((p) => p.userId == user.id),
|
||||||
|
)}
|
||||||
user={user}
|
user={user}
|
||||||
event={event}
|
event={event}
|
||||||
key={event.id}
|
key={event.id}
|
||||||
@@ -9,6 +9,7 @@ import { Button } from "../../../../_components/ui/Button";
|
|||||||
import { DateInput } from "../../../../_components/ui/DateInput";
|
import { DateInput } from "../../../../_components/ui/DateInput";
|
||||||
import { upsertParticipant } from "../../../events/actions";
|
import { upsertParticipant } from "../../../events/actions";
|
||||||
import { deleteAppoinement, upsertAppointment } from "../action";
|
import { deleteAppoinement, upsertAppointment } from "../action";
|
||||||
|
import { handleParticipantFinished } from "../../../../../helper/events";
|
||||||
|
|
||||||
interface AppointmentModalProps {
|
interface AppointmentModalProps {
|
||||||
event?: Event;
|
event?: Event;
|
||||||
@@ -127,6 +128,9 @@ export const AppointmentModal = ({
|
|||||||
attended: true,
|
attended: true,
|
||||||
appointmentCancelled: false,
|
appointmentCancelled: false,
|
||||||
});
|
});
|
||||||
|
if (!event.finisherMoodleCourseId) {
|
||||||
|
await handleParticipantFinished(row.original.id.toString());
|
||||||
|
}
|
||||||
participantTableRef.current?.refresh();
|
participantTableRef.current?.refresh();
|
||||||
}}
|
}}
|
||||||
className="btn btn-outline btn-info btn-sm"
|
className="btn btn-outline btn-info btn-sm"
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { UseFormReturn } from "react-hook-form";
|
|||||||
import { upsertParticipant } from "../../../events/actions";
|
import { upsertParticipant } from "../../../events/actions";
|
||||||
import { RefObject } from "react";
|
import { RefObject } from "react";
|
||||||
import { deleteParticipant } from "../action";
|
import { deleteParticipant } from "../action";
|
||||||
|
import { handleParticipantFinished } from "../../../../../helper/events";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
interface ParticipantModalProps {
|
interface ParticipantModalProps {
|
||||||
participantForm: UseFormReturn<Participant>;
|
participantForm: UseFormReturn<Participant>;
|
||||||
@@ -30,6 +33,27 @@ export const ParticipantModal = ({ participantForm, ref }: ParticipantModalProps
|
|||||||
})}
|
})}
|
||||||
className="space-y-1"
|
className="space-y-1"
|
||||||
>
|
>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
if (!participantForm.watch("id")) return;
|
||||||
|
|
||||||
|
const participant = participantForm.getValues();
|
||||||
|
await handleParticipantFinished(participant.id.toString()).catch((e) => {
|
||||||
|
const error = e as AxiosError;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data
|
||||||
|
) {
|
||||||
|
toast.error(`Fehler: ${error.response.data.error}`);
|
||||||
|
} else {
|
||||||
|
toast.error("Unbekannter Fehler beim ausführen des Workflows");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Event-abgeschlossen Workflow ausführen
|
||||||
|
</Button>
|
||||||
<Switch form={participantForm} name="attended" label="Termin Teilgenommen" />
|
<Switch form={participantForm} name="attended" label="Termin Teilgenommen" />
|
||||||
<Switch form={participantForm} name="appointmentCancelled" label="Termin abgesagt" />
|
<Switch form={participantForm} name="appointmentCancelled" label="Termin abgesagt" />
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
BADGES,
|
BADGES,
|
||||||
ConnectedAircraft,
|
ConnectedAircraft,
|
||||||
ConnectedDispatcher,
|
ConnectedDispatcher,
|
||||||
|
DiscordAccount,
|
||||||
PERMISSION,
|
PERMISSION,
|
||||||
Report,
|
Report,
|
||||||
Station,
|
Station,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
LockOpen1Icon,
|
LockOpen1Icon,
|
||||||
HobbyKnifeIcon,
|
HobbyKnifeIcon,
|
||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
|
DiscordLogoIcon,
|
||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import { Button } from "../../../../../_components/ui/Button";
|
import { Button } from "../../../../../_components/ui/Button";
|
||||||
import { Select } from "../../../../../_components/ui/Select";
|
import { Select } from "../../../../../_components/ui/Select";
|
||||||
@@ -34,6 +36,7 @@ import Link from "next/link";
|
|||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { Error } from "_components/Error";
|
import { Error } from "_components/Error";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
|
import { setStandardName } from "../../../../../../helper/discord";
|
||||||
|
|
||||||
interface ProfileFormProps {
|
interface ProfileFormProps {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -362,6 +365,7 @@ export const UserReports = ({ user }: { user: User }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface AdminFormProps {
|
interface AdminFormProps {
|
||||||
|
discordAccount?: DiscordAccount;
|
||||||
user: User;
|
user: User;
|
||||||
dispoTime: {
|
dispoTime: {
|
||||||
hours: number;
|
hours: number;
|
||||||
@@ -380,7 +384,13 @@ interface AdminFormProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdminForm = ({ user, dispoTime, pilotTime, reports }: AdminFormProps) => {
|
export const AdminForm = ({
|
||||||
|
user,
|
||||||
|
dispoTime,
|
||||||
|
pilotTime,
|
||||||
|
reports,
|
||||||
|
discordAccount,
|
||||||
|
}: AdminFormProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -444,6 +454,27 @@ export const AdminForm = ({ user, dispoTime, pilotTime, reports }: AdminFormProp
|
|||||||
<HobbyKnifeIcon /> User Entperren
|
<HobbyKnifeIcon /> User Entperren
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{discordAccount && (
|
||||||
|
<div className="tooltip flex-1" data-tip={`Name: ${discordAccount.username}`}>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await setStandardName({
|
||||||
|
memberId: discordAccount.discordId,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
toast.success("Standard Name wurde gesetzt!", {
|
||||||
|
style: {
|
||||||
|
background: "var(--color-base-100)",
|
||||||
|
color: "var(--color-base-content)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="btn-sm w-full btn-outline btn-info"
|
||||||
|
>
|
||||||
|
<DiscordLogoIcon /> Name und Berechtigungen setzen
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { PersonIcon } from "@radix-ui/react-icons";
|
import { PersonIcon } from "@radix-ui/react-icons";
|
||||||
import { prisma, User } from "@repo/db";
|
import { prisma } from "@repo/db";
|
||||||
import { AdminForm, ConnectionHistory, ProfileForm, UserReports } from "./_components/forms";
|
import { AdminForm, ConnectionHistory, ProfileForm, UserReports } from "./_components/forms";
|
||||||
import { Error } from "../../../../_components/Error";
|
import { Error } from "../../../../_components/Error";
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
const user: User | null = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: id,
|
id: id,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
discordAccounts: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
||||||
@@ -88,7 +91,6 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
|||||||
open: totalReportsOpen,
|
open: totalReportsOpen,
|
||||||
total60Days: totalReports60Days,
|
total60Days: totalReports60Days,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!user) return <Error statusCode={404} title="User not found" />;
|
if (!user) return <Error statusCode={404} title="User not found" />;
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
@@ -102,7 +104,13 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
|||||||
<ProfileForm user={user} />
|
<ProfileForm user={user} />
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
||||||
<AdminForm user={user} dispoTime={dispoTime} pilotTime={pilotTime} reports={reports} />
|
<AdminForm
|
||||||
|
user={user}
|
||||||
|
dispoTime={dispoTime}
|
||||||
|
pilotTime={pilotTime}
|
||||||
|
reports={reports}
|
||||||
|
discordAccount={user.discordAccounts[0]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
||||||
<UserReports user={user} />
|
<UserReports user={user} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { DrawingPinFilledIcon, EnterIcon } from "@radix-ui/react-icons";
|
import { DrawingPinFilledIcon } from "@radix-ui/react-icons";
|
||||||
import { Event, Participant, EventAppointment, User } from "@repo/db";
|
import { Event, Participant, EventAppointment, User } from "@repo/db";
|
||||||
import ModalBtn from "./modalBtn";
|
import ModalBtn from "./modalBtn";
|
||||||
import MDEditor from "@uiw/react-md-editor";
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
@@ -26,14 +26,10 @@ export const KursItem = ({
|
|||||||
<h2 className="card-title">{event.name}</h2>
|
<h2 className="card-title">{event.name}</h2>
|
||||||
<div className="absolute top-0 right-0 m-4">
|
<div className="absolute top-0 right-0 m-4">
|
||||||
{event.type === "COURSE" && (
|
{event.type === "COURSE" && (
|
||||||
<span className="badge badge-info badge-outline">
|
<span className="badge badge-info badge-outline">Zusatzqualifikation</span>
|
||||||
Zusatzqualifikation
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
{event.type === "OBLIGATED_COURSE" && (
|
{event.type === "OBLIGATED_COURSE" && (
|
||||||
<span className="badge badge-secondary badge-outline">
|
<span className="badge badge-secondary badge-outline">Verpflichtend</span>
|
||||||
Verpflichtend
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
@@ -65,10 +61,7 @@ export const KursItem = ({
|
|||||||
<b className="text-gray-600 text-left mr-2">Abzeichen:</b>
|
<b className="text-gray-600 text-left mr-2">Abzeichen:</b>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{event.requiredBadges.map((badge) => (
|
{event.requiredBadges.map((badge) => (
|
||||||
<div
|
<div className="badge badge-secondary badge-outline" key={badge}>
|
||||||
className="badge badge-secondary badge-outline"
|
|
||||||
key={badge}
|
|
||||||
>
|
|
||||||
{badge}
|
{badge}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { Select } from "../../../_components/ui/Select";
|
import { Select } from "../../../_components/ui/Select";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { eventCompleted } from "../../../../helper/events";
|
import { eventCompleted, handleParticipantEnrolled } from "../../../../helper/events";
|
||||||
|
|
||||||
interface ModalBtnProps {
|
interface ModalBtnProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -123,13 +123,12 @@ 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}
|
||||||
options={dates.map((date) => ({
|
options={dates.map((date) => ({
|
||||||
label: `${new Date(
|
label: `${new Date(
|
||||||
date.appointmentDate,
|
date.appointmentDate,
|
||||||
).toLocaleString()} - (${(date as any)._count.Participants}/${event.maxParticipants})`,
|
).toLocaleString()} - (${(date as any).Participants.length}/${event.maxParticipants})`,
|
||||||
value: date.id,
|
value: date.id,
|
||||||
}))}
|
}))}
|
||||||
name="eventAppointmentId"
|
name="eventAppointmentId"
|
||||||
@@ -226,12 +225,14 @@ const ModalBtn = ({
|
|||||||
const data = selectAppointmentForm.getValues();
|
const data = selectAppointmentForm.getValues();
|
||||||
if (!data.eventAppointmentId) return;
|
if (!data.eventAppointmentId) return;
|
||||||
|
|
||||||
await upsertParticipant({
|
const participant = await upsertParticipant({
|
||||||
...data,
|
...data,
|
||||||
enscriptionDate: new Date(),
|
enscriptionDate: new Date(),
|
||||||
statusLog: data.statusLog?.filter((log) => log !== null),
|
statusLog: data.statusLog?.filter((log) => log !== null),
|
||||||
appointmentCancelled: false,
|
appointmentCancelled: false,
|
||||||
});
|
});
|
||||||
|
await handleParticipantEnrolled(participant.id.toString());
|
||||||
|
|
||||||
router.refresh();
|
router.refresh();
|
||||||
closeModal();
|
closeModal();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ export default async function RootLayout({
|
|||||||
>
|
>
|
||||||
<div className="hero-overlay bg-opacity-30"></div>
|
<div className="hero-overlay bg-opacity-30"></div>
|
||||||
<div>
|
<div>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster
|
||||||
|
containerStyle={{ zIndex: "999999999" }}
|
||||||
|
position="top-center"
|
||||||
|
reverseOrder={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Card */}
|
{/* Card */}
|
||||||
<div className="hero-content text-neutral-content text-center w-full max-w-full h-full m-10">
|
<div className="hero-content text-neutral-content text-center w-full max-w-full h-full m-10">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Events from "./_components/Events";
|
import Events from "./_components/FeaturedEvents";
|
||||||
import { Stats } from "./_components/Stats";
|
import { Stats } from "./_components/Stats";
|
||||||
import { Badges } from "./_components/Badges";
|
import { Badges } from "./_components/Badges";
|
||||||
import { RecentFlights } from "(app)/_components/RecentFlights";
|
import { RecentFlights } from "(app)/_components/RecentFlights";
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import {
|
import { ButtonHTMLAttributes, DetailedHTMLProps, useEffect, useState } from "react";
|
||||||
ButtonHTMLAttributes,
|
|
||||||
DetailedHTMLProps,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { cn } from "../../../helper/cn";
|
import { cn } from "../../../helper/cn";
|
||||||
|
|
||||||
export const Button = ({
|
export const Button = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
...props
|
...props
|
||||||
}: DetailedHTMLProps<
|
}: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> & {
|
||||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
||||||
HTMLButtonElement
|
|
||||||
> & {
|
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const [isLoadingState, setIsLoadingState] = useState(isLoading);
|
const [isLoadingState, setIsLoadingState] = useState(isLoading);
|
||||||
@@ -34,9 +26,7 @@ export const Button = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isLoadingState && (
|
{isLoadingState && <span className="loading loading-spinner loading-sm"></span>}
|
||||||
<span className="loading loading-spinner loading-sm"></span>
|
|
||||||
)}
|
|
||||||
{props.children as any}
|
{props.children as any}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import axios, { AxiosError } from "axios";
|
import axios from "axios";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { DISCORD_ROLES, DiscordAccount, getPublicUser, prisma, PrismaClient } from "@repo/db";
|
import { DiscordAccount, prisma } from "@repo/db";
|
||||||
import { getServerSession } from "../auth/[...nextauth]/auth";
|
import { getServerSession } from "../auth/[...nextauth]/auth";
|
||||||
import { addRolesToMember, removeRolesFromMember, renameMember } from "../../../helper/discord";
|
import { setStandardName } from "../../../helper/discord";
|
||||||
|
|
||||||
export const GET = async (req: NextRequest) => {
|
export const GET = async (req: NextRequest) => {
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
const code = req.nextUrl.searchParams.get("code");
|
const code = req.nextUrl.searchParams.get("code");
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return NextResponse.redirect(`${process.env.NEXTAUTH_URL}/login`);
|
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_HUB_URL}/login`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -72,17 +72,10 @@ export const GET = async (req: NextRequest) => {
|
|||||||
where: { id: session.user.id },
|
where: { id: session.user.id },
|
||||||
});
|
});
|
||||||
if (user) {
|
if (user) {
|
||||||
await renameMember(discordUser.id, `${getPublicUser(user).fullName} - ${user?.publicId}`);
|
await setStandardName({
|
||||||
}
|
memberId: discordUser.id,
|
||||||
if (user?.permissions.includes("PILOT")) {
|
userId: user.id,
|
||||||
await addRolesToMember(discordUser.id, [DISCORD_ROLES.PILOT]);
|
});
|
||||||
} else {
|
|
||||||
await removeRolesFromMember(discordUser.id, [DISCORD_ROLES.PILOT]);
|
|
||||||
}
|
|
||||||
if (user?.permissions.includes("DISPO")) {
|
|
||||||
await addRolesToMember(discordUser.id, [DISCORD_ROLES.ONLINE_DISPATCHER]);
|
|
||||||
} else {
|
|
||||||
await removeRolesFromMember(discordUser.id, [DISCORD_ROLES.PILOT]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_HUB_URL}/settings`);
|
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_HUB_URL}/settings`);
|
||||||
|
|||||||
@@ -37,3 +37,20 @@ export const removeRolesFromMember = async (memberId: string, roleIds: string[])
|
|||||||
console.error("Error removing roles from member:", error);
|
console.error("Error removing roles from member:", error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setStandardName = async ({
|
||||||
|
memberId,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
memberId: string;
|
||||||
|
userId: string;
|
||||||
|
}) => {
|
||||||
|
discordAxiosClient
|
||||||
|
.post("/helper/set-standard-name", {
|
||||||
|
memberId,
|
||||||
|
userId,
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error removing roles from member:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Event, Participant } from "@repo/db";
|
import { Event, Participant } from "@repo/db";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export const eventCompleted = (event: Event, participant?: Participant) => {
|
export const eventCompleted = (event: Event, participant?: Participant) => {
|
||||||
if (!participant) return false;
|
if (!participant) return false;
|
||||||
@@ -6,3 +7,13 @@ export const eventCompleted = (event: Event, participant?: Participant) => {
|
|||||||
if (event.hasPresenceEvents && !participant.attended) return false;
|
if (event.hasPresenceEvents && !participant.attended) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleParticipantFinished = async (participantId: string) =>
|
||||||
|
axios.post(`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/event/handle-participant-finished`, {
|
||||||
|
participantId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const handleParticipantEnrolled = async (participantId: string) =>
|
||||||
|
axios.post(`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/event/handle-participant-enrolled`, {
|
||||||
|
participantId,
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user