From 048a590e74eac3447372d8abd66025ad355ebd7e Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Mon, 27 Apr 2026 19:01:40 +0200
Subject: [PATCH 01/42] feat: unified upload system
---
backend/server.ts | 2 +-
.../src/controllers/im_export.controller.ts | 68 +++-
backend/src/controllers/news.controller.ts | 105 ++++--
backend/src/middlewares/multer.middleware.ts | 85 +++--
backend/src/routes/im_export.routes.ts | 13 +-
backend/src/routes/news.routes.ts | 6 +-
backend/src/utils/responses.ts | 13 +-
backend/src/utils/uploadDocuments.ts | 84 +++++
.../components/Admin/adminExportImport.tsx | 250 +------------
.../src/components/Admin/adminFileImport.tsx | 331 ++++++++++++++++++
frontend/src/components/Admin/adminNews.tsx | 153 ++++----
.../components/Plannings/planningSection.tsx | 4 +-
.../components/WEI_SDI_Food/foodSection.tsx | 2 +-
frontend/src/components/news/newsSection.tsx | 2 +-
frontend/src/interfaces/import.interface.ts | 5 +
.../services/requests/im_export.service.ts | 26 +-
.../src/services/requests/news.service.ts | 31 +-
17 files changed, 779 insertions(+), 401 deletions(-)
create mode 100644 backend/src/utils/uploadDocuments.ts
create mode 100644 frontend/src/components/Admin/adminFileImport.tsx
create mode 100644 frontend/src/interfaces/import.interface.ts
diff --git a/backend/server.ts b/backend/server.ts
index f6b425b..d691fef 100644
--- a/backend/server.ts
+++ b/backend/server.ts
@@ -62,7 +62,7 @@ async function startServer() {
app.use('/api/discord', authenticateUser, discordRoutes);
app.use('/api/tent', authenticateUser, tentRoutes);
app.use('/api/bus', authenticateUser, busRoutes);
- app.use("/api/uploads/imgnews", express.static(path.join(__dirname, "/uploads/imgnews")));
+ app.use("/api/uploads/news", express.static(path.join(__dirname, "/uploads/news")));
app.use("/api/uploads/foodmenu", express.static(path.join(__dirname, "/uploads/foodmenu")));
app.use("/api/uploads/plannings", express.static(path.join(__dirname, "/uploads/plannings")));
app.use("/api/exports/bus", express.static(path.join(__dirname, "/exports/bus")));
diff --git a/backend/src/controllers/im_export.controller.ts b/backend/src/controllers/im_export.controller.ts
index 3e5978f..60ae264 100644
--- a/backend/src/controllers/im_export.controller.ts
+++ b/backend/src/controllers/im_export.controller.ts
@@ -8,6 +8,7 @@ import * as team_service from "../services/team.service";
import * as user_service from '../services/user.service';
import { Error, Ok } from "../utils/responses";
import { spreadsheet_id } from "../utils/secret";
+import { getLatestUploadedDocument, isSafeUploadSegment, removeUploadedDocuments, toUploadedDocumentStatus } from "../utils/uploadDocuments";
export const exportAllDataToSheets = async (req: Request, res: Response) => {
try {
@@ -108,6 +109,7 @@ export const updateFoodMenu = async (req: Request, res: Response) => {
const file = req.file;
try {
+
// Supprimer l'ancien Menu si un nouveau est uploadé
if (file) {
const targetDir = path.join(__dirname, "../../foodmenu");
@@ -116,6 +118,7 @@ export const updateFoodMenu = async (req: Request, res: Response) => {
fs.rmSync(targetDir, { recursive: true, force: true });
fs.mkdirSync(targetDir);
}
+
}
Ok(res, { msg: "Menu mis à jour avec succès" });
@@ -127,19 +130,7 @@ export const updateFoodMenu = async (req: Request, res: Response) => {
};
export const updatePlannings = async (req: Request, res: Response) => {
- const file = req.file;
-
try {
- // Supprimer l'ancien Planning si un nouveau est uploadé
- if (file) {
- const targetDir = path.join(__dirname, "../../plannings");
-
- if (fs.existsSync(targetDir)) {
- fs.rmSync(targetDir, { recursive: true, force: true });
- fs.mkdirSync(targetDir);
- }
- }
-
Ok(res, { msg: "Planning mis à jour avec succès" });
return;
} catch (err) {
@@ -157,3 +148,56 @@ export const exportUsersCSV = async (req: Request, res: Response) => {
Error(res, { msg: "Erreur lors de l'export CSV" });
}
};
+
+export const getUploadedDocumentStatus = async (req: Request, res: Response) => {
+ const { category, item } = req.params;
+
+ if (!isSafeUploadSegment(category) || !isSafeUploadSegment(item)) {
+ Error(res, { msg: "Paramètres invalides" });
+ return;
+ }
+
+ try {
+ const latestDocument = await getLatestUploadedDocument(category, item);
+
+ Ok(res, {
+ data: toUploadedDocumentStatus(category, latestDocument),
+ });
+ } catch (err: any) {
+ if (err?.code === "ENOENT") {
+ Ok(res, {
+ data: toUploadedDocumentStatus(category, null),
+ });
+ return;
+ }
+
+ console.error(err);
+ Error(res, { msg: "Erreur lors de la vérification du document" });
+ }
+};
+
+export const deleteDocument = async (req: Request, res: Response) => {
+ const { category, item } = req.params;
+
+ if (!isSafeUploadSegment(category) || !isSafeUploadSegment(item)) {
+ Error(res, { msg: "Paramètres invalides" });
+ return;
+ }
+
+ try {
+ const deletedCount = await removeUploadedDocuments(category, item);
+
+ if (deletedCount === 0) {
+ return Ok(res, { msg: "Aucun document à supprimer" });
+ }
+
+ Ok(res, {});
+ } catch (err: any) {
+ if (err?.code === "ENOENT") {
+ Ok(res, {});
+ return;
+ }
+
+ Error(res, { msg: "Erreur lors de la vérification du document" });
+ }
+};
diff --git a/backend/src/controllers/news.controller.ts b/backend/src/controllers/news.controller.ts
index a910932..0b97ac2 100644
--- a/backend/src/controllers/news.controller.ts
+++ b/backend/src/controllers/news.controller.ts
@@ -7,19 +7,72 @@ import * as user_service from '../services/user.service';
import * as template from "../utils/emailtemplates";
import { Error, Ok } from "../utils/responses";
+const toStoredUploadPath = (imageUrl: string) => {
+ if (!imageUrl) {
+ return null;
+ }
+
+ let normalized = imageUrl.trim();
+ if (!normalized) {
+ return null;
+ }
+
+ // Accept absolute URLs and keep only the pathname part.
+ if (normalized.startsWith("http://") || normalized.startsWith("https://")) {
+ try {
+ normalized = new URL(normalized).pathname;
+ } catch {
+ return null;
+ }
+ }
+
+ if (normalized.startsWith("/api/")) {
+ normalized = normalized.slice(4);
+ }
+
+ if (!normalized.startsWith("/uploads/")) {
+ return null;
+ }
+
+ return normalized;
+};
+
+const resolveStoredImagePath = (imageUrl: string) => {
+ const storedPath = toStoredUploadPath(imageUrl);
+ if (!storedPath) {
+ return null;
+ }
+
+ return path.resolve(process.cwd(), storedPath.replace(/^\//, ""));
+};
+
+const deleteImageIfExists = (imageUrl: string) => {
+ const imagePath = resolveStoredImagePath(imageUrl);
+ if (!imagePath) {
+ return;
+ }
+
+ if (fs.existsSync(imagePath)) {
+ fs.unlinkSync(imagePath);
+ }
+};
+
export const createNews = async (req: Request, res: Response) => {
- const { title, description, type, published, target } = req.body;
+ const { title, description, type, published, target, image_url } = req.body;
const file = req.file;
try {
- const image_url = file ? `/api/uploads/imgnews/${file.filename}` : undefined;
+ const resolvedImageUrl = file
+ ? `/uploads/news/${file.filename}`
+ : image_url;
+
const news = await news_service.createNews(
title,
description,
type,
- published,
+ published === true || published === "true",
target,
- image_url);
+ resolvedImageUrl);
Ok(res, { msg: "Actu créée avec succès", data: news });
} catch (err) {
console.error(err);
@@ -103,41 +156,53 @@ export const deleteNews = async (req: Request, res: Response) => {
try {
const existing = await news_service.getNewsById(Number(newsId));
if (existing?.image_url) {
- const imagePath = path.join(__dirname, "../../", existing.image_url);
- if (fs.existsSync(imagePath)) {
- fs.unlinkSync(imagePath);
- }
+ deleteImageIfExists(existing.image_url);
}
+
await news_service.deleteNews(Number(newsId));
Ok(res, { msg: "Actus supprimée avec succès !" });
- ;
- } catch {
+ } catch (err) {
+ console.error(err);
Error(res, { msg: "Erreur lors de la suppression de l'actus" });
}
};
export const updateNews = async (req: Request, res: Response) => {
- const { id, title, description, type, target } = req.body;
+ const { id, title, description, type, target, image_url } = req.body;
const file = req.file;
- const image_url = file ? `/api/uploads/imgnews/${file.filename}` : undefined;
+ const hasImageUrlField = Object.prototype.hasOwnProperty.call(req.body, "image_url");
+ const resolvedImageUrl = file
+ ? `/uploads/news/${file.filename}`
+ : hasImageUrlField
+ ? (image_url ?? null)
+ : undefined;
try {
const existing = await news_service.getNewsById(Number(id));
if (!existing) {
Error(res, { msg: "Actu introuvable" });
+ return;
}
- // Supprimer l'ancienne image si une nouvelle est uploadée
- if (file && existing.image_url) {
- const oldPath = path.join(__dirname, "../../", existing.image_url);
- if (fs.existsSync(oldPath)) {
- fs.unlinkSync(oldPath);
- }
+ const shouldReplaceImage = typeof resolvedImageUrl === "string";
+ const shouldRemoveImage = resolvedImageUrl === null;
+
+ // Supprimer l'ancienne image si elle est remplacée ou explicitement supprimée.
+ if (existing.image_url && ((shouldReplaceImage && existing.image_url !== resolvedImageUrl) || shouldRemoveImage)) {
+ deleteImageIfExists(existing.image_url);
}
- const updates: any = { title, description, type, target };
- if (image_url) updates.image_url = image_url;
+ const updates: {
+ title: string;
+ description: string;
+ type: string;
+ target: string;
+ image_url?: string | null;
+ } = { title, description, type, target };
+ if (resolvedImageUrl !== undefined) {
+ updates.image_url = resolvedImageUrl;
+ }
const updated = await news_service.updateNews(Number(id), updates);
diff --git a/backend/src/middlewares/multer.middleware.ts b/backend/src/middlewares/multer.middleware.ts
index 1ce4a37..6f4baac 100644
--- a/backend/src/middlewares/multer.middleware.ts
+++ b/backend/src/middlewares/multer.middleware.ts
@@ -3,13 +3,29 @@ import fs from "fs/promises";
import multer from "multer";
import path from "path";
import { Error } from "../utils/responses";
+import { isSafeUploadSegment, removeUploadedDocuments } from "../utils/uploadDocuments";
-export const createUploadMiddleware = (
- relativeUploadDir: string,
- modifiedName: boolean = true
-) => {
- const uploadPath = path.resolve(process.cwd(), relativeUploadDir);
+export enum MIMEType {
+ PDF = "application/pdf",
+ PNG = "image/png",
+ JPEG = "image/jpeg",
+}
+const acceptedMIMETypesByItem: Record> = {
+ foodmenu: {
+ menu: [MIMEType.PDF],
+ },
+ news: {},
+ plannings: {
+ tc: [MIMEType.PDF],
+ bacheloria: [MIMEType.PDF],
+ fise: [MIMEType.PDF],
+ fisea: [MIMEType.PDF],
+ master: [MIMEType.PDF],
+ },
+};
+
+export const createUploadMiddleware = () => {
// On stocke d'abord en mémoire pour vérifier le type réel
const storage = multer.memoryStorage();
@@ -24,46 +40,73 @@ export const createUploadMiddleware = (
const verifyAndSave = async (
req: Request,
res: Response,
- next: NextFunction
+ next: NextFunction,
) => {
try {
if (!req.file) return Error(res, { msg: "Aucun fichier reçu" });
+
+ const category = req.params.category;
+ const item = req.params.item;
+
+ if (!category || !item) {
+ return Error(res, { msg: "Catégorie ou item manquant" });
+ }
+
+ if (!isSafeUploadSegment(category) || !isSafeUploadSegment(item)) {
+ return Error(res, { msg: "Paramètres invalides" });
+ }
+
const { originalname, buffer } = req.file;
// Vérif du vrai type
const { fileTypeFromBuffer } = await import("file-type");
const detected = await fileTypeFromBuffer(buffer);
- console.log(
- `[UPLOAD] Type détecté: ${detected?.mime || "inconnu"}`
- );
- const isImage = detected?.mime?.startsWith("image/");
- const isPDF = detected?.mime === "application/pdf";
+ const detectedMime = detected?.mime as MIMEType | undefined;
+
+ const acceptedTypes = category === "news" ? [MIMEType.PNG, MIMEType.JPEG] : acceptedMIMETypesByItem[category]?.[item];
+
- if (!isImage && !isPDF) {
- Error(res, { msg: "Seules les images et les PDF sont autorisés" });
+
+ if (!acceptedTypes) {
+ return Error(res, { msg: "Catégorie ou item inconnu" });
+ }
+
+ const isAccepted = !!detectedMime && acceptedTypes.includes(detectedMime);
+
+ if (!isAccepted) {
+ Error(res, { msg: "Type de fichier non autorisé" });
return;
}
+ const uploadPath = path.resolve(process.cwd(), "uploads", category);
+
// Création dossier si nécessaire
await fs.mkdir(uploadPath, { recursive: true });
+ if (category === "news") {
+ if (!/^\d+$/.test(item)) {
+ return Error(res, { msg: "Le nom du fichier doit être composé uniquement de chiffres." });
+ }
+
+ const currentEntries = await fs.readdir(uploadPath);
+ if (currentEntries.length >= 100) {
+ return Error(res, { msg: "Le nombre maximal d'images d'actualités a été atteind." });
+ }
+ }
+
+ // Supprime tout document existant avec le même basename, quelle que soit l'extension.
+ await removeUploadedDocuments(category, item);
+
const ext = path.extname(originalname);
- const baseName = path.basename(originalname, ext);
- const timestamp = Date.now();
- const finalName = modifiedName
- ? `${baseName}-${timestamp}${ext}`
- : originalname;
+ const finalName = `${item}${ext}`;
const finalPath = path.join(uploadPath, finalName);
// Sauvegarde du fichier sur disque
await fs.writeFile(finalPath, buffer);
- // On rajoute le chemin pour les middlewares suivants
- (req as any).savedFilePath = finalPath;
-
next();
} catch (err) {
next(err);
diff --git a/backend/src/routes/im_export.routes.ts b/backend/src/routes/im_export.routes.ts
index 8d73cbe..38077cd 100644
--- a/backend/src/routes/im_export.routes.ts
+++ b/backend/src/routes/im_export.routes.ts
@@ -3,13 +3,18 @@ import * as imexportController from '../controllers/im_export.controller';
import { createUploadMiddleware } from "../middlewares/multer.middleware";
import { checkRole } from '../middlewares/user.middleware';
-const uploadFoodMenu = createUploadMiddleware("uploads/foodmenu/", false);
-const uploadPlannings = createUploadMiddleware("uploads/plannings/", false);
+const uploadMiddleware = createUploadMiddleware();
const imexportRouter = express.Router();
-imexportRouter.post('/admin/foodimport', checkRole("Admin", []), uploadFoodMenu.multerUpload.single("foodFile"), uploadFoodMenu.verifyAndSave, imexportController.updateFoodMenu);
-imexportRouter.post('/admin/plannings', checkRole("Admin", []), uploadPlannings.multerUpload.single("planningFile"), uploadPlannings.verifyAndSave, imexportController.updatePlannings);
+imexportRouter.post(
+ '/admin/import/:category/:item', checkRole("Admin", []),
+ uploadMiddleware.multerUpload.single("file"),
+ uploadMiddleware.verifyAndSave,
+ imexportController.updateFoodMenu);
+
imexportRouter.post('/admin/exportgsheet', checkRole("Admin", []), imexportController.exportAllDataToSheets);
imexportRouter.get('/admin/exportbus', checkRole("Admin", []), imexportController.exportUsersCSV);
+imexportRouter.get('/admin/document/:category/:item', checkRole("Admin", []), imexportController.getUploadedDocumentStatus);
+imexportRouter.delete('/admin/document/:category/:item', checkRole("Admin", []), imexportController.deleteDocument);
export default imexportRouter;
diff --git a/backend/src/routes/news.routes.ts b/backend/src/routes/news.routes.ts
index cc21fe2..7b74e4b 100644
--- a/backend/src/routes/news.routes.ts
+++ b/backend/src/routes/news.routes.ts
@@ -1,14 +1,12 @@
import express from "express";
import * as newsController from "../controllers/news.controller";
-import { createUploadMiddleware } from "../middlewares/multer.middleware";
import { checkRole } from "../middlewares/user.middleware";
-const uploadImgNews = createUploadMiddleware("uploads/imgnews/", true);
const newsRouter = express.Router();
//Admin routes
-newsRouter.post("/admin/createnews", checkRole("Admin", ["Communication"]), uploadImgNews.multerUpload.single("file"), uploadImgNews.verifyAndSave, newsController.createNews);
-newsRouter.post("/admin/updatenews", checkRole("Admin", ["Communication"]), uploadImgNews.multerUpload.single("file"), uploadImgNews.verifyAndSave, newsController.updateNews);
+newsRouter.post("/admin/createnews", checkRole("Admin", ["Communication"]), newsController.createNews);
+newsRouter.post("/admin/updatenews", checkRole("Admin", ["Communication"]), newsController.updateNews);
newsRouter.get("/admin/all", checkRole("Admin", ["Communication"]), newsController.listAllNews);
newsRouter.post("/admin/publish", checkRole("Admin", ["Communication"]), newsController.publishNews);
newsRouter.delete("/admin/deletenews", checkRole("Admin", ["Communication"]), newsController.deleteNews);
diff --git a/backend/src/utils/responses.ts b/backend/src/utils/responses.ts
index 1e6931b..acc1ff8 100644
--- a/backend/src/utils/responses.ts
+++ b/backend/src/utils/responses.ts
@@ -28,6 +28,12 @@ export const Unauthorized = (res: Response, details: { data?: any, msg?: string
res.status(Code.UNAUTHORIZED).json(new HttpResponse(Code.UNAUTHORIZED, msg, details.data));
};
+export const Forbidden = (res: Response, details: { data?: any, msg?: string }) => {
+ const msg = details.msg || 'Forbidden';
+ res.status(Code.FORBIDDEN).json(new HttpResponse(Code.FORBIDDEN, msg, details.data));
+};
+
+
export const Conflict = (res: Response, details: { data?: any, msg?: string }) => {
const msg = details.msg || "Conflict";
res.status(Code.CONFLICT).json(new HttpResponse(Code.CONFLICT, msg, details.data));
@@ -40,11 +46,12 @@ export const Teapot = (res: Response, details: { data?: any, msg?: string }) =>
export enum Code {
OK = 200,
+ CREATED = 201,
ACCEPTED = 202,
- NOT_FOUND = 404,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
- CREATED = 201,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
CONFLICT = 409,
IM_A_TEAPOT = 418,
ISE = 500
@@ -76,6 +83,8 @@ export class HttpResponse {
return 'INTERNAL_SERVER_ERROR';
case Code.UNAUTHORIZED:
return 'UNAUTHORIZED';
+ case Code.FORBIDDEN:
+ return 'FORBIDDEN';
case Code.CONFLICT:
return 'CONFLICT';
case Code.IM_A_TEAPOT:
diff --git a/backend/src/utils/uploadDocuments.ts b/backend/src/utils/uploadDocuments.ts
new file mode 100644
index 0000000..91d61dd
--- /dev/null
+++ b/backend/src/utils/uploadDocuments.ts
@@ -0,0 +1,84 @@
+import fs from "fs/promises";
+import path from "path";
+
+export type UploadedDocument = {
+ name: string;
+ fullPath: string;
+ extension: string | null;
+ mtimeMs: number;
+};
+
+export type UploadedDocumentStatus = {
+ exists: boolean;
+ extension: string | null;
+ fileName: string | null;
+ relativePath: string | null;
+};
+
+export const isSafeUploadSegment = (value: string) => /^[a-zA-Z0-9_-]+$/.test(value);
+
+export const getUploadDirectory = (category: string) => path.resolve(process.cwd(), "uploads", category);
+
+export const findUploadedDocuments = async (category: string, item: string): Promise => {
+ const uploadsDir = getUploadDirectory(category);
+
+ const entries = await fs.readdir(uploadsDir);
+ const candidates = entries.filter((name) => path.parse(name).name === item);
+
+ return Promise.all(
+ candidates.map(async (name) => {
+ const fullPath = path.join(uploadsDir, name);
+ const stats = await fs.stat(fullPath);
+
+ return {
+ name,
+ fullPath,
+ extension: path.extname(name).replace(/^\./, "").toLowerCase() || null,
+ mtimeMs: stats.mtimeMs,
+ };
+ })
+ );
+};
+
+export const getLatestUploadedDocument = async (
+ category: string,
+ item: string,
+): Promise => {
+ const documents = await findUploadedDocuments(category, item);
+
+ if (documents.length === 0) {
+ return null;
+ }
+
+ documents.sort((a, b) => b.mtimeMs - a.mtimeMs);
+ return documents[0];
+};
+
+export const removeUploadedDocuments = async (category: string, item: string): Promise => {
+ const documents = await findUploadedDocuments(category, item);
+
+ await Promise.all(documents.map((document) => fs.unlink(document.fullPath)));
+
+ return documents.length;
+};
+
+export const toUploadedDocumentStatus = (
+ category: string,
+ document: UploadedDocument | null,
+): UploadedDocumentStatus => {
+ if (!document) {
+ return {
+ exists: false,
+ extension: null,
+ fileName: null,
+ relativePath: null,
+ };
+ }
+
+ return {
+ exists: true,
+ extension: document.extension,
+ fileName: document.name,
+ relativePath: `/uploads/${category}/${document.name}`,
+ };
+};
diff --git a/frontend/src/components/Admin/adminExportImport.tsx b/frontend/src/components/Admin/adminExportImport.tsx
index 883b8ab..009086b 100644
--- a/frontend/src/components/Admin/adminExportImport.tsx
+++ b/frontend/src/components/Admin/adminExportImport.tsx
@@ -1,9 +1,10 @@
-import { FileText } from "lucide-react";
-import { type ChangeEvent, useState } from "react";
+import { useState } from "react";
-import { exportBus, exportDb, importFoodMenu, importPlannings } from "../../services/requests/im_export.service";
+import { exportBus, exportDb } from "../../services/requests/im_export.service";
import { Button } from "../ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
+import { AdminFileImport } from "./adminFileImport";
+
export const AdminExportConnect = () => {
const [loading, setLoading] = useState<{ db: boolean; bus: boolean }>({
@@ -101,106 +102,16 @@ export const AdminExportConnect = () => {
export const AdminImportFoodMenu = () => {
- const [menu, setMenu] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const [message, setMessage] = useState("");
-
- const handleFileChange = (e: ChangeEvent) => {
- setError(null);
- setMessage("");
- if (e.target.files && e.target.files.length > 0) {
- const selected = e.target.files[0];
- if (selected.type !== "application/pdf") {
- setError("Seuls les fichiers PDF sont autorisés");
- setMenu(null);
- } else {
- setMenu(selected);
- }
- }
- };
-
- const handleImport = async () => {
- if (!menu) {
- setError("Veuillez sélectionner un fichier PDF avant d'importer.");
- return;
- }
-
- setLoading(true);
- setError(null);
- setMessage("");
-
- try {
- const formData = new FormData();
- formData.append("foodFile", menu);
-
- const response = await importFoodMenu(formData);
- setMessage(response.message || "Importation réussie !");
- } catch (err) {
- console.error("Erreur lors de l'importation du menu", err);
- setError("Une erreur est survenue pendant l'importation.");
- } finally {
- setLoading(false);
- }
- };
return (
- Importer le menu au format PDF
+ Importer le menu
-
- {/* Rappel des règles de nommage */}
-
- ⚠️ Le fichier doit être nommé FoodMenu.pdf
-
-
-
- {/* Input fichier masqué */}
-
-
-
- Choisir un fichier
-
-
- {/* Affiche le nom du fichier sélectionné */}
- {menu && (
-
-
- {menu.name}
-
- )}
-
- {/* Bouton importer */}
-
- {loading ? "Import en cours..." : "Importer le PDF"}
-
-
-
- {error && (
- {error}
- )}
- {message && (
-
- {message}
-
- )}
+
);
@@ -208,154 +119,19 @@ export const AdminImportFoodMenu = () => {
export const AdminImportPlannings = () => {
- const [planning, setPlanning] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const [message, setMessage] = useState("");
- const [selectedPlanning, setSelectedPlanning] = useState("");
-
- const handleFileChange = (e: ChangeEvent) => {
- setError(null);
- setMessage("");
- if (e.target.files && e.target.files.length > 0) {
- const selected = e.target.files[0];
- if (selected.type !== "application/pdf") {
- setError("Seuls les fichiers PDF sont autorisés");
- setPlanning(null);
- } else {
- setPlanning(selected);
- }
- }
- };
-
- const handleImport = async (planningName: string) => {
- if (!planning) {
- setError("Veuillez sélectionner un fichier PDF avant d'importer.");
- return;
- }
-
- setLoading(true);
- setError(null);
- setMessage("");
- setSelectedPlanning(planningName);
-
- try {
- const formData = new FormData();
- formData.append("planningFile", planning);
-
- const response = await importPlannings(formData);
- setMessage(response.message || `Importation réussie pour ${planningName} !`);
- } catch (err) {
- console.error("Erreur lors de l'importation du planning", err);
- setError("Une erreur est survenue pendant l'importation.");
- } finally {
- setLoading(false);
- }
- };
-
return (
- Importer les plannings au format PDF
+ Importer les plannings
-
-
- {/* Rappel des règles de nommage */}
-
- ⚠️ Le fichier doit être nommé en minuscules, sans accents, au format
- filiere.pdf (ex: tc.pdf, bachelor.pdf)
-
-
-
- {/* Input fichier masqué */}
-
-
-
- Choisir un fichier
-
-
- {/* Affiche le nom du fichier sélectionné */}
- {planning && (
-
-
- {planning.name}
-
- )}
-
- {/* Différents boutons d'import */}
-
- handleImport("Planning TC")}
- disabled={loading}
- className="bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-6 rounded-xl shadow-md"
- >
- {loading && selectedPlanning === "Planning TC"
- ? "Import en cours..."
- : "Planning TC"}
-
-
- handleImport("Planning Bachelor IA")}
- disabled={loading}
- className="bg-violet-600 hover:bg-violet-700 text-white py-2 px-6 rounded-xl shadow-md"
- >
- {loading && selectedPlanning === "Planning Bachelor IA"
- ? "Import en cours..."
- : "Planning Bachelor IA"}
-
-
- handleImport("Planning Branche (non-alternant)")}
- disabled={loading}
- className="bg-purple-600 hover:bg-purple-700 text-white py-2 px-6 rounded-xl shadow-md"
- >
- {loading && selectedPlanning === "Planning Branche (non-alternant)"
- ? "Import en cours..."
- : "Planning Branche (non-alternant)"}
-
-
- handleImport("Planning Branche FISEA (alternants)")}
- disabled={loading}
- className="bg-pink-600 hover:bg-pink-700 text-white py-2 px-6 rounded-xl shadow-md"
- >
- {loading && selectedPlanning === "Planning Branche FISEA (alternants)"
- ? "Import en cours..."
- : "Planning Branche FISEA (alternants)"}
-
-
- handleImport("Planning Master")}
- disabled={loading}
- className="bg-green-600 hover:bg-green-700 text-white py-2 px-6 rounded-xl shadow-md"
- >
- {loading && selectedPlanning === "Planning Master"
- ? "Import en cours..."
- : "Planning Master"}
-
-
-
-
- {/* Messages */}
- {error && (
- {error}
- )}
- {message && (
-
- {message}
-
- )}
+
+
+
+
+
+
);
diff --git a/frontend/src/components/Admin/adminFileImport.tsx b/frontend/src/components/Admin/adminFileImport.tsx
new file mode 100644
index 0000000..8274db5
--- /dev/null
+++ b/frontend/src/components/Admin/adminFileImport.tsx
@@ -0,0 +1,331 @@
+import { ExternalLink, FilePenLine, FilePlus, FileText, Trash } from "lucide-react";
+import { type ChangeEvent, useCallback, useEffect, useState } from "react";
+import Swal from "sweetalert2";
+
+import { MIMEType } from "../../interfaces/import.interface";
+import { checkIfExistingDocument, deleteFile, importFile } from "../../services/requests/im_export.service";
+import { Card, CardContent } from "../ui/card";
+
+type AdminFileImportProps = {
+ category: string;
+ item?: string;
+ acceptedTypes?: MIMEType[];
+ title: string;
+ draft?: boolean;
+ draftInitialUrl?: string | null;
+ onDraftFileChange?: (file: File | null) => void;
+ onDraftDelete?: () => void;
+ onDraftSubmitReady?: (submit: (itemOverride?: string) => Promise) => void;
+};
+
+type ExistingFile = {
+ exists: boolean;
+ extension: string | null;
+ fileName: string | null;
+ relativePath: string | null;
+};
+
+export const AdminFileImport = (
+ {
+ category,
+ item,
+ acceptedTypes = [MIMEType.PDF],
+ title,
+ draft = false,
+ draftInitialUrl,
+ onDraftFileChange,
+ onDraftDelete,
+ onDraftSubmitReady,
+ }: AdminFileImportProps,
+) => {
+ const [existingFile, setExistingFile] = useState(null);
+ const [fileURL, setFileURL] = useState(null);
+ const [draftFile, setDraftFile] = useState(null);
+ const [draftPreviewURL, setDraftPreviewURL] = useState(null);
+
+ const resolvePublicFileUrl = useCallback((url: string) => {
+ if (url.startsWith("http://") || url.startsWith("https://")) {
+ return url;
+ }
+
+ const baseUrl = import.meta.env.VITE_API_URL as string;
+ if (url.startsWith("/api/") && baseUrl.endsWith("/api")) {
+ return `${baseUrl}${url.slice(4)}`;
+ }
+
+ return `${baseUrl}${url}`;
+ }, []);
+
+ const getExtensionFromPath = useCallback((url: string) => {
+ const cleanUrl = url.split("?")[0].split("#")[0];
+ const fileName = cleanUrl.split("/").pop() ?? "";
+ if (!fileName.includes(".")) {
+ return null;
+ }
+
+ return fileName.split(".").pop()?.toLowerCase() ?? null;
+ }, []);
+
+ const checkFileStatus = useCallback(async () => {
+ if (draft || !item) {
+ return;
+ }
+
+ try {
+ const status = await checkIfExistingDocument(category, item);
+ setExistingFile(status);
+
+ if (status.exists && status.relativePath) {
+ setFileURL(resolvePublicFileUrl(status.relativePath));
+ } else {
+ setFileURL(null);
+ }
+ } catch {
+ setExistingFile(null);
+ setFileURL(null);
+ }
+ }, [category, draft, item, resolvePublicFileUrl]);
+
+ useEffect(() => {
+ if (!draft || draftFile || draftPreviewURL) {
+ return;
+ }
+
+ if (!draftInitialUrl) {
+ setExistingFile(null);
+ setFileURL(null);
+ return;
+ }
+
+ const resolvedUrl = resolvePublicFileUrl(draftInitialUrl);
+ const extension = getExtensionFromPath(draftInitialUrl);
+ const fileName = draftInitialUrl.split("?")[0].split("#")[0].split("/").pop() ?? null;
+
+ setExistingFile({
+ exists: true,
+ extension,
+ fileName,
+ relativePath: null,
+ });
+ setFileURL(resolvedUrl);
+ }, [draft, draftFile, draftInitialUrl, draftPreviewURL, getExtensionFromPath, resolvePublicFileUrl]);
+
+ useEffect(() => {
+ if (!draft) {
+ void checkFileStatus();
+ }
+ }, [checkFileStatus, draft]);
+
+ const submitDraftUpload = useCallback(async (itemOverride?: string): Promise => {
+ if (!draft || !draftFile) {
+ return null;
+ }
+
+ const targetItem = itemOverride ?? item;
+
+ if (!targetItem) {
+ throw new Error("L'item est requis pour la soumission du brouillon.");
+ }
+
+ const formData = new FormData();
+ formData.append("file", draftFile);
+
+ await importFile(formData, category, targetItem);
+
+ const status = await checkIfExistingDocument(category, targetItem);
+ setExistingFile(status);
+ if (status.exists && status.relativePath) {
+ setFileURL(resolvePublicFileUrl(status.relativePath));
+ }
+
+ setDraftFile(null);
+ if (draftPreviewURL) {
+ URL.revokeObjectURL(draftPreviewURL);
+ setDraftPreviewURL(null);
+ }
+ onDraftFileChange?.(null);
+
+ return status.relativePath ?? null;
+ }, [category, draft, draftFile, draftPreviewURL, item, onDraftFileChange, resolvePublicFileUrl]);
+
+ useEffect(() => {
+ if (draft && onDraftSubmitReady) {
+ onDraftSubmitReady(submitDraftUpload);
+ }
+ }, [draft, onDraftSubmitReady, submitDraftUpload]);
+
+ const inputId = `${category}-${item ?? "draft"}-fileInput`;
+
+ const handleFileChange = async (e: ChangeEvent) => {
+ if (!e.target.files || e.target.files.length === 0) {
+ Swal.fire({
+ icon: "error",
+ title: "Aucun fichier sélectionné"
+ });
+ return;
+ }
+
+ const selected = e.target.files[0];
+ if (!selected || !acceptedTypes.includes(selected.type as MIMEType)) {
+ Swal.fire({
+ icon: "error",
+ title: "Format incompatible",
+ text: "Le fichier sélectionné n'est pas autorisé pour ce champ."
+ });
+ return;
+ }
+
+ if (draft) {
+ const extension = selected.name.includes(".")
+ ? selected.name.split(".").pop()?.toLowerCase() ?? null
+ : null;
+
+ if (draftPreviewURL) {
+ URL.revokeObjectURL(draftPreviewURL);
+ }
+ const localPreviewURL = URL.createObjectURL(selected);
+
+ setDraftFile(selected);
+ setExistingFile({
+ exists: true,
+ extension,
+ fileName: selected.name,
+ relativePath: null,
+ });
+ setDraftPreviewURL(localPreviewURL);
+ setFileURL(localPreviewURL);
+ onDraftFileChange?.(selected);
+ return;
+ }
+
+ if (!item) {
+ Swal.fire({
+ icon: "error",
+ title: "Configuration invalide",
+ text: "L'item est requis pour l'upload direct.",
+ });
+ return;
+ }
+
+ try {
+ const formData = new FormData();
+ formData.append("file", selected);
+
+ const response = await importFile(formData, category, item);
+ await checkFileStatus();
+ await Swal.fire("✅ Import réussi", response.message, "success");
+ } catch (err) {
+ console.error("Erreur lors de l'importation du menu", err);
+ Swal.fire({
+ icon: "error",
+ title: "Erreur",
+ text: "Erreur lors de l'importation du menu.",
+ });
+ }
+ };
+
+ const handleDelete = async () => {
+ if (draft) {
+ if (draftPreviewURL) {
+ URL.revokeObjectURL(draftPreviewURL);
+ }
+ setDraftFile(null);
+ setDraftPreviewURL(null);
+ setExistingFile(null);
+ setFileURL(null);
+ onDraftFileChange?.(null);
+ onDraftDelete?.();
+ return;
+ }
+
+ if (!item) {
+ return;
+ }
+
+ const confirm = await Swal.fire({
+ title: `Supprimer le document ${title} ?`,
+ text: "Cette action est irreversible.",
+ icon: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#2563eb",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "🚍 Oui",
+ cancelButtonText: "Annuler",
+ });
+
+ if (confirm.isConfirmed) {
+ try {
+ const response = await deleteFile(category, item);
+ await checkFileStatus();
+ await Swal.fire("✅ Suppression réussie", response.message, "success");
+ } catch (err) {
+ console.error("Erreur lors de la suppression du document", err);
+ Swal.fire({
+ icon: "error",
+ title: "Erreur",
+ text: "Erreur lors de la suppression du document.",
+ });
+ }
+ }
+ };
+
+ return (
+
+
+
+
+
{title}
+
+
+ {existingFile?.exists ? (
+ <>
+
+
.{existingFile?.extension}
+ >
+ ) : (
+
+ )}
+
+
+
+
`${ext}`).join(",")}
+ onChange={handleFileChange}
+ className="hidden"
+ />
+
+
+ {existingFile?.exists ? (
+
+ ) : (
+
+ )}
+
+
+ {(existingFile?.exists || !!draftPreviewURL) && (
+ <>
+ {(draftPreviewURL || fileURL) && (
+
+
+
+
+
+ )}
+
+
+
+
+ >
+ )}
+
+
+
+
+ );
+};
diff --git a/frontend/src/components/Admin/adminNews.tsx b/frontend/src/components/Admin/adminNews.tsx
index 4ce24f1..eab5435 100644
--- a/frontend/src/components/Admin/adminNews.tsx
+++ b/frontend/src/components/Admin/adminNews.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import Swal from "sweetalert2";
+import { MIMEType } from "../../interfaces/import.interface";
import { type News } from "../../interfaces/news.interface";
import {
createNews,
@@ -13,13 +14,14 @@ import { Button } from "../ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";
+import { AdminFileImport } from "./adminFileImport";
export const AdminNews = () => {
const [newsList, setNewsList] = useState([]);
const [loading, setLoading] = useState(true);
const [editingId, setEditingId] = useState(null);
- const [selectedFile, setSelectedFile] = useState(null);
- const [previewUrl, setPreviewUrl] = useState(null);
+ const [editingImageUrl, setEditingImageUrl] = useState(null);
+ const [submitDraftImage, setSubmitDraftImage] = useState<((itemOverride?: string) => Promise) | null>(null);
const [formData, setFormData] = useState({
title: "",
@@ -52,41 +54,78 @@ export const AdminNews = () => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
- const handleImageChange = (e: React.ChangeEvent) => {
- const file = e.target.files?.[0];
- if (file) {
- setSelectedFile(file);
- setPreviewUrl(URL.createObjectURL(file));
+ const getErrorMessage = (err: unknown, fallback: string) => {
+ if (typeof err === "object" && err !== null && "response" in err) {
+ const response = (err as { response?: { data?: { msg?: string } } }).response;
+ if (response?.data?.msg) {
+ return response.data.msg;
+ }
}
+
+ return fallback;
};
- const handleCreateOrUpdate = async () => {
- try {
- const formDataToSend = new FormData();
+ const resolveNewsImageUrl = (imageUrl: string) => {
+ if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
+ return imageUrl;
+ }
- if (selectedFile) {
- formDataToSend.append("file", selectedFile);
- }
+ const baseUrl = import.meta.env.VITE_API_URL as string;
+ if (imageUrl.startsWith("/api/") && baseUrl.endsWith("/api")) {
+ return `${baseUrl}${imageUrl.slice(4)}`;
+ }
+
+ return `${baseUrl}${imageUrl}`;
+ };
- formDataToSend.append("title", formData.title);
- formDataToSend.append("description", formData.description);
- formDataToSend.append("type", formData.type);
- formDataToSend.append("published", String(formData.published));
- formDataToSend.append("target", formData.target);
+ const handleCreateOrUpdate = async () => {
+ try {
+ const payload = {
+ title: formData.title,
+ description: formData.description,
+ type: formData.type,
+ published: formData.published,
+ target: formData.target,
+ };
let response;
if (editingId) {
- formDataToSend.append("id", String(editingId));
- response = await updateNews(formDataToSend);
+ const uploadedImageUrl = submitDraftImage
+ ? await submitDraftImage(String(editingId))
+ : null;
+
+ const image_url = uploadedImageUrl
+ ? uploadedImageUrl
+ : editingImageUrl === null
+ ? null
+ : undefined;
+
+ response = await updateNews({
+ ...payload,
+ id: String(editingId),
+ image_url,
+ });
} else {
- response = await createNews(formDataToSend);
+ response = await createNews(payload);
+
+ const createdId = response?.data?.id;
+ if (createdId && submitDraftImage) {
+ const image_url = await submitDraftImage(String(createdId));
+ if (image_url) {
+ await updateNews({
+ ...payload,
+ id: String(createdId),
+ image_url,
+ });
+ }
+ }
}
await Swal.fire("✅ Succès", response.message, "success");
resetForm();
fetchNews();
- } catch (err: any) {
- Swal.fire("❌ Erreur", err.response?.data?.msg || "Erreur lors de la sauvegarde", "error");
+ } catch (err: unknown) {
+ Swal.fire("❌ Erreur", getErrorMessage(err, "Erreur lors de la sauvegarde"), "error");
}
};
@@ -137,8 +176,8 @@ export const AdminNews = () => {
const response = await publishNews(news, sendEmail.isConfirmed);
await Swal.fire("✅ Publiée", response.message, "success");
fetchNews();
- } catch (err: any) {
- Swal.fire("❌ Erreur", err.response?.data?.msg || "Erreur lors de la publication", "error");
+ } catch (err: unknown) {
+ Swal.fire("❌ Erreur", getErrorMessage(err, "Erreur lors de la publication"), "error");
}
};
@@ -151,6 +190,7 @@ export const AdminNews = () => {
target: news.target,
});
setEditingId(news.id);
+ setEditingImageUrl(news.image_url ?? null);
window.scrollTo({ top: 0, behavior: "smooth" });
};
@@ -162,14 +202,10 @@ export const AdminNews = () => {
published: false,
target: "Tous",
});
- setSelectedFile(null);
- setPreviewUrl(null);
setEditingId(null);
+ setEditingImageUrl(null);
};
- const handleRemoveImage = () => {
- setPreviewUrl(null);
- };
return (
@@ -194,48 +230,19 @@ export const AdminNews = () => {
placeholder="Contenu de l'actu"
/>
- {/* Image upload amélioré */}
-
-
document.getElementById("fileInput")?.click()}
- className="bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-semibold"
- >
- Choisir une image
-
-
-
-
- {selectedFile && (
-
{selectedFile.name}
- )}
-
- {previewUrl && (
-
-
-
-
-
- Retirer l'image
-
-
- )}
-
+ {
+ setEditingImageUrl(null);
+ setSubmitDraftImage(null);
+ }}
+ onDraftSubmitReady={(submit) => setSubmitDraftImage(() => submit)}
+ />
{
{news.image_url && (
diff --git a/frontend/src/components/Plannings/planningSection.tsx b/frontend/src/components/Plannings/planningSection.tsx
index 3ad598b..80de0b1 100644
--- a/frontend/src/components/Plannings/planningSection.tsx
+++ b/frontend/src/components/Plannings/planningSection.tsx
@@ -14,11 +14,11 @@ const plannings: Planning[] = [
},
{
name: "Planning Bachelor IA",
- url: `${import.meta.env.VITE_API_URL}/uploads/plannings/bachelor.pdf`,
+ url: `${import.meta.env.VITE_API_URL}/uploads/plannings/bachelor_ia.pdf`,
},
{
name: "Planning Branche (non-alternant)",
- url: `${import.meta.env.VITE_API_URL}/uploads/plannings/branche.pdf`,
+ url: `${import.meta.env.VITE_API_URL}/uploads/plannings/fise.pdf`,
},
{
name: "Planning Branche FISEA (alternants)",
diff --git a/frontend/src/components/WEI_SDI_Food/foodSection.tsx b/frontend/src/components/WEI_SDI_Food/foodSection.tsx
index 2d33ca5..44ee840 100644
--- a/frontend/src/components/WEI_SDI_Food/foodSection.tsx
+++ b/frontend/src/components/WEI_SDI_Food/foodSection.tsx
@@ -9,7 +9,7 @@ export const FoodSection = () => {
const [isMenuAvailable, setIsMenuAvailable] = useState(false);
const permission = getPermission();
- const menuUrl = `${import.meta.env.VITE_API_URL}/uploads/foodmenu/FoodMenu.pdf`;
+ const menuUrl = `${import.meta.env.VITE_API_URL}/uploads/foodmenu/menu.pdf`;
useEffect(() => {
const script = document.createElement("script");
diff --git a/frontend/src/components/news/newsSection.tsx b/frontend/src/components/news/newsSection.tsx
index 40d4434..ea31b0f 100644
--- a/frontend/src/components/news/newsSection.tsx
+++ b/frontend/src/components/news/newsSection.tsx
@@ -70,7 +70,7 @@ export const MyNews = () => {
>
{news.image_url && (
diff --git a/frontend/src/interfaces/import.interface.ts b/frontend/src/interfaces/import.interface.ts
new file mode 100644
index 0000000..d77f71a
--- /dev/null
+++ b/frontend/src/interfaces/import.interface.ts
@@ -0,0 +1,5 @@
+export enum MIMEType {
+ PDF = "application/pdf",
+ PNG = "image/png",
+ JPEG = "image/jpeg",
+}
diff --git a/frontend/src/services/requests/im_export.service.ts b/frontend/src/services/requests/im_export.service.ts
index f4a15c0..dd0c4da 100644
--- a/frontend/src/services/requests/im_export.service.ts
+++ b/frontend/src/services/requests/im_export.service.ts
@@ -1,5 +1,12 @@
import api from "../api";
+type ExistingDocumentStatus = {
+ exists: boolean;
+ extension: string | null;
+ fileName: string | null;
+ relativePath: string | null;
+};
+
// Fonction export
export const exportDb = async () => {
const response = await api.post('/imexport/admin/exportgsheet');
@@ -7,21 +14,24 @@ export const exportDb = async () => {
};
// Fonction import
-export const importFoodMenu = async (formData: FormData) => {
- const response = await api.post('/imexport/admin/foodimport', formData, {
+export const importFile = async (formData: FormData, category: string, item: string) => {
+ const response = await api.post(`/imexport/admin/import/${category}/${item}`, formData, {
headers: { "Content-Type": "multipart/form-data" },
});
return response.data;
};
-export const importPlannings = async (formData: FormData) => {
- const response = await api.post('/imexport/admin/plannings', formData, {
- headers: { "Content-Type": "multipart/form-data" },
- });
+export const exportBus = async () => {
+ const response = await api.get('/imexport/admin/exportbus');
return response.data;
};
-export const exportBus = async () => {
- const response = await api.get('/imexport/admin/exportbus');
+export const checkIfExistingDocument = async (category: string, item: string) => {
+ const response = await api.get('/imexport/admin/document/' + category + '/' + item);
+ return response.data.data as ExistingDocumentStatus;
+};
+
+export const deleteFile = async (category: string, item: string) => {
+ const response = await api.delete('/imexport/admin/document/' + category + '/' + item);
return response.data;
};
diff --git a/frontend/src/services/requests/news.service.ts b/frontend/src/services/requests/news.service.ts
index 41ad898..7ace94e 100644
--- a/frontend/src/services/requests/news.service.ts
+++ b/frontend/src/services/requests/news.service.ts
@@ -1,21 +1,27 @@
+import { type News } from '../../interfaces/news.interface';
import api from '../api';
+type NewsPayload = {
+ id?: string;
+ title: string;
+ description: string;
+ type: string;
+ published: boolean;
+ target: string;
+ image_url?: string | null;
+};
+
export const getAllNews = async () => {
const res = await api.get("/news/admin/all");
return res.data.data;
};
-export const createNews = async (formData: FormData) => {
- const response = await api.post("/news/admin/createnews", formData,
- {
- headers: {
- "Content-Type": "multipart/form-data",
- },
- });
+export const createNews = async (payload: NewsPayload) => {
+ const response = await api.post("/news/admin/createnews", payload);
return response.data;
};
-export const publishNews = async (news: any, sendEmail: boolean) => {
+export const publishNews = async (news: Pick, sendEmail: boolean) => {
const res = await api.post("/news/admin/publish", {
id: news.id,
sendEmail: sendEmail
@@ -33,12 +39,7 @@ export const deleteNews = async (newsId: number) => {
return res.data;
};
-export const updateNews = async (formData: FormData) => {
- const response = await api.post("/news/admin/updatenews", formData,
- {
- headers: {
- "Content-Type": "multipart/form-data",
- },
- });
+export const updateNews = async (payload: NewsPayload) => {
+ const response = await api.post("/news/admin/updatenews", payload);
return response.data;
};
From 240c6144e541578bcbeeac9af1f80c0b71b08997 Mon Sep 17 00:00:00 2001
From: "Antoine D."
Date: Tue, 23 Jun 2026 19:10:27 +0200
Subject: [PATCH 02/42] chore: fix CVE
---
backend/package-lock.json | 1158 +++---
backend/package.json | 4 +-
frontend/package-lock.json | 372 +-
frontend/pnpm-lock.yaml | 7155 ++++++++++++++++++++++++++++++++++++
4 files changed, 7988 insertions(+), 701 deletions(-)
create mode 100644 frontend/pnpm-lock.yaml
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 190c70d..51d98ab 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -23,13 +23,13 @@
"esbuild-register": "^3.6.0",
"express": "^5.1.0",
"file-type": "^21.0.0",
- "googleapis": "^148.0.0",
+ "googleapis": "^173.0.0",
"handlebars": "^4.7.8",
"jsdom": "^26.1.0",
"json2csv": "^6.0.0-alpha.2",
"jsonwebtoken": "^9.0.2",
"multer": "^2.1.1",
- "nodemailer": "^8.0.3",
+ "nodemailer": "^9.0.1",
"papaparse": "^5.5.2",
"pg": "^8.14.1",
"pg-promise": "^12.6.2",
@@ -225,395 +225,6 @@
"source-map-support": "^0.5.21"
}
},
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
- "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
- "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
- "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
- "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
- "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
- "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
- "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
- "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
- "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
- "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
- "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
- "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
- "cpu": [
- "mips64el"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
- "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
- "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
- "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
- "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
- "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
- "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
- "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
- "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
- "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
- "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
- "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/android-arm": "0.18.20",
- "@esbuild/android-arm64": "0.18.20",
- "@esbuild/android-x64": "0.18.20",
- "@esbuild/darwin-arm64": "0.18.20",
- "@esbuild/darwin-x64": "0.18.20",
- "@esbuild/freebsd-arm64": "0.18.20",
- "@esbuild/freebsd-x64": "0.18.20",
- "@esbuild/linux-arm": "0.18.20",
- "@esbuild/linux-arm64": "0.18.20",
- "@esbuild/linux-ia32": "0.18.20",
- "@esbuild/linux-loong64": "0.18.20",
- "@esbuild/linux-mips64el": "0.18.20",
- "@esbuild/linux-ppc64": "0.18.20",
- "@esbuild/linux-riscv64": "0.18.20",
- "@esbuild/linux-s390x": "0.18.20",
- "@esbuild/linux-x64": "0.18.20",
- "@esbuild/netbsd-x64": "0.18.20",
- "@esbuild/openbsd-x64": "0.18.20",
- "@esbuild/sunos-x64": "0.18.20",
- "@esbuild/win32-arm64": "0.18.20",
- "@esbuild/win32-ia32": "0.18.20",
- "@esbuild/win32-x64": "0.18.20"
- }
- },
"node_modules/@esbuild-kit/esm-loader": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
@@ -1264,6 +875,23 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -1292,6 +920,16 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1713,9 +1351,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1871,11 +1509,22 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@@ -2085,21 +1734,46 @@
}
},
"node_modules/axios": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
- "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
+ "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.11",
+ "follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
+ "https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
+ "node_modules/axios/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/axios/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
@@ -2385,7 +2059,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
@@ -2398,7 +2071,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
@@ -2507,7 +2179,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -2531,6 +2202,15 @@
"node": ">=18"
}
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
@@ -2947,6 +2627,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -2962,6 +2648,12 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "license": "MIT"
+ },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -3564,6 +3256,29 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3699,17 +3414,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
@@ -3736,6 +3467,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -3809,33 +3552,31 @@
}
},
"node_modules/gaxios": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
- "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.5.tgz",
+ "integrity": "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
- "is-stream": "^2.0.0",
- "node-fetch": "^2.6.9",
- "uuid": "^9.0.1"
+ "node-fetch": "^3.3.2"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/gcp-metadata": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
- "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
+ "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
"license": "Apache-2.0",
"dependencies": {
- "gaxios": "^6.1.1",
- "google-logging-utils": "^0.0.2",
+ "gaxios": "^7.0.0",
+ "google-logging-utils": "^1.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/generator-function": {
@@ -3915,6 +3656,27 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -3928,6 +3690,30 @@
"node": ">= 6"
}
},
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/globals": {
"version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
@@ -3959,59 +3745,92 @@
}
},
"node_modules/google-auth-library": {
- "version": "9.15.1",
- "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
- "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
+ "version": "10.7.0",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.7.0.tgz",
+ "integrity": "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
- "gaxios": "^6.1.1",
- "gcp-metadata": "^6.1.0",
- "gtoken": "^7.0.0",
+ "gaxios": "^7.1.4",
+ "gcp-metadata": "8.1.2",
+ "google-logging-utils": "1.1.3",
"jws": "^4.0.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/google-logging-utils": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
- "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
+ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/googleapis": {
+ "version": "173.0.0",
+ "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-173.0.0.tgz",
+ "integrity": "sha512-xEJJYLZ4qeenVyfzispNfRjCe9bsv7CzBv5zYFLvScOze9snJ8S9W6hjQ729CWPQt5mvn/JrcRaCHzQiukt0ng==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "google-auth-library": "^10.2.0",
+ "googleapis-common": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/googleapis-common": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.2.tgz",
+ "integrity": "sha512-5MXeQzIZaqCH7B+HJWqhQm946VARpZep6acbWSr/fcgF2cQANq7allgX+i/G0EqF0WyUxB277gtWMzRYHMl9tg==",
"license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "gaxios": "7.1.3",
+ "google-auth-library": "10.5.0",
+ "google-logging-utils": "1.1.3",
+ "qs": "^6.7.0",
+ "url-template": "^2.0.8"
+ },
"engines": {
- "node": ">=14"
+ "node": ">=18.0.0"
}
},
- "node_modules/googleapis": {
- "version": "148.0.0",
- "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-148.0.0.tgz",
- "integrity": "sha512-8PDG5VItm6E1TdZWDqtRrUJSlBcNwz0/MwCa6AL81y/RxPGXJRUwKqGZfCoVX1ZBbfr3I4NkDxBmeTyOAZSWqw==",
+ "node_modules/googleapis-common/node_modules/gaxios": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz",
+ "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==",
"license": "Apache-2.0",
"dependencies": {
- "google-auth-library": "^9.0.0",
- "googleapis-common": "^7.0.0"
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "node-fetch": "^3.3.2",
+ "rimraf": "^5.0.1"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=18"
}
},
- "node_modules/googleapis-common": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz",
- "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==",
+ "node_modules/googleapis-common/node_modules/google-auth-library": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz",
+ "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==",
"license": "Apache-2.0",
"dependencies": {
- "extend": "^3.0.2",
- "gaxios": "^6.0.3",
- "google-auth-library": "^9.7.0",
- "qs": "^6.7.0",
- "url-template": "^2.0.8",
- "uuid": "^9.0.0"
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^7.0.0",
+ "gcp-metadata": "^8.0.0",
+ "google-logging-utils": "^1.0.0",
+ "gtoken": "^8.0.0",
+ "jws": "^4.0.0"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=18"
}
},
"node_modules/gopd": {
@@ -4027,16 +3846,16 @@
}
},
"node_modules/gtoken": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
- "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz",
+ "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==",
"license": "MIT",
"dependencies": {
- "gaxios": "^6.0.0",
+ "gaxios": "^7.0.0",
"jws": "^4.0.0"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=18"
}
},
"node_modules/handlebars": {
@@ -4140,9 +3959,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -4520,6 +4339,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-generator-function": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
@@ -4675,18 +4503,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/is-string": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
@@ -4795,14 +4611,38 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -5157,6 +4997,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5164,9 +5013,9 @@
"license": "MIT"
},
"node_modules/multer": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
- "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz",
+ "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
@@ -5226,9 +5075,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
"funding": [
{
"type": "github",
@@ -5265,6 +5114,26 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"license": "MIT"
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@@ -5295,51 +5164,27 @@
}
},
"node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
- "whatwg-url": "^5.0.0"
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
},
"engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/node-fetch/node_modules/tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
- "license": "MIT"
- },
- "node_modules/node-fetch/node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "license": "BSD-2-Clause"
- },
- "node_modules/node-fetch/node_modules/whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "license": "MIT",
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemailer": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz",
- "integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.1.tgz",
+ "integrity": "sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -5600,6 +5445,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
"node_modules/papaparse": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
@@ -5660,7 +5511,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5673,6 +5523,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/path-to-regexp": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
@@ -5852,9 +5718,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"funding": [
{
"type": "opencollective",
@@ -5871,7 +5737,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -5980,9 +5846,9 @@
}
},
"node_modules/qs": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
- "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -6172,6 +6038,21 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/rimraf": {
+ "version": "5.0.10",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
+ "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^10.3.7"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -6409,7 +6290,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -6422,7 +6302,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -6500,6 +6379,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -6599,6 +6490,65 @@
"safe-buffer": "~5.2.0"
}
},
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string.prototype.trim": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
@@ -6658,6 +6608,43 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -7652,19 +7639,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
- "node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -7693,6 +7667,15 @@
"node": ">=18"
}
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@@ -7740,7 +7723,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -7857,6 +7839,94 @@
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"license": "MIT"
},
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -7864,9 +7934,9 @@
"license": "ISC"
},
"node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
diff --git a/backend/package.json b/backend/package.json
index 3d8de70..f03b302 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -35,13 +35,13 @@
"esbuild-register": "^3.6.0",
"express": "^5.1.0",
"file-type": "^21.0.0",
- "googleapis": "^148.0.0",
+ "googleapis": "^173.0.0",
"handlebars": "^4.7.8",
"jsdom": "^26.1.0",
"json2csv": "^6.0.0-alpha.2",
"jsonwebtoken": "^9.0.2",
"multer": "^2.1.1",
- "nodemailer": "^8.0.3",
+ "nodemailer": "^9.0.1",
"papaparse": "^5.5.2",
"pg": "^8.14.1",
"pg-promise": "^12.6.2",
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index b6dbcda..0a9d81d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -87,12 +87,12 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -101,30 +101,30 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
- "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
- "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
- "license": "MIT",
- "dependencies": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.0",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.27.3",
- "@babel/helpers": "^7.27.6",
- "@babel/parser": "^7.28.0",
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.0",
- "@babel/types": "^7.28.0",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -140,13 +140,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
- "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@babel/types": "^7.28.2",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -168,13 +168,13 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.27.2",
- "@babel/helper-validator-option": "^7.27.1",
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@@ -205,9 +205,9 @@
}
},
"node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -227,27 +227,27 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
- "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.3"
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -308,52 +308,52 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
- "version": "7.27.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
- "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.27.6"
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
- "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.2"
+ "@babel/types": "^7.29.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -438,31 +438,31 @@
}
},
"node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
- "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.3",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.3",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.2",
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
"debug": "^4.3.1"
},
"engines": {
@@ -470,13 +470,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
- "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1478,6 +1478,16 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -4006,16 +4016,42 @@
}
},
"node_modules/axios": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
- "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
+ "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.11",
+ "follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
+ "https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
+ "node_modules/axios/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/axios/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -4067,6 +4103,18 @@
],
"license": "MIT"
},
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.38",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
+ "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/bl": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
@@ -4126,9 +4174,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.25.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
- "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+ "version": "4.28.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
+ "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
"funding": [
{
"type": "opencollective",
@@ -4145,10 +4193,11 @@
],
"license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001726",
- "electron-to-chromium": "^1.5.173",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.3"
+ "baseline-browser-mapping": "^2.10.38",
+ "caniuse-lite": "^1.0.30001799",
+ "electron-to-chromium": "^1.5.376",
+ "node-releases": "^2.0.48",
+ "update-browserslist-db": "^1.2.3"
},
"bin": {
"browserslist": "cli.js"
@@ -4248,9 +4297,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001727",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
- "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
+ "version": "1.0.30001799",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
+ "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
"funding": [
{
"type": "opencollective",
@@ -4818,9 +4867,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.189",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.189.tgz",
- "integrity": "sha512-y9D1ntS1ruO/pZ/V2FtLE+JXLQe28XoRpZ7QCCo0T8LdQladzdcOVQZH/IWLVJvCw12OGMb6hYOeOAjntCmJRQ==",
+ "version": "1.5.377",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.377.tgz",
+ "integrity": "sha512-cH1jZgJHoezfTnKfKwnScpHywTFVnJUNITDPREFdhNjiuD502+QFpG0Qk7G8jhsV/f+CEAFlIrzP1fT+IMb92g==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -5653,12 +5702,12 @@
}
},
"node_modules/express-rate-limit": {
- "version": "8.3.1",
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
- "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
+ "version": "8.5.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
+ "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
"license": "MIT",
"dependencies": {
- "ip-address": "10.1.0"
+ "ip-address": "^10.2.0"
},
"engines": {
"node": ">= 16"
@@ -5753,9 +5802,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
- "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"funding": [
{
"type": "github",
@@ -5927,16 +5976,16 @@
}
},
"node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
@@ -6340,9 +6389,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -6367,9 +6416,9 @@
}
},
"node_modules/hono": {
- "version": "4.12.14",
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
- "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
+ "version": "4.12.27",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz",
+ "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
@@ -6511,9 +6560,9 @@
}
},
"node_modules/ip-address": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
- "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"license": "MIT",
"engines": {
"node": ">= 12"
@@ -7059,9 +7108,19 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -7756,9 +7815,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
"funding": [
{
"type": "github",
@@ -7847,10 +7906,13 @@
}
},
"node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "license": "MIT"
+ "version": "2.0.48",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz",
+ "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
},
"node_modules/npm-run-path": {
"version": "5.3.0",
@@ -8268,9 +8330,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"funding": [
{
"type": "opencollective",
@@ -8287,7 +8349,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -8382,9 +8444,9 @@
}
},
"node_modules/qs": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
- "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -8556,9 +8618,9 @@
}
},
"node_modules/react-router": {
- "version": "7.13.2",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.2.tgz",
- "integrity": "sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==",
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz",
+ "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -8578,12 +8640,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.13.2",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.2.tgz",
- "integrity": "sha512-aR7SUORwTqAW0JDeiWF07e9SBE9qGpByR9I8kJT5h/FrBKxPMS6TiC7rmVO+gC0q52Bx7JnjWe8Z1sR9faN4YA==",
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz",
+ "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==",
"license": "MIT",
"dependencies": {
- "react-router": "7.13.2"
+ "react-router": "7.18.0"
},
"engines": {
"node": ">=20.0.0"
@@ -9702,9 +9764,9 @@
}
},
"node_modules/tar": {
- "version": "7.5.13",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz",
- "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==",
+ "version": "7.5.16",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
+ "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
@@ -10096,9 +10158,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"funding": [
{
"type": "opencollective",
@@ -10227,9 +10289,9 @@
}
},
"node_modules/vite": {
- "version": "6.4.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
- "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
new file mode 100644
index 0000000..b8c5751
--- /dev/null
+++ b/frontend/pnpm-lock.yaml
@@ -0,0 +1,7155 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@headlessui/react':
+ specifier: ^2.2.7
+ version: 2.2.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-accordion':
+ specifier: ^1.2.12
+ version: 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-checkbox':
+ specifier: ^1.1.5
+ version: 1.3.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-label':
+ specifier: ^2.1.3
+ version: 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-popover':
+ specifier: ^1.1.7
+ version: 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-select':
+ specifier: ^2.1.7
+ version: 2.3.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-slot':
+ specifier: ^1.2.0
+ version: 1.3.0(@types/react@19.2.17)(react@19.2.7)
+ '@tailwindcss/vite':
+ specifier: ^4.1.3
+ version: 4.3.1(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0))
+ '@tanstack/react-query':
+ specifier: ^5.85.5
+ version: 5.101.1(react@19.2.7)
+ axios:
+ specifier: ^1.8.4
+ version: 1.18.1
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ date-fns:
+ specifier: ^3.6.0
+ version: 3.6.0
+ framer-motion:
+ specifier: ^12.23.12
+ version: 12.41.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ jwt-decode:
+ specifier: ^4.0.0
+ version: 4.0.0
+ lucide-react:
+ specifier: ^0.488.0
+ version: 0.488.0(react@19.2.7)
+ react:
+ specifier: ^19.1.0
+ version: 19.2.7
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.2.7(react@19.2.7)
+ react-hook-form:
+ specifier: ^7.55.0
+ version: 7.80.0(react@19.2.7)
+ react-icons:
+ specifier: ^5.5.0
+ version: 5.6.0(react@19.2.7)
+ react-router-dom:
+ specifier: ^7.5.0
+ version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react-select:
+ specifier: ^5.10.1
+ version: 5.10.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ shadcn:
+ specifier: ^2.10.0
+ version: 2.10.0(@types/node@22.20.0)(typescript@5.7.3)
+ sweetalert2:
+ specifier: ^11.23.0
+ version: 11.26.25
+ swiper:
+ specifier: ^12.1.3
+ version: 12.2.0
+ tailwind-merge:
+ specifier: ^3.2.0
+ version: 3.6.0
+ tailwindcss:
+ specifier: ^4.1.3
+ version: 4.3.1
+ tw-animate-css:
+ specifier: ^1.2.5
+ version: 1.4.0
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.21.0
+ version: 9.39.4
+ '@heroicons/react':
+ specifier: ^2.2.0
+ version: 2.2.0(react@19.2.7)
+ '@types/node':
+ specifier: ^22.14.1
+ version: 22.20.0
+ '@types/react':
+ specifier: ^19.0.10
+ version: 19.2.17
+ '@types/react-dom':
+ specifier: ^19.0.4
+ version: 19.2.3(@types/react@19.2.17)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.4
+ version: 4.7.0(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0))
+ eslint:
+ specifier: ^9.21.0
+ version: 9.39.4(jiti@2.7.0)
+ eslint-plugin-import:
+ specifier: ^2.32.0
+ version: 2.32.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0))
+ eslint-plugin-jsx-a11y:
+ specifier: ^6.10.2
+ version: 6.10.2(eslint@9.39.4(jiti@2.7.0))
+ eslint-plugin-react:
+ specifier: ^7.37.5
+ version: 7.37.5(eslint@9.39.4(jiti@2.7.0))
+ eslint-plugin-react-hooks:
+ specifier: ^5.1.0
+ version: 5.2.0(eslint@9.39.4(jiti@2.7.0))
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.19
+ version: 0.4.26(eslint@9.39.4(jiti@2.7.0))
+ eslint-plugin-simple-import-sort:
+ specifier: ^13.0.0
+ version: 13.0.0(eslint@9.39.4(jiti@2.7.0))
+ eslint-plugin-unused-imports:
+ specifier: ^4.4.1
+ version: 4.4.1(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0))
+ globals:
+ specifier: ^15.15.0
+ version: 15.15.0
+ typescript:
+ specifier: ~5.7.2
+ version: 5.7.3
+ typescript-eslint:
+ specifier: ^8.24.1
+ version: 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ vite:
+ specifier: ^6.4.2
+ version: 6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
+
+packages:
+
+ '@antfu/ni@23.3.1':
+ resolution: {integrity: sha512-C90iyzm/jLV7Lomv2UzwWUzRv9WZr1oRsFRKsX5HjQL4EXrbi9H/RtBkjCP+NF+ABZXUKpAa4F1dkoTaea4zHg==}
+ hasBin: true
+
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.29.7':
+ resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.29.7':
+ resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.7':
+ resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-annotate-as-pure@7.29.7':
+ resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.29.7':
+ resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-create-class-features-plugin@7.29.7':
+ resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-globals@7.29.7':
+ resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-member-expression-to-functions@7.29.7':
+ resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.29.7':
+ resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.29.7':
+ resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-optimise-call-expression@7.29.7':
+ resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-plugin-utils@7.29.7':
+ resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-replace-supers@7.29.7':
+ resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.29.7':
+ resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.29.7':
+ resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.7':
+ resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-syntax-typescript@7.29.7':
+ resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-self@7.29.7':
+ resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.29.7':
+ resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-typescript@7.29.7':
+ resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.29.7':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.29.7':
+ resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.7':
+ resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.7':
+ resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
+ engines: {node: '>=6.9.0'}
+
+ '@emotion/babel-plugin@11.13.5':
+ resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
+
+ '@emotion/cache@11.14.0':
+ resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
+
+ '@emotion/hash@0.9.2':
+ resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
+
+ '@emotion/memoize@0.9.0':
+ resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
+
+ '@emotion/react@11.14.0':
+ resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@emotion/serialize@1.3.3':
+ resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
+
+ '@emotion/sheet@1.4.0':
+ resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
+
+ '@emotion/unitless@0.10.0':
+ resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0':
+ resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@emotion/utils@1.4.2':
+ resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
+
+ '@emotion/weak-memoize@0.4.0':
+ resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.9.1':
+ resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.2':
+ resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.2':
+ resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.5':
+ resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.39.4':
+ resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.1':
+ resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@floating-ui/core@1.7.5':
+ resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
+
+ '@floating-ui/dom@1.7.6':
+ resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
+
+ '@floating-ui/react-dom@2.1.8':
+ resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/react@0.26.28':
+ resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.11':
+ resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
+
+ '@headlessui/react@2.2.10':
+ resolution: {integrity: sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^18 || ^19 || ^19.0.0-rc
+
+ '@heroicons/react@2.2.0':
+ resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==}
+ peerDependencies:
+ react: '>= 16 || ^19.0.0-rc'
+
+ '@hono/node-server@1.19.14':
+ resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
+ '@humanfs/core@0.19.2':
+ resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.8':
+ resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/types@0.15.0':
+ resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@inquirer/ansi@2.0.7':
+ resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+
+ '@inquirer/confirm@6.1.1':
+ resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/core@11.2.1':
+ resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/figures@2.0.7':
+ resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+
+ '@inquirer/type@4.0.7':
+ resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==}
+ engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@internationalized/date@3.12.2':
+ resolution: {integrity: sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==}
+
+ '@internationalized/number@3.6.7':
+ resolution: {integrity: sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==}
+
+ '@internationalized/string@3.2.9':
+ resolution: {integrity: sha512-kzP/M/mbQxODlmOt4bIQZ2SBVUWUSqMLXooXixnX7noche8WHaQcA+nwFN1K2KCF/cp+LDUhcJsCicwkvhD1pg==}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@modelcontextprotocol/sdk@1.29.0':
+ resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@cfworker/json-schema': ^4.1.1
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ '@cfworker/json-schema':
+ optional: true
+
+ '@mswjs/interceptors@0.41.9':
+ resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==}
+ engines: {node: '>=18'}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@open-draft/deferred-promise@2.2.0':
+ resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
+
+ '@open-draft/deferred-promise@3.0.0':
+ resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==}
+
+ '@open-draft/logger@0.3.0':
+ resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
+
+ '@open-draft/until@2.1.0':
+ resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
+
+ '@radix-ui/number@1.1.2':
+ resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==}
+
+ '@radix-ui/primitive@1.1.4':
+ resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==}
+
+ '@radix-ui/react-accordion@1.2.14':
+ resolution: {integrity: sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-arrow@1.1.10':
+ resolution: {integrity: sha512-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-checkbox@1.3.5':
+ resolution: {integrity: sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collapsible@1.1.14':
+ resolution: {integrity: sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collection@1.1.10':
+ resolution: {integrity: sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-compose-refs@1.1.3':
+ resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context@1.1.4':
+ resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-direction@1.1.2':
+ resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.13':
+ resolution: {integrity: sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-focus-guards@1.1.4':
+ resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.10':
+ resolution: {integrity: sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-id@1.1.2':
+ resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-label@2.1.10':
+ resolution: {integrity: sha512-ib0zvq2ZsAqKm5tRnqGJn3vOxSgIts5ToxsXT0q1S/GfLD1Zj7UOEnkw8u2w6sRmn47djpQWuSU1DCL1R29/yw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popover@1.1.17':
+ resolution: {integrity: sha512-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popper@1.3.1':
+ resolution: {integrity: sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.12':
+ resolution: {integrity: sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-presence@1.1.6':
+ resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.1.6':
+ resolution: {integrity: sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-select@2.3.1':
+ resolution: {integrity: sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slot@1.3.0':
+ resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-callback-ref@1.1.2':
+ resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.2.3':
+ resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-effect-event@0.0.3':
+ resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.2':
+ resolution: {integrity: sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.2':
+ resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.2':
+ resolution: {integrity: sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.2':
+ resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.2':
+ resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-visually-hidden@1.2.6':
+ resolution: {integrity: sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/rect@1.1.2':
+ resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==}
+
+ '@react-aria/focus@3.22.1':
+ resolution: {integrity: sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/interactions@3.28.1':
+ resolution: {integrity: sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-types/shared@3.36.0':
+ resolution: {integrity: sha512-DkP/H0C2YjjS7gZWKNqOmU8a16qHPjQNdzMwmTq9SzplM6Iw0kVMTZ0OIoe6FOgGqa+FwMsE2QbPjh/n3g/jXQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
+ '@rollup/rollup-android-arm-eabi@4.62.2':
+ resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.62.2':
+ resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.62.2':
+ resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.62.2':
+ resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.62.2':
+ resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.62.2':
+ resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
+ resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.62.2':
+ resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-arm64-gnu@4.62.2':
+ resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm64-musl@4.62.2':
+ resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-loong64-gnu@4.62.2':
+ resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-loong64-musl@4.62.2':
+ resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.62.2':
+ resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-ppc64-musl@4.62.2':
+ resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.62.2':
+ resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-riscv64-musl@4.62.2':
+ resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-s390x-gnu@4.62.2':
+ resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-gnu@4.62.2':
+ resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-musl@4.62.2':
+ resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-openbsd-x64@4.62.2':
+ resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.62.2':
+ resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.62.2':
+ resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.62.2':
+ resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.62.2':
+ resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.62.2':
+ resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rtsao/scc@1.1.0':
+ resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+
+ '@swc/helpers@0.5.23':
+ resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
+
+ '@tailwindcss/node@4.3.1':
+ resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==}
+
+ '@tailwindcss/oxide-android-arm64@4.3.1':
+ resolution: {integrity: sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.3.1':
+ resolution: {integrity: sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.3.1':
+ resolution: {integrity: sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.3.1':
+ resolution: {integrity: sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1':
+ resolution: {integrity: sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==}
+ engines: {node: '>= 20'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.3.1':
+ resolution: {integrity: sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.3.1':
+ resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.3.1':
+ resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.3.1':
+ resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.3.1':
+ resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.3.1':
+ resolution: {integrity: sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.3.1':
+ resolution: {integrity: sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.3.1':
+ resolution: {integrity: sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==}
+ engines: {node: '>= 20'}
+
+ '@tailwindcss/vite@4.3.1':
+ resolution: {integrity: sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7 || ^8
+
+ '@tanstack/query-core@5.101.1':
+ resolution: {integrity: sha512-Y6Y92dkXtNqx67m2pMSxUsA3zOCwv862JexZRP8/EPwvKXMPu9m8rv43spiXWzOUIggQ3SQApttALStzhA8B4g==}
+
+ '@tanstack/react-query@5.101.1':
+ resolution: {integrity: sha512-ZnONUuQKJe1bJMStXUL1s5uKN9FcfC28j5cK+iDZcdSHtUv1wtin1cGc/Oewhf2Oc4eKY7lggtpvT/AbMmhHew==}
+ peerDependencies:
+ react: ^18 || ^19
+
+ '@tanstack/react-virtual@3.14.3':
+ resolution: {integrity: sha512-k/cnHPVaOfn46hSbiY6n4Dzf4QjCGWSF40zR5QIIYUqPAjpA6TN7InfYmcMiDVQGP2iUn9xsRbAl8u1v3UmeVQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tanstack/virtual-core@3.17.1':
+ resolution: {integrity: sha512-VZyW2Uiml5tmBZwPGrSD3Sz73OxzljQMCmzYHsUTPEuTsERf5xwa+uWb01xEzkz3ZSYTjj8NEb/mKHvgKxyZdA==}
+
+ '@ts-morph/common@0.19.0':
+ resolution: {integrity: sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/json5@0.0.29':
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ '@types/node@22.20.0':
+ resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==}
+
+ '@types/parse-json@4.0.2':
+ resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react-transition-group@4.4.12':
+ resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
+ peerDependencies:
+ '@types/react': '*'
+
+ '@types/react@19.2.17':
+ resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
+
+ '@types/set-cookie-parser@2.4.10':
+ resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==}
+
+ '@types/statuses@2.0.6':
+ resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
+
+ '@typescript-eslint/eslint-plugin@8.62.0':
+ resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.62.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/parser@8.62.0':
+ resolution: {integrity: sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/project-service@8.62.0':
+ resolution: {integrity: sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/scope-manager@8.62.0':
+ resolution: {integrity: sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.62.0':
+ resolution: {integrity: sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/type-utils@8.62.0':
+ resolution: {integrity: sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/types@8.62.0':
+ resolution: {integrity: sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.62.0':
+ resolution: {integrity: sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/utils@8.62.0':
+ resolution: {integrity: sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/visitor-keys@8.62.0':
+ resolution: {integrity: sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ accepts@2.0.0:
+ resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
+ engines: {node: '>= 0.6'}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.17.0:
+ resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv@6.15.0:
+ resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
+
+ ajv@8.20.0:
+ resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-includes@3.1.9:
+ resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlast@1.2.5:
+ resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlastindex@1.2.6:
+ resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.3:
+ resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.3:
+ resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.tosorted@1.1.4:
+ resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ ast-types-flow@0.0.8:
+ resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+
+ ast-types@0.16.1:
+ resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
+ engines: {node: '>=4'}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ axe-core@4.12.1:
+ resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==}
+ engines: {node: '>=4'}
+
+ axios@1.18.1:
+ resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
+
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ babel-plugin-macros@3.1.0:
+ resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
+ engines: {node: '>=10', npm: '>=6'}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ baseline-browser-mapping@2.10.38:
+ resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ bl@5.1.0:
+ resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==}
+
+ body-parser@2.3.0:
+ resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
+ engines: {node: '>=18'}
+
+ brace-expansion@1.1.15:
+ resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
+
+ brace-expansion@2.1.1:
+ resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==}
+
+ brace-expansion@5.0.6:
+ resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
+ engines: {node: 18 || 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.28.4:
+ resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.9:
+ resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ caniuse-lite@1.0.30001799:
+ resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chalk@5.6.2:
+ resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
+ cli-cursor@4.0.0:
+ resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ cli-spinners@2.9.2:
+ resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
+ engines: {node: '>=6'}
+
+ cli-width@4.1.0:
+ resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
+ engines: {node: '>= 12'}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ clone@1.0.4:
+ resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
+ engines: {node: '>=0.8'}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ code-block-writer@12.0.0:
+ resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ content-disposition@1.1.0:
+ resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
+ engines: {node: '>=18'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
+ content-type@2.0.0:
+ resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
+ engines: {node: '>=18'}
+
+ convert-source-map@1.9.0:
+ resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie-signature@1.2.2:
+ resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
+ engines: {node: '>=6.6.0'}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ cookie@1.1.1:
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+ engines: {node: '>=18'}
+
+ cors@2.8.6:
+ resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
+ engines: {node: '>= 0.10'}
+
+ cosmiconfig@7.1.0:
+ resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+ engines: {node: '>=10'}
+
+ cosmiconfig@8.3.6:
+ resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ damerau-levenshtein@1.0.8:
+ resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+
+ data-uri-to-buffer@4.0.1:
+ resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
+ engines: {node: '>= 12'}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ date-fns@3.6.0:
+ resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+
+ debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ defaults@1.0.4:
+ resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ diff@5.2.2:
+ resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==}
+ engines: {node: '>=0.3.1'}
+
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+
+ dom-helpers@5.2.1:
+ resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
+ electron-to-chromium@1.5.377:
+ resolution: {integrity: sha512-cH1jZgJHoezfTnKfKwnScpHywTFVnJUNITDPREFdhNjiuD502+QFpG0Qk7G8jhsV/f+CEAFlIrzP1fT+IMb92g==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
+ enhanced-resolve@5.21.6:
+ resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==}
+ engines: {node: '>=10.13.0'}
+
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+ es-abstract-get@1.0.0:
+ resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==}
+ engines: {node: '>= 0.4'}
+
+ es-abstract@1.24.2:
+ resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-iterator-helpers@1.3.3:
+ resolution: {integrity: sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==}
+ engines: {node: '>= 0.4'}
+
+ es-object-atoms@1.1.2:
+ resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-shim-unscopables@1.1.0:
+ resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.1:
+ resolution: {integrity: sha512-CxN9N56HYfd2m/acc/NOFrZQsN9kU4eh+2kk6A707Kz1krH8tKmfrs5RnftB8WNX80T0NS7vSQsDOlg23diR2g==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-import-resolver-node@0.3.10:
+ resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==}
+
+ eslint-module-utils@2.13.0:
+ resolution: {integrity: sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+
+ eslint-plugin-import@2.32.0:
+ resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+
+ eslint-plugin-jsx-a11y@6.10.2:
+ resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
+
+ eslint-plugin-react-hooks@5.2.0:
+ resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react-refresh@0.4.26:
+ resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==}
+ peerDependencies:
+ eslint: '>=8.40'
+
+ eslint-plugin-react@7.37.5:
+ resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+ eslint-plugin-simple-import-sort@13.0.0:
+ resolution: {integrity: sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==}
+ peerDependencies:
+ eslint: '>=5.0.0'
+
+ eslint-plugin-unused-imports@4.4.1:
+ resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==}
+ peerDependencies:
+ '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
+ eslint: ^10.0.0 || ^9.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ '@typescript-eslint/eslint-plugin':
+ optional: true
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@5.0.1:
+ resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ eslint@9.39.4:
+ resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ eventsource-parser@3.1.0:
+ resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
+ engines: {node: '>=18.0.0'}
+
+ eventsource@3.0.7:
+ resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
+ engines: {node: '>=18.0.0'}
+
+ execa@7.2.0:
+ resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
+ engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
+
+ express-rate-limit@8.5.2:
+ resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ express: '>= 4.11'
+
+ express@5.2.1:
+ resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
+ engines: {node: '>= 18'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fast-string-truncated-width@3.0.3:
+ resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==}
+
+ fast-string-width@3.0.2:
+ resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
+
+ fast-uri@3.1.2:
+ resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==}
+
+ fast-wrap-ansi@0.2.2:
+ resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
+
+ fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fetch-blob@3.2.0:
+ resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
+ engines: {node: ^12.20 || >= 14.13}
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ finalhandler@2.1.1:
+ resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
+ engines: {node: '>= 18.0.0'}
+
+ find-root@1.1.0:
+ resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.4.2:
+ resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
+
+ follow-redirects@1.16.0:
+ resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ form-data@4.0.6:
+ resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
+ engines: {node: '>= 6'}
+
+ formdata-polyfill@4.0.10:
+ resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
+ engines: {node: '>=12.20.0'}
+
+ forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+
+ framer-motion@12.41.0:
+ resolution: {integrity: sha512-OHAMNiCEON1RDBlRGuulsN5AD8ptMjvk5QWfFmYmBLPZ3zFGIJe60kQucQQf4cez1OzQmjYBWDY+dYfISkUdqg==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
+ fresh@2.0.0:
+ resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+ engines: {node: '>= 0.8'}
+
+ fs-extra@11.3.5:
+ resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==}
+ engines: {node: '>=14.14'}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.2.0:
+ resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+ engines: {node: '>= 0.4'}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
+ get-own-enumerable-keys@1.0.0:
+ resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==}
+ engines: {node: '>=14.16'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@15.15.0:
+ resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
+ engines: {node: '>=18'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ graphql@16.14.2:
+ resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==}
+ engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
+ headers-polyfill@5.0.1:
+ resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==}
+
+ hoist-non-react-statics@3.3.2:
+ resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+
+ hono@4.12.27:
+ resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==}
+ engines: {node: '>=16.9.0'}
+
+ http-errors@2.0.1:
+ resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
+ engines: {node: '>= 0.8'}
+
+ https-proxy-agent@5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+
+ https-proxy-agent@6.2.1:
+ resolution: {integrity: sha512-ONsE3+yfZF2caH5+bJlcddtWqNI3Gvs5A38+ngvljxaBiRXRswym2c7yf8UAeFpRFKjFNHIFEHqR/OLAWJzyiA==}
+ engines: {node: '>= 14'}
+
+ human-signals@4.3.1:
+ resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
+ engines: {node: '>=14.18.0'}
+
+ iconv-lite@0.7.2:
+ resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
+ engines: {node: '>=0.10.0'}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ ignore@7.0.5:
+ resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ ip-address@10.2.0:
+ resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
+ engines: {node: '>= 12'}
+
+ ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.16.2:
+ resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-document.all@1.0.0:
+ resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-interactive@2.0.0:
+ resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
+ engines: {node: '>=12'}
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-node-process@1.2.0:
+ resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-obj@3.0.0:
+ resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==}
+ engines: {node: '>=12'}
+
+ is-promise@4.0.0:
+ resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-regexp@3.1.0:
+ resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
+ engines: {node: '>=12'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-stream@3.0.0:
+ resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-unicode-supported@1.3.0:
+ resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
+ engines: {node: '>=12'}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ iterator.prototype@1.1.5:
+ resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+ engines: {node: '>= 0.4'}
+
+ jiti@2.7.0:
+ resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
+ hasBin: true
+
+ jose@6.2.3:
+ resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.2.0:
+ resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-schema-typed@8.0.2:
+ resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonfile@6.2.1:
+ resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
+
+ jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
+
+ jwt-decode@4.0.0:
+ resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
+ engines: {node: '>=18'}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ kleur@3.0.3:
+ resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+ engines: {node: '>=6'}
+
+ kleur@4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+
+ language-subtag-registry@0.3.23:
+ resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
+
+ language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lightningcss-android-arm64@1.32.0:
+ resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.32.0:
+ resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.32.0:
+ resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.32.0:
+ resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-x64-musl@1.32.0:
+ resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.32.0:
+ resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+ engines: {node: '>= 12.0.0'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ log-symbols@5.1.0:
+ resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==}
+ engines: {node: '>=12'}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lucide-react@0.488.0:
+ resolution: {integrity: sha512-ronlL0MyKut4CEzBY/ai2ZpKPxyWO4jUqdAkm2GNK5Zn3Rj+swDz+3lvyAUXN0PNqPKIX6XM9Xadwz/skLs/pQ==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ media-typer@1.1.0:
+ resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
+ engines: {node: '>= 0.8'}
+
+ memoize-one@6.0.0:
+ resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
+
+ merge-descriptors@2.0.0:
+ resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
+ engines: {node: '>=18'}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@3.0.2:
+ resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
+ engines: {node: '>=18'}
+
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
+ mimic-fn@4.0.0:
+ resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
+ engines: {node: '>=12'}
+
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+
+ minimatch@7.4.9:
+ resolution: {integrity: sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==}
+ engines: {node: '>=10'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ mkdirp@2.1.6:
+ resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ motion-dom@12.41.0:
+ resolution: {integrity: sha512-Lk3J39fOGg6xNr1KRZsN6usDyBf8aP7MEbUPez1VCughHt79OrP7VGqNrPyFL0riaT7WS8t9DRw1M3BHtM/xKw==}
+
+ motion-utils@12.39.0:
+ resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ msw@2.14.6:
+ resolution: {integrity: sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ typescript: '>= 4.8.x'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ mute-stream@3.0.0:
+ resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+
+ nanoid@3.3.15:
+ resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ node-domexception@1.0.0:
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+ engines: {node: '>=10.5.0'}
+ deprecated: Use your platform's native DOMException instead
+
+ node-exports-info@1.6.2:
+ resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==}
+ engines: {node: '>= 0.4'}
+
+ node-fetch@3.3.2:
+ resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ node-releases@2.0.48:
+ resolution: {integrity: sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==}
+ engines: {node: '>=18'}
+
+ npm-run-path@5.3.0:
+ resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ object.entries@1.1.9:
+ resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+ engines: {node: '>= 0.4'}
+
+ object.fromentries@2.0.8:
+ resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+ engines: {node: '>= 0.4'}
+
+ object.groupby@1.0.3:
+ resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
+ engines: {node: '>= 0.4'}
+
+ object.values@1.2.1:
+ resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+ engines: {node: '>= 0.4'}
+
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+
+ onetime@6.0.0:
+ resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
+ engines: {node: '>=12'}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ ora@6.3.1:
+ resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ outvariant@1.4.3:
+ resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
+
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ engines: {node: '>= 0.4'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
+ path-browserify@1.0.1:
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-key@4.0.0:
+ resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+ engines: {node: '>=12'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-to-regexp@6.3.0:
+ resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+
+ path-to-regexp@8.4.2:
+ resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+
+ pkce-challenge@5.0.1:
+ resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
+ engines: {node: '>=16.20.0'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ postcss@8.5.15:
+ resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prompts@2.4.2:
+ resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+ engines: {node: '>= 6'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+
+ proxy-from-env@2.1.0:
+ resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+ engines: {node: '>=10'}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ qs@6.15.2:
+ resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
+ engines: {node: '>=0.6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+
+ raw-body@3.0.2:
+ resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
+ engines: {node: '>= 0.10'}
+
+ react-aria@3.50.0:
+ resolution: {integrity: sha512-S0Os6QZk33fzUAKu1QLT9afoUaCBt1ZNdoiq0n2YMVgKIdNIQS8zxiZ8O9hYE6QyDkHKjD6q39LQZ+qaSAIgjw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ react-dom@19.2.7:
+ resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==}
+ peerDependencies:
+ react: ^19.2.7
+
+ react-hook-form@7.80.0:
+ resolution: {integrity: sha512-4P+fk6oXsxY+6xSj7Euhc2sumQD8zQqCuVHoJwoyp9EchP+IUW9OESB7uHFJOKsIBQ4MQqYE84INJFqUCYNoOg==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
+ react-icons@5.6.0:
+ resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==}
+ peerDependencies:
+ react: '*'
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.7.2:
+ resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-router-dom@7.18.0:
+ resolution: {integrity: sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.18.0:
+ resolution: {integrity: sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ react-select@5.10.2:
+ resolution: {integrity: sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react-stately@3.48.0:
+ resolution: {integrity: sha512-ImicSAG+lTotAe5izcs1fz49Zk48w7pDusqYg04WaPhCoej8BJ24soMu3iLXIrsi273s4P1gZrYGrqReMfgEEA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-transition-group@4.4.5:
+ resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+ peerDependencies:
+ react: '>=16.6.0'
+ react-dom: '>=16.6.0'
+
+ react@19.2.7:
+ resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==}
+ engines: {node: '>=0.10.0'}
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ recast@0.23.11:
+ resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
+ engines: {node: '>= 4'}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve@1.22.12:
+ resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ resolve@2.0.0-next.7:
+ resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ restore-cursor@4.0.0:
+ resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ rettime@0.11.11:
+ resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rollup@4.62.2:
+ resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ router@2.2.0:
+ resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
+ engines: {node: '>= 18'}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ safe-array-concat@1.1.4:
+ resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ send@1.2.1:
+ resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
+ engines: {node: '>= 18'}
+
+ serve-static@2.2.1:
+ resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
+ engines: {node: '>= 18'}
+
+ set-cookie-parser@2.7.2:
+ resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
+
+ set-cookie-parser@3.1.0:
+ resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==}
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+ shadcn@2.10.0:
+ resolution: {integrity: sha512-/zxjmHGbaYVFtI6bUridFVV7VFStIv3vU/w1h7xenhz7KRzc9pqHsyFvcExZprG7dlA5kW9knRgv8+Cl/H7w9w==}
+ hasBin: true
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.1:
+ resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.1:
+ resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
+ engines: {node: '>= 0.4'}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ statuses@2.0.2:
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+ engines: {node: '>= 0.8'}
+
+ stdin-discarder@0.1.0:
+ resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ strict-event-emitter@0.5.1:
+ resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string.prototype.includes@2.0.1:
+ resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.repeat@1.0.0:
+ resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+ string.prototype.trim@1.2.11:
+ resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.10:
+ resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ stringify-object@5.0.0:
+ resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==}
+ engines: {node: '>=14.16'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
+ engines: {node: '>=12'}
+
+ strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+
+ strip-final-newline@3.0.0:
+ resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
+ engines: {node: '>=12'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ stylis@4.2.0:
+ resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ sweetalert2@11.26.25:
+ resolution: {integrity: sha512-+hunCOJdJ6FLj04T9YSLvvZXRjsvIkTeTKP2e4VF8CaBias961BTnWiSFAy7F/CM5eq3QK2Rraoc5Gzftslvkg==}
+
+ swiper@12.2.0:
+ resolution: {integrity: sha512-K8uXsBZU6ME97Ia3xbBge8IRCnR1lOmIILzvY/jGVic7dSTQ530s3uO8RvXbPUtkkXLWIwmZLRPbtDxRWVAFdg==}
+ engines: {node: '>= 4.7.0'}
+
+ tabbable@6.5.0:
+ resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==}
+
+ tagged-tag@1.0.0:
+ resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
+ engines: {node: '>=20'}
+
+ tailwind-merge@3.6.0:
+ resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
+
+ tailwindcss@4.3.1:
+ resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==}
+
+ tapable@2.3.3:
+ resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
+ engines: {node: '>=6'}
+
+ tiny-invariant@1.3.3:
+ resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ tldts-core@7.4.4:
+ resolution: {integrity: sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==}
+
+ tldts@7.4.4:
+ resolution: {integrity: sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==}
+ hasBin: true
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
+ tough-cookie@6.0.1:
+ resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
+ engines: {node: '>=16'}
+
+ ts-api-utils@2.5.0:
+ resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ ts-morph@18.0.0:
+ resolution: {integrity: sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==}
+
+ tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+
+ tsconfig-paths@4.2.0:
+ resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
+ engines: {node: '>=6'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tw-animate-css@1.4.0:
+ resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ type-fest@5.7.0:
+ resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==}
+ engines: {node: '>=20'}
+
+ type-is@2.1.0:
+ resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
+ engines: {node: '>= 18'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.8:
+ resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==}
+ engines: {node: '>= 0.4'}
+
+ typescript-eslint@8.62.0:
+ resolution: {integrity: sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ typescript@5.7.3:
+ resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
+ until-async@3.0.2:
+ resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-isomorphic-layout-effect@1.2.1:
+ resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ vite@6.4.3:
+ resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ wcwidth@1.0.1:
+ resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
+
+ web-streams-polyfill@3.3.3:
+ resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
+ engines: {node: '>= 8'}
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-typed-array@1.1.22:
+ resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yaml@1.10.3:
+ resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==}
+ engines: {node: '>= 6'}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.3:
+ resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==}
+ engines: {node: '>=12'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod-to-json-schema@3.25.2:
+ resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
+ peerDependencies:
+ zod: ^3.25.28 || ^4
+
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
+snapshots:
+
+ '@antfu/ni@23.3.1': {}
+
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.29.7': {}
+
+ '@babel/core@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
+ '@babel/helpers': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.7':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-annotate-as-pure@7.29.7':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@babel/helper-compilation-targets@7.29.7':
+ dependencies:
+ '@babel/compat-data': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
+ browserslist: 4.28.4
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-annotate-as-pure': 7.29.7
+ '@babel/helper-member-expression-to-functions': 7.29.7
+ '@babel/helper-optimise-call-expression': 7.29.7
+ '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.29.7
+ '@babel/traverse': 7.29.7
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-globals@7.29.7': {}
+
+ '@babel/helper-member-expression-to-functions@7.29.7':
+ dependencies:
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-imports@7.29.7':
+ dependencies:
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-optimise-call-expression@7.29.7':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@babel/helper-plugin-utils@7.29.7': {}
+
+ '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-member-expression-to-functions': 7.29.7
+ '@babel/helper-optimise-call-expression': 7.29.7
+ '@babel/traverse': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.29.7':
+ dependencies:
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-string-parser@7.29.7': {}
+
+ '@babel/helper-validator-identifier@7.29.7': {}
+
+ '@babel/helper-validator-option@7.29.7': {}
+
+ '@babel/helpers@7.29.7':
+ dependencies:
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+
+ '@babel/parser@7.29.7':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-annotate-as-pure': 7.29.7
+ '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.29.7
+ '@babel/helper-skip-transparent-expression-wrappers': 7.29.7
+ '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/runtime@7.29.7': {}
+
+ '@babel/template@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+
+ '@babel/traverse@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-globals': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.7':
+ dependencies:
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+
+ '@emotion/babel-plugin@11.13.5':
+ dependencies:
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/runtime': 7.29.7
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/serialize': 1.3.3
+ babel-plugin-macros: 3.1.0
+ convert-source-map: 1.9.0
+ escape-string-regexp: 4.0.0
+ find-root: 1.1.0
+ source-map: 0.5.7
+ stylis: 4.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/cache@11.14.0':
+ dependencies:
+ '@emotion/memoize': 0.9.0
+ '@emotion/sheet': 1.4.0
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ stylis: 4.2.0
+
+ '@emotion/hash@0.9.2': {}
+
+ '@emotion/memoize@0.9.0': {}
+
+ '@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/cache': 11.14.0
+ '@emotion/serialize': 1.3.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.7)
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ hoist-non-react-statics: 3.3.2
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/serialize@1.3.3':
+ dependencies:
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/unitless': 0.10.0
+ '@emotion/utils': 1.4.2
+ csstype: 3.2.3
+
+ '@emotion/sheet@1.4.0': {}
+
+ '@emotion/unitless@0.10.0': {}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+
+ '@emotion/utils@1.4.2': {}
+
+ '@emotion/weak-memoize@0.4.0': {}
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))':
+ dependencies:
+ eslint: 9.39.4(jiti@2.7.0)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.21.2':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3
+ minimatch: 3.1.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.5':
+ dependencies:
+ ajv: 6.15.0
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.2.0
+ minimatch: 3.1.5
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.39.4': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
+ '@floating-ui/core@1.7.5':
+ dependencies:
+ '@floating-ui/utils': 0.2.11
+
+ '@floating-ui/dom@1.7.6':
+ dependencies:
+ '@floating-ui/core': 1.7.5
+ '@floating-ui/utils': 0.2.11
+
+ '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@floating-ui/dom': 1.7.6
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
+ '@floating-ui/react@0.26.28(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@floating-ui/utils': 0.2.11
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ tabbable: 6.5.0
+
+ '@floating-ui/utils@0.2.11': {}
+
+ '@headlessui/react@2.2.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@floating-ui/react': 0.26.28(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@react-aria/focus': 3.22.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@react-aria/interactions': 3.28.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@tanstack/react-virtual': 3.14.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ use-sync-external-store: 1.6.0(react@19.2.7)
+
+ '@heroicons/react@2.2.0(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+
+ '@hono/node-server@1.19.14(hono@4.12.27)':
+ dependencies:
+ hono: 4.12.27
+
+ '@humanfs/core@0.19.2':
+ dependencies:
+ '@humanfs/types': 0.15.0
+
+ '@humanfs/node@0.16.8':
+ dependencies:
+ '@humanfs/core': 0.19.2
+ '@humanfs/types': 0.15.0
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanfs/types@0.15.0': {}
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@inquirer/ansi@2.0.7': {}
+
+ '@inquirer/confirm@6.1.1(@types/node@22.20.0)':
+ dependencies:
+ '@inquirer/core': 11.2.1(@types/node@22.20.0)
+ '@inquirer/type': 4.0.7(@types/node@22.20.0)
+ optionalDependencies:
+ '@types/node': 22.20.0
+
+ '@inquirer/core@11.2.1(@types/node@22.20.0)':
+ dependencies:
+ '@inquirer/ansi': 2.0.7
+ '@inquirer/figures': 2.0.7
+ '@inquirer/type': 4.0.7(@types/node@22.20.0)
+ cli-width: 4.1.0
+ fast-wrap-ansi: 0.2.2
+ mute-stream: 3.0.0
+ signal-exit: 4.1.0
+ optionalDependencies:
+ '@types/node': 22.20.0
+
+ '@inquirer/figures@2.0.7': {}
+
+ '@inquirer/type@4.0.7(@types/node@22.20.0)':
+ optionalDependencies:
+ '@types/node': 22.20.0
+
+ '@internationalized/date@3.12.2':
+ dependencies:
+ '@swc/helpers': 0.5.23
+
+ '@internationalized/number@3.6.7':
+ dependencies:
+ '@swc/helpers': 0.5.23
+
+ '@internationalized/string@3.2.9':
+ dependencies:
+ '@swc/helpers': 0.5.23
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
+ dependencies:
+ '@hono/node-server': 1.19.14(hono@4.12.27)
+ ajv: 8.20.0
+ ajv-formats: 3.0.1(ajv@8.20.0)
+ content-type: 1.0.5
+ cors: 2.8.6
+ cross-spawn: 7.0.6
+ eventsource: 3.0.7
+ eventsource-parser: 3.1.0
+ express: 5.2.1
+ express-rate-limit: 8.5.2(express@5.2.1)
+ hono: 4.12.27
+ jose: 6.2.3
+ json-schema-typed: 8.0.2
+ pkce-challenge: 5.0.1
+ raw-body: 3.0.2
+ zod: 3.25.76
+ zod-to-json-schema: 3.25.2(zod@3.25.76)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@mswjs/interceptors@0.41.9':
+ dependencies:
+ '@open-draft/deferred-promise': 2.2.0
+ '@open-draft/logger': 0.3.0
+ '@open-draft/until': 2.1.0
+ is-node-process: 1.2.0
+ outvariant: 1.4.3
+ strict-event-emitter: 0.5.1
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
+
+ '@open-draft/deferred-promise@2.2.0': {}
+
+ '@open-draft/deferred-promise@3.0.0': {}
+
+ '@open-draft/logger@0.3.0':
+ dependencies:
+ is-node-process: 1.2.0
+ outvariant: 1.4.3
+
+ '@open-draft/until@2.1.0': {}
+
+ '@radix-ui/number@1.1.2': {}
+
+ '@radix-ui/primitive@1.1.4': {}
+
+ '@radix-ui/react-accordion@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.4
+ '@radix-ui/react-collapsible': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-arrow@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-checkbox@1.3.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.4
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-collapsible@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.4
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-collection@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-context@1.1.4(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-direction@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-dismissable-layer@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.4
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-escape-keydown': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-focus-scope@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-label@2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-popover@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.4
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7)
+ aria-hidden: 1.2.6
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-popper@1.3.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-arrow': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/rect': 1.1.2
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-portal@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-presence@1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-primitive@2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-select@2.3.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/number': 1.1.2
+ '@radix-ui/primitive': 1.1.4
+ '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ aria-hidden: 1.2.6
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7)
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-escape-keydown@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-previous@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-rect@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@radix-ui/rect': 1.1.2
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-use-size@1.1.2(@types/react@19.2.17)(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ '@radix-ui/react-visually-hidden@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+ '@types/react-dom': 19.2.3(@types/react@19.2.17)
+
+ '@radix-ui/rect@1.1.2': {}
+
+ '@react-aria/focus@3.22.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@swc/helpers': 0.5.23
+ react: 19.2.7
+ react-aria: 3.50.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react-dom: 19.2.7(react@19.2.7)
+
+ '@react-aria/interactions@3.28.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@react-types/shared': 3.36.0(react@19.2.7)
+ '@swc/helpers': 0.5.23
+ react: 19.2.7
+ react-aria: 3.50.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ react-dom: 19.2.7(react@19.2.7)
+
+ '@react-types/shared@3.36.0(react@19.2.7)':
+ dependencies:
+ react: 19.2.7
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+ '@rollup/rollup-android-arm-eabi@4.62.2':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.62.2':
+ optional: true
+
+ '@rtsao/scc@1.1.0': {}
+
+ '@swc/helpers@0.5.23':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.3.1':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.21.6
+ jiti: 2.7.0
+ lightningcss: 1.32.0
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.3.1
+
+ '@tailwindcss/oxide-android-arm64@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.3.1':
+ optional: true
+
+ '@tailwindcss/oxide@4.3.1':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.3.1
+ '@tailwindcss/oxide-darwin-arm64': 4.3.1
+ '@tailwindcss/oxide-darwin-x64': 4.3.1
+ '@tailwindcss/oxide-freebsd-x64': 4.3.1
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.1
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.3.1
+ '@tailwindcss/oxide-linux-arm64-musl': 4.3.1
+ '@tailwindcss/oxide-linux-x64-gnu': 4.3.1
+ '@tailwindcss/oxide-linux-x64-musl': 4.3.1
+ '@tailwindcss/oxide-wasm32-wasi': 4.3.1
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1
+ '@tailwindcss/oxide-win32-x64-msvc': 4.3.1
+
+ '@tailwindcss/vite@4.3.1(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0))':
+ dependencies:
+ '@tailwindcss/node': 4.3.1
+ '@tailwindcss/oxide': 4.3.1
+ tailwindcss: 4.3.1
+ vite: 6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
+
+ '@tanstack/query-core@5.101.1': {}
+
+ '@tanstack/react-query@5.101.1(react@19.2.7)':
+ dependencies:
+ '@tanstack/query-core': 5.101.1
+ react: 19.2.7
+
+ '@tanstack/react-virtual@3.14.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+ dependencies:
+ '@tanstack/virtual-core': 3.17.1
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
+ '@tanstack/virtual-core@3.17.1': {}
+
+ '@ts-morph/common@0.19.0':
+ dependencies:
+ fast-glob: 3.3.3
+ minimatch: 7.4.9
+ mkdirp: 2.1.6
+ path-browserify: 1.0.1
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@types/estree@1.0.9': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/json5@0.0.29': {}
+
+ '@types/node@22.20.0':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/parse-json@4.0.2': {}
+
+ '@types/react-dom@19.2.3(@types/react@19.2.17)':
+ dependencies:
+ '@types/react': 19.2.17
+
+ '@types/react-transition-group@4.4.12(@types/react@19.2.17)':
+ dependencies:
+ '@types/react': 19.2.17
+
+ '@types/react@19.2.17':
+ dependencies:
+ csstype: 3.2.3
+
+ '@types/set-cookie-parser@2.4.10':
+ dependencies:
+ '@types/node': 22.20.0
+
+ '@types/statuses@2.0.6': {}
+
+ '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ '@typescript-eslint/parser': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ '@typescript-eslint/scope-manager': 8.62.0
+ '@typescript-eslint/type-utils': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ '@typescript-eslint/utils': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ '@typescript-eslint/visitor-keys': 8.62.0
+ eslint: 9.39.4(jiti@2.7.0)
+ ignore: 7.0.5
+ natural-compare: 1.4.0
+ ts-api-utils: 2.5.0(typescript@5.7.3)
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.62.0
+ '@typescript-eslint/types': 8.62.0
+ '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.7.3)
+ '@typescript-eslint/visitor-keys': 8.62.0
+ debug: 4.4.3
+ eslint: 9.39.4(jiti@2.7.0)
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.62.0(typescript@5.7.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@5.7.3)
+ '@typescript-eslint/types': 8.62.0
+ debug: 4.4.3
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.62.0':
+ dependencies:
+ '@typescript-eslint/types': 8.62.0
+ '@typescript-eslint/visitor-keys': 8.62.0
+
+ '@typescript-eslint/tsconfig-utils@8.62.0(typescript@5.7.3)':
+ dependencies:
+ typescript: 5.7.3
+
+ '@typescript-eslint/type-utils@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.62.0
+ '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.7.3)
+ '@typescript-eslint/utils': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ debug: 4.4.3
+ eslint: 9.39.4(jiti@2.7.0)
+ ts-api-utils: 2.5.0(typescript@5.7.3)
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.62.0': {}
+
+ '@typescript-eslint/typescript-estree@8.62.0(typescript@5.7.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.62.0(typescript@5.7.3)
+ '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@5.7.3)
+ '@typescript-eslint/types': 8.62.0
+ '@typescript-eslint/visitor-keys': 8.62.0
+ debug: 4.4.3
+ minimatch: 10.2.5
+ semver: 7.8.5
+ tinyglobby: 0.2.17
+ ts-api-utils: 2.5.0(typescript@5.7.3)
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0))
+ '@typescript-eslint/scope-manager': 8.62.0
+ '@typescript-eslint/types': 8.62.0
+ '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.7.3)
+ eslint: 9.39.4(jiti@2.7.0)
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.62.0':
+ dependencies:
+ '@typescript-eslint/types': 8.62.0
+ eslint-visitor-keys: 5.0.1
+
+ '@vitejs/plugin-react@4.7.0(vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0))':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7)
+ '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ accepts@2.0.0:
+ dependencies:
+ mime-types: 3.0.2
+ negotiator: 1.0.0
+
+ acorn-jsx@5.3.2(acorn@8.17.0):
+ dependencies:
+ acorn: 8.17.0
+
+ acorn@8.17.0: {}
+
+ agent-base@6.0.2:
+ dependencies:
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ agent-base@7.1.4: {}
+
+ ajv-formats@3.0.1(ajv@8.20.0):
+ optionalDependencies:
+ ajv: 8.20.0
+
+ ajv@6.15.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ajv@8.20.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.2
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ argparse@2.0.1: {}
+
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
+ aria-query@5.3.2: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ array-includes@3.1.9:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ is-string: 1.1.1
+ math-intrinsics: 1.1.0
+
+ array.prototype.findlast@1.2.5:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.findlastindex@1.2.6:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flat@1.3.3:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flatmap@1.3.3:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.tosorted@1.1.4:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.1.0
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ ast-types-flow@0.0.8: {}
+
+ ast-types@0.16.1:
+ dependencies:
+ tslib: 2.8.1
+
+ async-function@1.0.0: {}
+
+ asynckit@0.4.0: {}
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ axe-core@4.12.1: {}
+
+ axios@1.18.1:
+ dependencies:
+ follow-redirects: 1.16.0
+ form-data: 4.0.6
+ https-proxy-agent: 5.0.1
+ proxy-from-env: 2.1.0
+ transitivePeerDependencies:
+ - debug
+ - supports-color
+
+ axobject-query@4.1.0: {}
+
+ babel-plugin-macros@3.1.0:
+ dependencies:
+ '@babel/runtime': 7.29.7
+ cosmiconfig: 7.1.0
+ resolve: 1.22.12
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@4.0.4: {}
+
+ base64-js@1.5.1: {}
+
+ baseline-browser-mapping@2.10.38: {}
+
+ bl@5.1.0:
+ dependencies:
+ buffer: 6.0.3
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
+ body-parser@2.3.0:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 2.0.0
+ debug: 4.4.3
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ on-finished: 2.4.1
+ qs: 6.15.2
+ raw-body: 3.0.2
+ type-is: 2.1.0
+ transitivePeerDependencies:
+ - supports-color
+
+ brace-expansion@1.1.15:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.1.1:
+ dependencies:
+ balanced-match: 1.0.2
+
+ brace-expansion@5.0.6:
+ dependencies:
+ balanced-match: 4.0.4
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.28.4:
+ dependencies:
+ baseline-browser-mapping: 2.10.38
+ caniuse-lite: 1.0.30001799
+ electron-to-chromium: 1.5.377
+ node-releases: 2.0.48
+ update-browserslist-db: 1.2.3(browserslist@4.28.4)
+
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
+ bytes@3.1.2: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.9:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ caniuse-lite@1.0.30001799: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chalk@5.6.2: {}
+
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
+ cli-cursor@4.0.0:
+ dependencies:
+ restore-cursor: 4.0.0
+
+ cli-spinners@2.9.2: {}
+
+ cli-width@4.1.0: {}
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ clone@1.0.4: {}
+
+ clsx@2.1.1: {}
+
+ code-block-writer@12.0.0: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ commander@10.0.1: {}
+
+ concat-map@0.0.1: {}
+
+ content-disposition@1.1.0: {}
+
+ content-type@1.0.5: {}
+
+ content-type@2.0.0: {}
+
+ convert-source-map@1.9.0: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie-signature@1.2.2: {}
+
+ cookie@0.7.2: {}
+
+ cookie@1.1.1: {}
+
+ cors@2.8.6:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
+ cosmiconfig@7.1.0:
+ dependencies:
+ '@types/parse-json': 4.0.2
+ import-fresh: 3.3.1
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ yaml: 1.10.3
+
+ cosmiconfig@8.3.6(typescript@5.7.3):
+ dependencies:
+ import-fresh: 3.3.1
+ js-yaml: 4.2.0
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ optionalDependencies:
+ typescript: 5.7.3
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ csstype@3.2.3: {}
+
+ damerau-levenshtein@1.0.8: {}
+
+ data-uri-to-buffer@4.0.1: {}
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ date-fns@3.6.0: {}
+
+ debug@3.2.7:
+ dependencies:
+ ms: 2.1.3
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ deep-is@0.1.4: {}
+
+ deepmerge@4.3.1: {}
+
+ defaults@1.0.4:
+ dependencies:
+ clone: 1.0.4
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ delayed-stream@1.0.0: {}
+
+ depd@2.0.0: {}
+
+ detect-libc@2.1.2: {}
+
+ detect-node-es@1.1.0: {}
+
+ diff@5.2.2: {}
+
+ doctrine@2.1.0:
+ dependencies:
+ esutils: 2.0.3
+
+ dom-helpers@5.2.1:
+ dependencies:
+ '@babel/runtime': 7.29.7
+ csstype: 3.2.3
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ ee-first@1.1.1: {}
+
+ electron-to-chromium@1.5.377: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ encodeurl@2.0.0: {}
+
+ enhanced-resolve@5.21.6:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.3
+
+ error-ex@1.3.4:
+ dependencies:
+ is-arrayish: 0.2.1
+
+ es-abstract-get@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ is-callable: 1.2.7
+ object-inspect: 1.13.4
+
+ es-abstract@1.24.2:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.1
+ function.prototype.name: 1.2.0
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.4
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.4
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.11
+ string.prototype.trimend: 1.0.10
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.8
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.22
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-iterator-helpers@1.3.3:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.1.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ iterator.prototype: 1.1.5
+ math-intrinsics: 1.1.0
+
+ es-object-atoms@1.1.2:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.4
+
+ es-shim-unscopables@1.1.0:
+ dependencies:
+ hasown: 2.0.4
+
+ es-to-primitive@1.3.1:
+ dependencies:
+ es-abstract-get: 1.0.0
+ es-errors: 1.3.0
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ escalade@3.2.0: {}
+
+ escape-html@1.0.3: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-import-resolver-node@0.3.10:
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.16.2
+ resolve: 2.0.0-next.7
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-module-utils@2.13.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ debug: 3.2.7
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ eslint: 9.39.4(jiti@2.7.0)
+ eslint-import-resolver-node: 0.3.10
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ '@rtsao/scc': 1.1.0
+ array-includes: 3.1.9
+ array.prototype.findlastindex: 1.2.6
+ array.prototype.flat: 1.3.3
+ array.prototype.flatmap: 1.3.3
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 9.39.4(jiti@2.7.0)
+ eslint-import-resolver-node: 0.3.10
+ eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4(jiti@2.7.0))
+ hasown: 2.0.4
+ is-core-module: 2.16.2
+ is-glob: 4.0.3
+ minimatch: 3.1.5
+ object.fromentries: 2.0.8
+ object.groupby: 1.0.3
+ object.values: 1.2.1
+ semver: 6.3.1
+ string.prototype.trimend: 1.0.10
+ tsconfig-paths: 3.15.0
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ aria-query: 5.3.2
+ array-includes: 3.1.9
+ array.prototype.flatmap: 1.3.3
+ ast-types-flow: 0.0.8
+ axe-core: 4.12.1
+ axobject-query: 4.1.0
+ damerau-levenshtein: 1.0.8
+ emoji-regex: 9.2.2
+ eslint: 9.39.4(jiti@2.7.0)
+ hasown: 2.0.4
+ jsx-ast-utils: 3.3.5
+ language-tags: 1.0.9
+ minimatch: 3.1.5
+ object.fromentries: 2.0.8
+ safe-regex-test: 1.1.0
+ string.prototype.includes: 2.0.1
+
+ eslint-plugin-react-hooks@5.2.0(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ eslint: 9.39.4(jiti@2.7.0)
+
+ eslint-plugin-react-refresh@0.4.26(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ eslint: 9.39.4(jiti@2.7.0)
+
+ eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.findlast: 1.2.5
+ array.prototype.flatmap: 1.3.3
+ array.prototype.tosorted: 1.1.4
+ doctrine: 2.1.0
+ es-iterator-helpers: 1.3.3
+ eslint: 9.39.4(jiti@2.7.0)
+ estraverse: 5.3.0
+ hasown: 2.0.4
+ jsx-ast-utils: 3.3.5
+ minimatch: 3.1.5
+ object.entries: 1.1.9
+ object.fromentries: 2.0.8
+ object.values: 1.2.1
+ prop-types: 15.8.1
+ resolve: 2.0.0-next.7
+ semver: 6.3.1
+ string.prototype.matchall: 4.0.12
+ string.prototype.repeat: 1.0.0
+
+ eslint-plugin-simple-import-sort@13.0.0(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ eslint: 9.39.4(jiti@2.7.0)
+
+ eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0)):
+ dependencies:
+ eslint: 9.39.4(jiti@2.7.0)
+ optionalDependencies:
+ '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint-visitor-keys@5.0.1: {}
+
+ eslint@9.39.4(jiti@2.7.0):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.21.2
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.5
+ '@eslint/js': 9.39.4
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.8
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.9
+ ajv: 6.15.0
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.7.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.5
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 2.7.0
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.17.0
+ acorn-jsx: 5.3.2(acorn@8.17.0)
+ eslint-visitor-keys: 4.2.1
+
+ esprima@4.0.1: {}
+
+ esquery@1.7.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ etag@1.8.1: {}
+
+ eventsource-parser@3.1.0: {}
+
+ eventsource@3.0.7:
+ dependencies:
+ eventsource-parser: 3.1.0
+
+ execa@7.2.0:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 4.3.1
+ is-stream: 3.0.0
+ merge-stream: 2.0.0
+ npm-run-path: 5.3.0
+ onetime: 6.0.0
+ signal-exit: 3.0.7
+ strip-final-newline: 3.0.0
+
+ express-rate-limit@8.5.2(express@5.2.1):
+ dependencies:
+ express: 5.2.1
+ ip-address: 10.2.0
+
+ express@5.2.1:
+ dependencies:
+ accepts: 2.0.0
+ body-parser: 2.3.0
+ content-disposition: 1.1.0
+ content-type: 1.0.5
+ cookie: 0.7.2
+ cookie-signature: 1.2.2
+ debug: 4.4.3
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 2.1.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ merge-descriptors: 2.0.0
+ mime-types: 3.0.2
+ on-finished: 2.4.1
+ once: 1.4.0
+ parseurl: 1.3.3
+ proxy-addr: 2.0.7
+ qs: 6.15.2
+ range-parser: 1.2.1
+ router: 2.2.0
+ send: 1.2.1
+ serve-static: 2.2.1
+ statuses: 2.0.2
+ type-is: 2.1.0
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fast-string-truncated-width@3.0.3: {}
+
+ fast-string-width@3.0.2:
+ dependencies:
+ fast-string-truncated-width: 3.0.3
+
+ fast-uri@3.1.2: {}
+
+ fast-wrap-ansi@0.2.2:
+ dependencies:
+ fast-string-width: 3.0.2
+
+ fastq@1.20.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fdir@6.5.0(picomatch@4.0.4):
+ optionalDependencies:
+ picomatch: 4.0.4
+
+ fetch-blob@3.2.0:
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 3.3.3
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ finalhandler@2.1.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ find-root@1.1.0: {}
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.4.2
+ keyv: 4.5.4
+
+ flatted@3.4.2: {}
+
+ follow-redirects@1.16.0: {}
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ form-data@4.0.6:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.4
+ mime-types: 2.1.35
+
+ formdata-polyfill@4.0.10:
+ dependencies:
+ fetch-blob: 3.2.0
+
+ forwarded@0.2.0: {}
+
+ framer-motion@12.41.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ motion-dom: 12.41.0
+ motion-utils: 12.39.0
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
+ fresh@2.0.0: {}
+
+ fs-extra@11.3.5:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.1
+ universalify: 2.0.1
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.2.0:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+ hasown: 2.0.4
+ is-callable: 1.2.7
+ is-document.all: 1.0.0
+
+ functions-have-names@1.2.3: {}
+
+ generator-function@2.0.1: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.4
+ math-intrinsics: 1.1.0
+
+ get-nonce@1.0.1: {}
+
+ get-own-enumerable-keys@1.0.0: {}
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.2
+
+ get-stream@6.0.1: {}
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ globals@14.0.0: {}
+
+ globals@15.15.0: {}
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ graphql@16.14.2: {}
+
+ has-bigints@1.1.0: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
+ headers-polyfill@5.0.1:
+ dependencies:
+ '@types/set-cookie-parser': 2.4.10
+ set-cookie-parser: 3.1.0
+
+ hoist-non-react-statics@3.3.2:
+ dependencies:
+ react-is: 16.13.1
+
+ hono@4.12.27: {}
+
+ http-errors@2.0.1:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.2
+ toidentifier: 1.0.1
+
+ https-proxy-agent@5.0.1:
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@6.2.1:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ human-signals@4.3.1: {}
+
+ iconv-lite@0.7.2:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ieee754@1.2.1: {}
+
+ ignore@5.3.2: {}
+
+ ignore@7.0.5: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ inherits@2.0.4: {}
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.4
+ side-channel: 1.1.1
+
+ ip-address@10.2.0: {}
+
+ ipaddr.js@1.9.1: {}
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-arrayish@0.2.1: {}
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.16.2:
+ dependencies:
+ hasown: 2.0.4
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-document.all@1.0.0:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-generator-function@1.1.2:
+ dependencies:
+ call-bound: 1.0.4
+ generator-function: 2.0.1
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-interactive@2.0.0: {}
+
+ is-map@2.0.3: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-node-process@1.2.0: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-obj@3.0.0: {}
+
+ is-promise@4.0.0: {}
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.4
+
+ is-regexp@3.1.0: {}
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-stream@3.0.0: {}
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.22
+
+ is-unicode-supported@1.3.0: {}
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ isarray@2.0.5: {}
+
+ isexe@2.0.0: {}
+
+ iterator.prototype@1.1.5:
+ dependencies:
+ define-data-property: 1.1.4
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ has-symbols: 1.1.0
+ set-function-name: 2.0.2
+
+ jiti@2.7.0: {}
+
+ jose@6.2.3: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.2.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-parse-even-better-errors@2.3.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json-schema-typed@8.0.2: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@1.0.2:
+ dependencies:
+ minimist: 1.2.8
+
+ json5@2.2.3: {}
+
+ jsonfile@6.2.1:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ jsx-ast-utils@3.3.5:
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.flat: 1.3.3
+ object.assign: 4.1.7
+ object.values: 1.2.1
+
+ jwt-decode@4.0.0: {}
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ kleur@3.0.3: {}
+
+ kleur@4.1.5: {}
+
+ language-subtag-registry@0.3.23: {}
+
+ language-tags@1.0.9:
+ dependencies:
+ language-subtag-registry: 0.3.23
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lightningcss-android-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.32.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.32.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ optional: true
+
+ lightningcss@1.32.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.32.0
+ lightningcss-darwin-arm64: 1.32.0
+ lightningcss-darwin-x64: 1.32.0
+ lightningcss-freebsd-x64: 1.32.0
+ lightningcss-linux-arm-gnueabihf: 1.32.0
+ lightningcss-linux-arm64-gnu: 1.32.0
+ lightningcss-linux-arm64-musl: 1.32.0
+ lightningcss-linux-x64-gnu: 1.32.0
+ lightningcss-linux-x64-musl: 1.32.0
+ lightningcss-win32-arm64-msvc: 1.32.0
+ lightningcss-win32-x64-msvc: 1.32.0
+
+ lines-and-columns@1.2.4: {}
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.merge@4.6.2: {}
+
+ log-symbols@5.1.0:
+ dependencies:
+ chalk: 5.6.2
+ is-unicode-supported: 1.3.0
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lucide-react@0.488.0(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ math-intrinsics@1.1.0: {}
+
+ media-typer@1.1.0: {}
+
+ memoize-one@6.0.0: {}
+
+ merge-descriptors@2.0.0: {}
+
+ merge-stream@2.0.0: {}
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.2
+
+ mime-db@1.52.0: {}
+
+ mime-db@1.54.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime-types@3.0.2:
+ dependencies:
+ mime-db: 1.54.0
+
+ mimic-fn@2.1.0: {}
+
+ mimic-fn@4.0.0: {}
+
+ minimatch@10.2.5:
+ dependencies:
+ brace-expansion: 5.0.6
+
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.15
+
+ minimatch@7.4.9:
+ dependencies:
+ brace-expansion: 2.1.1
+
+ minimist@1.2.8: {}
+
+ mkdirp@2.1.6: {}
+
+ motion-dom@12.41.0:
+ dependencies:
+ motion-utils: 12.39.0
+
+ motion-utils@12.39.0: {}
+
+ ms@2.1.3: {}
+
+ msw@2.14.6(@types/node@22.20.0)(typescript@5.7.3):
+ dependencies:
+ '@inquirer/confirm': 6.1.1(@types/node@22.20.0)
+ '@mswjs/interceptors': 0.41.9
+ '@open-draft/deferred-promise': 3.0.0
+ '@types/statuses': 2.0.6
+ cookie: 1.1.1
+ graphql: 16.14.2
+ headers-polyfill: 5.0.1
+ is-node-process: 1.2.0
+ outvariant: 1.4.3
+ path-to-regexp: 6.3.0
+ picocolors: 1.1.1
+ rettime: 0.11.11
+ statuses: 2.0.2
+ strict-event-emitter: 0.5.1
+ tough-cookie: 6.0.1
+ type-fest: 5.7.0
+ until-async: 3.0.2
+ yargs: 17.7.3
+ optionalDependencies:
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - '@types/node'
+
+ mute-stream@3.0.0: {}
+
+ nanoid@3.3.15: {}
+
+ natural-compare@1.4.0: {}
+
+ negotiator@1.0.0: {}
+
+ node-domexception@1.0.0: {}
+
+ node-exports-info@1.6.2:
+ dependencies:
+ array.prototype.flatmap: 1.3.3
+ es-errors: 1.3.0
+ object.entries: 1.1.9
+ semver: 6.3.1
+
+ node-fetch@3.3.2:
+ dependencies:
+ data-uri-to-buffer: 4.0.1
+ fetch-blob: 3.2.0
+ formdata-polyfill: 4.0.10
+
+ node-releases@2.0.48: {}
+
+ npm-run-path@5.3.0:
+ dependencies:
+ path-key: 4.0.0
+
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ object.entries@1.1.9:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ object.fromentries@2.0.8:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-object-atoms: 1.1.2
+
+ object.groupby@1.0.3:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+
+ object.values@1.2.1:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ onetime@5.1.2:
+ dependencies:
+ mimic-fn: 2.1.0
+
+ onetime@6.0.0:
+ dependencies:
+ mimic-fn: 4.0.0
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ ora@6.3.1:
+ dependencies:
+ chalk: 5.6.2
+ cli-cursor: 4.0.0
+ cli-spinners: 2.9.2
+ is-interactive: 2.0.0
+ is-unicode-supported: 1.3.0
+ log-symbols: 5.1.0
+ stdin-discarder: 0.1.0
+ strip-ansi: 7.2.0
+ wcwidth: 1.0.1
+
+ outvariant@1.4.3: {}
+
+ own-keys@1.0.1:
+ dependencies:
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ error-ex: 1.3.4
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ parseurl@1.3.3: {}
+
+ path-browserify@1.0.1: {}
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ path-key@4.0.0: {}
+
+ path-parse@1.0.7: {}
+
+ path-to-regexp@6.3.0: {}
+
+ path-to-regexp@8.4.2: {}
+
+ path-type@4.0.0: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.2: {}
+
+ picomatch@4.0.4: {}
+
+ pkce-challenge@5.0.1: {}
+
+ possible-typed-array-names@1.1.0: {}
+
+ postcss@8.5.15:
+ dependencies:
+ nanoid: 3.3.15
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prelude-ls@1.2.1: {}
+
+ prompts@2.4.2:
+ dependencies:
+ kleur: 3.0.3
+ sisteransi: 1.0.5
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ proxy-addr@2.0.7:
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+
+ proxy-from-env@2.1.0: {}
+
+ punycode@2.3.1: {}
+
+ qs@6.15.2:
+ dependencies:
+ side-channel: 1.1.1
+
+ queue-microtask@1.2.3: {}
+
+ range-parser@1.2.1: {}
+
+ raw-body@3.0.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ unpipe: 1.0.0
+
+ react-aria@3.50.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ '@internationalized/date': 3.12.2
+ '@internationalized/number': 3.6.7
+ '@internationalized/string': 3.2.9
+ '@react-types/shared': 3.36.0(react@19.2.7)
+ '@swc/helpers': 0.5.23
+ aria-hidden: 1.2.6
+ clsx: 2.1.1
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ react-stately: 3.48.0(react@19.2.7)
+ use-sync-external-store: 1.6.0(react@19.2.7)
+
+ react-dom@19.2.7(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+ scheduler: 0.27.0
+
+ react-hook-form@7.80.0(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+
+ react-icons@5.6.0(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+
+ react-is@16.13.1: {}
+
+ react-refresh@0.17.0: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+ react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+ react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7)
+ react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.7)
+ use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.7)
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ react-router-dom@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+
+ react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ cookie: 1.1.1
+ react: 19.2.7
+ set-cookie-parser: 2.7.2
+ optionalDependencies:
+ react-dom: 19.2.7(react@19.2.7)
+
+ react-select@5.10.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@emotion/cache': 11.14.0
+ '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7)
+ '@floating-ui/dom': 1.7.6
+ '@types/react-transition-group': 4.4.12(@types/react@19.2.17)
+ memoize-one: 6.0.0
+ prop-types: 15.8.1
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+ react-transition-group: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.17)(react@19.2.7)
+ transitivePeerDependencies:
+ - '@types/react'
+ - supports-color
+
+ react-stately@3.48.0(react@19.2.7):
+ dependencies:
+ '@internationalized/date': 3.12.2
+ '@internationalized/number': 3.6.7
+ '@internationalized/string': 3.2.9
+ '@react-types/shared': 3.36.0(react@19.2.7)
+ '@swc/helpers': 0.5.23
+ react: 19.2.7
+ use-sync-external-store: 1.6.0(react@19.2.7)
+
+ react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.2.7
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ dependencies:
+ '@babel/runtime': 7.29.7
+ dom-helpers: 5.2.1
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 19.2.7
+ react-dom: 19.2.7(react@19.2.7)
+
+ react@19.2.7: {}
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ recast@0.23.11:
+ dependencies:
+ ast-types: 0.16.1
+ esprima: 4.0.1
+ source-map: 0.6.1
+ tiny-invariant: 1.3.3
+ tslib: 2.8.1
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ require-directory@2.1.1: {}
+
+ require-from-string@2.0.2: {}
+
+ resolve-from@4.0.0: {}
+
+ resolve@1.22.12:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.2
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ resolve@2.0.0-next.7:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.2
+ node-exports-info: 1.6.2
+ object-keys: 1.1.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ restore-cursor@4.0.0:
+ dependencies:
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+
+ rettime@0.11.11: {}
+
+ reusify@1.1.0: {}
+
+ rollup@4.62.2:
+ dependencies:
+ '@types/estree': 1.0.9
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.62.2
+ '@rollup/rollup-android-arm64': 4.62.2
+ '@rollup/rollup-darwin-arm64': 4.62.2
+ '@rollup/rollup-darwin-x64': 4.62.2
+ '@rollup/rollup-freebsd-arm64': 4.62.2
+ '@rollup/rollup-freebsd-x64': 4.62.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.62.2
+ '@rollup/rollup-linux-arm-musleabihf': 4.62.2
+ '@rollup/rollup-linux-arm64-gnu': 4.62.2
+ '@rollup/rollup-linux-arm64-musl': 4.62.2
+ '@rollup/rollup-linux-loong64-gnu': 4.62.2
+ '@rollup/rollup-linux-loong64-musl': 4.62.2
+ '@rollup/rollup-linux-ppc64-gnu': 4.62.2
+ '@rollup/rollup-linux-ppc64-musl': 4.62.2
+ '@rollup/rollup-linux-riscv64-gnu': 4.62.2
+ '@rollup/rollup-linux-riscv64-musl': 4.62.2
+ '@rollup/rollup-linux-s390x-gnu': 4.62.2
+ '@rollup/rollup-linux-x64-gnu': 4.62.2
+ '@rollup/rollup-linux-x64-musl': 4.62.2
+ '@rollup/rollup-openbsd-x64': 4.62.2
+ '@rollup/rollup-openharmony-arm64': 4.62.2
+ '@rollup/rollup-win32-arm64-msvc': 4.62.2
+ '@rollup/rollup-win32-ia32-msvc': 4.62.2
+ '@rollup/rollup-win32-x64-gnu': 4.62.2
+ '@rollup/rollup-win32-x64-msvc': 4.62.2
+ fsevents: 2.3.3
+
+ router@2.2.0:
+ dependencies:
+ debug: 4.4.3
+ depd: 2.0.0
+ is-promise: 4.0.0
+ parseurl: 1.3.3
+ path-to-regexp: 8.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ safe-array-concat@1.1.4:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ safer-buffer@2.1.2: {}
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ semver@7.8.5: {}
+
+ send@1.2.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ mime-types: 3.0.2
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ serve-static@2.2.1:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 1.2.1
+ transitivePeerDependencies:
+ - supports-color
+
+ set-cookie-parser@2.7.2: {}
+
+ set-cookie-parser@3.1.0: {}
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+
+ setprototypeof@1.2.0: {}
+
+ shadcn@2.10.0(@types/node@22.20.0)(typescript@5.7.3):
+ dependencies:
+ '@antfu/ni': 23.3.1
+ '@babel/core': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7)
+ '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76)
+ commander: 10.0.1
+ cosmiconfig: 8.3.6(typescript@5.7.3)
+ deepmerge: 4.3.1
+ diff: 5.2.2
+ execa: 7.2.0
+ fast-glob: 3.3.3
+ fs-extra: 11.3.5
+ https-proxy-agent: 6.2.1
+ kleur: 4.1.5
+ msw: 2.14.6(@types/node@22.20.0)(typescript@5.7.3)
+ node-fetch: 3.3.2
+ ora: 6.3.1
+ postcss: 8.5.15
+ prompts: 2.4.2
+ recast: 0.23.11
+ stringify-object: 5.0.0
+ ts-morph: 18.0.0
+ tsconfig-paths: 4.2.0
+ zod: 3.25.76
+ zod-to-json-schema: 3.25.2(zod@3.25.76)
+ transitivePeerDependencies:
+ - '@cfworker/json-schema'
+ - '@types/node'
+ - supports-color
+ - typescript
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.1
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ signal-exit@3.0.7: {}
+
+ signal-exit@4.1.0: {}
+
+ sisteransi@1.0.5: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map@0.5.7: {}
+
+ source-map@0.6.1: {}
+
+ statuses@2.0.2: {}
+
+ stdin-discarder@0.1.0:
+ dependencies:
+ bl: 5.1.0
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ strict-event-emitter@0.5.1: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string.prototype.includes@2.0.1:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+
+ string.prototype.matchall@4.0.12:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ regexp.prototype.flags: 1.5.4
+ set-function-name: 2.0.2
+ side-channel: 1.1.1
+
+ string.prototype.repeat@1.0.0:
+ dependencies:
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+
+ string.prototype.trim@1.2.11:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-object-atoms: 1.1.2
+ has-property-descriptors: 1.0.2
+ safe-regex-test: 1.1.0
+
+ string.prototype.trimend@1.0.10:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ stringify-object@5.0.0:
+ dependencies:
+ get-own-enumerable-keys: 1.0.0
+ is-obj: 3.0.0
+ is-regexp: 3.1.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.2.0:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ strip-bom@3.0.0: {}
+
+ strip-final-newline@3.0.0: {}
+
+ strip-json-comments@3.1.1: {}
+
+ stylis@4.2.0: {}
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ sweetalert2@11.26.25: {}
+
+ swiper@12.2.0: {}
+
+ tabbable@6.5.0: {}
+
+ tagged-tag@1.0.0: {}
+
+ tailwind-merge@3.6.0: {}
+
+ tailwindcss@4.3.1: {}
+
+ tapable@2.3.3: {}
+
+ tiny-invariant@1.3.3: {}
+
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+
+ tldts-core@7.4.4: {}
+
+ tldts@7.4.4:
+ dependencies:
+ tldts-core: 7.4.4
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ toidentifier@1.0.1: {}
+
+ tough-cookie@6.0.1:
+ dependencies:
+ tldts: 7.4.4
+
+ ts-api-utils@2.5.0(typescript@5.7.3):
+ dependencies:
+ typescript: 5.7.3
+
+ ts-morph@18.0.0:
+ dependencies:
+ '@ts-morph/common': 0.19.0
+ code-block-writer: 12.0.0
+
+ tsconfig-paths@3.15.0:
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
+ tsconfig-paths@4.2.0:
+ dependencies:
+ json5: 2.2.3
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
+ tslib@2.8.1: {}
+
+ tw-animate-css@1.4.0: {}
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ type-fest@5.7.0:
+ dependencies:
+ tagged-tag: 1.0.0
+
+ type-is@2.1.0:
+ dependencies:
+ content-type: 2.0.0
+ media-typer: 1.1.0
+ mime-types: 3.0.2
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.8:
+ dependencies:
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typescript-eslint@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ '@typescript-eslint/parser': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.7.3)
+ '@typescript-eslint/utils': 8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.7.3)
+ eslint: 9.39.4(jiti@2.7.0)
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@5.7.3: {}
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ undici-types@6.21.0: {}
+
+ universalify@2.0.1: {}
+
+ unpipe@1.0.0: {}
+
+ until-async@3.0.2: {}
+
+ update-browserslist-db@1.2.3(browserslist@4.28.4):
+ dependencies:
+ browserslist: 4.28.4
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ use-isomorphic-layout-effect@1.2.1(@types/react@19.2.17)(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.2.7
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.17
+
+ use-sync-external-store@1.6.0(react@19.2.7):
+ dependencies:
+ react: 19.2.7
+
+ util-deprecate@1.0.2: {}
+
+ vary@1.1.2: {}
+
+ vite@6.4.3(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+ postcss: 8.5.15
+ rollup: 4.62.2
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ '@types/node': 22.20.0
+ fsevents: 2.3.3
+ jiti: 2.7.0
+ lightningcss: 1.32.0
+
+ wcwidth@1.0.1:
+ dependencies:
+ defaults: 1.0.4
+
+ web-streams-polyfill@3.3.3: {}
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.2.0
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.2
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.22
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-typed-array@1.1.22:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrappy@1.0.2: {}
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yaml@1.10.3: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.3:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yocto-queue@0.1.0: {}
+
+ zod-to-json-schema@3.25.2(zod@3.25.76):
+ dependencies:
+ zod: 3.25.76
+
+ zod@3.25.76: {}
From 71e455b9c92f3f420d481ac429721ddc17040c3e Mon Sep 17 00:00:00 2001
From: "Antoine D."
Date: Wed, 24 Jun 2026 11:00:38 +0200
Subject: [PATCH 03/42] fix: google jwt type
---
backend/src/services/im_export.service.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/backend/src/services/im_export.service.ts b/backend/src/services/im_export.service.ts
index 2c70456..ba7b756 100644
--- a/backend/src/services/im_export.service.ts
+++ b/backend/src/services/im_export.service.ts
@@ -1,5 +1,4 @@
import { existsSync, mkdirSync, writeFileSync } from "fs";
-import { JWT } from 'google-auth-library';
import { google } from 'googleapis';
import { parse } from "json2csv";
import * as user_service from './user.service';
@@ -9,7 +8,7 @@ import path from 'path';
const keyFilePath = path.resolve(__dirname, '../utils/google_credentials.json');
// Crée une instance JWT en utilisant la clé du service account
-const jwtClient = new JWT({
+const jwtClient = new google.auth.JWT({
keyFile: keyFilePath,
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
From 90a0151757ab61a135c46b070c8964d29b656932 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 24 Jun 2026 13:08:57 +0200
Subject: [PATCH 04/42] fix: remove some any
---
backend/src/controllers/im_export.controller.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/backend/src/controllers/im_export.controller.ts b/backend/src/controllers/im_export.controller.ts
index 60ae264..a65a0cf 100644
--- a/backend/src/controllers/im_export.controller.ts
+++ b/backend/src/controllers/im_export.controller.ts
@@ -163,8 +163,8 @@ export const getUploadedDocumentStatus = async (req: Request, res: Response) =>
Ok(res, {
data: toUploadedDocumentStatus(category, latestDocument),
});
- } catch (err: any) {
- if (err?.code === "ENOENT") {
+ } catch (err: unknown) {
+ if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
Ok(res, {
data: toUploadedDocumentStatus(category, null),
});
@@ -192,8 +192,8 @@ export const deleteDocument = async (req: Request, res: Response) => {
}
Ok(res, {});
- } catch (err: any) {
- if (err?.code === "ENOENT") {
+ } catch (err: unknown) {
+ if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
Ok(res, {});
return;
}
From d1ae9e22e0b819f1b70cec22de75ce76ac7e15f4 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 24 Jun 2026 13:33:31 +0200
Subject: [PATCH 05/42] fix(import): add missing "_" in "bachelor_ia" item type
filter
---
backend/src/middlewares/multer.middleware.ts | 2 +-
casNew.xml | 20 ++++++++++++++
casOld.xml | 11 ++++++++
integration.utt.fr-key.pem | 28 ++++++++++++++++++++
integration.utt.fr.pem | 24 +++++++++++++++++
5 files changed, 84 insertions(+), 1 deletion(-)
create mode 100644 casNew.xml
create mode 100644 casOld.xml
create mode 100644 integration.utt.fr-key.pem
create mode 100644 integration.utt.fr.pem
diff --git a/backend/src/middlewares/multer.middleware.ts b/backend/src/middlewares/multer.middleware.ts
index 6f4baac..648a871 100644
--- a/backend/src/middlewares/multer.middleware.ts
+++ b/backend/src/middlewares/multer.middleware.ts
@@ -18,7 +18,7 @@ const acceptedMIMETypesByItem: Record> = {
news: {},
plannings: {
tc: [MIMEType.PDF],
- bacheloria: [MIMEType.PDF],
+ bachelor_ia: [MIMEType.PDF],
fise: [MIMEType.PDF],
fisea: [MIMEType.PDF],
master: [MIMEType.PDF],
diff --git a/casNew.xml b/casNew.xml
new file mode 100644
index 0000000..79ae7ff
--- /dev/null
+++ b/casNew.xml
@@ -0,0 +1,20 @@
+
+
+ dodinart
+
+ 10.18.5.22
+ false
+ 2026-04-29T14:15:17.903700123Z
+ false
+ mfa-esupotp
+ EsupOtpAuthenticationHandler
+ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0
+ EsupOtpCredential
+ dodinart
+ EsupOtpAuthenticationHandler
+ unknown
+ 193.50.230.213
+ false
+
+
+
diff --git a/casOld.xml b/casOld.xml
new file mode 100644
index 0000000..5d5a4a8
--- /dev/null
+++ b/casOld.xml
@@ -0,0 +1,11 @@
+
+
+ dodinart
+
+ dodinart
+ arthur.dodin@utt.fr
+ DODIN
+ Arthur
+
+
+
diff --git a/integration.utt.fr-key.pem b/integration.utt.fr-key.pem
new file mode 100644
index 0000000..9715f79
--- /dev/null
+++ b/integration.utt.fr-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCtif9VpeK8oNba
+IfHlIyElLPsCYDLdfcYMNQUyGIyZGNa053rJuf7O5zyClVZb2WzLUXXOGROM4pZi
+qE5D5CN0hbGLW2oPB4tuLhdl5CTse1iOqT0QYT4Uh+0760gug97lvDGaZlWVclEc
+YpXsaz0qEFOORkGzm0E/7Q83dTkBEjI4yT0RpgM444DOGhaqpCM0hItUPaaIgHj9
+jwi8Z4leOQWW5edmPL2WoJmCs8z89iKn156e8V3Rudvax5kB8JZNrfKy0t+Dxq8D
+fR/aZwFh2Zwm5VMbvsyiPD9EAJe85dVcmgDJ2msqzA2KXrCpuybXaBQNDvL1F7JQ
+sPnEFj5FAgMBAAECggEAdZRugCVpPszrRdiCAPKQXpPfbninfhLdwR+baZngiUqf
+DutX8p4m2yEpioCMhqcGY6vJ2d57tJVBt465mJ5Wt3huFzHU5mICQqDQEaVGr0PT
+eLWKPjLk0RxXzKVZvspyl82u/iIgEqXl9wmE8y6lvn5ZXqiTk5G54ApKqRSvjt0b
+lpk5qDPcgDeEKHmy/5GY6Xw5fbnohyeU2pROd/5iANRxxhh3zZehV+BE8IvX6m+Y
+rKgQnj+1c3gL9idOUXI4ouHGLQXpUmF7FzHHZxDXjZpIwgnCV5wQHY15Mjraucrl
+10gs7f0BUgh3bPGVMJOLoZP2yy7swPZJmMPwZKhDAQKBgQDM/KhXlyZ5GfWEbWt5
+4z1E3JaAlkqrrnwh6pvJASU8usSSXHp9kdPg9/UvWIgXTox1LS0gIiRg7g3v3nmv
+UXCM65vpkvZ6dSoLmxG7y1Y4A9bFPBC95BaMiRRCa5XwIuVmlIMmZ6ruqLS2eTnL
+gGHUSmDMpsRNriQeWpjmi+9gVQKBgQDYudowoENqXYpEms7gtZJ/RLDAc4joz9LI
+MuIKFwOEDv9JCPbCjngKxIbEHvVl/hF5eaWk2P8lsGAT2GcR1ZQ/xxBMytWlvdAl
+5/b8sdQfAGtE5K6BysumjIVqObAEQcLQ3ta+B3PR7n2Vh/LRRT+bt4g72dccRcIB
+UmvfPIeWMQKBgChDpKl31iXJdJFrkMXjXeCN174wR0CLyHQ9Chakc/UG1p/NLH5H
+y6+P5QhEwo6ZbjuCATAjpLOpbvFj6NEIFSyJBxoNNP7+zqBy+DvECA5+qowZbUxv
+ZgJ61pDpYw1FPXw1xcEgcdHpL338N98CO7UgWv038K01fIC92PTIHd/1AoGBALXZ
+DBktACQppLD37IpkEC41ptF0n/YpG0XcXAn3UX3nT5EqslKBVHxEdoftKh+QVX1F
+8xUk9sHbAmLke2ddfG0fTLACqc3OPO7xei6Bz+jLYzaFY1+Il+SBmBiPmv+XZi34
+LNt6SVZm9H0Ze0bZAgxYrTj7CiGw7p5JWDYSBfqhAoGAXInJFpLndIQPRylTya7w
+fJRZQ6vuLCN/BHdCu7DNPMt7kGGym4pJ+88DXrUkBmvoJ/ivzpuHY8MJXhp7cE5L
+tsU43N6mfqBgeYP/utdMNKw6istk0d+4xSLiyF8fuBVIinDHny1oxV9Ww5TScTPe
+Dg+Kdon5jypMQzPFEpB9xdY=
+-----END PRIVATE KEY-----
diff --git a/integration.utt.fr.pem b/integration.utt.fr.pem
new file mode 100644
index 0000000..53c55da
--- /dev/null
+++ b/integration.utt.fr.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEFjCCAn6gAwIBAgIRALShiUHfdwNDV0zIXo5dFUAwDQYJKoZIhvcNAQELBQAw
+XzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMRowGAYDVQQLDBFkb2Rp
+bkBBcnRodXJEb2RpbjEhMB8GA1UEAwwYbWtjZXJ0IGRvZGluQEFydGh1ckRvZGlu
+MB4XDTI2MDQyOTEzMzkzNloXDTI4MDcyOTEzMzkzNlowRTEnMCUGA1UEChMebWtj
+ZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMRowGAYDVQQLDBFkb2RpbkBBcnRo
+dXJEb2RpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2J/1Wl4ryg
+1toh8eUjISUs+wJgMt19xgw1BTIYjJkY1rTnesm5/s7nPIKVVlvZbMtRdc4ZE4zi
+lmKoTkPkI3SFsYtbag8Hi24uF2XkJOx7WI6pPRBhPhSH7TvrSC6D3uW8MZpmVZVy
+URxilexrPSoQU45GQbObQT/tDzd1OQESMjjJPRGmAzjjgM4aFqqkIzSEi1Q9poiA
+eP2PCLxniV45BZbl52Y8vZagmYKzzPz2IqfXnp7xXdG529rHmQHwlk2t8rLS34PG
+rwN9H9pnAWHZnCblUxu+zKI8P0QAl7zl1VyaAMnaayrMDYpesKm7JtdoFA0O8vUX
+slCw+cQWPkUCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG
+AQUFBwMBMB8GA1UdIwQYMBaAFCiT+Oto6lg8pQlhwjiu0sL8L0xgMB0GA1UdEQQW
+MBSCEmludGVncmF0aW9uLnV0dC5mcjANBgkqhkiG9w0BAQsFAAOCAYEAlip+Dy7G
+UuHwqHE3MkkJZbhmc7Vv1zTa4Lr/HULKGF16egdqU3BDAYD8KrWjQwJgOd/gSATT
+Z0jSRv7AZqKVcl99hTcvDyEWFoHZDPyyjbHr0pwfr3WBtEu5FOaE/ntTqVAX93tQ
+Qy/pWFhJ9Grj6SC1dKc7DV8hgzYdxLt+QwM0t01niPdOsrC63DDcJ2Avxdw/SrCR
+pjFDy2ilP1htmX96KnzO5LDGtl6T4mKEogOIz8Q5xenxWVosgrxVq6xa3tNmcBDh
+if3AtNdWPNDtJdU4+LIVtlHps0t6MEqj+oqPSPpaMTS1iNmjtR5O63+NN8yE44/K
+/oMX0tVx1X3kjRDq9t9vlY2Bq0TBqlfHD2Aw7rzFD9rURQdyBqY8hzfDvoaMUrSo
+rcwubTgJky3d8s6HHSsK4ix0hTbTPO1FrHSfAl4FMg5M/LStfEvtgyUmK6MZDWyY
+yjkxLaVseJsG8D0IFME3yOosaN7eVPMx71Qc+bPOQhgXjNsabC94dcjO
+-----END CERTIFICATE-----
From d0f2cd4c95d921a9281b891ddc9f42bda6aec10f Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 24 Jun 2026 14:19:47 +0200
Subject: [PATCH 06/42] feat: roadbook can be disable by empty env var
---
.../src/components/roadbook/roadbookCard.tsx | 42 ++++++++++++-------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/frontend/src/components/roadbook/roadbookCard.tsx b/frontend/src/components/roadbook/roadbookCard.tsx
index 1d30ca4..456d8c7 100644
--- a/frontend/src/components/roadbook/roadbookCard.tsx
+++ b/frontend/src/components/roadbook/roadbookCard.tsx
@@ -3,7 +3,12 @@ import { Link } from "react-router-dom";
import { Button } from "../ui/button";
import { Card } from "../ui/card";
-export const RoadBookCard = () => (
+export const RoadBookCard = () => {
+
+ const roadbookUrlFrench = import.meta.env.VITE_ROADBOOK_URL_FRENCH;
+ const roadbookUrlEnglish = import.meta.env.VITE_ROADBOOK_URL_ENGLISH;
+
+ return (
@@ -27,19 +32,28 @@ export const RoadBookCard = () => (
-
-
- 🔗
- Accéder à la version Française
-
-
- {/*
-
- English Version
-
- */}
-
An english version will be available soon !
+ {roadbookUrlFrench ? (
+
+
+ 🔗
+ Accéder à la version Française
+
+
+ ) : (
+
+ Le Roadbook n'est pas encore disponible.
+
+ )}
+ {roadbookUrlEnglish ? (
+
+
+ English Version
+
+
+ ) : (
+
An english version will be available soon !
+ )}
-);
+)};
From 7e47caeca26e7b1464e2222bbe80180c2ed34272 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 24 Jun 2026 14:20:00 +0200
Subject: [PATCH 07/42] fix(ci): best control of github env & vars
---
.github/workflows/ci.yaml | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 47fb8aa..a0881c2 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -77,7 +77,7 @@ jobs:
build-front:
runs-on: self-hosted
- if : github.event_name == 'pull_request'
+ if : github.event_name == 'pull_request'
needs:
- lint-front
steps:
@@ -101,6 +101,7 @@ jobs:
deploy-api:
runs-on: self-hosted
if : github.event_name == 'push'
+ environment: ${{ github.ref == 'refs/heads/dev' && 'development' || 'production' }}
needs:
- lint-api
steps:
@@ -128,6 +129,7 @@ jobs:
deploy-front:
runs-on: self-hosted
if : github.event_name == 'push'
+ environment: ${{ github.ref == 'refs/heads/dev' && 'development' || 'production' }}
needs:
- lint-front
steps:
@@ -150,11 +152,11 @@ jobs:
context: ./frontend
push: true
build-args: |
- VITE_API_URL=${{ github.ref == 'refs/heads/prod' && 'https://integration.utt.fr/api' || 'https://integration.dev.uttnetgroup.fr/api' }}
- VITE_SERVICE_URL=${{ github.ref == 'refs/heads/prod' && 'https://integration.utt.fr/' || 'https://integration.dev.uttnetgroup.fr/' }}
- VITE_ANALYTICS_WEBSITE_ID=${{ github.ref == 'refs/heads/prod' && secrets.ANALYTICS_WEBSITE_ID_PROD || secrets.ANALYTICS_WEBSITE_ID_DEV }}
- VITE_ROADBOOK_URL_FRENCH=${{ secrets.ROADBOOK_URL_FRENCH }}
- VITE_ROADBOOK_URL_ENGLISH=${{ secrets.ROADBOOK_URL_ENGLISH }}
- VITE_CAS_LOGIN_URL=${{ secrets.CAS_LOGIN_URL }}
+ VITE_API_URL=${{ vars.API_URL }}
+ VITE_SERVICE_URL=${{ vars.SERVICE_URL }}
+ VITE_ANALYTICS_WEBSITE_ID=${{ secrets.ANALYTICS_WEBSITE_ID }}
+ VITE_ROADBOOK_URL_FRENCH=${{ vars.ROADBOOK_URL_FRENCH }}
+ VITE_ROADBOOK_URL_ENGLISH=${{ vars.ROADBOOK_URL_ENGLISH }}
+ VITE_CAS_LOGIN_URL=${{ vars.CAS_LOGIN_URL }}
tags: |
${{ secrets.REGISTRY_URL }}/integration/front:${{ github.ref == 'refs/heads/prod' && 'prod' || 'dev' }}
From 011574324629a51021910c40e1cdbf9421c33026 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 24 Jun 2026 14:57:04 +0200
Subject: [PATCH 08/42] feat: legals & privacy pages uses ungdev org vars
---
.github/workflows/ci.yaml | 15 +++++
.../src/components/legals/legalsSection.tsx | 60 +++++++++++--------
.../src/components/privacy/privacySection.tsx | 22 ++++---
3 files changed, 64 insertions(+), 33 deletions(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 47fb8aa..f58d461 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -156,5 +156,20 @@ jobs:
VITE_ROADBOOK_URL_FRENCH=${{ secrets.ROADBOOK_URL_FRENCH }}
VITE_ROADBOOK_URL_ENGLISH=${{ secrets.ROADBOOK_URL_ENGLISH }}
VITE_CAS_LOGIN_URL=${{ secrets.CAS_LOGIN_URL }}
+ VITE_ASSOCIATION_ADDRESS=${{ vars.ASSOCIATION_ADDRESS }}
+ VITE_ASSOCIATION_EMAIL=${{ vars.ASSOCIATION_EMAIL }}
+ VITE_ASSOCIATION_NAME=${{ vars.ASSOCIATION_NAME }}
+ VITE_ASSOCIATION_PHONE=${{ vars.ASSOCIATION_PHONE }}
+ VITE_ASSOCIATION_RNA=${{ vars.ASSOCIATION_RNA }}
+ VITE_ASSOCIATION_RCS=${{ vars.ASSOCIATION_RCS }}
+ VITE_BDE_ADDRESS=${{ vars.BDE_ADDRESS }}
+ VITE_BDE_EMAIL=${{ vars.BDE_EMAIL }}
+ VITE_BDE_NAME=${{ vars.BDE_NAME }}
+ VITE_BDE_PHONE=${{ vars.BDE_PHONE }}
+ VITE_BDE_RNA=${{ vars.BDE_RNA }}
+ VITE_BDE_SIRET=${{ vars.BDE_SIRET }}
+ VITE_BDE_SIREN=${{ vars.BDE_SIREN }}
+ VITE_DPO_EMAIL=${{ vars.DPO_EMAIL }}
+ VITE_DPO_NAME=${{ vars.DPO_NAME }}
tags: |
${{ secrets.REGISTRY_URL }}/integration/front:${{ github.ref == 'refs/heads/prod' && 'prod' || 'dev' }}
diff --git a/frontend/src/components/legals/legalsSection.tsx b/frontend/src/components/legals/legalsSection.tsx
index 611ac16..a0b19cc 100644
--- a/frontend/src/components/legals/legalsSection.tsx
+++ b/frontend/src/components/legals/legalsSection.tsx
@@ -1,6 +1,22 @@
import { Link } from "react-router-dom";
-export const LegalsSection = () => (
+export const LegalsSection = () => {
+
+ const ASSOCIATION_ADDRESS = import.meta.env.VITE_ASSOCIATION_ADDRESS;
+ const ASSOCIATION_EMAIL = import.meta.env.VITE_ASSOCIATION_EMAIL;
+ const ASSOCIATION_NAME = import.meta.env.VITE_ASSOCIATION_NAME;
+ const ASSOCIATION_PHONE = import.meta.env.VITE_ASSOCIATION_PHONE;
+ const ASSOCIATION_RNA = import.meta.env.VITE_ASSOCIATION_RNA;
+ const ASSOCIATION_RCS = import.meta.env.VITE_ASSOCIATION_RCS;
+ const BDE_ADDRESS = import.meta.env.VITE_BDE_ADDRESS;
+ const BDE_EMAIL = import.meta.env.VITE_BDE_EMAIL;
+ const BDE_NAME = import.meta.env.VITE_BDE_NAME;
+ const BDE_PHONE = import.meta.env.VITE_BDE_PHONE;
+ const BDE_RNA = import.meta.env.VITE_BDE_RNA;
+ const BDE_SIRET = import.meta.env.VITE_BDE_SIRET;
+ const BDE_SIREN = import.meta.env.VITE_BDE_SIREN;
+
+ return (
@@ -13,28 +29,24 @@ export const LegalsSection = () => (
Editeur du Site
-
BDE UTT
+
{BDE_NAME}
Association loi 1901
-
N° RNA : W103000735
-
N° SIRET : 44838667200019
-
N° SIREN : 448386672
+
N° RNA : {BDE_RNA}
+
N° SIRET : {BDE_SIRET}
+
N° SIREN : {BDE_SIREN}
- Le projet Intégration UTT est porté par l'association BDE UTT.
+ Le projet Intégration UTT est porté par l'association {BDE_NAME}.
@@ -42,22 +54,18 @@ export const LegalsSection = () => (
Propriétaire et Hébergeur
-
UTT Net Group
+
{ASSOCIATION_NAME}
Association loi 1901
-
N° RNA : W103000699
-
N° RCS : 500164249
+
N° RNA : {ASSOCIATION_RNA}
+
N° RCS : {ASSOCIATION_RCS}
@@ -80,17 +88,17 @@ export const LegalsSection = () => (
Droits d'Auteur
- L'ensemble du contenu de ce site est protegé par le droit d'auteur. Sauf mention contraire, les contenus relatifs au projet Integration UTT sont diffusés sous la responsabilité du BDE UTT. Toute reproduction, distribution ou modification est interdite sans autorisation écrite préalable.
+ L'ensemble du contenu de ce site est protegé par le droit d'auteur. Sauf mention contraire, les contenus relatifs au projet Integration UTT sont diffusés sous la responsabilité du {BDE_NAME}. Toute reproduction, distribution ou modification est interdite sans autorisation écrite préalable.
Credits
- Ce site est developpé pour le projet Integration UTT du BDE UTT, avec le support technique de l'association UTT Net Group.
+ Ce site est developpé pour le projet Integration UTT du {BDE_NAME}, avec le support technique de l'association {ASSOCIATION_NAME}.
-);
+)};
diff --git a/frontend/src/components/privacy/privacySection.tsx b/frontend/src/components/privacy/privacySection.tsx
index 9e5dd69..890fa56 100644
--- a/frontend/src/components/privacy/privacySection.tsx
+++ b/frontend/src/components/privacy/privacySection.tsx
@@ -1,4 +1,12 @@
-export const PrivacySection = () => (
+export const PrivacySection = () => {
+
+ const ASSOCIATION_NAME = import.meta.env.VITE_ASSOCIATION_NAME
+ const ASSOCIATION_ADDRESS = import.meta.env.VITE_ASSOCIATION_ADDRESS
+ const BDE_NAME = import.meta.env.VITE_BDE_NAME;
+ const DPO_EMAIL = import.meta.env.VITE_DPO_EMAIL;
+ const DPO_NAME = import.meta.env.VITE_DPO_NAME;
+
+ return (
@@ -10,7 +18,7 @@ export const PrivacySection = () => (
Vie Privée et Données à Caractère Personnel
- A l'Université de Technologie de Troyes et au sein des associations BDE UTT et UTT Net Group, nous respectons votre vie privée. Les données collectées et utilisées par la plateforme Integration UTT sont nécessaires pour la gestion des membres, des inscriptions aux évènements et des services propoés pendant l'intégration.
+ A l'Université de Technologie de Troyes et au sein des associations {BDE_NAME} et {ASSOCIATION_NAME}, nous respectons votre vie privée. Les données collectées et utilisées par la plateforme Integration UTT sont nécessaires pour la gestion des membres, des inscriptions aux évènements et des services propoés pendant l'intégration.
@@ -80,12 +88,12 @@ export const PrivacySection = () => (
Contacter le délégué à la protection des données :{" "}
-
- ung+dpo@utt.fr
+
+ {DPO_EMAIL}
- Par courrier : UTT Net Group, 12 rue Marie Curie, CS 42060, 10004 TROYES CEDEX
+ Par courrier : {ASSOCIATION_NAME}, {ASSOCIATION_ADDRESS}
@@ -96,7 +104,7 @@ export const PrivacySection = () => (
Responsable du Traitement
- Le responsable du traitement des données pour la plateforme Intégration UTT est Arthur Dodin , Président de l'association UTT Net Group.
+ Le responsable du traitement des données pour la plateforme Intégration UTT est {DPO_NAME} , Président de l'association {ASSOCIATION_NAME}.
L'équipe technique et les administrateurs du site pourront accéder aux données dans le cadre de la gestion de la plateforme et du support technique.
@@ -123,4 +131,4 @@ export const PrivacySection = () => (
-);
+)};
From e46e9f7ee522eb7d182075ccdccf35efdc0c5b19 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 24 Jun 2026 15:32:18 +0200
Subject: [PATCH 09/42] fix(ci): add missing args & env in Dockerfile
---
.github/workflows/ci.yaml | 1 -
frontend/Dockerfile | 31 ++++++++++++++++++++++++++++++-
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index ae170ad..899d900 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -173,6 +173,5 @@ jobs:
VITE_BDE_SIREN=${{ vars.BDE_SIREN }}
VITE_DPO_EMAIL=${{ vars.DPO_EMAIL }}
VITE_DPO_NAME=${{ vars.DPO_NAME }}
-
tags: |
${{ secrets.REGISTRY_URL }}/integration/front:${{ github.ref == 'refs/heads/prod' && 'prod' || 'dev' }}
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 28d729c..072d033 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -7,6 +7,21 @@ ARG VITE_API_URL="https://integration.utt.fr/api"
ARG VITE_ANALYTICS_WEBSITE_ID=""
ARG VITE_ROADBOOK_URL_FRENCH=""
ARG VITE_ROADBOOK_URL_ENGLISH=""
+ARG VITE_ASSOCIATION_ADDRESS=""
+ARG VITE_ASSOCIATION_EMAIL=""
+ARG VITE_ASSOCIATION_NAME=""
+ARG VITE_ASSOCIATION_PHONE=""
+ARG VITE_ASSOCIATION_RNA=""
+ARG VITE_ASSOCIATION_RCS=""
+ARG VITE_BDE_ADDRESS=""
+ARG VITE_BDE_EMAIL=""
+ARG VITE_BDE_NAME=""
+ARG VITE_BDE_PHONE=""
+ARG VITE_BDE_RNA=""
+ARG VITE_BDE_SIRET=""
+ARG VITE_BDE_SIREN=""
+ARG VITE_DPO_EMAIL=""
+ARG VITE_DPO_NAME=""
ENV VITE_CAS_LOGIN_URL=${VITE_CAS_LOGIN_URL}
ENV VITE_SERVICE_URL=${VITE_SERVICE_URL}
@@ -14,6 +29,21 @@ ENV VITE_API_URL=${VITE_API_URL}
ENV VITE_ANALYTICS_WEBSITE_ID=${VITE_ANALYTICS_WEBSITE_ID}
ENV VITE_ROADBOOK_URL_FRENCH=${VITE_ROADBOOK_URL_FRENCH}
ENV VITE_ROADBOOK_URL_ENGLISH=${VITE_ROADBOOK_URL_ENGLISH}
+ENV VITE_ASSOCIATION_ADDRESS=${VITE_ASSOCIATION_ADDRESS}
+ENV VITE_ASSOCIATION_EMAIL=${VITE_ASSOCIATION_EMAIL}
+ENV VITE_ASSOCIATION_NAME=${VITE_ASSOCIATION_NAME}
+ENV VITE_ASSOCIATION_PHONE=${VITE_ASSOCIATION_PHONE}
+ENV VITE_ASSOCIATION_RNA=${VITE_ASSOCIATION_RNA}
+ENV VITE_ASSOCIATION_RCS=${VITE_ASSOCIATION_RCS}
+ENV VITE_BDE_ADDRESS=${VITE_BDE_ADDRESS}
+ENV VITE_BDE_EMAIL=${VITE_BDE_EMAIL}
+ENV VITE_BDE_NAME=${VITE_BDE_NAME}
+ENV VITE_BDE_PHONE=${VITE_BDE_PHONE}
+ENV VITE_BDE_RNA=${VITE_BDE_RNA}
+ENV VITE_BDE_SIRET=${VITE_BDE_SIRET}
+ENV VITE_BDE_SIREN=${VITE_BDE_SIREN}
+ENV VITE_DPO_EMAIL=${VITE_DPO_EMAIL}
+ENV VITE_DPO_NAME=${VITE_DPO_NAME}
COPY package.json package-lock.json ./
RUN npm install -g npm@latest
@@ -32,4 +62,3 @@ COPY --from=builder /app/dist ./dist
EXPOSE 4000
CMD ["serve", "-s", "dist", "-l", "4000"]
-
From fb6e98fea72f0517af3c9befb76083f6923e917c Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Wed, 24 Jun 2026 18:52:54 +0200
Subject: [PATCH 10/42] refactor(mail): new templates & notebook features
---
backend/server.ts | 1 +
backend/src/controllers/email.controller.ts | 199 ++++++---
backend/src/controllers/news.controller.ts | 10 +-
backend/src/database/initdb/initUser.ts | 4 +-
backend/src/middlewares/multer.middleware.ts | 4 +
backend/src/services/email.service.ts | 10 +-
.../src/templates/email/attribution-bus.html | 120 ++++++
backend/src/templates/email/custom.html | 46 ++
backend/src/templates/email/notebook.html | 61 +++
backend/src/templates/email/notify-news.html | 22 +
.../email/notify-tent-confirmation.html | 27 ++
.../src/templates/email/reset-password.html | 79 ++++
backend/src/templates/email/welcome.html | 107 +++++
backend/src/utils/emailtemplates.ts | 395 ++----------------
backend/src/utils/secret.ts | 7 +-
frontend/src/components/Admin/adminEmail.tsx | 49 ++-
.../components/Admin/adminExportImport.tsx | 16 +
frontend/src/pages/admin.tsx | 6 +-
.../src/services/requests/email.service.ts | 23 +-
19 files changed, 751 insertions(+), 435 deletions(-)
create mode 100644 backend/src/templates/email/attribution-bus.html
create mode 100644 backend/src/templates/email/custom.html
create mode 100644 backend/src/templates/email/notebook.html
create mode 100644 backend/src/templates/email/notify-news.html
create mode 100644 backend/src/templates/email/notify-tent-confirmation.html
create mode 100644 backend/src/templates/email/reset-password.html
create mode 100644 backend/src/templates/email/welcome.html
diff --git a/backend/server.ts b/backend/server.ts
index d691fef..d1def34 100644
--- a/backend/server.ts
+++ b/backend/server.ts
@@ -63,6 +63,7 @@ async function startServer() {
app.use('/api/tent', authenticateUser, tentRoutes);
app.use('/api/bus', authenticateUser, busRoutes);
app.use("/api/uploads/news", express.static(path.join(__dirname, "/uploads/news")));
+ app.use("/api/uploads/notebooks", express.static(path.join(__dirname, "/uploads/notebooks")));
app.use("/api/uploads/foodmenu", express.static(path.join(__dirname, "/uploads/foodmenu")));
app.use("/api/uploads/plannings", express.static(path.join(__dirname, "/uploads/plannings")));
app.use("/api/exports/bus", express.static(path.join(__dirname, "/exports/bus")));
diff --git a/backend/src/controllers/email.controller.ts b/backend/src/controllers/email.controller.ts
index 0a90833..93780b5 100644
--- a/backend/src/controllers/email.controller.ts
+++ b/backend/src/controllers/email.controller.ts
@@ -1,10 +1,11 @@
import { type Request, type Response } from 'express';
-import sanitizeHtml from 'sanitize-html';
import { sendEmail } from '../services/email.service';
import * as registration_service from '../services/registration.service';
import * as user_service from '../services/user.service';
import * as template from '../utils/emailtemplates';
import { Error, Ok } from '../utils/responses';
+import { email_from, service_url } from '../utils/secret';
+import { getLatestUploadedDocument } from '../utils/uploadDocuments';
export interface EmailOptions {
from: string;
@@ -16,33 +17,115 @@ export interface EmailOptions {
bcc: string[];
}
+type TemplateData = Record;
+
+type TemplateRenderer = {
+ fileName: string;
+ buildData: (data: TemplateData) => TemplateData;
+};
+
+const templateRenderers: Record = {
+ custom: {
+ fileName: 'custom.html',
+ buildData: (data) => {
+ const typedData = data as { title?: string; content?: string };
+ return {
+ title: typedData.title ?? '',
+ content: typedData.content ?? '',
+ };
+ },
+ },
+ templateNotebook: {
+ fileName: template.templateNotebook,
+ buildData: (data) => {
+ const typedData = data as {
+ notebook_fr?: string;
+ notebook_en?: string;
+ };
+ return {
+ notebook_fr: typedData.notebook_fr,
+ notebook_en: typedData.notebook_en
+ };
+ },
+ },
+ templateAttributionBus: {
+ fileName: template.templateAttributionBus,
+ buildData: (data) => {
+ const typedData = data as { bus?: string; time?: string };
+ return { bus: typedData.bus, time: typedData.time };
+ },
+ },
+ templateWelcome: {
+ fileName: template.templateWelcome,
+ buildData: (data) => {
+ const typedData = data as { token?: string };
+ return { token: typedData.token };
+ },
+ },
+ templateNotifyNews: {
+ fileName: template.templateNotifyNews,
+ buildData: (data) => {
+ const typedData = data as { title?: string };
+ return { title: typedData.title };
+ },
+ },
+ templateNotifyTentConfirmation: {
+ fileName: template.templateNotifyTentConfirmation,
+ buildData: (data) => {
+ const typedData = data as { user1?: string; user2?: string; confirmed?: boolean };
+ return {
+ user1: typedData.user1,
+ user2: typedData.user2,
+ confirmed: typedData.confirmed,
+ };
+ },
+ },
+ templateResetPassword: {
+ fileName: template.templateResetPassword,
+ buildData: (data) => {
+ const typedData = data as { resetLink?: string };
+ return { resetLink: typedData.resetLink };
+ },
+ },
+};
+
// Fonction pour générer l'HTML à partir du template
-export const generateEmailHtml = (templateName: string, data: any) => {
- switch (templateName) {
- case 'templateNotebook':
- return template.compileTemplate({ notebook: data.notebook }, template.templateNotebook);
-
- case 'templateAttributionBus':
- return template.compileTemplate({ bus: data.bus, time: data.time }, template.templateAttributionBus);
-
- case 'templateWelcome':
- return template.compileTemplate({ token: data.token }, template.templateWelcome);
-
- case 'templateNotifyNews':
- return template.compileTemplate(
- { title: data.title, description: data.description },
- template.templateNotifyNews
- );
-
- case 'templateNotifyTentConfirmation':
- return template.compileTemplate(
- { user1: data.user1, user2: data.user2, confirmed: data.confirmed },
- template.templateNotifyTentConfirmation
- );
-
- default:
- return null;
+export const generateEmailHtml = (templateName: string, data: TemplateData) => {
+ const renderer = templateRenderers[templateName];
+ if (!renderer) {
+ return null;
}
+
+ return template.compileTemplate(renderer.buildData(data), renderer.fileName);
+};
+
+const defaultPreviewData: Record = {
+ custom: {
+ title: 'Titre de démonstration',
+ content: 'Premier paragraphe.\nDeuxième ligne conservée.\n\nNouvel alinéa.',
+ },
+ templateNotebook: {
+ notebook_fr: `${service_url}api/uploads/notebooks/fr.pdf`,
+ notebook_en: `${service_url}api/uploads/notebooks/en.pdf`
+ },
+ templateAttributionBus: {
+ bus: 'bus',
+ time: '09h00',
+ },
+ templateWelcome: {
+ token: 'preview-token',
+ },
+ templateNotifyNews: {
+ title: 'Titre de démonstration',
+ },
+ templateNotifyTentConfirmation: {
+ user1: 'Utilisateur 1',
+ user2: 'Utilisateur 2',
+ confirmed: true,
+ },
+ templateResetPassword: {
+ resetLink: `${service_url}resetpassword?token=preview-token`,
+ },
};
// Fonction utilitaire pour récupérer les destinataires
@@ -56,14 +139,12 @@ const getRecipients = async (permission: string | undefined, sendTo: string[] |
};
export const handleSendEmail = async (req: Request, res: Response) => {
- const { subject, templateName, permission, sendTo, html } = req.body.payload;
+ const { subject, templateName, permission, sendTo, html, title, content } = req.body.payload;
try {
// Récupérer les destinataires
const recipients = await getRecipients(permission, sendTo);
-
-
if (!recipients.length) {
Error(res, { msg: 'Aucun destinataire trouvé.' });
return;
@@ -72,30 +153,48 @@ export const handleSendEmail = async (req: Request, res: Response) => {
for (const recp of recipients) {
let htmlEmail = '';
- if (templateName !== 'custom') {
-
- if (templateName === "templateWelcome") {
- const user = await user_service.getUserByEmail(recp);
- const token = await registration_service.getRegistrationByUserId(user.id);
- if (!token) continue;
- // Générer le contenu HTML du mail
- htmlEmail = generateEmailHtml(templateName, { token: token });
-
+ if (templateName === 'custom') {
+ htmlEmail = generateEmailHtml('custom', {
+ title: title || subject,
+ content: content || html || '',
+ });
+ } else if (templateName === 'templateWelcome') {
+ const user = await user_service.getUserByEmail(recp);
+ if (!user) {
+ continue;
}
- if (templateName === "templateNotebook") {
- htmlEmail = generateEmailHtml(templateName, { notebook: 'https://drive.google.com/file/d/1Tl8UeILFlAdj9IC2vy3gYXdXCOzD4ugX/view?usp=sharing' });
+
+ const registrationToken = await registration_service.getRegistrationByUserId(user.id);
+ if (!registrationToken) {
+ continue;
}
- if (templateName === "templateAttributionBus") {
- htmlEmail = generateEmailHtml(templateName, { bus: 'bus', time: '09h00' });
+
+ htmlEmail = generateEmailHtml(templateName, { token: registrationToken });
+ } else if (templateName === 'templateNotebook') {
+ const notebook_fr = await getLatestUploadedDocument('notebooks', 'fr');
+ const notebook_en = await getLatestUploadedDocument('notebooks', 'en');
+
+ if (!notebook_fr || !notebook_en) {
+ return Error(res, {
+ msg: 'Cahier de vacances manquant (fr ou en).'
+ });
}
+
+ htmlEmail = generateEmailHtml(templateName, {
+ notebook_fr: `${service_url}api/uploads/notebooks/fr.pdf`,
+ notebook_en: `${service_url}api/uploads/notebooks/en.pdf`
+ });
+ } else {
+ htmlEmail = generateEmailHtml(templateName, defaultPreviewData[templateName] || {});
}
- else {
- htmlEmail = sanitizeHtml(html || '');
+ if (!htmlEmail) {
+ Error(res, { msg: 'Template HTML introuvable ou invalide.' });
+ return;
}
const emailOptions: EmailOptions = {
- from: "integration@utt.fr",
+ from: email_from,
to: [recp],
cc: [],
bcc: [],
@@ -107,7 +206,6 @@ export const handleSendEmail = async (req: Request, res: Response) => {
await sendEmail(emailOptions);
}
-
Ok(res, { msg: 'Email envoyé avec succès !' });
return;
} catch (err) {
@@ -118,11 +216,16 @@ export const handleSendEmail = async (req: Request, res: Response) => {
};
export const handlePreviewEmail = async (req: Request, res: Response) => {
- const { templateName } = req.body;
+ const { templateName, title, content, ...rest } = req.body.payload ?? req.body;
try {
// Générer le contenu HTML pour l'aperçu
- const htmlEmail = generateEmailHtml(templateName, {});
+ const htmlEmail = generateEmailHtml(templateName, {
+ ...(defaultPreviewData[templateName] || {}),
+ ...rest,
+ title,
+ content,
+ });
if (!htmlEmail) {
Error(res, { msg: "Nom de template invalide" });
diff --git a/backend/src/controllers/news.controller.ts b/backend/src/controllers/news.controller.ts
index 0b97ac2..4534bc1 100644
--- a/backend/src/controllers/news.controller.ts
+++ b/backend/src/controllers/news.controller.ts
@@ -4,8 +4,8 @@ import path from "path";
import * as email_service from "../services/email.service";
import * as news_service from "../services/news.service";
import * as user_service from '../services/user.service';
-import * as template from "../utils/emailtemplates";
import { Error, Ok } from "../utils/responses";
+import { generateEmailHtml } from './email.controller';
const toStoredUploadPath = (imageUrl: string) => {
if (!imageUrl) {
@@ -120,8 +120,12 @@ export const publishNews = async (req: Request, res: Response) => {
const news = await news_service.getNewsById(Number(id));
if (sendEmail) {
- // Génération du mail HTML
- const html = template.compileTemplate({ title: news.title }, template.templateNotifyNews);
+ const html = generateEmailHtml('templateNotifyNews', { title: news.title });
+
+ if (!html) {
+ Error(res, { msg: 'Template de notification introuvable.' });
+ return;
+ }
const recipients = news.target === "Tous"
? (await user_service.getUsersAdmin()).map(u => u.email)
diff --git a/backend/src/database/initdb/initUser.ts b/backend/src/database/initdb/initUser.ts
index 1ea653c..48f4fb8 100644
--- a/backend/src/database/initdb/initUser.ts
+++ b/backend/src/database/initdb/initUser.ts
@@ -1,12 +1,12 @@
import { userSchema } from "../../schemas/Basic/user.schema";
import { hashPassword } from "../../services/auth.service";
-import { zimbra_password } from "../../utils/secret";
+import { email_password } from "../../utils/secret";
import { db } from "../db"; // Assurez-vous que votre instance db est correcte
export const initUser = async () => {
const existingUser = await db.select().from(userSchema).limit(1);
- const hashedPassword = await hashPassword(zimbra_password);
+ const hashedPassword = await hashPassword(email_password);
// Si il n'y a pas de ligne existante, insérer une nouvelle ligne
if (existingUser.length === 0) {
diff --git a/backend/src/middlewares/multer.middleware.ts b/backend/src/middlewares/multer.middleware.ts
index 648a871..6b787df 100644
--- a/backend/src/middlewares/multer.middleware.ts
+++ b/backend/src/middlewares/multer.middleware.ts
@@ -16,6 +16,10 @@ const acceptedMIMETypesByItem: Record> = {
menu: [MIMEType.PDF],
},
news: {},
+ notebooks: {
+ fr: [MIMEType.PDF],
+ en: [MIMEType.PDF],
+ },
plannings: {
tc: [MIMEType.PDF],
bachelor_ia: [MIMEType.PDF],
diff --git a/backend/src/services/email.service.ts b/backend/src/services/email.service.ts
index fbe972f..0d6d445 100644
--- a/backend/src/services/email.service.ts
+++ b/backend/src/services/email.service.ts
@@ -1,5 +1,5 @@
import nodemailer from 'nodemailer';
-import { zimbra_host, zimbra_password, zimbra_user } from '../utils/secret';
+import { email_from, email_host, email_password, email_user } from '../utils/secret';
interface EmailOptions {
from: string;
@@ -14,12 +14,12 @@ interface EmailOptions {
export const sendEmail = async (options: EmailOptions): Promise => {
try {
const transporter = nodemailer.createTransport({
- host: zimbra_host,
+ host: email_host,
port: 587,
secure: false,
auth: {
- user: zimbra_user,
- pass: zimbra_password,
+ user: email_user,
+ pass: email_password,
},
tls: {
rejectUnauthorized: false,
@@ -27,7 +27,7 @@ export const sendEmail = async (options: EmailOptions): Promise => {
});
const mailOptions = {
- from: options.from,
+ from: options.from || email_from,
to: options.to ? options.to.join(', ') : '',
subject: options.subject,
text: options.text,
diff --git a/backend/src/templates/email/attribution-bus.html b/backend/src/templates/email/attribution-bus.html
new file mode 100644
index 0000000..ffee593
--- /dev/null
+++ b/backend/src/templates/email/attribution-bus.html
@@ -0,0 +1,120 @@
+
+
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ Le Week-End d'Intégration approhe à grands pas !
+ Si tu reçois ce message c'est que tu pars au WEI (youhouu !), tu trouveras dans celui-ci le bus avec lequel tu vas te rendre sur le lieu pour ce week-end.
+ Fais bien attention à ne pas être en retard sous peine de rater ton bus, ça serait embêtant à la fois pour toi et pour nous.
+ Autre point très important : les essentiels pour le WEI. Tu trouveras ci-dessous un rappel des objets obligatoires à ramener pour passer un bon week-end. Il risque de pleuvoir alors prévoyez bien en conséquence !
+
+ Duvet et matelas gonflable/tapis de sol 🛏️ (si vous n'avez pas de duvet, vous ne partirez pas)
+ Gourde, Tupperware, couverts, gobby (=écocup) 🍴
+ Vêtements : changes pour 2 jours, pull, maillot de bain 👙
+ Manteau imperméable 🧥
+ Affaires salissables : change complet & chaussures (à mettre dès le départ en bus) 🚌
+ Produits d'hygiène : brosse à dent, serviette, nécessaire de toilette 🪥
+ Tongues/crocs pour les douches 🩴
+ Papiers importants : Carte d'identité, CB & liquide, autorisation parentale (pour les mineurs) 💳
+ Ta place au WEI 📩
+ Crème solaire & anti-moustique ☀️
+ De quoi grignoter (prenez un pique-nique à manger avant de prendre le bus, pas dans le bus) 😋
+
+ 🚫 Affaires interdites :
+
+ Boissons autres que de l'eau
+ Substances illicites
+ Armes blanches
+ Déodorant en spray
+
+ Pour rappel, voici la vidéo des indispensables du WEI ici
+ Concernant ton bus, tu as été attribué au bus {{bus}}
+ Maintenant il faut que tu sois présent en amphi de verdure à l'UTT à {{time}}
+ Voilà, toute l'équipe de l'intégration te souhaite un excellent WEI ;)
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+ Cordialement, L'équipe intégration UTT
+ Si vous avez des questions, n'hésitez pas à nous contacter .
+
+
+
+
+
+
+
+ The Integration Weekend is just around the corner!
+ If you are receiving this message, it means you're coming to the WEI (woohoo!). In this email, you'll find the bus you have been assigned to travel to the venue for the weekend.
+ Please make sure not to be late , otherwise you may miss your bus, which would be inconvenient for both you and us.
+ Another very important point: the WEI essentials. Below, you'll find a reminder of the mandatory items you need to bring to have a great weekend. Rain is possible, so make sure to pack accordingly!
+
+ Sleeping bag and inflatable mattress/sleeping mat 🛏️ (if you do not have a sleeping bag, you will not be allowed to leave)
+ Water bottle, Tupperware container, cutlery, gobby (= reusable cup) 🍴
+ Clothing: changes of clothes for 2 days, sweater, swimsuit 👙
+ Waterproof jacket 🧥
+ Clothes you don't mind getting dirty: a full change of clothes & shoes (to be worn from the moment you board the bus) 🚌
+ Toiletries: toothbrush, towel, personal hygiene essentials 🪥
+ Flip-flops/Crocs for the showers 🩴
+ Important documents: ID card, bank card & cash, parental authorization form (for minors) 💳
+ Your WEI ticket 📩
+ Sunscreen & mosquito repellent ☀️
+ Something to snack on (bring a picnic to eat before boarding the bus, not on the bus) 😋
+
+ 🚫 Prohibited items:
+
+ Any drinks other than water
+ Illegal substances
+ Bladed weapons
+ Spray deodorant
+
+ As a reminder, you can find the WEI essentials video here
+ Regarding your bus assignment, you have been assigned to {{bus}}
+ You must now be present at the UTT Green Amphitheater at {{time}}
+ That's it! The entire Integration Team wishes you an amazing WEI ;)
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+ If you have any questions, feel free to contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/templates/email/custom.html b/backend/src/templates/email/custom.html
new file mode 100644
index 0000000..3a16289
--- /dev/null
+++ b/backend/src/templates/email/custom.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Réinitialisation de mot de passe
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ {{title}}
+ {{content}}
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+ Cordialement, L'équipe intégration UTT
+ Si vous avez des questions, n'hésitez pas à nous contacter .
+
+
+
+
+
+
+
+
diff --git a/backend/src/templates/email/notebook.html b/backend/src/templates/email/notebook.html
new file mode 100644
index 0000000..5c3993b
--- /dev/null
+++ b/backend/src/templates/email/notebook.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ Un peu de travail...
+ Si tu reçois ce mail, c'est que tu es sur le point de rejoindre l'UTT et de vivre tes premières années en école supérieure.
+ Mais après toutes ces vacances, il est important de ne pas s'endormir et de vite se remettre au travail !
+ C'est pourquoi l'intégration te propose un cahier de vacances qui te permettra de te remettre à niveau.
+ Toutes les bases y sont revues, de la terminale… jusqu'au CP. À toi de nous prouver que tu en es capable ! Méthodologie et rigueur seront nécessaires pour en venir à bout (et pas mal d'humour également).
+ Ce cahier sera examiné par un jury extrêmement talentueux : des ingénieurs hors pair, ayant déjà prouvé leur valeur lors d'un concours de Ricard sur la plage de Banyuls-sur-Mer.
+ À toi de leur montrer que tu peux égaler leurs compétences ! Ce jury n'hésitera pas à te récompenser pour tes efforts si tu nous renvoies tes réponses à cette adresse mail.
+ Alors si tu veux y participer, tu peux le télécharger juste ici et le renvoyer à clement.duranson@utt.fr avant le dimanche 30 août.
+
+ Cahier de vacances !
+
+ Nous serons présents sur les réseaux tout au long de l'été pour te tenir informé(e), te partager des astuces, et plein d'autres trucs trop cools ! Rejoins le site de l'intégration pour bien être informé des actus ! Tu as reçu dans le premier mail de notre part, un lien pour réinitialiser ton mot de passe et te connecter.
+
+ Accéder au site
+
+ Alors, bon courage à toi, nous sommes impatients de lire tes meilleures réponses.
+ À très vite !
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+ Cordialement, L'équipe intégration UTT
+ Si vous avez des questions, n'hésitez pas à nous contacter .
+
+
+
+
+
+
+
+
diff --git a/backend/src/templates/email/notify-news.html b/backend/src/templates/email/notify-news.html
new file mode 100644
index 0000000..94fc85f
--- /dev/null
+++ b/backend/src/templates/email/notify-news.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Intégration UTT
+
+
+
+
+
+
Intégration UTT
+
+
🗞️ Nouvelle actu !
+
{{title}}
+
👉 Rendez-vous sur le site de l'inté dans l'onglet News pour en savoir plus.
+
+ Accéder au site
+
+
+
+
diff --git a/backend/src/templates/email/notify-tent-confirmation.html b/backend/src/templates/email/notify-tent-confirmation.html
new file mode 100644
index 0000000..a77e7fe
--- /dev/null
+++ b/backend/src/templates/email/notify-tent-confirmation.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+ Intégration UTT
+
+
+
+
+
+
Intégration UTT
+
+
⛺ Mise à jour de ta tente
+
+ La tente entre {{user1}} et {{user2}} a été
+
+ {{#if confirmed}}validée{{else}}invalidée{{/if}}
+ .
+
+
👉 Tu peux consulter l'état de ta tente sur le site de l'inté dans l'onglet Tentes .
+
+ Accéder au site
+
+
+
+
diff --git a/backend/src/templates/email/reset-password.html b/backend/src/templates/email/reset-password.html
new file mode 100644
index 0000000..0be8879
--- /dev/null
+++ b/backend/src/templates/email/reset-password.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+ Réinitialisation de mot de passe
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ Réinitialisation de mot de passe
+ Bonjour,
+ Vous avez demandé à réinitialiser votre mot de passe. Cliquez sur le bouton ci-dessous pour choisir un nouveau mot de passe :
+
+ Réinitialiser mon mot de passe
+
+ Attention, le lien n'est valide que pendant 1h.
+ Si vous n'avez pas demandé cette réinitialisation, veuillez ignorer cet e-mail.
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+ Cordialement, L'équipe intégration UTT
+ Si vous avez des questions, n'hésitez pas à nous contacter .
+
+
+
+
+
+
+
+ Password Reset
+ Hello,
+ You requested to reset your password. Click the button below to choose a new password:
+
+ Reset My Password
+
+ Please note that this link is only valid for 1 hour.
+ If you did not request this password reset, please ignore this email.
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+ If you have any questions, feel free to contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/templates/email/welcome.html b/backend/src/templates/email/welcome.html
new file mode 100644
index 0000000..030870b
--- /dev/null
+++ b/backend/src/templates/email/welcome.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ Salut à toi jeune nouveau !
+ Bravo pour ton admission à l'UTT ! Nous sommes l'équipe d'intégration, des étudiants bénévoles qui préparent minutieusement ton arrivée pour que celle-ci reste inoubliable.
+ Un tas d'événements incroyables, dont la participation est basée sur le volontariat, t'attendent dès le Lundi 31 Août que tu arrives en 1ère année, en 3ème année, en master ou en Bachelor !
+ Tout est fait pour que tu t'éclates et que tu rencontres les personnes qui feront de ton passage à l'UTT un moment inoubliable. Mais avant toute chose, il faut te préparer.
+ Assure-toi de réaliser les tâches suivantes avant ton arrivée :
+
+ 1. Se connecter sur le site de l'intégration
+ Pour pouvoir te connecter au site de l'intégration il te suffit de changer ton mot de passe en cliquant sur ce lien suivant :
+
+ Changer ton mot de passe
+
+ Attention, ce lien est valable uniquement une fois !
+ Une fois cela fait, tu pourras te connecter à ton compte et y retrouver toutes les informations relatives aux événements de la semaine via le lien suivant :
+
+ https://integration.utt.fr
+
+
+ 2. Parrainage
+ Lorsque tu arrives à l'UTT, un.e étudiant.e plus ancien.ne devient ton parrain ou ta marraine. Il ou elle sera ton contact privilégié pour découvrir l'école mais aussi la vie étudiante troyenne et répondre à toutes tes questions que ce soit sur l'UTT, les logements, les cours, la vie à Troyes,...
+ Pour t'attribuer quelqu'un qui te correspond au mieux on t'invite à remplir
+ ce questionnaire
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+ Cordialement, L'équipe intégration UTT
+ Si vous avez des questions, n'hésitez pas à nous contacter .
+
+
+
+
+
+
+
+ Hello there, new arrival!
+ Congratulations on being admitted to UTT! We are the integration team, a group of volunteer students who carefully prepare your arrival so that it becomes truly unforgettable.
+ A lot of incredible events, all based on voluntary participation, are waiting for you starting on Monday, August 31st , whether you are entering your first year, third year, a master's program, or a bachelor's program!
+ Everything is set up so you can have fun and meet the people who will make your time at UTT unforgettable. But first, you need to get ready.
+ Make sure you complete the following tasks before you arrive:
+
+ 1. Log in to the integration website
+ To log in to the integration website, you simply need to change your password by clicking the following link:
+
+ Change your password
+
+ Warning: this link is valid only once!
+ Once that is done, you will be able to log into your account and find all the information about the week's events through the following link:
+
+ https://integration.utt.fr
+
+
+ 2. Mentorship
+ When you arrive at UTT, an older student will become your sponsor or mentor. They will be your main contact to help you discover the school as well as student life in Troyes, and to answer all your questions about UTT, housing, classes, and life in Troyes.
+ To be matched with someone who suits you best, we invite you to fill out
+ this questionnaire
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+ If you have any questions, feel free to contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/utils/emailtemplates.ts b/backend/src/utils/emailtemplates.ts
index 309068e..c4538c1 100644
--- a/backend/src/utils/emailtemplates.ts
+++ b/backend/src/utils/emailtemplates.ts
@@ -1,364 +1,41 @@
+import fs from 'fs';
import Handlebars from 'handlebars';
+import path from 'path';
+
+const templateDirectories = [
+ path.resolve(__dirname, '../templates/email'),
+ path.resolve(process.cwd(), 'src/templates/email'),
+ path.resolve(process.cwd(), 'backend/src/templates/email'),
+];
+
+const templateCache = new Map();
+
+const readTemplate = (templateFileName: string) => {
+ const cachedTemplate = templateCache.get(templateFileName);
+ if (cachedTemplate) {
+ return cachedTemplate;
+ }
+
+ for (const directory of templateDirectories) {
+ const templatePath = path.join(directory, templateFileName);
+ if (fs.existsSync(templatePath)) {
+ const templateContent = fs.readFileSync(templatePath, 'utf8');
+ templateCache.set(templateFileName, templateContent);
+ return templateContent;
+ }
+ }
+
+ throw new Error(`Template HTML introuvable: ${templateFileName}`);
+};
-// Template pour l'e-mail de réinitialisation de mot de passe
-export const templateResetPassword = `
-
-
-
-
-
- Réinitialisation de mot de passe
-
-
-
-
-
-
-
-
-
- Réinitialisation de mot de passe
-
-
-
-
- Bonjour,
- Vous avez demandé à réinitialiser votre mot de passe. Cliquez sur le bouton ci-dessous pour choisir un nouveau mot de passe :
-
- Réinitialiser mon mot de passe
-
- Attention le lien n'est valide que pendant 1h.
- Si vous n'avez pas demandé cette réinitialisation, veuillez ignorer cet e-mail.
- Merci,
- L'équipe intégration UTT
-
-
-
-
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
-
-`;
-
-export const templateNotebook = `
-
-
-
- Integration UTT
-
-
-
-
-
-
-
-
INTEGRATION UTT
-
-
-
-
-
Salut à toi !!!!
-
-
Si tu reçois ce mail, c'est que tu es sur le point de rejoindre l'UTT et de vivre tes premières années en école supérieure.
-
-
Mais après toutes ces vacances, il est important de ne pas s'endormir et de vite se remettre au travail !
-
-
C'est pourquoi l'intégration te propose un cahier de vacances qui te permettra de te remettre à niveau.
-
-
Toutes les bases y sont revues, de la terminale… jusqu'au CP. À toi de nous prouver que tu en es capable ! Méthodologie et rigueur seront nécessaires pour en venir à bout (et pas mal d'humour également).
-
-
Ce cahier sera examiné par un jury extrêmement talentueux : des ingénieurs hors pair, ayant déjà prouvé leur valeur lors d'un concours de Ricard sur la plage de Banyuls-sur-Mer.
-
-
À toi de leur montrer que tu peux égaler leurs compétences ! Ce jury n'hésitera pas à te récompenser pour tes efforts si tu nous renvoies tes réponses à cette adresse mail.
-
-
Alors si tu veux y participer, tu peux le télécharger juste ici et le renvoyer à clement.duranson@utt.fr avant le dimanche 31 août.
-
-
Cahier de vacances !
-
-
Nous serons présents sur les réseaux tout au long de l'été pour te tenir informé(e), te partager des astuces, et plein d'autres trucs trop cools ! Rejoins le site de l'intégration pour bien être informé des actus ! Tu as reçu dans le premier mail de notre part, un lien pour réinitialiser ton mot de passe et te connecter.
-
-
Inscris toi !
-
-
Pense aussi à rejoindre notre Discord, c'est uniquement par ce biais que tu pourras contacter tes chefs d'équipe et en savoir plus sur l'intégration !
-
-
Rejoindre Discord
-
-
Alors, bon courage à toi, nous sommes impatients de lire tes meilleures réponses.
-
-
À très vite !
-
-
Toute l'équipe de l'intégration
-
-
-
-
-
Rejoins nous sur les réseaux !
-
-
-
-
-
-`;
-
-export const templateAttributionBus = `
-
-
-
-
-
- Intégration UTT
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Salut !
- Si tu reçois ce message c'est que tu pars au WEI (youhouu !), tu trouveras dans celui-ci le bus avec lequel tu vas te rendre sur le lieu pour ce week-end.
- Fais bien attention à ne pas être en retard sous peine de rater ton bus, ça serait embêtant à la fois pour toi et pour nous.
- Autre point très important : les essentiels pour le WEI. Tu trouveras ci-dessous un rappel des objets obligatoires à ramener pour passer un bon week-end. Il risque de pleuvoir alors prévoyez bien en conséquence !
-
-
-
- Duvet et matelas gonflable/tapis de sol 🛏️ (si vous n'avez pas de duvet, vous ne partirez pas)
- Gourde, Tupperware, couverts, gobby (=écocup) 🍴
- Vêtements : changes pour 2 jours, pull, maillot de bain 👙
- Manteau imperméable 🧥
- Affaires salissables : change complet & chaussures (à mettre dès le départ en bus) 🚌
- Produits d'hygiène : brosse à dent, serviette, nécessaire de toilette 🪥
- Tongues/crocs pour les douches 🩴
- Papiers importants : Carte d'identité, CB & liquide, autorisation parentale (pour les mineurs) 💳
- Ta place au WEI 📩
- Crème solaire & anti-moustique ☀️
- De quoi grignoter (prenez un pique-nique à manger avant de prendre le bus, pas dans le bus) 😋
-
-
-
- 🚫 Affaires interdites :
-
- Boissons autres que de l'eau
- Substances illicites
- Armes blanches
- Déodorant en spray
-
-
- Pour rappel, voici la vidéo des indispensables du WEI ici
- Concernant ton bus, tu as été attribué au bus {{bus}}
- Maintenant il faut que tu sois présent en amphi de verdure à l'UTT à {{time}}
- Voilà, toute l'équipe de l'intégration te souhaite un excellent WEI ;)
-
-
-
-
-
-
-
-
-`;
-
-export const templateWelcome = `
-
-
-
-
-
- Intégration UTT
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Salut à toi jeune nouveau !
- Bravo pour ton admission à l'UTT ! Nous sommes l'équipe d'intégration, des étudiants bénévoles qui préparent minutieusement ton arrivée pour que celle-ci reste inoubliable.
- Un tas d'événements incroyables, dont la participation est basée sur le volontariat, t'attendent dès le Lundi 1er Septembre que tu arrives en 1ère année, en 3ème année, en master ou en Bachelor.
- Tout est fait pour que tu t'éclates et que tu rencontres les personnes qui feront de ton passage à l'UTT un moment inoubliable. Mais avant toute chose, il faut te préparer.
- Assure-toi de réaliser les tâches suivantes avant ton arrivée :
- Pour pouvoir te connecter au site de l'intégration il te suffit de changer ton mot de passe en cliquant sur ce lien suivant :
-
- Changer ton mot de passe
- Attention, ce lien est valable uniquement une fois !
- Une fois cela fait, tu pourras te connecter à ton compte et y retrouver toutes les informations relatives aux événements de la semaine via le lien suivant :
https://integration.utt.fr
Pense aussi à lier ton compte discord via la rubrique "Mon Compte" pour échanger avec les membres de ton équipe et avec les autres arrivants.
-
- Lorsque tu arrives à l'UTT, un.e étudiant.e plus ancien.ne devient ton parrain ou ta marraine. Il ou elle sera ton contact privilégié pour découvrir l'école mais aussi la vie étudiante troyenne et répondre à toutes tes questions que ce soit sur l'UTT, les logements, les cours, la vie à Troyes,...
- Pour t'attribuer quelqu'un qui te correspond au mieux on t'invite à remplir ce questionnaire
- Pense à nous rejoindre sur les réseaux sociaux !
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Hello there, newcomer!
- Congratulations on your admission to UTT! We are the integration team - volunteer students who are carefully preparing your arrival to make it truly unforgettable.
- A bunch of amazing events, all based on voluntary participation, await you starting on Monday, September 1st , whether you're arriving in your 1st year, 3rd year, Master's or Bachelor's program.
- Everything is set up for you to have fun and meet the people who will make your time at UTT unforgettable. But first things first - it's time to get ready.
- Please make sure to complete the following tasks before you arrive:
- To access the integration website, you just need to change your password by clicking the following link:
-
- Change your password
- Warning: this link is valid only once!
- Once that's done, you'll be able to log into your account and find all the information about the integration week events here:
https://integration.utt.fr
Also, don't forget to link your Discord account via the "My Account" section so you can connect with your team and the other newcomers.
-
- When you arrive at UTT, an older student will become your mentor ("parrain" or "marraine"). They will be your main contact to help you discover the school and student life in Troyes, and to answer any questions you may have about UTT, housing, classes, life in Troyes, etc.
- To match you with someone who fits you best, we invite you to fill out this questionnaire
- Don't forget to follow us on social media!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-export const templateNotifyNews = `
-
-
-
-
-
-
-
Intégration UTT
-
-
-
-
🗞️ Nouvelle actu !
-
{{title}}
-
👉 Rendez-vous sur le site de l'inté dans l'onglet News pour en savoir plus.
-
-
-
- Accéder au site
-
-
-
-`;
-
-export const templateNotifyTentConfirmation = `
-
-
-
-
-
-
-
Intégration UTT
-
-
-
-
⛺ Mise à jour de ta tente
-
- La tente entre {{user1}} et {{user2}} a été
-
- {{#if confirmed}}validée{{else}}dévalidée{{/if}}
- .
-
-
-
- 👉 Tu peux consulter l'état de ta tente sur le site de l'inté dans l'onglet Tentes .
-
-
-
-
- Accéder au site
-
-
-
-`;
+export const templateResetPassword = 'reset-password.html';
+export const templateNotebook = 'notebook.html';
+export const templateAttributionBus = 'attribution-bus.html';
+export const templateWelcome = 'welcome.html';
+export const templateNotifyNews = 'notify-news.html';
+export const templateNotifyTentConfirmation = 'notify-tent-confirmation.html';
-// Fonction pour compiler le template
-export const compileTemplate = (data: any, templateName: string) => {
- const compiledTemplate = Handlebars.compile(templateName);
+export const compileTemplate = (data: any, templateFileName: string) => {
+ const compiledTemplate = Handlebars.compile(readTemplate(templateFileName));
return compiledTemplate(data);
};
diff --git a/backend/src/utils/secret.ts b/backend/src/utils/secret.ts
index 924cabd..30acfdc 100644
--- a/backend/src/utils/secret.ts
+++ b/backend/src/utils/secret.ts
@@ -22,9 +22,10 @@ export const api_utt_password = process.env.API_UTT_PASSWORD || "default";
export const api_utt_auth_url = process.env.API_UTT_AUTH_URL || "default";
export const api_utt_admis_url = process.env.API_UTT_ADMIS_URL || "default";
export const api_utt_admis_url_ismajor = process.env.API_UTT_ADMIS_URL_ISMAJOR || "default";
-export const zimbra_host = process.env.ZIMBRA_HOST || "default";
-export const zimbra_user = process.env.ZIMBRA_USER || "default";
-export const zimbra_password = process.env.ZIMBRA_PASSWORD || "default";
+export const email_host = process.env.EMAIL_HOST || "default";
+export const email_user = process.env.EMAIL_USER || "default";
+export const email_password = process.env.EMAIL_PASSWORD || "default";
+export const email_from = process.env.EMAIL_FROM || "default";
export const discord_client_id = process.env.DISCORD_CLIENT_ID || "default";
export const discord_client_secret = process.env.DISCORD_CLIENT_SECRET || "default";
export const discord_redirect_uri = process.env.DISCORD_REDIRECT_URI || "default";
diff --git a/frontend/src/components/Admin/adminEmail.tsx b/frontend/src/components/Admin/adminEmail.tsx
index 3cbd539..8737caa 100644
--- a/frontend/src/components/Admin/adminEmail.tsx
+++ b/frontend/src/components/Admin/adminEmail.tsx
@@ -9,14 +9,20 @@ import { Button } from '../ui/button';
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Input } from '../ui/input';
+type SelectOption = {
+ value: string;
+ label: string;
+};
+
export const AdminEmail = () => {
const [subject, setSubject] = useState('');
const [templateName, setTemplateName] = useState('');
const [format] = useState<'html' | 'txt'>('html');
const [isCustom, setIsCustom] = useState(false);
+ const [customTitle, setCustomTitle] = useState('');
const [customContent, setCustomContent] = useState('');
const [permission, setPermission] = useState(null);
- const [sendTo, setSendTo] = useState([]);
+ const [sendTo, setSendTo] = useState([]);
const [preview, setPreview] = useState('');
const [users, setUsers] = useState([]);
@@ -48,9 +54,14 @@ export const AdminEmail = () => {
const handlePreview = async () => {
try {
if (isCustom) {
- setPreview(customContent);
+ const html = await emailPreview({
+ templateName: 'custom',
+ title: customTitle || subject,
+ content: customContent,
+ });
+ setPreview(html);
} else {
- const html = await emailPreview(templateName);
+ const html = await emailPreview({ templateName });
setPreview(html);
}
} catch {
@@ -63,22 +74,31 @@ export const AdminEmail = () => {
const emails = sendTo.map((u) => u.value);
-
const payload = {
subject,
templateName: isCustom ? 'custom' : templateName,
format,
permission,
sendTo: permission ? null : emails,
+ title: isCustom ? customTitle || subject : undefined,
+ content: isCustom ? customContent : undefined,
html: isCustom ? customContent : undefined,
};
- const res = await sendEmail(payload);
- Swal.fire({
- icon: 'success',
- title: 'Email envoyé',
- text: res.message,
- });
+ try {
+ const res = await sendEmail(payload);
+ Swal.fire({
+ icon: 'success',
+ title: 'Email envoyé',
+ text: res.message,
+ });
+ } catch(error) {
+ Swal.fire({
+ title: "Erreur ❌",
+ text: error?.response?.data?.message || "Une erreur est survenue.",
+ icon: "error",
+ });
+ }
};
const confirmSend = async () => {
@@ -121,6 +141,13 @@ export const AdminEmail = () => {
/>
✏️ Rédiger un mail personnalisé
+ {isCustom && (
+ setCustomTitle(e.target.value)}
+ />
+ )}
{!isCustom ? (
{
({ value: u.email, label: `${u.firstName} ${u.lastName}` }))}
- onChange={(val) => setSendTo(val as any)}
+ onChange={(val) => setSendTo((val ?? []) as SelectOption[])}
/>
)}
diff --git a/frontend/src/components/Admin/adminExportImport.tsx b/frontend/src/components/Admin/adminExportImport.tsx
index 009086b..fbc78d9 100644
--- a/frontend/src/components/Admin/adminExportImport.tsx
+++ b/frontend/src/components/Admin/adminExportImport.tsx
@@ -136,3 +136,19 @@ export const AdminImportPlannings = () => {
);
};
+
+export const AdminImportNotebooks = () => {
+ return (
+
+
+
+ Importer les cahiers de vacances
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/pages/admin.tsx b/frontend/src/pages/admin.tsx
index 76a021b..edd8af9 100644
--- a/frontend/src/pages/admin.tsx
+++ b/frontend/src/pages/admin.tsx
@@ -8,7 +8,7 @@ import ChallengeEditor from "../components/Admin/AdminChallenge/adminChallengeEd
import { AdminValidatedChallengesList } from "../components/Admin/AdminChallenge/adminChallengeValidatedList";
import { AdminEmail } from "../components/Admin/adminEmail";
import { AdminEvents } from "../components/Admin/adminEvent";
-import { AdminExportConnect, AdminImportFoodMenu, AdminImportPlannings } from "../components/Admin/adminExportImport";
+import { AdminExportConnect, AdminImportFoodMenu, AdminImportNotebooks, AdminImportPlannings } from "../components/Admin/adminExportImport";
import { AdminFactionManagement } from "../components/Admin/adminFaction";
import { AdminRolePointsManager } from "../components/Admin/adminGames";
import { AdminLayout } from "../components/Admin/adminLayout";
@@ -107,6 +107,10 @@ export const AdminPageExport: React.FC = () => (
+
+
+
+
);
diff --git a/frontend/src/services/requests/email.service.ts b/frontend/src/services/requests/email.service.ts
index 8a8f6bc..2d53f34 100644
--- a/frontend/src/services/requests/email.service.ts
+++ b/frontend/src/services/requests/email.service.ts
@@ -1,11 +1,28 @@
import api from "../api";
-export const sendEmail = async (payload: any) => {
+type SendEmailPayload = {
+ subject: string;
+ templateName: string;
+ format?: 'html' | 'txt';
+ permission: string | null;
+ sendTo: string[] | null;
+ title?: string;
+ content?: string;
+ html?: string;
+};
+
+type PreviewEmailPayload = {
+ templateName: string;
+ title?: string;
+ content?: string;
+};
+
+export const sendEmail = async (payload: SendEmailPayload) => {
const response = await api.post('/email/admin/sendemail', { payload });
return response.data;
};
-export const emailPreview = async (templateName: any) => {
- const response = await api.post('email/admin/previewemail', { templateName })
+export const emailPreview = async (payload: PreviewEmailPayload) => {
+ const response = await api.post('/email/admin/previewemail', { payload });
return response.data.data
}
From ef62ebc0812478a6a5f5ddf7a3fa1939cc65c82c Mon Sep 17 00:00:00 2001
From: "Antoine D."
Date: Thu, 25 Jun 2026 10:51:25 +0200
Subject: [PATCH 11/42] chore: add .env.exemple and backend dist in gitignore
---
.gitignore | 1 +
backend/.env.exemple | 12 ++++++++++++
frontend/.env.exemple | 5 +++++
3 files changed, 18 insertions(+)
create mode 100644 backend/.env.exemple
create mode 100644 frontend/.env.exemple
diff --git a/.gitignore b/.gitignore
index 270d1c2..47046e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ frontend/dist/
backend/node_modules/
backend/uploads/
backend/exports/
+backend/dist
# Autres fichiers à ignorer
.DS_Store
diff --git a/backend/.env.exemple b/backend/.env.exemple
new file mode 100644
index 0000000..b6b2162
--- /dev/null
+++ b/backend/.env.exemple
@@ -0,0 +1,12 @@
+DATABASE_URL=postgresql://admin:password@localhost:5432/integration-dev
+OUTSIDE_DATABASE_URL=postgresql://admin:password@localhost:5432/integration-dev
+POSTGRES_PASSWORD=password
+POSTGRES_USER=admin
+POSTGRES_DB=integration-dev
+POSTGRES_HOST=localhost
+POSTGRES_PORT=5432
+SERVER_PORT=4001
+ZIMBRA_PASSWORD=codecode
+SHOTGUN_PASSWORD=vodka
+CAS_VALIDATE_URL=
+CAS_LOGIN_URL=
\ No newline at end of file
diff --git a/frontend/.env.exemple b/frontend/.env.exemple
new file mode 100644
index 0000000..4095081
--- /dev/null
+++ b/frontend/.env.exemple
@@ -0,0 +1,5 @@
+VITE_ROADBOOK_URL_FRENCH=
+VITE_ROADBOOK_URL_ENGLISH=
+VITE_SERVICE_URL=https://localhost:4000
+VITE_API_URL =http://localhost:4001/api
+VITE_CAS_LOGIN_URL=https://auth.utt.fr/cas/login
\ No newline at end of file
From b0037bab0c556b0300a8f677e1e5893e085cfa32 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Tue, 30 Jun 2026 00:57:31 +0200
Subject: [PATCH 12/42] feat(email): permanences reminders
---
backend/.env.example | 23 +++
backend/server.ts | 3 +
backend/src/controllers/auth.controller.ts | 11 +-
backend/src/controllers/bus.controller.ts | 6 +-
backend/src/controllers/email.controller.ts | 126 +----------------
backend/src/controllers/news.controller.ts | 5 +-
.../src/controllers/permanence.controller.ts | 28 ++++
backend/src/controllers/tent.controller.ts | 6 +-
backend/src/email/email.preview-data.ts | 36 +++++
backend/src/email/email.registry.ts | 81 +++++++++++
backend/src/email/email.renderer.ts | 25 ++++
.../templates}/attribution-bus.html | 0
.../email => email/templates}/custom.html | 0
.../email => email/templates}/notebook.html | 0
.../templates}/notify-news.html | 0
.../templates/notify-permanence-reminder.html | 78 +++++++++++
.../templates}/notify-tent-confirmation.html | 0
.../templates}/reset-password.html | 0
.../email => email/templates}/welcome.html | 0
backend/src/errors/permanence.error.ts | 7 +
.../src/middlewares/automation.middleware.ts | 22 +++
backend/src/routes/automation.routes.ts | 11 ++
backend/src/services/email.service.ts | 25 ++--
backend/src/services/permanence.service.ts | 131 +++++++++++++++---
backend/src/utils/emailtemplates.ts | 41 ------
backend/src/utils/secret.ts | 1 +
backend/tsconfig.json | 4 +-
backend/types/email.d.ts | 36 +++++
backend/types/permanence.d.ts | 34 +++++
29 files changed, 532 insertions(+), 208 deletions(-)
create mode 100644 backend/.env.example
create mode 100644 backend/src/email/email.preview-data.ts
create mode 100644 backend/src/email/email.registry.ts
create mode 100644 backend/src/email/email.renderer.ts
rename backend/src/{templates/email => email/templates}/attribution-bus.html (100%)
rename backend/src/{templates/email => email/templates}/custom.html (100%)
rename backend/src/{templates/email => email/templates}/notebook.html (100%)
rename backend/src/{templates/email => email/templates}/notify-news.html (100%)
create mode 100644 backend/src/email/templates/notify-permanence-reminder.html
rename backend/src/{templates/email => email/templates}/notify-tent-confirmation.html (100%)
rename backend/src/{templates/email => email/templates}/reset-password.html (100%)
rename backend/src/{templates/email => email/templates}/welcome.html (100%)
create mode 100644 backend/src/errors/permanence.error.ts
create mode 100644 backend/src/middlewares/automation.middleware.ts
create mode 100644 backend/src/routes/automation.routes.ts
delete mode 100644 backend/src/utils/emailtemplates.ts
create mode 100644 backend/types/email.d.ts
create mode 100644 backend/types/permanence.d.ts
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..e8a333d
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,23 @@
+DATABASE_URL=postgresql://admin:password@localhost:5432/integration-dev
+OUTSIDE_DATABASE_URL=postgresql://admin:password@localhost:5432/integration-dev
+
+POSTGRES_PASSWORD=password
+POSTGRES_USER=admin
+POSTGRES_DB=integration-dev
+POSTGRES_HOST=localhost
+POSTGRES_PORT=5432
+
+SERVER_PORT=4001
+SERVICE_URL=http://localhost:4001/
+
+SHOTGUN_PASSWORD=vodka
+
+CAS_VALIDATE_URL=
+CAS_LOGIN_URL=
+
+EMAIL_HOST=
+EMAIL_USER=
+EMAIL_PASSWORD=
+EMAIL_FROM=
+
+AUTOMATION_TOKEN=
diff --git a/backend/server.ts b/backend/server.ts
index d1def34..02128c2 100644
--- a/backend/server.ts
+++ b/backend/server.ts
@@ -10,7 +10,9 @@ import { initUser } from './src/database/initdb/initUser';
import { initEvent } from './src/database/initdb/initevent';
import { initRoles } from './src/database/initdb/initrole';
import { authenticateUser } from './src/middlewares/auth.middleware';
+import { authenticateAutomation } from './src/middlewares/automation.middleware';
import authRoutes from './src/routes/auth.routes';
+import automationRoutes from './src/routes/automation.routes';
import busRoutes from './src/routes/bus.routes';
import challengeRoutes from './src/routes/challenge.routes';
import defaultRoute from './src/routes/default.routes';
@@ -48,6 +50,7 @@ async function startServer() {
// Utilisation des routes d'authentification
app.use('/api', defaultRoute)
app.use('/api/auth', authRoutes);
+ app.use('/api/automation', authenticateAutomation, automationRoutes);
app.use('/api/authadmin', authenticateUser, authRoutes);
app.use('/api/role', authenticateUser, roleRoutes);
app.use('/api/user', authenticateUser, userRoutes);
diff --git a/backend/src/controllers/auth.controller.ts b/backend/src/controllers/auth.controller.ts
index c71871c..2719570 100644
--- a/backend/src/controllers/auth.controller.ts
+++ b/backend/src/controllers/auth.controller.ts
@@ -2,16 +2,17 @@ import bcrypt from 'bcryptjs';
import bigInt from 'big-integer';
import { type Request, type Response } from 'express';
import { sign, verify } from 'jsonwebtoken';
+import type { EmailOptions } from '../../types/email';
+import { templateResetPassword } from '../email/email.registry';
+import { compileTemplate } from '../email/email.renderer';
import * as auth_service from '../services/auth.service';
import * as email_service from '../services/email.service';
import * as registration_service from '../services/registration.service';
import * as role_service from '../services/role.service';
import * as user_service from '../services/user.service';
-import * as template from '../utils/emailtemplates';
import { Error, Ok, Unauthorized } from '../utils/responses';
-import { jwtSecret, service_url } from '../utils/secret';
+import { email_from, jwtSecret, service_url } from '../utils/secret';
import { decodeToken } from '../utils/token';
-import { type EmailOptions } from './email.controller';
// Fonction de connexion
export const login = async (req: Request, res: Response) => {
@@ -166,7 +167,7 @@ export const requestPasswordUser = async (req: Request, res: Response) => {
// Générer le contenu HTML du mail
- const htmlEmail = template.compileTemplate({ resetLink: resetLink }, template.templateResetPassword);
+ const htmlEmail = compileTemplate({ resetLink: resetLink }, templateResetPassword);
if (!htmlEmail) {
Error(res, { msg: "Nom de template invalide" });
@@ -175,7 +176,7 @@ export const requestPasswordUser = async (req: Request, res: Response) => {
// Préparer les options d'email
const emailOptions: EmailOptions = {
- from: "integration@utt.fr",
+ from: email_from,
to: [user_email],
cc: [],
bcc: [],
diff --git a/backend/src/controllers/bus.controller.ts b/backend/src/controllers/bus.controller.ts
index 5b3a142..561b50c 100644
--- a/backend/src/controllers/bus.controller.ts
+++ b/backend/src/controllers/bus.controller.ts
@@ -1,8 +1,8 @@
import { type Request, type Response } from "express";
import * as bus_service from "../services/bus.service";
-import { sendEmail } from "../services/email.service";
+import { generateEmailHtml, sendEmail } from "../services/email.service";
import { Error, Ok } from "../utils/responses";
-import { generateEmailHtml } from "./email.controller";
+import { email_from } from "../utils/secret";
interface MulterRequest extends Request {
file?: Express.Multer.File;
@@ -23,7 +23,7 @@ export const sendBusAttributionEmails = async (req: Request, res: Response) => {
});
const emailOptions = {
- from: "integration@utt.fr",
+ from: email_from,
to: [attr.email],
cc: [],
bcc: [],
diff --git a/backend/src/controllers/email.controller.ts b/backend/src/controllers/email.controller.ts
index 93780b5..02f8d95 100644
--- a/backend/src/controllers/email.controller.ts
+++ b/backend/src/controllers/email.controller.ts
@@ -1,133 +1,13 @@
import { type Request, type Response } from 'express';
-import { sendEmail } from '../services/email.service';
+import type { EmailOptions } from "../../types/email";
+import { defaultPreviewData } from '../email/email.preview-data';
+import { generateEmailHtml, sendEmail } from '../services/email.service';
import * as registration_service from '../services/registration.service';
import * as user_service from '../services/user.service';
-import * as template from '../utils/emailtemplates';
import { Error, Ok } from '../utils/responses';
import { email_from, service_url } from '../utils/secret';
import { getLatestUploadedDocument } from '../utils/uploadDocuments';
-export interface EmailOptions {
- from: string;
- to: string[];
- subject: string;
- text?: string;
- html: string;
- cc: string[];
- bcc: string[];
-}
-
-type TemplateData = Record;
-
-type TemplateRenderer = {
- fileName: string;
- buildData: (data: TemplateData) => TemplateData;
-};
-
-const templateRenderers: Record = {
- custom: {
- fileName: 'custom.html',
- buildData: (data) => {
- const typedData = data as { title?: string; content?: string };
- return {
- title: typedData.title ?? '',
- content: typedData.content ?? '',
- };
- },
- },
- templateNotebook: {
- fileName: template.templateNotebook,
- buildData: (data) => {
- const typedData = data as {
- notebook_fr?: string;
- notebook_en?: string;
- };
- return {
- notebook_fr: typedData.notebook_fr,
- notebook_en: typedData.notebook_en
- };
- },
- },
- templateAttributionBus: {
- fileName: template.templateAttributionBus,
- buildData: (data) => {
- const typedData = data as { bus?: string; time?: string };
- return { bus: typedData.bus, time: typedData.time };
- },
- },
- templateWelcome: {
- fileName: template.templateWelcome,
- buildData: (data) => {
- const typedData = data as { token?: string };
- return { token: typedData.token };
- },
- },
- templateNotifyNews: {
- fileName: template.templateNotifyNews,
- buildData: (data) => {
- const typedData = data as { title?: string };
- return { title: typedData.title };
- },
- },
- templateNotifyTentConfirmation: {
- fileName: template.templateNotifyTentConfirmation,
- buildData: (data) => {
- const typedData = data as { user1?: string; user2?: string; confirmed?: boolean };
- return {
- user1: typedData.user1,
- user2: typedData.user2,
- confirmed: typedData.confirmed,
- };
- },
- },
- templateResetPassword: {
- fileName: template.templateResetPassword,
- buildData: (data) => {
- const typedData = data as { resetLink?: string };
- return { resetLink: typedData.resetLink };
- },
- },
-};
-
-// Fonction pour générer l'HTML à partir du template
-export const generateEmailHtml = (templateName: string, data: TemplateData) => {
- const renderer = templateRenderers[templateName];
- if (!renderer) {
- return null;
- }
-
- return template.compileTemplate(renderer.buildData(data), renderer.fileName);
-};
-
-const defaultPreviewData: Record = {
- custom: {
- title: 'Titre de démonstration',
- content: 'Premier paragraphe.\nDeuxième ligne conservée.\n\nNouvel alinéa.',
- },
- templateNotebook: {
- notebook_fr: `${service_url}api/uploads/notebooks/fr.pdf`,
- notebook_en: `${service_url}api/uploads/notebooks/en.pdf`
- },
- templateAttributionBus: {
- bus: 'bus',
- time: '09h00',
- },
- templateWelcome: {
- token: 'preview-token',
- },
- templateNotifyNews: {
- title: 'Titre de démonstration',
- },
- templateNotifyTentConfirmation: {
- user1: 'Utilisateur 1',
- user2: 'Utilisateur 2',
- confirmed: true,
- },
- templateResetPassword: {
- resetLink: `${service_url}resetpassword?token=preview-token`,
- },
-};
-
// Fonction utilitaire pour récupérer les destinataires
const getRecipients = async (permission: string | undefined, sendTo: string[] | undefined) => {
if (permission) {
diff --git a/backend/src/controllers/news.controller.ts b/backend/src/controllers/news.controller.ts
index 4534bc1..3b40a52 100644
--- a/backend/src/controllers/news.controller.ts
+++ b/backend/src/controllers/news.controller.ts
@@ -2,10 +2,11 @@ import { type Request, type Response } from "express";
import fs from "fs";
import path from "path";
import * as email_service from "../services/email.service";
+import { generateEmailHtml } from "../services/email.service";
import * as news_service from "../services/news.service";
import * as user_service from '../services/user.service';
import { Error, Ok } from "../utils/responses";
-import { generateEmailHtml } from './email.controller';
+import { email_from } from "../utils/secret";
const toStoredUploadPath = (imageUrl: string) => {
if (!imageUrl) {
@@ -136,7 +137,7 @@ export const publishNews = async (req: Request, res: Response) => {
}
const email = {
- from: "integration@utt.fr",
+ from: email_from,
to: [],
subject: `[INTEGRATION UTT] Nouvelle actu : ${news.title}`,
html: html,
diff --git a/backend/src/controllers/permanence.controller.ts b/backend/src/controllers/permanence.controller.ts
index 6f89390..956fdae 100644
--- a/backend/src/controllers/permanence.controller.ts
+++ b/backend/src/controllers/permanence.controller.ts
@@ -371,3 +371,31 @@ export const claimMember = async (req: Request, res: Response) => {
Error(res, { msg: "Erreur lors de la mise à jour du statut du membre" });
}
};
+
+export const sendHourlyNotificationToUsers = async (req: Request, res: Response) => {
+
+ const notifications = await permanence_service.getHourlyNotifications();
+
+ if (notifications.length === 0) {
+ Ok(res, { msg: "Aucune notification horaire à envoyer." });
+ return;
+ }
+
+ permanence_service.sendNotifications(notifications);
+
+ Ok(res, { msg: "Notifications horaires envoyées avec succès" });
+};
+
+export const sendDailyNotificationToUsers = async (req: Request, res: Response) => {
+
+ const notifications = await permanence_service.getDailyNotifications();
+
+ if (notifications.length === 0) {
+ Ok(res, { msg: "Aucune notification quotidienne à envoyer." });
+ return;
+ }
+
+ permanence_service.sendNotifications(notifications);
+
+ Ok(res, { msg: "Notifications quotidiennes envoyées avec succès" });
+};
diff --git a/backend/src/controllers/tent.controller.ts b/backend/src/controllers/tent.controller.ts
index 5dd067b..ed62e44 100644
--- a/backend/src/controllers/tent.controller.ts
+++ b/backend/src/controllers/tent.controller.ts
@@ -1,9 +1,9 @@
import { type Request, type Response } from "express";
-import { sendEmail } from "../services/email.service";
+import { generateEmailHtml, sendEmail } from "../services/email.service";
import * as tent_service from "../services/tent.service";
import { getUserById } from "../services/user.service";
import { Error, Ok } from "../utils/responses";
-import { generateEmailHtml } from "./email.controller";
+import { email_from } from "../utils/secret";
export const createTent = async (req: Request, res: Response) => {
const { userId2 } = req.body;
@@ -86,7 +86,7 @@ export const toggleTentConfirmation = async (req: Request, res: Response) => {
// Options d'email
const emailOptions = {
- from: "integration@utt.fr",
+ from: email_from,
to: [user1.email, user2.email],
subject: confirmed
? "🎉 Votre tente a été validée !"
diff --git a/backend/src/email/email.preview-data.ts b/backend/src/email/email.preview-data.ts
new file mode 100644
index 0000000..f2742b1
--- /dev/null
+++ b/backend/src/email/email.preview-data.ts
@@ -0,0 +1,36 @@
+import type { TemplateData } from "../../types/email";
+import { service_url } from '../utils/secret';
+
+export const defaultPreviewData: Record = {
+ custom: {
+ title: 'Titre de démonstration',
+ content: 'Premier paragraphe.\nDeuxième ligne conservée.\n\nNouvel alinéa.',
+ },
+ templateNotebook: {
+ notebook_fr: `${service_url}api/uploads/notebooks/fr.pdf`,
+ notebook_en: `${service_url}api/uploads/notebooks/en.pdf`
+ },
+ templateAttributionBus: {
+ bus: 'bus',
+ time: '09h00',
+ },
+ templateWelcome: {
+ token: 'preview-token',
+ },
+ templateNotifyNews: {
+ title: 'Titre de démonstration',
+ },
+ templateNotifyTentConfirmation: {
+ user1: 'Utilisateur 1',
+ user2: 'Utilisateur 2',
+ confirmed: true,
+ },
+ templateResetPassword: {
+ resetLink: `${service_url}resetpassword?token=preview-token`,
+ },
+ templateNotifyPermanenceReminder: {
+ permanence: {
+
+ }
+ }
+};
diff --git a/backend/src/email/email.registry.ts b/backend/src/email/email.registry.ts
new file mode 100644
index 0000000..a1f4972
--- /dev/null
+++ b/backend/src/email/email.registry.ts
@@ -0,0 +1,81 @@
+import type { PermanenceEmailData, TemplateRenderer } from "../../types/email";
+
+export const templateResetPassword = 'reset-password.html';
+const templateNotebook = 'notebook.html';
+const templateAttributionBus = 'attribution-bus.html';
+const templateWelcome = 'welcome.html';
+const templateNotifyNews = 'notify-news.html';
+const templateNotifyTentConfirmation = 'notify-tent-confirmation.html';
+const templateNotifyPermanenceReminder = 'notify-permanence-reminder.html';
+
+export const templateRenderers: Record = {
+ custom: {
+ fileName: 'custom.html',
+ buildData: (data) => {
+ const typedData = data as { title?: string; content?: string };
+ return {
+ title: typedData.title ?? '',
+ content: typedData.content ?? '',
+ };
+ },
+ },
+ templateNotebook: {
+ fileName: templateNotebook,
+ buildData: (data) => {
+ const typedData = data as {
+ notebook_fr?: string;
+ notebook_en?: string;
+ };
+ return {
+ notebook_fr: typedData.notebook_fr,
+ notebook_en: typedData.notebook_en
+ };
+ },
+ },
+ templateAttributionBus: {
+ fileName: templateAttributionBus,
+ buildData: (data) => {
+ const typedData = data as { bus?: string; time?: string };
+ return { bus: typedData.bus, time: typedData.time };
+ },
+ },
+ templateWelcome: {
+ fileName: templateWelcome,
+ buildData: (data) => {
+ const typedData = data as { token?: string };
+ return { token: typedData.token };
+ },
+ },
+ templateNotifyNews: {
+ fileName: templateNotifyNews,
+ buildData: (data) => {
+ const typedData = data as { title?: string };
+ return { title: typedData.title };
+ },
+ },
+ templateNotifyTentConfirmation: {
+ fileName: templateNotifyTentConfirmation,
+ buildData: (data) => {
+ const typedData = data as { user1?: string; user2?: string; confirmed?: boolean };
+ return {
+ user1: typedData.user1,
+ user2: typedData.user2,
+ confirmed: typedData.confirmed,
+ };
+ },
+ },
+ templateResetPassword: {
+ fileName: templateResetPassword,
+ buildData: (data) => {
+ const typedData = data as { resetLink?: string };
+ return { resetLink: typedData.resetLink };
+ },
+ },
+ templateNotifyPermanenceReminder: {
+ fileName: templateNotifyPermanenceReminder,
+ buildData: (data) => {
+ const typedData = data as PermanenceEmailData;
+ return typedData;
+ },
+ }
+};
diff --git a/backend/src/email/email.renderer.ts b/backend/src/email/email.renderer.ts
new file mode 100644
index 0000000..ec3fef5
--- /dev/null
+++ b/backend/src/email/email.renderer.ts
@@ -0,0 +1,25 @@
+import fs from "fs";
+import Handlebars from "handlebars";
+import path from "path";
+
+const templateCache = new Map();
+
+const readTemplate = (templateFileName: string) => {
+ const cached = templateCache.get(templateFileName);
+ if (cached) return cached;
+
+ const templatePath = path.join(path.resolve(__dirname, "./templates"), templateFileName);
+
+ if (fs.existsSync(templatePath)) {
+ const content = fs.readFileSync(templatePath, "utf8");
+ templateCache.set(templateFileName, content);
+ return content;
+ }
+
+ throw new Error(`Template introuvable: ${templateFileName}`);
+};
+
+export const compileTemplate = (data: any, fileName: string) => {
+ const compiled = Handlebars.compile(readTemplate(fileName));
+ return compiled(data);
+};
diff --git a/backend/src/templates/email/attribution-bus.html b/backend/src/email/templates/attribution-bus.html
similarity index 100%
rename from backend/src/templates/email/attribution-bus.html
rename to backend/src/email/templates/attribution-bus.html
diff --git a/backend/src/templates/email/custom.html b/backend/src/email/templates/custom.html
similarity index 100%
rename from backend/src/templates/email/custom.html
rename to backend/src/email/templates/custom.html
diff --git a/backend/src/templates/email/notebook.html b/backend/src/email/templates/notebook.html
similarity index 100%
rename from backend/src/templates/email/notebook.html
rename to backend/src/email/templates/notebook.html
diff --git a/backend/src/templates/email/notify-news.html b/backend/src/email/templates/notify-news.html
similarity index 100%
rename from backend/src/templates/email/notify-news.html
rename to backend/src/email/templates/notify-news.html
diff --git a/backend/src/email/templates/notify-permanence-reminder.html b/backend/src/email/templates/notify-permanence-reminder.html
new file mode 100644
index 0000000..3a3b6b9
--- /dev/null
+++ b/backend/src/email/templates/notify-permanence-reminder.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ Rappel de permanence
+
+ {{permName}}
+ Entre le {{permBeginDate}} à {{permBeginHour}} et le {{permEndDate}} à {{permEndHour}}
+ Point de rendez-vous: {{permLocation}}
+ Description de la permanence: {{permDescription}}
+ Pour le bon déroulement des permanences, nous vous demandons de vous présenter 15 minutes à l'avance sur les lieux.
+ Le ou la reponsable de permanence effectuera l'appel. Seules les permanences honorées seront comptabilisés pour le total de permanences des chefs et cheffes d'équipe.
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+ Cordialement, L'équipe intégration UTT
+ Si vous avez des questions, n'hésitez pas à nous contacter .
+
+
+
+
+
+
+
+ Permanence reminder
+
+ {{permName}}
+ From the {{permBeginDate}} at {{permBeginHour}} to the {{permEndDate}} at {{permEndHour}}
+ Meeting point: {{permLocation}}
+ Description: {{permDescription}}
+ To ensure the smooth running of the permanence hours, we ask that you arrive 15 minutes early on the premises.
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+ If you have any questions, feel free to contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/templates/email/notify-tent-confirmation.html b/backend/src/email/templates/notify-tent-confirmation.html
similarity index 100%
rename from backend/src/templates/email/notify-tent-confirmation.html
rename to backend/src/email/templates/notify-tent-confirmation.html
diff --git a/backend/src/templates/email/reset-password.html b/backend/src/email/templates/reset-password.html
similarity index 100%
rename from backend/src/templates/email/reset-password.html
rename to backend/src/email/templates/reset-password.html
diff --git a/backend/src/templates/email/welcome.html b/backend/src/email/templates/welcome.html
similarity index 100%
rename from backend/src/templates/email/welcome.html
rename to backend/src/email/templates/welcome.html
diff --git a/backend/src/errors/permanence.error.ts b/backend/src/errors/permanence.error.ts
new file mode 100644
index 0000000..1092c0f
--- /dev/null
+++ b/backend/src/errors/permanence.error.ts
@@ -0,0 +1,7 @@
+export class UnauthorizedError extends Error { }
+export class AlreadyRegisteredError extends Error { }
+export class PermanenceNotFoundError extends Error { }
+export class PermanenceClosedError extends Error { }
+export class PermanenceFullError extends Error { }
+export class UnregisterDeadlineError extends Error { }
+export class RegisterDeadlineError extends Error { }
diff --git a/backend/src/middlewares/automation.middleware.ts b/backend/src/middlewares/automation.middleware.ts
new file mode 100644
index 0000000..d3a41bf
--- /dev/null
+++ b/backend/src/middlewares/automation.middleware.ts
@@ -0,0 +1,22 @@
+import { type NextFunction, type Request, type Response } from "express";
+import { Unauthorized } from "../utils/responses"; // Assurez-vous que cette fonction est bien définie
+import { automation_token } from "../utils/secret";
+
+export const authenticateAutomation = (req: Request, res: Response, next: NextFunction) => {
+ try {
+
+ const { token } = req.body;
+
+ if (!token) {
+ return Unauthorized(res, { msg: "Unauthorized: Missing or malformed token" });
+ }
+
+ if (token !== automation_token) {
+ return Unauthorized(res, { msg: "Unauthorized: Invalid token" })
+ }
+
+ next();
+ } catch {
+ return Unauthorized(res, { msg: "Unauthorized: Invalid token" });
+ }
+};
diff --git a/backend/src/routes/automation.routes.ts b/backend/src/routes/automation.routes.ts
new file mode 100644
index 0000000..6fdec9a
--- /dev/null
+++ b/backend/src/routes/automation.routes.ts
@@ -0,0 +1,11 @@
+import express from "express";
+import * as permanenceController from "../controllers/permanence.controller";
+
+
+const automationRoutes = express.Router();
+
+// Permanences routes
+automationRoutes.post("/permanence/notification/hourly", permanenceController.sendHourlyNotificationToUsers);
+automationRoutes.post("/permanence/notification/daily", permanenceController.sendDailyNotificationToUsers);
+
+export default automationRoutes;
diff --git a/backend/src/services/email.service.ts b/backend/src/services/email.service.ts
index 0d6d445..53f9abc 100644
--- a/backend/src/services/email.service.ts
+++ b/backend/src/services/email.service.ts
@@ -1,15 +1,22 @@
import nodemailer from 'nodemailer';
+import type { EmailOptions, TemplateData } from "../../types/email";
+import { templateRenderers } from "../email/email.registry";
+import { compileTemplate } from "../email/email.renderer";
import { email_from, email_host, email_password, email_user } from '../utils/secret';
-interface EmailOptions {
- from: string;
- to: string[];
- subject: string;
- text?: string;
- html?: string;
- cc?: string[];
- bcc?: string[];
-}
+export const generateEmailHtml = (
+ templateName: string,
+ data: TemplateData
+) => {
+ const renderer = templateRenderers[templateName];
+
+ if (!renderer) return null;
+
+ return compileTemplate(
+ renderer.buildData(data),
+ renderer.fileName
+ );
+};
export const sendEmail = async (options: EmailOptions): Promise => {
try {
diff --git a/backend/src/services/permanence.service.ts b/backend/src/services/permanence.service.ts
index b47e69a..ae76a0b 100644
--- a/backend/src/services/permanence.service.ts
+++ b/backend/src/services/permanence.service.ts
@@ -1,30 +1,15 @@
import { and, eq, inArray, sql } from "drizzle-orm";
import fs from "fs";
import Papa from "papaparse";
+import type { PermanenceEmailData } from "../../types/email";
+import type { CsvPermanence, LightUser, Notification, Permanence } from "../../types/permanence";
import { db } from "../database/db";
+import { AlreadyRegisteredError, PermanenceClosedError, PermanenceFullError, PermanenceNotFoundError, RegisterDeadlineError, UnauthorizedError, UnregisterDeadlineError } from "../errors/permanence.error";
import { permanenceSchema } from "../schemas/Basic/permanence.schema";
import { userSchema } from "../schemas/Basic/user.schema";
import { respoPermanenceSchema, userPermanenceSchema } from "../schemas/Relational/userpermanences.schema";
-
-type CsvPermanence = {
- name: string;
- description: string;
- location: string;
- start_at: string;
- end_at: string;
- capacity: string;
- is_open: string;
- difficulty: string;
-};
-
-// Classes d'erreurs personnalisées
-class UnauthorizedError extends Error { }
-class AlreadyRegisteredError extends Error { }
-class PermanenceNotFoundError extends Error { }
-class PermanenceClosedError extends Error { }
-class PermanenceFullError extends Error { }
-class UnregisterDeadlineError extends Error { }
-class RegisterDeadlineError extends Error { }
+import { email_from } from "../utils/secret";
+import { generateEmailHtml, sendEmail } from "./email.service";
export const getPermanenceById = async (permId: number) => {
const permanence = await db.query.permanenceSchema.findFirst({
@@ -493,3 +478,109 @@ export const claimMember = async (userId: number, permId: number, claimed: boole
)
);
};
+
+export const getDailyNotifications = async (): Promise => {
+ const permanences = await db.query.permanenceSchema.findMany({
+ where: sql`
+ ${permanenceSchema.start_at} >= CURRENT_DATE + INTERVAL '1 day'
+ AND ${permanenceSchema.start_at} < CURRENT_DATE + INTERVAL '2 day'
+ `,
+ orderBy: permanenceSchema.start_at,
+ });
+
+ return getMembersFromPermanences(permanences);
+}
+
+export const getHourlyNotifications = async (): Promise => {
+ const permanences = await db.query.permanenceSchema.findMany({
+ where: sql`
+ ${permanenceSchema.start_at} >= date_trunc('hour', now()) + interval '1 hour'
+ AND ${permanenceSchema.start_at} < date_trunc('hour', now()) + interval '2 hour'
+ `,
+ orderBy: permanenceSchema.start_at,
+ });
+
+ return getMembersFromPermanences(permanences);
+};
+
+// Cette fonction est vouée à disparaitre lors du passage à Prisma, avec un simple "with"
+export const getMembersFromPermanences = async (permanences: Permanence[]): Promise<{
+ permanence: Permanence,
+ members: LightUser[]
+}[]> => {
+ return await Promise.all(
+ permanences.map(async (perm) => {
+ const members = await db
+ .select({
+ id: userSchema.id,
+ firstName: userSchema.first_name,
+ lastName: userSchema.last_name,
+ email: userSchema.email,
+ })
+ .from(userPermanenceSchema)
+ .innerJoin(userSchema, eq(userSchema.id, userPermanenceSchema.user_id))
+ .where(eq(userPermanenceSchema.permanence_id, perm.id));
+
+ return {
+ permanence: perm,
+ members: members
+ };
+ })
+ );
+}
+
+export const sendNotifications = async (
+ notifications: Notification[]
+) => {
+ for (const notification of notifications) {
+
+ const permanenceEmailData: PermanenceEmailData = {
+ permName: notification.permanence.name,
+ permBeginDate:
+ new Intl.DateTimeFormat("fr-FR", {
+ day: "2-digit",
+ month: "long",
+ }).format(notification.permanence.start_at),
+ permBeginHour:
+ new Intl.DateTimeFormat("fr-FR", {
+ hour: "2-digit",
+ minute: "2-digit",
+ }).format(notification.permanence.start_at),
+ permEndDate:
+ new Intl.DateTimeFormat("fr-FR", {
+ day: "2-digit",
+ month: "long",
+ }).format(notification.permanence.end_at),
+ permEndHour:
+ new Intl.DateTimeFormat("fr-FR", {
+ hour: "2-digit",
+ minute: "2-digit",
+ }).format(notification.permanence.end_at),
+ permLocation: notification.permanence.location,
+ permDescription: notification.permanence.description
+ }
+ const subject = `[RAPPEL] Permanence - ${notification.permanence.name}`
+
+ const htmlEmail = generateEmailHtml(
+ "templateNotifyPermanenceReminder",
+ permanenceEmailData
+ );
+
+ for (const member of notification.members) {
+ try {
+
+ const emailOptions = {
+ from: email_from,
+ to: [member.email],
+ subject: subject,
+ text: "",
+ html: htmlEmail,
+ };
+
+ await sendEmail(emailOptions);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ }
+};
diff --git a/backend/src/utils/emailtemplates.ts b/backend/src/utils/emailtemplates.ts
deleted file mode 100644
index c4538c1..0000000
--- a/backend/src/utils/emailtemplates.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import fs from 'fs';
-import Handlebars from 'handlebars';
-import path from 'path';
-
-const templateDirectories = [
- path.resolve(__dirname, '../templates/email'),
- path.resolve(process.cwd(), 'src/templates/email'),
- path.resolve(process.cwd(), 'backend/src/templates/email'),
-];
-
-const templateCache = new Map();
-
-const readTemplate = (templateFileName: string) => {
- const cachedTemplate = templateCache.get(templateFileName);
- if (cachedTemplate) {
- return cachedTemplate;
- }
-
- for (const directory of templateDirectories) {
- const templatePath = path.join(directory, templateFileName);
- if (fs.existsSync(templatePath)) {
- const templateContent = fs.readFileSync(templatePath, 'utf8');
- templateCache.set(templateFileName, templateContent);
- return templateContent;
- }
- }
-
- throw new Error(`Template HTML introuvable: ${templateFileName}`);
-};
-
-export const templateResetPassword = 'reset-password.html';
-export const templateNotebook = 'notebook.html';
-export const templateAttributionBus = 'attribution-bus.html';
-export const templateWelcome = 'welcome.html';
-export const templateNotifyNews = 'notify-news.html';
-export const templateNotifyTentConfirmation = 'notify-tent-confirmation.html';
-
-export const compileTemplate = (data: any, templateFileName: string) => {
- const compiledTemplate = Handlebars.compile(readTemplate(templateFileName));
- return compiledTemplate(data);
-};
diff --git a/backend/src/utils/secret.ts b/backend/src/utils/secret.ts
index 30acfdc..f7c51fc 100644
--- a/backend/src/utils/secret.ts
+++ b/backend/src/utils/secret.ts
@@ -30,3 +30,4 @@ export const discord_client_id = process.env.DISCORD_CLIENT_ID || "default";
export const discord_client_secret = process.env.DISCORD_CLIENT_SECRET || "default";
export const discord_redirect_uri = process.env.DISCORD_REDIRECT_URI || "default";
export const shotgun_password = process.env.SHOTGUN_PASSWORD || "";
+export const automation_token = process.env.AUTOMATION_TOKEN || "";
diff --git a/backend/tsconfig.json b/backend/tsconfig.json
index 5e26616..8f88859 100644
--- a/backend/tsconfig.json
+++ b/backend/tsconfig.json
@@ -12,6 +12,6 @@
"outDir": "./dist",
"resolveJsonModule": true
},
- "include": ["src/**/*.ts"],
+ "include": ["src/**/*.ts", "types/permanence.d.ts"],
"exclude": ["node_modules"]
-}
\ No newline at end of file
+}
diff --git a/backend/types/email.d.ts b/backend/types/email.d.ts
new file mode 100644
index 0000000..d7b5575
--- /dev/null
+++ b/backend/types/email.d.ts
@@ -0,0 +1,36 @@
+export interface EmailOptions {
+ from: string;
+ to: string[];
+ subject: string;
+ text?: string;
+ html: string;
+ cc: string[];
+ bcc: string[];
+}
+
+export type TemplateData = Record;
+
+export type TemplateRenderer = {
+ fileName: string;
+ buildData: (data: TemplateData) => TemplateData;
+};
+
+export interface EmailOptions {
+ from: string;
+ to: string[];
+ subject: string;
+ text?: string;
+ html?: string;
+ cc?: string[];
+ bcc?: string[];
+}
+
+export interface PermanenceEmailData extends TemplateData {
+ permName: string;
+ permBeginDate: string;
+ permBeginHour: string;
+ permEndDate: string;
+ permEndHour: string;
+ permLocation: string;
+ permDescription: string;
+}
diff --git a/backend/types/permanence.d.ts b/backend/types/permanence.d.ts
new file mode 100644
index 0000000..190fbb6
--- /dev/null
+++ b/backend/types/permanence.d.ts
@@ -0,0 +1,34 @@
+export type CsvPermanence = {
+ name: string;
+ description: string;
+ location: string;
+ start_at: string;
+ end_at: string;
+ capacity: string;
+ is_open: string;
+ difficulty: string;
+};
+
+export type Notification = {
+ permanence: Permanence;
+ members: LightUser[]
+}
+
+export type Permanence = {
+ id: number;
+ name: string;
+ description: string;
+ location: string;
+ start_at: Date;
+ end_at: Date;
+ capacity: number;
+ is_open: boolean;
+ difficulty: number;
+}
+
+export type LightUser = {
+ id: number;
+ firstName: string;
+ lastName: string;
+ email: string;
+}
From 6a9b39a012b02601a0e05b1a590f8189c62c8ea9 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Tue, 30 Jun 2026 01:06:53 +0200
Subject: [PATCH 13/42] refactor(email): align news email with other emails
templates
---
backend/src/email/templates/notify-news.html | 81 ++++++++++++++++----
1 file changed, 68 insertions(+), 13 deletions(-)
diff --git a/backend/src/email/templates/notify-news.html b/backend/src/email/templates/notify-news.html
index 94fc85f..00b819e 100644
--- a/backend/src/email/templates/notify-news.html
+++ b/backend/src/email/templates/notify-news.html
@@ -5,18 +5,73 @@
Intégration UTT
-
-
-
-
-
Intégration UTT
-
-
🗞️ Nouvelle actu !
-
{{title}}
-
👉 Rendez-vous sur le site de l'inté dans l'onglet News pour en savoir plus.
-
- Accéder au site
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ Nouvelle actu !
+
+ {{title}}
+ 👉 Rendez-vous sur le site de l'inté dans l'onglet News pour en savoir plus.
+
+ Accéder au site
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+ Cordialement, L'équipe intégration UTT
+ Si vous avez des questions, n'hésitez pas à nous contacter .
+
+
+
+
+
+
+
+ New news !
+
+ {{title}}
+ 👉 Visit the integration website in the News tab to find out more.
+
+ Click here
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+ If you have any questions, feel free to contact us .
+
+
+
+
+
+
From 1f04508b29cbd732380be26f464fe25a3f3c2759 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Tue, 30 Jun 2026 18:13:09 +0200
Subject: [PATCH 14/42] refactor(email): improve front
---
backend/.prettierrc | 10 +
backend/src/controllers/auth.controller.ts | 99 ++---
backend/src/controllers/bus.controller.ts | 27 +-
.../src/controllers/challenge.controller.ts | 59 +--
backend/src/controllers/discord.controller.ts | 16 +-
backend/src/controllers/email.controller.ts | 30 +-
backend/src/controllers/event.controller.ts | 86 ++--
backend/src/controllers/faction.controller.ts | 16 +-
.../src/controllers/im_export.controller.ts | 176 ++++----
backend/src/controllers/news.controller.ts | 76 ++--
.../src/controllers/permanence.controller.ts | 112 +++--
backend/src/controllers/role.controller.ts | 64 +--
backend/src/controllers/team.controller.ts | 80 ++--
backend/src/controllers/tent.controller.ts | 46 +-
backend/src/controllers/user.controller.ts | 41 +-
backend/src/email/email.preview-data.ts | 10 +-
backend/src/email/email.registry.ts | 8 +-
backend/src/email/email.renderer.ts | 10 +-
.../src/email/templates/attribution-bus.html | 395 ++++++++++++-----
backend/src/email/templates/custom.html | 128 ++++--
backend/src/email/templates/notebook.html | 219 ++++++---
backend/src/email/templates/notify-news.html | 223 +++++++---
.../templates/notify-permanence-reminder.html | 216 +++++----
.../templates/notify-tent-confirmation.html | 73 ++-
.../src/email/templates/reset-password.html | 234 ++++++----
backend/src/email/templates/welcome.html | 334 +++++++++-----
backend/src/errors/permanence.error.ts | 14 +-
.../src/middlewares/automation.middleware.ts | 15 +-
backend/src/routes/automation.routes.ts | 9 +-
backend/src/routes/email.routes.ts | 4 +-
backend/src/services/email.service.ts | 41 +-
frontend/.prettierrc | 10 +
frontend/src/App.tsx | 416 +++++++++++++-----
frontend/src/components/Admin/adminEmail.tsx | 180 ++++----
frontend/src/components/Admin/adminEvent.tsx | 81 ++--
.../ui/horizontalMultipleSelect.tsx | 71 +++
.../components/ui/horizontalSingleSelect.tsx | 51 +++
.../src/services/requests/email.service.ts | 8 +-
38 files changed, 2380 insertions(+), 1308 deletions(-)
create mode 100644 backend/.prettierrc
create mode 100644 frontend/.prettierrc
create mode 100644 frontend/src/components/ui/horizontalMultipleSelect.tsx
create mode 100644 frontend/src/components/ui/horizontalSingleSelect.tsx
diff --git a/backend/.prettierrc b/backend/.prettierrc
new file mode 100644
index 0000000..b7db128
--- /dev/null
+++ b/backend/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "semi": true,
+ "trailingComma": "all",
+ "singleQuote": true,
+ "printWidth": 120,
+ "tabWidth": 4,
+ "arrowParens": "always",
+ "endOfLine": "lf",
+ "bracketSameLine": true
+}
diff --git a/backend/src/controllers/auth.controller.ts b/backend/src/controllers/auth.controller.ts
index 2719570..6e5cf8a 100644
--- a/backend/src/controllers/auth.controller.ts
+++ b/backend/src/controllers/auth.controller.ts
@@ -41,7 +41,6 @@ export const register = async (req: Request, res: Response) => {
}
};
-
export const handlecasticket = async (req: Request, res: Response) => {
try {
const ticket = req.query.ticket as string;
@@ -53,14 +52,25 @@ export const handlecasticket = async (req: Request, res: Response) => {
// Assurez-vous que user.email est un string
let user = await user_service.getUserByEmail(CASuser.email.toLowerCase());
if (!user) {
- const password = bigInt.randBetween(bigInt(2).pow(255), bigInt(2).pow(256).minus(1)).toString()
- await user_service.createUser(CASuser.givenName, CASuser.sn, CASuser.email, true, "Student", " ", password)
- user = await user_service.getUserByEmail(CASuser.email.toLowerCase())
+ const password = bigInt.randBetween(bigInt(2).pow(255), bigInt(2).pow(256).minus(1)).toString();
+ await user_service.createUser(
+ CASuser.givenName,
+ CASuser.sn,
+ CASuser.email,
+ true,
+ 'Student',
+ ' ',
+ password,
+ );
+ user = await user_service.getUserByEmail(CASuser.email.toLowerCase());
}
- const id = user?.id
+ const id = user?.id;
- if (!id) { Error(res, { msg: "Pas d'id" }); return; }
+ if (!id) {
+ Error(res, { msg: "Pas d'id" });
+ return;
+ }
await user_service.updateUserStudent(CASuser.givenName, CASuser.sn, CASuser.email);
@@ -75,10 +85,7 @@ export const handlecasticket = async (req: Request, res: Response) => {
const token = auth_service.generateToken(enrichedUser);
-
- Ok(res, { data: { token } })
-
-
+ Ok(res, { data: { token } });
} else {
Unauthorized(res, { msg: 'Unauthorized: Invalid user email' });
}
@@ -88,75 +95,67 @@ export const handlecasticket = async (req: Request, res: Response) => {
} catch {
Unauthorized(res, { msg: 'Unauthorized: Invalid token' });
}
-}
-
+};
export const isTokenValid = async (req: Request, res: Response) => {
try {
- const authHeader = req.headers["authorization"];
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
+ const authHeader = req.headers['authorization'];
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
Unauthorized(res, {
- msg: "Unauthorized: Missing or malformed token",
+ msg: 'Unauthorized: Missing or malformed token',
data: false,
});
return;
}
- const token = authHeader.split(" ")[1];
+ const token = authHeader.split(' ')[1];
// Décoder et valider le token
const decodedToken = decodeToken(token);
if (!decodedToken) {
Unauthorized(res, {
- msg: "Unauthorized: Token has expired or is invalid",
+ msg: 'Unauthorized: Token has expired or is invalid',
data: false,
});
- return
+ return;
}
-
// Vérifier que l'email est bien présent dans le token
if (!decodedToken.userEmail) {
Unauthorized(res, {
- msg: "Unauthorized: Invalid token content",
+ msg: 'Unauthorized: Invalid token content',
data: false,
});
- return
+ return;
}
// Répondre une seule fois
Ok(res, { data: true });
- return
+ return;
} catch {
- Error(res, { msg: "Unauthorized: Token validation failed" });
- return
+ Error(res, { msg: 'Unauthorized: Token validation failed' });
+ return;
}
};
-
export const completeRegistration = async (req: Request, res: Response) => {
-
const { token, password } = req.body;
try {
-
- await auth_service.completeRegistration(token, password)
- Ok(res, { msg: "Inscription complétée avec succès.", data: true })
-
+ await auth_service.completeRegistration(token, password);
+ Ok(res, { msg: 'Inscription complétée avec succès.', data: true });
} catch (error) {
- Error(res, { msg: error.message || "Une erreur est survenue." });
+ Error(res, { msg: error.message || 'Une erreur est survenue.' });
}
-
-}
+};
export const requestPasswordUser = async (req: Request, res: Response) => {
-
- const { user_email } = req.body
+ const { user_email } = req.body;
const user = await user_service.getUserByEmail(user_email);
if (!user) {
Error(res, { msg: 'User not found' });
- return
+ return;
}
// Générer un token JWT
@@ -165,12 +164,11 @@ export const requestPasswordUser = async (req: Request, res: Response) => {
// Créer le lien de réinitialisation
const resetLink = `${service_url}ResetPassword?token=${token}`;
-
// Générer le contenu HTML du mail
const htmlEmail = compileTemplate({ resetLink: resetLink }, templateResetPassword);
if (!htmlEmail) {
- Error(res, { msg: "Nom de template invalide" });
+ Error(res, { msg: 'Nom de template invalide' });
return;
}
@@ -187,19 +185,17 @@ export const requestPasswordUser = async (req: Request, res: Response) => {
try {
// Envoyer l'e-mail
await email_service.sendEmail(emailOptions);
- Ok(res, { msg: 'Email for password reste sent !' })
- return
+ Ok(res, { msg: 'Email for password reste sent !' });
+ return;
} catch {
Error(res, { msg: 'Error when reseting password' });
- return
+ return;
}
-
-}
+};
export const resetPasswordUser = async (req: Request, res: Response) => {
const { token, password } = req.body;
-
try {
// Vérifiez et décodez le token
const decoded: any = verify(token, jwtSecret);
@@ -208,7 +204,7 @@ export const resetPasswordUser = async (req: Request, res: Response) => {
const user = await user_service.getUserById(decoded.userId);
if (!user) {
Error(res, { msg: 'Utilisateur non trouvé' });
- return
+ return;
}
// Hash du nouveau mot de passe
@@ -217,29 +213,30 @@ export const resetPasswordUser = async (req: Request, res: Response) => {
// Mettez à jour le mot de passe de l'utilisateur
await user_service.updateUserPassword(Number(user.userId), hashedPassword);
Ok(res, { msg: 'Mot de passe réinitialisé avec succès' });
- return
+ return;
} catch (error) {
console.log(error);
Error(res, { msg: 'Token invalid or expire' });
- return
+ return;
}
-}
+};
export const renewToken = async (req: Request, res: Response) => {
const { userId } = req.body;
try {
-
const userToken = await registration_service.getRegistrationByUserId(userId);
if (userToken) {
await auth_service.deleteUserRegistrationToken(userId);
}
- const newToken = await auth_service.createRegistrationToken(userId)
+ const newToken = await auth_service.createRegistrationToken(userId);
Ok(res, {
- msg: 'Token renouvelé, vous pouvez renvoyer un email de bienvenu avec ce lien : https://integration.utt.fr/Register?token=' + newToken,
+ msg:
+ 'Token renouvelé, vous pouvez renvoyer un email de bienvenu avec ce lien : https://integration.utt.fr/Register?token=' +
+ newToken,
});
} catch (err) {
Error(res, { msg: err.message });
diff --git a/backend/src/controllers/bus.controller.ts b/backend/src/controllers/bus.controller.ts
index 561b50c..89f1406 100644
--- a/backend/src/controllers/bus.controller.ts
+++ b/backend/src/controllers/bus.controller.ts
@@ -1,8 +1,8 @@
-import { type Request, type Response } from "express";
-import * as bus_service from "../services/bus.service";
-import { generateEmailHtml, sendEmail } from "../services/email.service";
-import { Error, Ok } from "../utils/responses";
-import { email_from } from "../utils/secret";
+import { type Request, type Response } from 'express';
+import * as bus_service from '../services/bus.service';
+import { generateEmailHtml, sendEmail } from '../services/email.service';
+import { Error, Ok } from '../utils/responses';
+import { email_from } from '../utils/secret';
interface MulterRequest extends Request {
file?: Express.Multer.File;
@@ -13,13 +13,14 @@ export const sendBusAttributionEmails = async (req: Request, res: Response) => {
const attributions = await bus_service.getAllBusAttributions();
if (!attributions.length) {
- Error(res, { msg: "Aucune attribution de bus trouvée." });
+ Error(res, { msg: 'Aucune attribution de bus trouvée.' });
return;
}
for (const attr of attributions) {
- const htmlEmail = generateEmailHtml("templateAttributionBus", {
- bus: attr.bus, time: attr.departure_time
+ const htmlEmail = generateEmailHtml('templateAttributionBus', {
+ bus: attr.bus,
+ time: attr.departure_time,
});
const emailOptions = {
@@ -27,9 +28,9 @@ export const sendBusAttributionEmails = async (req: Request, res: Response) => {
to: [attr.email],
cc: [],
bcc: [],
- subject: `Attribution Bus - ${attr.firstName ?? ""} ${attr.lastName ?? ""}`,
+ subject: `Attribution Bus - ${attr.firstName ?? ''} ${attr.lastName ?? ''}`,
text: `Votre bus attribué est le numéro ${attr.bus}`,
- html: htmlEmail || "",
+ html: htmlEmail || '',
};
await sendEmail(emailOptions);
@@ -46,13 +47,13 @@ export const uploadbusCSV = async (req: MulterRequest, res: Response) => {
try {
const file = req.file;
if (!file) {
- Error(res, { msg: "Fichier CSV manquant." });
+ Error(res, { msg: 'Fichier CSV manquant.' });
}
await bus_service.importBusFromCSV(file.path);
- Ok(res, { msg: "Importation réalisée avec succès." });
+ Ok(res, { msg: 'Importation réalisée avec succès.' });
} catch (error) {
- console.error("Erreur import CSV :", error);
+ console.error('Erreur import CSV :', error);
Error(res, { msg: "Échec de l'importation." });
}
};
diff --git a/backend/src/controllers/challenge.controller.ts b/backend/src/controllers/challenge.controller.ts
index ee02edf..636b505 100644
--- a/backend/src/controllers/challenge.controller.ts
+++ b/backend/src/controllers/challenge.controller.ts
@@ -1,6 +1,6 @@
-import { type Request, type Response } from "express";
-import * as challenge_service from "../services/challenge.service";
-import { Created, Error, Ok, Unauthorized } from "../utils/responses";
+import { type Request, type Response } from 'express';
+import * as challenge_service from '../services/challenge.service';
+import { Created, Error, Ok, Unauthorized } from '../utils/responses';
export const createChallenge = async (req: Request, res: Response) => {
const { title, description, category, points } = req.body;
@@ -8,9 +8,9 @@ export const createChallenge = async (req: Request, res: Response) => {
try {
const challenge = await challenge_service.createChallenge(title, description, category, points, adminId);
- Created(res, { msg: "Challenge créé avec succès", data: challenge });
+ Created(res, { msg: 'Challenge créé avec succès', data: challenge });
} catch (err) {
- Error(res, { msg: "Erreur lors de la création du challenge : " + err });
+ Error(res, { msg: 'Erreur lors de la création du challenge : ' + err });
}
};
@@ -19,9 +19,9 @@ export const deleteChallenge = async (req: Request, res: Response) => {
try {
await challenge_service.deleteChallenge(Number(challengeId));
- Ok(res, { msg: "Challenge supprimée avec succès" });
+ Ok(res, { msg: 'Challenge supprimée avec succès' });
} catch (err) {
- Error(res, { msg: "Erreur lors de la suppression du challenge : " + err });
+ Error(res, { msg: 'Erreur lors de la suppression du challenge : ' + err });
}
};
@@ -29,15 +29,20 @@ export const validateChallenge = async (req: Request, res: Response) => {
const adminId = req.user.userId;
const { challengeId, type, targetId } = req.body;
- if (!["user", "team", "faction"].includes(type)) {
- return Unauthorized(res, { msg: "Type de validation invalide." });
+ if (!['user', 'team', 'faction'].includes(type)) {
+ return Unauthorized(res, { msg: 'Type de validation invalide.' });
}
try {
- const validation = await challenge_service.validateChallenge({ challengeId, type, targetId, validatedBy: adminId });
- Ok(res, { msg: "Challenge validé avec succès", data: validation });
+ const validation = await challenge_service.validateChallenge({
+ challengeId,
+ type,
+ targetId,
+ validatedBy: adminId,
+ });
+ Ok(res, { msg: 'Challenge validé avec succès', data: validation });
} catch (err) {
- Error(res, { msg: "Erreur lors de la validation du challenge : " + err });
+ Error(res, { msg: 'Erreur lors de la validation du challenge : ' + err });
}
};
export const unvalidateChallenge = async (req: Request, res: Response) => {
@@ -45,7 +50,7 @@ export const unvalidateChallenge = async (req: Request, res: Response) => {
try {
const unvalidation = await challenge_service.unvalidateChallenge({ challengeId, factionId, teamId, userId });
- Ok(res, { msg: "Challenge invalidé avec succès", data: unvalidation });
+ Ok(res, { msg: 'Challenge invalidé avec succès', data: unvalidation });
} catch (err) {
Error(res, { msg: "Erreur lors de l'invalidation du challenge : " + err });
}
@@ -57,7 +62,7 @@ export const addPointsToFaction = async (req: Request, res: Response) => {
try {
const result = await challenge_service.modifyFactionPoints({ title, factionId, points, reason, adminId });
- Ok(res, { msg: "Points ajoutés à la faction", data: result });
+ Ok(res, { msg: 'Points ajoutés à la faction', data: result });
} catch (err) {
Error(res, { msg: "Erreur lors de l'ajout de points : " + err });
}
@@ -68,10 +73,16 @@ export const removePointsFromFaction = async (req: Request, res: Response) => {
const { title, factionId, points, reason } = req.body;
try {
- const result = await challenge_service.modifyFactionPoints({ title, factionId, points: -Math.abs(points), reason, adminId });
- Ok(res, { msg: "Points retirés à la faction", data: result });
+ const result = await challenge_service.modifyFactionPoints({
+ title,
+ factionId,
+ points: -Math.abs(points),
+ reason,
+ adminId,
+ });
+ Ok(res, { msg: 'Points retirés à la faction', data: result });
} catch (err) {
- Error(res, { msg: "Erreur lors du retrait de points : " + err });
+ Error(res, { msg: 'Erreur lors du retrait de points : ' + err });
}
};
@@ -85,23 +96,20 @@ export const updateChallenge = async (req: Request, res: Response) => {
category,
points,
});
- Ok(res, { msg: "Challenge mis à jour avec succès", data: updated });
+ Ok(res, { msg: 'Challenge mis à jour avec succès', data: updated });
} catch (err) {
- Error(res, { msg: "Erreur lors de la mise à jour : " + err });
+ Error(res, { msg: 'Erreur lors de la mise à jour : ' + err });
}
};
export const getValidatedChallenges = async (req: Request, res: Response) => {
-
try {
const challengesValidated = await challenge_service.getValidatedChallenges();
Ok(res, { data: challengesValidated });
} catch (err) {
- Error(res, { msg: "Erreur lors de la récupération des challenges validés " + err });
+ Error(res, { msg: 'Erreur lors de la récupération des challenges validés ' + err });
}
};
-
-
// === PUBLIC ===
export const getAllChallenges = async (req: Request, res: Response) => {
@@ -109,18 +117,17 @@ export const getAllChallenges = async (req: Request, res: Response) => {
const challenges = await challenge_service.getAllChallenges();
Ok(res, { data: challenges });
} catch (err) {
- Error(res, { msg: "Erreur lors de la récupération des challenges : " + err });
+ Error(res, { msg: 'Erreur lors de la récupération des challenges : ' + err });
}
};
export const getTotalFactionPoints = async (req: Request, res: Response) => {
-
const { factionId } = req.query;
try {
const points = await challenge_service.getTotalFactionPoints(Number(factionId));
Ok(res, { data: points });
} catch (err) {
- Error(res, { msg: "Erreur lors de la récupération des points : " + err });
+ Error(res, { msg: 'Erreur lors de la récupération des points : ' + err });
}
};
diff --git a/backend/src/controllers/discord.controller.ts b/backend/src/controllers/discord.controller.ts
index 50b9aea..5dcc35e 100644
--- a/backend/src/controllers/discord.controller.ts
+++ b/backend/src/controllers/discord.controller.ts
@@ -1,21 +1,23 @@
-import { type Request, type Response } from "express";
-import * as discord_service from "../services/discord.service";
-import { Error, Ok } from "../utils/responses";
+import { type Request, type Response } from 'express';
+import * as discord_service from '../services/discord.service';
+import { Error, Ok } from '../utils/responses';
export const createChallenge = async (req: Request, res: Response) => {
const { code } = req.body;
const userId = req.user?.userId;
if (!code) {
- Error(res, { msg: "Code manquant dans l'URL" })
+ Error(res, { msg: "Code manquant dans l'URL" });
return;
}
try {
const discordUser = await discord_service.syncDiscordUserId(String(code), userId);
- Ok(res, { msg: `Ton compte Discord (${discordUser.username}#${discordUser.discriminator}) a bien été lié à ton profil UTT.` });
+ Ok(res, {
+ msg: `Ton compte Discord (${discordUser.username}#${discordUser.discriminator}) a bien été lié à ton profil UTT.`,
+ });
} catch (err) {
- console.error("Erreur dans handleDiscordCallback:", err);
- Error(res, { msg: "Erreur pendant la liaison avec Discord" });
+ console.error('Erreur dans handleDiscordCallback:', err);
+ Error(res, { msg: 'Erreur pendant la liaison avec Discord' });
}
};
diff --git a/backend/src/controllers/email.controller.ts b/backend/src/controllers/email.controller.ts
index 02f8d95..03aef66 100644
--- a/backend/src/controllers/email.controller.ts
+++ b/backend/src/controllers/email.controller.ts
@@ -1,29 +1,19 @@
import { type Request, type Response } from 'express';
-import type { EmailOptions } from "../../types/email";
+import type { EmailOptions } from '../../types/email';
import { defaultPreviewData } from '../email/email.preview-data';
-import { generateEmailHtml, sendEmail } from '../services/email.service';
+import { generateEmailHtml, getRecipients, sendEmail } from '../services/email.service';
import * as registration_service from '../services/registration.service';
import * as user_service from '../services/user.service';
import { Error, Ok } from '../utils/responses';
import { email_from, service_url } from '../utils/secret';
import { getLatestUploadedDocument } from '../utils/uploadDocuments';
-// Fonction utilitaire pour récupérer les destinataires
-const getRecipients = async (permission: string | undefined, sendTo: string[] | undefined) => {
- if (permission) {
- const users = await user_service.getUsersbyPermission(permission);
- return users.map((user) => user.email);
- } else {
- return sendTo || [];
- }
-};
-
export const handleSendEmail = async (req: Request, res: Response) => {
- const { subject, templateName, permission, sendTo, html, title, content } = req.body.payload;
+ const { subject, templateName, recipientsGroups, sendTo, html, title, content } = req.body.payload;
try {
// Récupérer les destinataires
- const recipients = await getRecipients(permission, sendTo);
+ const recipients = await getRecipients(recipientsGroups, sendTo);
if (!recipients.length) {
Error(res, { msg: 'Aucun destinataire trouvé.' });
@@ -49,20 +39,22 @@ export const handleSendEmail = async (req: Request, res: Response) => {
continue;
}
- htmlEmail = generateEmailHtml(templateName, { token: registrationToken });
+ htmlEmail = generateEmailHtml(templateName, {
+ token: registrationToken,
+ });
} else if (templateName === 'templateNotebook') {
const notebook_fr = await getLatestUploadedDocument('notebooks', 'fr');
const notebook_en = await getLatestUploadedDocument('notebooks', 'en');
if (!notebook_fr || !notebook_en) {
return Error(res, {
- msg: 'Cahier de vacances manquant (fr ou en).'
+ msg: 'Cahier de vacances manquant (fr ou en).',
});
}
htmlEmail = generateEmailHtml(templateName, {
notebook_fr: `${service_url}api/uploads/notebooks/fr.pdf`,
- notebook_en: `${service_url}api/uploads/notebooks/en.pdf`
+ notebook_en: `${service_url}api/uploads/notebooks/en.pdf`,
});
} else {
htmlEmail = generateEmailHtml(templateName, defaultPreviewData[templateName] || {});
@@ -90,7 +82,7 @@ export const handleSendEmail = async (req: Request, res: Response) => {
return;
} catch (err) {
console.error(err);
- Error(res, { msg: 'Erreur lors de l\'envoi de l\'email.' });
+ Error(res, { msg: "Erreur lors de l'envoi de l'email." });
return;
}
};
@@ -108,7 +100,7 @@ export const handlePreviewEmail = async (req: Request, res: Response) => {
});
if (!htmlEmail) {
- Error(res, { msg: "Nom de template invalide" });
+ Error(res, { msg: 'Nom de template invalide' });
return;
}
diff --git a/backend/src/controllers/event.controller.ts b/backend/src/controllers/event.controller.ts
index f9c5970..90049f6 100644
--- a/backend/src/controllers/event.controller.ts
+++ b/backend/src/controllers/event.controller.ts
@@ -1,62 +1,64 @@
-import { type Request, type Response } from "express";
-import * as event_service from "../services/event.service";
-import * as team_service from "../services/team.service";
-import { Conflict, Error, Ok, Teapot, Unauthorized } from "../utils/responses";
-import { shotgun_password } from "../utils/secret";
+import { type Request, type Response } from 'express';
+import * as event_service from '../services/event.service';
+import * as team_service from '../services/team.service';
+import { Conflict, Error, Ok, Teapot, Unauthorized } from '../utils/responses';
+import { shotgun_password } from '../utils/secret';
type AuthenticatedRequest = Request & { user?: { userId?: number } };
export const checkShotgunStatus = async (req: Request, res: Response) => {
try {
const status = await event_service.getEventsStatus();
- Ok(res, ({ data: { status: Boolean(status?.shotgun_open), password: status?.shotgun_open ? shotgun_password : "" } }));
+ Ok(res, {
+ data: { status: Boolean(status?.shotgun_open), password: status?.shotgun_open ? shotgun_password : '' },
+ });
} catch (error) {
- Error(res, { msg: "Error while catching shotgun status :" + error })
+ Error(res, { msg: 'Error while catching shotgun status :' + error });
}
};
export const checkPreRegisterStatus = async (req: Request, res: Response) => {
try {
const status = await event_service.getEventsStatus();
- Ok(res, ({ data: status?.pre_registration_open }));
+ Ok(res, { data: status?.pre_registration_open });
} catch (error) {
- Error(res, { msg: "Error while catching pre-registration status :" + error })
+ Error(res, { msg: 'Error while catching pre-registration status :' + error });
}
};
export const checkSDIStatus = async (req: Request, res: Response) => {
try {
const status = await event_service.getEventsStatus();
- Ok(res, ({ data: status?.sdi_open }));
+ Ok(res, { data: status?.sdi_open });
} catch (error) {
- Error(res, { msg: "Error while catching SDI status :" + error })
+ Error(res, { msg: 'Error while catching SDI status :' + error });
}
};
export const checkWEIStatus = async (req: Request, res: Response) => {
try {
const status = await event_service.getEventsStatus();
- Ok(res, ({ data: status?.wei_open }));
+ Ok(res, { data: status?.wei_open });
} catch (error) {
- Error(res, { msg: "Error while catching WEI status :" + error })
+ Error(res, { msg: 'Error while catching WEI status :' + error });
}
};
export const checkFoodStatus = async (req: Request, res: Response) => {
try {
const status = await event_service.getEventsStatus();
- Ok(res, ({ data: status?.food_open }));
+ Ok(res, { data: status?.food_open });
} catch (error) {
- Error(res, { msg: "Error while catching Food status :" + error })
+ Error(res, { msg: 'Error while catching Food status :' + error });
}
};
export const checkChallStatus = async (req: Request, res: Response) => {
try {
const status = await event_service.getEventsStatus();
- Ok(res, ({ data: status?.chall_open }));
+ Ok(res, { data: status?.chall_open });
} catch (error) {
- Error(res, { msg: "Error while catching Challenge status :" + error })
+ Error(res, { msg: 'Error while catching Challenge status :' + error });
}
};
@@ -70,65 +72,63 @@ export const getShotgunAttempts = async (req: Request, res: Response) => {
}
const teamUsers = await team_service.getTeamUsers(attempt.teamId);
- const leaderCount = teamUsers.filter((user) => user.permission !== "Nouveau").length;
+ const leaderCount = teamUsers.filter((user) => user.permission !== 'Nouveau').length;
return { ...attempt, leaderCount };
- })
+ }),
);
Ok(res, { data: shotgunAttemptsWithLeaders });
} catch (error) {
- Error(res, { msg: "Erreur lors de la récupération des tentatives shotgun : " + error });
+ Error(res, { msg: 'Erreur lors de la récupération des tentatives shotgun : ' + error });
}
};
-
export const shotgunAttempt = async (req: Request, res: Response) => {
-
const { password } = req.body as { password?: string };
const userId = (req as AuthenticatedRequest).user?.userId;
if (!userId) {
- Unauthorized(res, { msg: "Utilisateur non authentifié." });
+ Unauthorized(res, { msg: 'Utilisateur non authentifié.' });
return;
}
if (!shotgun_password) {
- Error(res, { msg: "Mot de passe shotgun non configuré côté serveur." });
+ Error(res, { msg: 'Mot de passe shotgun non configuré côté serveur.' });
return;
}
if (password !== shotgun_password) {
- Teapot(res, { msg: "Le mot de passe shotgun est incorrect." });
+ Teapot(res, { msg: 'Le mot de passe shotgun est incorrect.' });
return;
}
const status = await event_service.getEventsStatus();
if (!status?.shotgun_open) {
- Unauthorized(res, { msg: "Le shotgun est fermé." });
+ Unauthorized(res, { msg: 'Le shotgun est fermé.' });
return;
}
try {
- const userTeam = await team_service.getUserTeam(userId)
+ const userTeam = await team_service.getUserTeam(userId);
if (!userTeam) {
Error(res, { msg: "Erreur : Tu n'as pas d'équipe !" });
return;
}
- const alreadyShotgun = await event_service.alreadyShotgun(userTeam)
+ const alreadyShotgun = await event_service.alreadyShotgun(userTeam);
if (alreadyShotgun) {
- Conflict(res, { msg: "Votre équipe est déjà dans le shotgun." });
+ Conflict(res, { msg: 'Votre équipe est déjà dans le shotgun.' });
return;
}
await event_service.validateShotgun(userTeam);
- Ok(res, { msg: "Shotgun validé !" });
+ Ok(res, { msg: 'Shotgun validé !' });
return;
} catch (error) {
- Error(res, { msg: "Erreur pendant le shotguns : " + error });
+ Error(res, { msg: 'Erreur pendant le shotguns : ' + error });
return;
}
};
@@ -138,9 +138,9 @@ export const togglePreRegistration = async (req: Request, res: Response) => {
try {
const result = await event_service.updatepreRegistrationStatus(preRegistrationOpen);
- Ok(res, { msg: "Paramètres mis à jour.", data: result });
+ Ok(res, { msg: 'Paramètres mis à jour.', data: result });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour." });
+ Error(res, { msg: 'Erreur lors de la mise à jour.' });
}
};
@@ -149,9 +149,9 @@ export const toggleShotgun = async (req: Request, res: Response) => {
try {
const result = await event_service.updateShotgunStatus(shotgunOpen);
- Ok(res, { msg: "Paramètres mis à jour.", data: result });
+ Ok(res, { msg: 'Paramètres mis à jour.', data: result });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour." });
+ Error(res, { msg: 'Erreur lors de la mise à jour.' });
}
};
@@ -160,9 +160,9 @@ export const toggleSDI = async (req: Request, res: Response) => {
try {
const result = await event_service.updateSDIStatus(sdiOpen);
- Ok(res, { msg: "Paramètres mis à jour.", data: result });
+ Ok(res, { msg: 'Paramètres mis à jour.', data: result });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour." });
+ Error(res, { msg: 'Erreur lors de la mise à jour.' });
}
};
@@ -171,9 +171,9 @@ export const toggleWEI = async (req: Request, res: Response) => {
try {
const result = await event_service.updateWEIStatus(weiOpen);
- Ok(res, { msg: "Paramètres mis à jour.", data: result });
+ Ok(res, { msg: 'Paramètres mis à jour.', data: result });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour." });
+ Error(res, { msg: 'Erreur lors de la mise à jour.' });
}
};
@@ -182,9 +182,9 @@ export const toggleFood = async (req: Request, res: Response) => {
try {
const result = await event_service.updateFoodStatus(foodOpen);
- Ok(res, { msg: "Paramètres mis à jour.", data: result });
+ Ok(res, { msg: 'Paramètres mis à jour.', data: result });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour." });
+ Error(res, { msg: 'Erreur lors de la mise à jour.' });
}
};
@@ -193,8 +193,8 @@ export const toggleChall = async (req: Request, res: Response) => {
try {
const result = await event_service.updateChallStatus(challOpen);
- Ok(res, { msg: "Paramètres mis à jour.", data: result });
+ Ok(res, { msg: 'Paramètres mis à jour.', data: result });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour." });
+ Error(res, { msg: 'Erreur lors de la mise à jour.' });
}
};
diff --git a/backend/src/controllers/faction.controller.ts b/backend/src/controllers/faction.controller.ts
index 312c72e..6af0041 100644
--- a/backend/src/controllers/faction.controller.ts
+++ b/backend/src/controllers/faction.controller.ts
@@ -8,7 +8,7 @@ export const getFactions = async (req: Request, res: Response) => {
Ok(res, { data: factions });
return;
} catch {
- Error(res, { msg: "Erreur lors de la récupération des factions" });
+ Error(res, { msg: 'Erreur lors de la récupération des factions' });
}
};
@@ -20,30 +20,30 @@ export const getFaction = async (req: Request, res: Response) => {
Ok(res, { data: faction });
return;
} catch {
- Error(res, { msg: "Erreur lors de la récupération des factions" });
+ Error(res, { msg: 'Erreur lors de la récupération des factions' });
}
};
export const createFaction = async (req: Request, res: Response) => {
- const { factionName } = req.body
+ const { factionName } = req.body;
try {
await faction_service.createFaction(factionName);
- Ok(res, { msg: "Faction crée avec succès !" });
+ Ok(res, { msg: 'Faction crée avec succès !' });
return;
} catch {
- Error(res, { msg: "Erreur lors de la création de la faction" });
+ Error(res, { msg: 'Erreur lors de la création de la faction' });
}
};
export const deleteFaction = async (req: Request, res: Response) => {
- const { factionId } = req.query
+ const { factionId } = req.query;
try {
await faction_service.deleteFaction(Number(factionId));
- Ok(res, { msg: "Faction supprimée avec succès !" });
+ Ok(res, { msg: 'Faction supprimée avec succès !' });
return;
} catch {
- Error(res, { msg: "Erreur lors de la suppression de la faction" });
+ Error(res, { msg: 'Erreur lors de la suppression de la faction' });
}
};
diff --git a/backend/src/controllers/im_export.controller.ts b/backend/src/controllers/im_export.controller.ts
index a65a0cf..a8f8480 100644
--- a/backend/src/controllers/im_export.controller.ts
+++ b/backend/src/controllers/im_export.controller.ts
@@ -1,14 +1,19 @@
-import { type Request, type Response } from "express";
-import fs from "fs";
-import path from "path";
-import * as event_service from "../services/event.service";
-import * as export_service from "../services/im_export.service";
-import * as permanence_service from "../services/permanence.service";
-import * as team_service from "../services/team.service";
+import { type Request, type Response } from 'express';
+import fs from 'fs';
+import path from 'path';
+import * as event_service from '../services/event.service';
+import * as export_service from '../services/im_export.service';
+import * as permanence_service from '../services/permanence.service';
+import * as team_service from '../services/team.service';
import * as user_service from '../services/user.service';
-import { Error, Ok } from "../utils/responses";
-import { spreadsheet_id } from "../utils/secret";
-import { getLatestUploadedDocument, isSafeUploadSegment, removeUploadedDocuments, toUploadedDocumentStatus } from "../utils/uploadDocuments";
+import { Error, Ok } from '../utils/responses';
+import { spreadsheet_id } from '../utils/secret';
+import {
+ getLatestUploadedDocument,
+ isSafeUploadSegment,
+ removeUploadedDocuments,
+ toUploadedDocumentStatus,
+} from '../utils/uploadDocuments';
export const exportAllDataToSheets = async (req: Request, res: Response) => {
try {
@@ -20,84 +25,104 @@ export const exportAllDataToSheets = async (req: Request, res: Response) => {
// 2. Mapping -> format pour Google Sheets (array de array)
const usersValues = [
- ["ID", "Prénom", "Nom", "Email", "Branche", "Permission", "Majeur", "Contact", "Discord", "Team", "Faction"],
- ...userList.map(u => [
+ [
+ 'ID',
+ 'Prénom',
+ 'Nom',
+ 'Email',
+ 'Branche',
+ 'Permission',
+ 'Majeur',
+ 'Contact',
+ 'Discord',
+ 'Team',
+ 'Faction',
+ ],
+ ...userList.map((u) => [
u.id ?? 0,
- u.first_name ?? "No first name",
- u.last_name ?? "No last name",
- u.email ?? "No email",
- u.branch ?? "No branch",
- u.permission ?? "No permissions",
- u.majeur ?? "Pas de données",
- u.contact ?? "No contact",
- u.discord_id ?? "No discord ID",
- u.teamName ?? "No Team",
- u.factionName ?? "No faction"
- ])
+ u.first_name ?? 'No first name',
+ u.last_name ?? 'No last name',
+ u.email ?? 'No email',
+ u.branch ?? 'No branch',
+ u.permission ?? 'No permissions',
+ u.majeur ?? 'Pas de données',
+ u.contact ?? 'No contact',
+ u.discord_id ?? 'No discord ID',
+ u.teamName ?? 'No Team',
+ u.factionName ?? 'No faction',
+ ]),
];
const teamsValues = [
- ["ID", "Nom", "Type", "Faction"],
- ...teamList.map(t => [
+ ['ID', 'Nom', 'Type', 'Faction'],
+ ...teamList.map((t) => [
t.id,
- t.name ?? "No name",
- t.type ?? "No type",
- t.teamFaction?.name ?? "No faction"
- ])
+ t.name ?? 'No name',
+ t.type ?? 'No type',
+ t.teamFaction?.name ?? 'No faction',
+ ]),
];
const permanenceValues = [
[
- "ID",
- "Nom",
- "Début",
- "Fin",
- "Lieu",
- "Responsables",
- "Inscrits (noms)",
- "Inscrits (emails)",
- "Présents",
- "Absents"
+ 'ID',
+ 'Nom',
+ 'Début',
+ 'Fin',
+ 'Lieu',
+ 'Responsables',
+ 'Inscrits (noms)',
+ 'Inscrits (emails)',
+ 'Présents',
+ 'Absents',
],
...permanenceList.map((p) => {
- const respoNames = p.respo ? p.respo.firstName + " " + p.respo.lastName : "Aucun";
- const userNames = p.users?.map((u) => `${u.first_name} ${u.last_name}`)?.join(" ; ") || "Aucun inscrit";
- const userEmails = p.users?.map((u) => u.email)?.join(" ; ") || "Aucun inscrit";
-
- const claimedUsers = p.users?.filter((u) => u.claimed)?.map((u) => `${u.first_name} ${u.last_name}`)?.join(" ; ") || "Aucun";
- const unclaimedUsers = p.users?.filter((u) => !u.claimed)?.map((u) => `${u.first_name} ${u.last_name}`)?.join(" ; ") || "Aucun";
+ const respoNames = p.respo ? p.respo.firstName + ' ' + p.respo.lastName : 'Aucun';
+ const userNames = p.users?.map((u) => `${u.first_name} ${u.last_name}`)?.join(' ; ') || 'Aucun inscrit';
+ const userEmails = p.users?.map((u) => u.email)?.join(' ; ') || 'Aucun inscrit';
+
+ const claimedUsers =
+ p.users
+ ?.filter((u) => u.claimed)
+ ?.map((u) => `${u.first_name} ${u.last_name}`)
+ ?.join(' ; ') || 'Aucun';
+ const unclaimedUsers =
+ p.users
+ ?.filter((u) => !u.claimed)
+ ?.map((u) => `${u.first_name} ${u.last_name}`)
+ ?.join(' ; ') || 'Aucun';
return [
p.id,
- p.name ?? "Sans nom",
- p.start_at ? new Date(p.start_at).toLocaleString("fr-FR") : "N/A",
- p.end_at ? new Date(p.end_at).toLocaleString("fr-FR") : "N/A",
- p.location ?? "Sans lieu",
+ p.name ?? 'Sans nom',
+ p.start_at ? new Date(p.start_at).toLocaleString('fr-FR') : 'N/A',
+ p.end_at ? new Date(p.end_at).toLocaleString('fr-FR') : 'N/A',
+ p.location ?? 'Sans lieu',
respoNames,
userNames,
userEmails,
claimedUsers,
- unclaimedUsers
+ unclaimedUsers,
];
- })
+ }),
];
const shotgunValues = [
- ["ID", "Nom de l'équipe", "Type", "Horodatage"],
- ...shotgunList.map(s => [
+ ['ID', "Nom de l'équipe", 'Type', 'Horodatage'],
+ ...shotgunList.map((s) => [
s.id,
- s.teamName ?? "No name",
- s.teamType ?? "No type",
- s.timestamp?.toISOString() ?? "No timestamp"
- ])
+ s.teamName ?? 'No name',
+ s.teamType ?? 'No type',
+ s.timestamp?.toISOString() ?? 'No timestamp',
+ ]),
];
// 3. Envoi vers les feuilles
- await export_service.writeToGoogleSheet(spreadsheet_id, "USER!A1", usersValues);
- await export_service.writeToGoogleSheet(spreadsheet_id, "TEAM!A1", teamsValues);
- await export_service.writeToGoogleSheet(spreadsheet_id, "PERMANENCES!A1", permanenceValues);
- await export_service.writeToGoogleSheet(spreadsheet_id, "SHOTGUN!A1", shotgunValues);
+ await export_service.writeToGoogleSheet(spreadsheet_id, 'USER!A1', usersValues);
+ await export_service.writeToGoogleSheet(spreadsheet_id, 'TEAM!A1', teamsValues);
+ await export_service.writeToGoogleSheet(spreadsheet_id, 'PERMANENCES!A1', permanenceValues);
+ await export_service.writeToGoogleSheet(spreadsheet_id, 'SHOTGUN!A1', shotgunValues);
- Ok(res, { msg: "Export réalisé avec succès !" });
+ Ok(res, { msg: 'Export réalisé avec succès !' });
} catch (error) {
console.error(error);
Error(res, { msg: "Erreur lors de l'export vers Google Sheets" });
@@ -105,37 +130,34 @@ export const exportAllDataToSheets = async (req: Request, res: Response) => {
};
export const updateFoodMenu = async (req: Request, res: Response) => {
-
const file = req.file;
try {
-
// Supprimer l'ancien Menu si un nouveau est uploadé
if (file) {
- const targetDir = path.join(__dirname, "../../foodmenu");
+ const targetDir = path.join(__dirname, '../../foodmenu');
if (fs.existsSync(targetDir)) {
fs.rmSync(targetDir, { recursive: true, force: true });
fs.mkdirSync(targetDir);
}
-
}
- Ok(res, { msg: "Menu mis à jour avec succès" });
+ Ok(res, { msg: 'Menu mis à jour avec succès' });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la mise à jour du Menu" });
+ Error(res, { msg: 'Erreur lors de la mise à jour du Menu' });
}
};
export const updatePlannings = async (req: Request, res: Response) => {
try {
- Ok(res, { msg: "Planning mis à jour avec succès" });
+ Ok(res, { msg: 'Planning mis à jour avec succès' });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la mise à jour du Planning" });
+ Error(res, { msg: 'Erreur lors de la mise à jour du Planning' });
}
};
@@ -153,7 +175,7 @@ export const getUploadedDocumentStatus = async (req: Request, res: Response) =>
const { category, item } = req.params;
if (!isSafeUploadSegment(category) || !isSafeUploadSegment(item)) {
- Error(res, { msg: "Paramètres invalides" });
+ Error(res, { msg: 'Paramètres invalides' });
return;
}
@@ -164,7 +186,7 @@ export const getUploadedDocumentStatus = async (req: Request, res: Response) =>
data: toUploadedDocumentStatus(category, latestDocument),
});
} catch (err: unknown) {
- if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
+ if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {
Ok(res, {
data: toUploadedDocumentStatus(category, null),
});
@@ -172,7 +194,7 @@ export const getUploadedDocumentStatus = async (req: Request, res: Response) =>
}
console.error(err);
- Error(res, { msg: "Erreur lors de la vérification du document" });
+ Error(res, { msg: 'Erreur lors de la vérification du document' });
}
};
@@ -180,7 +202,7 @@ export const deleteDocument = async (req: Request, res: Response) => {
const { category, item } = req.params;
if (!isSafeUploadSegment(category) || !isSafeUploadSegment(item)) {
- Error(res, { msg: "Paramètres invalides" });
+ Error(res, { msg: 'Paramètres invalides' });
return;
}
@@ -188,16 +210,16 @@ export const deleteDocument = async (req: Request, res: Response) => {
const deletedCount = await removeUploadedDocuments(category, item);
if (deletedCount === 0) {
- return Ok(res, { msg: "Aucun document à supprimer" });
+ return Ok(res, { msg: 'Aucun document à supprimer' });
}
Ok(res, {});
} catch (err: unknown) {
- if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
+ if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {
Ok(res, {});
return;
}
- Error(res, { msg: "Erreur lors de la vérification du document" });
+ Error(res, { msg: 'Erreur lors de la vérification du document' });
}
};
diff --git a/backend/src/controllers/news.controller.ts b/backend/src/controllers/news.controller.ts
index 3b40a52..8e24cd7 100644
--- a/backend/src/controllers/news.controller.ts
+++ b/backend/src/controllers/news.controller.ts
@@ -1,12 +1,12 @@
-import { type Request, type Response } from "express";
-import fs from "fs";
-import path from "path";
-import * as email_service from "../services/email.service";
-import { generateEmailHtml } from "../services/email.service";
-import * as news_service from "../services/news.service";
+import { type Request, type Response } from 'express';
+import fs from 'fs';
+import path from 'path';
+import * as email_service from '../services/email.service';
+import { generateEmailHtml } from '../services/email.service';
+import * as news_service from '../services/news.service';
import * as user_service from '../services/user.service';
-import { Error, Ok } from "../utils/responses";
-import { email_from } from "../utils/secret";
+import { Error, Ok } from '../utils/responses';
+import { email_from } from '../utils/secret';
const toStoredUploadPath = (imageUrl: string) => {
if (!imageUrl) {
@@ -19,7 +19,7 @@ const toStoredUploadPath = (imageUrl: string) => {
}
// Accept absolute URLs and keep only the pathname part.
- if (normalized.startsWith("http://") || normalized.startsWith("https://")) {
+ if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
try {
normalized = new URL(normalized).pathname;
} catch {
@@ -27,11 +27,11 @@ const toStoredUploadPath = (imageUrl: string) => {
}
}
- if (normalized.startsWith("/api/")) {
+ if (normalized.startsWith('/api/')) {
normalized = normalized.slice(4);
}
- if (!normalized.startsWith("/uploads/")) {
+ if (!normalized.startsWith('/uploads/')) {
return null;
}
@@ -44,7 +44,7 @@ const resolveStoredImagePath = (imageUrl: string) => {
return null;
}
- return path.resolve(process.cwd(), storedPath.replace(/^\//, ""));
+ return path.resolve(process.cwd(), storedPath.replace(/^\//, ''));
};
const deleteImageIfExists = (imageUrl: string) => {
@@ -63,18 +63,17 @@ export const createNews = async (req: Request, res: Response) => {
const file = req.file;
try {
- const resolvedImageUrl = file
- ? `/uploads/news/${file.filename}`
- : image_url;
+ const resolvedImageUrl = file ? `/uploads/news/${file.filename}` : image_url;
const news = await news_service.createNews(
title,
description,
type,
- published === true || published === "true",
+ published === true || published === 'true',
target,
- resolvedImageUrl);
- Ok(res, { msg: "Actu créée avec succès", data: news });
+ resolvedImageUrl,
+ );
+ Ok(res, { msg: 'Actu créée avec succès', data: news });
} catch (err) {
console.error(err);
Error(res, { msg: "Erreur lors de la création de l'actu" });
@@ -87,7 +86,7 @@ export const listAllNews = async (_req: Request, res: Response) => {
Ok(res, { data: news });
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la récupération des actus" });
+ Error(res, { msg: 'Erreur lors de la récupération des actus' });
}
};
@@ -97,7 +96,7 @@ export const listPublishedNews = async (_req: Request, res: Response) => {
Ok(res, { data: news });
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la récupération des actus publiées" });
+ Error(res, { msg: 'Erreur lors de la récupération des actus publiées' });
}
};
@@ -109,7 +108,7 @@ export const listPublishedNewsByType = async (req: Request, res: Response) => {
Ok(res, { data: news });
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la récupération des actus par type" });
+ Error(res, { msg: 'Erreur lors de la récupération des actus par type' });
}
};
@@ -128,12 +127,13 @@ export const publishNews = async (req: Request, res: Response) => {
return;
}
- const recipients = news.target === "Tous"
- ? (await user_service.getUsersAdmin()).map(u => u.email)
- : (await user_service.getUsersbyPermission(news.target)).map(u => u.email);
+ const recipients =
+ news.target === 'Tous'
+ ? (await user_service.getUsersAdmin()).map((u) => u.email)
+ : (await user_service.getUsersbyPermission(news.target)).map((u) => u.email);
if (recipients.length === 0) {
- Error(res, { msg: "No recipients" });
+ Error(res, { msg: 'No recipients' });
}
const email = {
@@ -148,15 +148,15 @@ export const publishNews = async (req: Request, res: Response) => {
await email_service.sendEmail(email);
}
- Ok(res, { msg: "Actu publiée" });
+ Ok(res, { msg: 'Actu publiée' });
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la publication ou de la notification" });
+ Error(res, { msg: 'Erreur lors de la publication ou de la notification' });
}
};
export const deleteNews = async (req: Request, res: Response) => {
- const { newsId } = req.query
+ const { newsId } = req.query;
try {
const existing = await news_service.getNewsById(Number(newsId));
@@ -165,8 +165,7 @@ export const deleteNews = async (req: Request, res: Response) => {
}
await news_service.deleteNews(Number(newsId));
- Ok(res, { msg: "Actus supprimée avec succès !" });
-
+ Ok(res, { msg: 'Actus supprimée avec succès !' });
} catch (err) {
console.error(err);
Error(res, { msg: "Erreur lors de la suppression de l'actus" });
@@ -176,25 +175,28 @@ export const deleteNews = async (req: Request, res: Response) => {
export const updateNews = async (req: Request, res: Response) => {
const { id, title, description, type, target, image_url } = req.body;
const file = req.file;
- const hasImageUrlField = Object.prototype.hasOwnProperty.call(req.body, "image_url");
+ const hasImageUrlField = Object.prototype.hasOwnProperty.call(req.body, 'image_url');
const resolvedImageUrl = file
? `/uploads/news/${file.filename}`
: hasImageUrlField
- ? (image_url ?? null)
- : undefined;
+ ? (image_url ?? null)
+ : undefined;
try {
const existing = await news_service.getNewsById(Number(id));
if (!existing) {
- Error(res, { msg: "Actu introuvable" });
+ Error(res, { msg: 'Actu introuvable' });
return;
}
- const shouldReplaceImage = typeof resolvedImageUrl === "string";
+ const shouldReplaceImage = typeof resolvedImageUrl === 'string';
const shouldRemoveImage = resolvedImageUrl === null;
// Supprimer l'ancienne image si elle est remplacée ou explicitement supprimée.
- if (existing.image_url && ((shouldReplaceImage && existing.image_url !== resolvedImageUrl) || shouldRemoveImage)) {
+ if (
+ existing.image_url &&
+ ((shouldReplaceImage && existing.image_url !== resolvedImageUrl) || shouldRemoveImage)
+ ) {
deleteImageIfExists(existing.image_url);
}
@@ -211,7 +213,7 @@ export const updateNews = async (req: Request, res: Response) => {
const updated = await news_service.updateNews(Number(id), updates);
- Ok(res, { msg: "Actu mise à jour avec succès", data: updated });
+ Ok(res, { msg: 'Actu mise à jour avec succès', data: updated });
} catch (err) {
console.error(err);
Error(res, { msg: "Erreur lors de la mise à jour de l'actu" });
diff --git a/backend/src/controllers/permanence.controller.ts b/backend/src/controllers/permanence.controller.ts
index 956fdae..4e3d770 100644
--- a/backend/src/controllers/permanence.controller.ts
+++ b/backend/src/controllers/permanence.controller.ts
@@ -1,6 +1,6 @@
-import { type Request, type Response } from "express";
-import * as permanence_service from "../services/permanence.service";
-import { Error, Ok } from "../utils/responses";
+import { type Request, type Response } from 'express';
+import * as permanence_service from '../services/permanence.service';
+import { Error, Ok } from '../utils/responses';
interface MulterRequest extends Request {
file?: Express.Multer.File;
@@ -12,11 +12,11 @@ const validatePermanenceData = (start_at: string, end_at: string) => {
const endDate = new Date(end_at);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
- return { valid: false, msg: "Les dates de début et de fin doivent être valides" };
+ return { valid: false, msg: 'Les dates de début et de fin doivent être valides' };
}
if (startDate >= endDate) {
- return { valid: false, msg: "La date de début doit être avant la date de fin" };
+ return { valid: false, msg: 'La date de début doit être avant la date de fin' };
}
return { valid: true };
@@ -27,7 +27,7 @@ export const createPermanence = async (req: Request, res: Response) => {
const { name, description, location, start_at, end_at, capacity, difficulty, respoId } = req.body;
if (!name || !location || !start_at || !end_at || !capacity || !difficulty) {
- Error(res, { msg: "Tous les champs sont requis" });
+ Error(res, { msg: 'Tous les champs sont requis' });
return;
}
@@ -48,15 +48,14 @@ export const createPermanence = async (req: Request, res: Response) => {
Number(difficulty),
Number(respoId),
);
- Ok(res, { msg: "Permanence créée avec succès" });
+ Ok(res, { msg: 'Permanence créée avec succès' });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la création de la permanence" });
+ Error(res, { msg: 'Erreur lors de la création de la permanence' });
}
};
-
export const updatePermanence = async (req: Request, res: Response) => {
const { permId, name, description, location, start_at, end_at, capacity, difficulty, respoId } = req.body;
@@ -78,29 +77,27 @@ export const updatePermanence = async (req: Request, res: Response) => {
end_at ? new Date(end_at) : perm.end_at,
capacity !== undefined ? Number(capacity) : perm.capacity,
difficulty !== undefined ? Number(difficulty) : perm.difficulty,
- Number(respoId)
+ Number(respoId),
);
- Ok(res, { msg: "Permanence mise à jour avec succès" });
+ Ok(res, { msg: 'Permanence mise à jour avec succès' });
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la mise à jour de la permanence" });
+ Error(res, { msg: 'Erreur lors de la mise à jour de la permanence' });
}
};
-
// ➕ Créer une permanence
export const deletePermanence = async (req: Request, res: Response) => {
-
const { permId } = req.query;
try {
await permanence_service.deletePermanence(Number(permId));
- Ok(res, { msg: "Permanence supprimée avec succès" });
+ Ok(res, { msg: 'Permanence supprimée avec succès' });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la suppression de la permanence" });
+ Error(res, { msg: 'Erreur lors de la suppression de la permanence' });
}
};
@@ -116,12 +113,12 @@ export const openPermanence = async (req: Request, res: Response) => {
try {
const permanence = await permanence_service.getPermanenceById(permId);
if (permanence.is_open === true) {
- Error(res, { msg: "La permanence est déjà ouverte" });
+ Error(res, { msg: 'La permanence est déjà ouverte' });
return;
}
await permanence_service.openPermanence(Number(permId));
- Ok(res, { msg: "Permanence ouverte avec succès" });
+ Ok(res, { msg: 'Permanence ouverte avec succès' });
} catch (err) {
console.error(err);
Error(res, { msg: "Erreur lors de l'ouverture de la permanence" });
@@ -140,16 +137,16 @@ export const closePermanence = async (req: Request, res: Response) => {
try {
const permanence = await permanence_service.getPermanenceById(permId);
if (permanence.is_open === false) {
- Error(res, { msg: "La permanence est déjà fermée" });
+ Error(res, { msg: 'La permanence est déjà fermée' });
return;
}
await permanence_service.closePermanence(Number(permId));
- Ok(res, { msg: "Permanence fermée avec succès" });
+ Ok(res, { msg: 'Permanence fermée avec succès' });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la fermeture de la permanence" });
+ Error(res, { msg: 'Erreur lors de la fermeture de la permanence' });
return;
}
};
@@ -159,21 +156,20 @@ export const applyToPermanence = async (req: Request, res: Response) => {
const { permId } = req.body;
const userId = req.user?.userId;
-
if (!userId || !permId) {
- Error(res, { msg: "Requête invalide, permId ou userId manquant" });
+ Error(res, { msg: 'Requête invalide, permId ou userId manquant' });
return;
}
try {
const permanence = await permanence_service.getPermanenceById(permId);
if (permanence.is_open === false) {
- Error(res, { msg: "La permanence est fermée, vous ne pouvez pas vous y inscrire" });
+ Error(res, { msg: 'La permanence est fermée, vous ne pouvez pas vous y inscrire' });
return;
}
await permanence_service.registerUserToPermanence(Number(userId), Number(permId));
- Ok(res, { msg: "Inscription réussie" });
+ Ok(res, { msg: 'Inscription réussie' });
return;
} catch (err) {
console.error(err);
@@ -188,17 +184,17 @@ export const leavePermanence = async (req: Request, res: Response) => {
const userId = req.user?.userId;
if (!userId || !permId) {
- Error(res, { msg: "Requête invalide, permId ou userId manquant" });
+ Error(res, { msg: 'Requête invalide, permId ou userId manquant' });
return;
}
try {
- await permanence_service.unregisterUserFromPermanence(Number(userId), Number(permId),);
- Ok(res, { msg: "Désinscription réussie" });
+ await permanence_service.unregisterUserFromPermanence(Number(userId), Number(permId));
+ Ok(res, { msg: 'Désinscription réussie' });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: err.message || "Erreur pendant la désinscription" });
+ Error(res, { msg: err.message || 'Erreur pendant la désinscription' });
return;
}
};
@@ -208,7 +204,7 @@ export const getMyPermanences = async (req: Request, res: Response) => {
const userId = req.user?.userId;
if (!userId) {
- Error(res, { msg: "Utilisateur non identifié" });
+ Error(res, { msg: 'Utilisateur non identifié' });
return;
}
@@ -218,7 +214,7 @@ export const getMyPermanences = async (req: Request, res: Response) => {
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur pendant la récupération des permanences" });
+ Error(res, { msg: 'Erreur pendant la récupération des permanences' });
return;
}
};
@@ -231,7 +227,7 @@ export const getAllPermanences = async (req: Request, res: Response) => {
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la récupération des permanences" });
+ Error(res, { msg: 'Erreur lors de la récupération des permanences' });
return;
}
};
@@ -244,36 +240,35 @@ export const getOpenPermanences = async (req: Request, res: Response) => {
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la récupération des permanences ouvertes" });
+ Error(res, { msg: 'Erreur lors de la récupération des permanences ouvertes' });
return;
}
};
export const getUsersInPermanence = async (req: Request, res: Response) => {
try {
- const { permId } = req.query
- const users = await permanence_service.getUsersInPermanence(Number(permId))
+ const { permId } = req.query;
+ const users = await permanence_service.getUsersInPermanence(Number(permId));
Ok(res, { data: users });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la récupération des utilisateurs par permanences" });
+ Error(res, { msg: 'Erreur lors de la récupération des utilisateurs par permanences' });
return;
}
};
export const addUserToPermanence = async (req: Request, res: Response) => {
-
const { permId, userId } = req.body;
if (!userId || !permId) {
- Error(res, { msg: "Requête invalide, permId ou userId manquant" });
+ Error(res, { msg: 'Requête invalide, permId ou userId manquant' });
return;
}
try {
await permanence_service.addUserToPermanence(Number(userId), Number(permId));
- Ok(res, { msg: "Inscription réussite" });
+ Ok(res, { msg: 'Inscription réussite' });
return;
} catch (err) {
console.error(err);
@@ -283,21 +278,20 @@ export const addUserToPermanence = async (req: Request, res: Response) => {
};
export const removeUserToPermanence = async (req: Request, res: Response) => {
-
const { permId, userId } = req.body;
if (!userId || !permId) {
- Error(res, { msg: "Requête invalide, permId ou userId manquant" });
+ Error(res, { msg: 'Requête invalide, permId ou userId manquant' });
return;
}
try {
await permanence_service.removeUserToPermanence(Number(userId), Number(permId));
- Ok(res, { msg: "Désinscription réussite" });
+ Ok(res, { msg: 'Désinscription réussite' });
return;
} catch (err) {
console.error(err);
- Error(res, { msg: err.message || "Erreur pendant la désinscription" });
+ Error(res, { msg: err.message || 'Erreur pendant la désinscription' });
return;
}
};
@@ -306,13 +300,13 @@ export const uploadPermanencesCSV = async (req: MulterRequest, res: Response) =>
try {
const file = req.file;
if (!file) {
- Error(res, { msg: "Fichier CSV manquant." });
+ Error(res, { msg: 'Fichier CSV manquant.' });
}
await permanence_service.importPermanencesFromCSV(file.path);
- Ok(res, { msg: "Importation réalisée avec succès." });
+ Ok(res, { msg: 'Importation réalisée avec succès.' });
} catch (error) {
- console.error("Erreur import CSV :", error);
+ console.error('Erreur import CSV :', error);
Error(res, { msg: "Échec de l'importation." });
}
};
@@ -321,18 +315,16 @@ export const isUserRespo = async (req: Request, res: Response) => {
const { userId } = req.query;
if (!userId) {
- Error(res, { msg: "userId est requis" });
+ Error(res, { msg: 'userId est requis' });
return;
}
try {
- const isRespo = await permanence_service.isUserRespoOfPermanence(
- Number(userId)
- );
+ const isRespo = await permanence_service.isUserRespoOfPermanence(Number(userId));
Ok(res, { data: isRespo });
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la vérification du responsable" });
+ Error(res, { msg: 'Erreur lors de la vérification du responsable' });
}
};
@@ -340,7 +332,7 @@ export const getRespoPermanencesWithMembers = async (req: Request, res: Response
const respoId = req.user?.userId;
if (!respoId) {
- Error(res, { msg: "respoId est requis" });
+ Error(res, { msg: 'respoId est requis' });
return;
}
@@ -349,7 +341,7 @@ export const getRespoPermanencesWithMembers = async (req: Request, res: Response
Ok(res, { data });
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la récupération des permanences du responsable" });
+ Error(res, { msg: 'Erreur lors de la récupération des permanences du responsable' });
}
};
@@ -357,7 +349,7 @@ export const claimMember = async (req: Request, res: Response) => {
const { userId, permId, claimed } = req.body;
if (userId === undefined || permId === undefined || claimed === undefined) {
- Error(res, { msg: "userId, permId et claimed sont requis" });
+ Error(res, { msg: 'userId, permId et claimed sont requis' });
return;
}
@@ -368,34 +360,32 @@ export const claimMember = async (req: Request, res: Response) => {
});
} catch (err) {
console.error(err);
- Error(res, { msg: "Erreur lors de la mise à jour du statut du membre" });
+ Error(res, { msg: 'Erreur lors de la mise à jour du statut du membre' });
}
};
export const sendHourlyNotificationToUsers = async (req: Request, res: Response) => {
-
const notifications = await permanence_service.getHourlyNotifications();
if (notifications.length === 0) {
- Ok(res, { msg: "Aucune notification horaire à envoyer." });
+ Ok(res, { msg: 'Aucune notification horaire à envoyer.' });
return;
}
permanence_service.sendNotifications(notifications);
- Ok(res, { msg: "Notifications horaires envoyées avec succès" });
+ Ok(res, { msg: 'Notifications horaires envoyées avec succès' });
};
export const sendDailyNotificationToUsers = async (req: Request, res: Response) => {
-
const notifications = await permanence_service.getDailyNotifications();
if (notifications.length === 0) {
- Ok(res, { msg: "Aucune notification quotidienne à envoyer." });
+ Ok(res, { msg: 'Aucune notification quotidienne à envoyer.' });
return;
}
permanence_service.sendNotifications(notifications);
- Ok(res, { msg: "Notifications quotidiennes envoyées avec succès" });
+ Ok(res, { msg: 'Notifications quotidiennes envoyées avec succès' });
};
diff --git a/backend/src/controllers/role.controller.ts b/backend/src/controllers/role.controller.ts
index 2855e95..dfa4d4f 100644
--- a/backend/src/controllers/role.controller.ts
+++ b/backend/src/controllers/role.controller.ts
@@ -1,6 +1,6 @@
-import { type Request, type Response } from "express";
-import * as role_service from "../services/role.service";
-import { Error, Ok } from "../utils/responses";
+import { type Request, type Response } from 'express';
+import * as role_service from '../services/role.service';
+import { Error, Ok } from '../utils/responses';
// 🎯 Préférences utilisateur
export const updateUserPreferences = async (req: Request, res: Response) => {
@@ -9,14 +9,14 @@ export const updateUserPreferences = async (req: Request, res: Response) => {
const { roleIds } = req.body;
if (!userId || !Array.isArray(roleIds)) {
- Error(res, { msg: "Données invalides" });
+ Error(res, { msg: 'Données invalides' });
}
await role_service.updateUserPreferences(userId, roleIds);
- Ok(res, { msg: "Préférences mises à jour avec succès" });
+ Ok(res, { msg: 'Préférences mises à jour avec succès' });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne serveur" });
+ Error(res, { msg: 'Erreur interne serveur' });
}
};
@@ -24,14 +24,14 @@ export const getUserPreferences = async (req: Request, res: Response) => {
try {
const userId = req.user?.userId;
- if (!userId) Error(res, { msg: "Utilisateur non authentifié" });
+ if (!userId) Error(res, { msg: 'Utilisateur non authentifié' });
const preferences = await role_service.getUserPreferences(userId);
const roleIds = preferences.map((pref) => pref.roleId);
- Ok(res, { msg: "Préférences récupérées", data: roleIds });
+ Ok(res, { msg: 'Préférences récupérées', data: roleIds });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne serveur" });
+ Error(res, { msg: 'Erreur interne serveur' });
}
};
@@ -39,13 +39,13 @@ export const getUserPreferences = async (req: Request, res: Response) => {
export const getUsersByRoleHandler = async (req: Request, res: Response) => {
try {
const { roleName } = req.params;
- if (!roleName) Error(res, { msg: "Nom du rôle requis" });
+ if (!roleName) Error(res, { msg: 'Nom du rôle requis' });
const users = await role_service.getUsersByRoleName(roleName);
- Ok(res, { msg: "Utilisateurs récupérés", data: users });
+ Ok(res, { msg: 'Utilisateurs récupérés', data: users });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne serveur" });
+ Error(res, { msg: 'Erreur interne serveur' });
}
};
@@ -55,7 +55,7 @@ export const addRoleToUser = async (req: Request, res: Response) => {
const { userId, roleIds } = req.body;
if (!userId || !Array.isArray(roleIds)) {
- Error(res, { msg: "userId et roleIds requis" });
+ Error(res, { msg: 'userId et roleIds requis' });
}
for (const roleId of roleIds) {
@@ -65,7 +65,7 @@ export const addRoleToUser = async (req: Request, res: Response) => {
}
}
- Ok(res, { msg: "Rôles ajoutés avec succès" });
+ Ok(res, { msg: 'Rôles ajoutés avec succès' });
} catch (error) {
console.error(error);
Error(res, { msg: "Erreur lors de l'ajout des rôles" });
@@ -78,14 +78,14 @@ export const deleteRoleToUser = async (req: Request, res: Response) => {
const { userId, roleId } = req.body;
if (!userId || !roleId) {
- Error(res, { msg: "userId et roleId requis" });
+ Error(res, { msg: 'userId et roleId requis' });
}
await role_service.removeRoleFromUser(userId, roleId);
- Ok(res, { msg: "Rôle supprimé avec succès" });
+ Ok(res, { msg: 'Rôle supprimé avec succès' });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur lors de la suppression du rôle" });
+ Error(res, { msg: 'Erreur lors de la suppression du rôle' });
}
};
@@ -96,7 +96,7 @@ export const getUsersWithRoles = async (req: Request, res: Response) => {
Ok(res, { data: users });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur lors de la récupération des utilisateurs" });
+ Error(res, { msg: 'Erreur lors de la récupération des utilisateurs' });
}
};
@@ -107,7 +107,7 @@ export const getRoles = async (req: Request, res: Response) => {
Ok(res, { data: roles });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur lors de la récupération des rôles" });
+ Error(res, { msg: 'Erreur lors de la récupération des rôles' });
}
};
@@ -115,13 +115,13 @@ export const getRoles = async (req: Request, res: Response) => {
export const getUserRoles = async (req: Request, res: Response) => {
try {
const { userId } = req.query;
- if (!userId) Error(res, { msg: "userId requis" });
+ if (!userId) Error(res, { msg: 'userId requis' });
const roles = await role_service.getUserRoles(Number(userId));
Ok(res, { data: roles });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur lors de la récupération des rôles utilisateur" });
+ Error(res, { msg: 'Erreur lors de la récupération des rôles utilisateur' });
}
};
@@ -134,12 +134,12 @@ export const addPointsToRole = async (req: Request, res: Response) => {
try {
const { roleId, points } = req.body;
- if (!roleId || typeof points !== "number") {
- Error(res, { msg: "roleId et points requis" });
+ if (!roleId || typeof points !== 'number') {
+ Error(res, { msg: 'roleId et points requis' });
}
await role_service.addPointsToRole(roleId, points);
- Ok(res, { msg: "Points ajoutés avec succès" });
+ Ok(res, { msg: 'Points ajoutés avec succès' });
} catch (error) {
console.error(error);
Error(res, { msg: "Erreur lors de l'ajout des points" });
@@ -151,15 +151,15 @@ export const removePointsFromRole = async (req: Request, res: Response) => {
try {
const { roleId, points } = req.body;
- if (!roleId || typeof points !== "number") {
- Error(res, { msg: "roleId et points requis" });
+ if (!roleId || typeof points !== 'number') {
+ Error(res, { msg: 'roleId et points requis' });
}
await role_service.removePointsFromRole(roleId, points);
- Ok(res, { msg: "Points retirés avec succès" });
+ Ok(res, { msg: 'Points retirés avec succès' });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur lors du retrait des points" });
+ Error(res, { msg: 'Erreur lors du retrait des points' });
}
};
@@ -170,7 +170,7 @@ export const getAllRolePoints = async (_req: Request, res: Response) => {
Ok(res, { data: roles });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur lors de la récupération des points" });
+ Error(res, { msg: 'Erreur lors de la récupération des points' });
}
};
@@ -178,14 +178,14 @@ export const getAllRolePoints = async (_req: Request, res: Response) => {
export const getRolePoints = async (req: Request, res: Response) => {
try {
const { roleId } = req.params;
- if (!roleId) Error(res, { msg: "roleId requis" });
+ if (!roleId) Error(res, { msg: 'roleId requis' });
const role = await role_service.getRolePoints(Number(roleId));
- if (!role) Error(res, { msg: "Rôle introuvable" });
+ if (!role) Error(res, { msg: 'Rôle introuvable' });
Ok(res, { data: role });
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur lors de la récupération des points du rôle" });
+ Error(res, { msg: 'Erreur lors de la récupération des points du rôle' });
}
};
diff --git a/backend/src/controllers/team.controller.ts b/backend/src/controllers/team.controller.ts
index b679821..b33de1a 100644
--- a/backend/src/controllers/team.controller.ts
+++ b/backend/src/controllers/team.controller.ts
@@ -1,10 +1,10 @@
-import { type Request, type Response } from "express";
-import { type Event } from "../schemas/Basic/event.schema";
+import { type Request, type Response } from 'express';
+import { type Event } from '../schemas/Basic/event.schema';
import * as event_service from '../services/event.service';
-import * as faction_service from "../services/faction.service";
-import * as team_service from "../services/team.service";
-import * as user_service from "../services/user.service";
-import { Error, Ok } from "../utils/responses";
+import * as faction_service from '../services/faction.service';
+import * as team_service from '../services/team.service';
+import * as user_service from '../services/user.service';
+import { Error, Ok } from '../utils/responses';
export const createNewTeam = async (req: Request, res: Response) => {
const { teamName, members } = req.body;
@@ -39,7 +39,7 @@ export const createNewTeam = async (req: Request, res: Response) => {
// Create the new team if no one is already in a team
const newTeam = await team_service.createTeam(teamName, members);
- Ok(res, { msg: "Équipe créée avec succès !", data: newTeam });
+ Ok(res, { msg: 'Équipe créée avec succès !', data: newTeam });
return;
} catch {
Error(res, { msg: "Erreur lors de la création de l'équipe." });
@@ -51,7 +51,7 @@ export const createNewTeamLight = async (req: Request, res: Response) => {
try {
await team_service.createTeamLight(teamName, factionId);
- Ok(res, { msg: "Equipe créée !" });
+ Ok(res, { msg: 'Equipe créée !' });
} catch {
Error(res, { msg: "Erreur lors de la création de l'équipe." });
}
@@ -63,7 +63,7 @@ export const getTeams = async (req: Request, res: Response) => {
Ok(res, { data: teams });
return;
} catch {
- Error(res, { msg: "Erreur lors de la récupération des équipes." });
+ Error(res, { msg: 'Erreur lors de la récupération des équipes.' });
}
};
@@ -73,7 +73,7 @@ export const getTeamsWithfactions = async (req: Request, res: Response) => {
Ok(res, { data: teams });
return;
} catch {
- Error(res, { msg: "Erreur lors de la récupération des équipes et de leur faction." });
+ Error(res, { msg: 'Erreur lors de la récupération des équipes et de leur faction.' });
}
};
@@ -82,7 +82,7 @@ export const modifyTeam = async (req: Request, res: Response) => {
const { teamID, teamName, teamMembers, factionID, type } = req.body;
if (!teamID) {
- Error(res, { msg: "teamID est requis pour la mise à jour." });
+ Error(res, { msg: 'teamID est requis pour la mise à jour.' });
}
const updatedTeam = await team_service.modifyTeam(teamID, teamMembers, factionID, teamName, type);
@@ -102,10 +102,10 @@ export const getTeamUsers = async (req: Request, res: Response) => {
return;
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: 'Erreur interne lors de la récupération des utilisateurs avec leurs rôles.' });
return;
}
-}
+};
export const getAllTeamsWithUsers = async (req: Request, res: Response) => {
try {
@@ -114,10 +114,10 @@ export const getAllTeamsWithUsers = async (req: Request, res: Response) => {
return;
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: 'Erreur interne lors de la récupération des utilisateurs avec leurs rôles.' });
return;
}
-}
+};
export const getTeamFaction = async (req: Request, res: Response) => {
const { teamId } = req.query;
@@ -129,21 +129,21 @@ export const getTeamFaction = async (req: Request, res: Response) => {
return;
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: 'Erreur interne lors de la récupération des utilisateurs avec leurs rôles.' });
return;
}
-}
+};
export const deleteTeam = async (req: Request, res: Response) => {
try {
const { teamID } = req.query; // Assumes the teamID is passed as a parameter
if (!teamID) {
- Error(res, { msg: "teamID est requis." });
+ Error(res, { msg: 'teamID est requis.' });
}
const deletedTeam = await team_service.deleteTeam(Number(teamID));
- Ok(res, { msg: "Équipe supprimée avec succès.", data: deletedTeam });
+ Ok(res, { msg: 'Équipe supprimée avec succès.', data: deletedTeam });
} catch (error) {
console.error(error);
Error(res, { msg: "Erreur lors de la suppression de l'équipe." });
@@ -152,7 +152,7 @@ export const deleteTeam = async (req: Request, res: Response) => {
export const teamDistribution = async (req: Request, res: Response) => {
try {
- const newStudents = await user_service.getUsersbyPermission("Nouveau");
+ const newStudents = await user_service.getUsersbyPermission('Nouveau');
const userswithteams = (await team_service.getUsersWithTeam()).map((entry: any) => entry.userId);
const teams = await team_service.getTeams();
@@ -163,46 +163,48 @@ export const teamDistribution = async (req: Request, res: Response) => {
// Filtrer les utilisateurs en fonction de la spécialité
const tcStudents = filteredStudents
- .filter((student: any) => student.branch === "TC")
+ .filter((student: any) => student.branch === 'TC')
.map((student: any) => ({
id: student.userId,
email: student.email,
- branch: student.branch
+ branch: student.branch,
}));
const otherStudents = filteredStudents
// .filter((student: any) => student.branch !== "TC" && student.branch !== "RI" && student.branch !== "MM") A decommenter pour ignorer les RI dans la répartition automatique
- .filter((student: any) => student.branch !== "TC" && student.branch !== "MM")
+ .filter((student: any) => student.branch !== 'TC' && student.branch !== 'MM')
.map((student: any) => ({
id: student.userId,
email: student.email,
- branch: student.branch
+ branch: student.branch,
}));
const PMOMStudents = filteredStudents
- .filter((student: any) => student.branch == "MM")
+ .filter((student: any) => student.branch == 'MM')
.map((student: any) => ({
id: student.userId,
email: student.email,
- branch: student.branch
+ branch: student.branch,
}));
// Filtrer les équipes en fonction de leur type
- const tcTeams = teams.filter(team => team.type === "TC");
- const PMOMTeams = teams.filter(team => team.type === "MM");
+ const tcTeams = teams.filter((team) => team.type === 'TC');
+ const PMOMTeams = teams.filter((team) => team.type === 'MM');
// const otherTeams = teams.filter(team => team.type !== "TC" && team.type !== "RI" && team.type !== "MM"); A decommenter pour ignorer les RI dans la répartition automatique
- const otherTeams = teams.filter(team => team.type !== "TC" && team.type !== "MM");
+ const otherTeams = teams.filter((team) => team.type !== 'TC' && team.type !== 'MM');
// Fonction pour assigner les utilisateurs à des équipes équilibrées
async function assignUsersToTeams(users: any, teams: any) {
// Calculer la taille actuelle des équipes
- const teamSizes = await Promise.all(teams.map(async (team: any) => {
- const members = await team_service.getTeamUsers(team.teamId);
- return {
- teamId: team.teamId,
- size: members.length
- };
- }));
+ const teamSizes = await Promise.all(
+ teams.map(async (team: any) => {
+ const members = await team_service.getTeamUsers(team.teamId);
+ return {
+ teamId: team.teamId,
+ size: members.length,
+ };
+ }),
+ );
// Trier les équipes par taille (ascendant)
teamSizes.sort((a: any, b: any) => a.size - b.size);
@@ -235,9 +237,9 @@ export const teamDistribution = async (req: Request, res: Response) => {
await assignUsersToTeams(PMOMStudents, PMOMTeams);
}
- Ok(res, { msg: "NewStudents distributed!" });
+ Ok(res, { msg: 'NewStudents distributed!' });
} catch (error) {
Error(res, { error });
- return
+ return;
}
-}
+};
diff --git a/backend/src/controllers/tent.controller.ts b/backend/src/controllers/tent.controller.ts
index ed62e44..51c2bbe 100644
--- a/backend/src/controllers/tent.controller.ts
+++ b/backend/src/controllers/tent.controller.ts
@@ -1,23 +1,23 @@
-import { type Request, type Response } from "express";
-import { generateEmailHtml, sendEmail } from "../services/email.service";
-import * as tent_service from "../services/tent.service";
-import { getUserById } from "../services/user.service";
-import { Error, Ok } from "../utils/responses";
-import { email_from } from "../utils/secret";
+import { type Request, type Response } from 'express';
+import { generateEmailHtml, sendEmail } from '../services/email.service';
+import * as tent_service from '../services/tent.service';
+import { getUserById } from '../services/user.service';
+import { Error, Ok } from '../utils/responses';
+import { email_from } from '../utils/secret';
export const createTent = async (req: Request, res: Response) => {
const { userId2 } = req.body;
const userId1 = req.user?.userId; // Créateur = utilisateur connecté
if (!userId1 || !userId2) {
- Error(res, { msg: "Identifiants utilisateurs manquants." });
+ Error(res, { msg: 'Identifiants utilisateurs manquants.' });
}
try {
await tent_service.createTent(userId1, userId2);
- Ok(res, { msg: "Tente réservée avec succès." });
+ Ok(res, { msg: 'Tente réservée avec succès.' });
} catch (err: any) {
- Error(res, { msg: err.message || "Erreur lors de la création de la tente." });
+ Error(res, { msg: err.message || 'Erreur lors de la création de la tente.' });
}
};
@@ -25,12 +25,12 @@ export const cancelTent = async (req: Request, res: Response) => {
const userId1 = req.user?.userId;
if (!userId1) {
- Error(res, { msg: "Identifiants utilisateurs manquants." });
+ Error(res, { msg: 'Identifiants utilisateurs manquants.' });
}
try {
await tent_service.cancelTent(userId1);
- Ok(res, { msg: "Tente annulée." });
+ Ok(res, { msg: 'Tente annulée.' });
} catch {
Error(res, { msg: "Erreur lors de l'annulation." });
}
@@ -39,13 +39,13 @@ export const cancelTent = async (req: Request, res: Response) => {
export const getUserTent = async (req: Request, res: Response) => {
const userId = req.user?.userId;
- if (!userId) Error(res, { msg: "Utilisateur non authentifié." });
+ if (!userId) Error(res, { msg: 'Utilisateur non authentifié.' });
try {
const tent = await tent_service.getTentByUser(userId);
Ok(res, { data: tent });
} catch {
- Error(res, { msg: "Erreur lors de la récupération." });
+ Error(res, { msg: 'Erreur lors de la récupération.' });
}
};
@@ -54,15 +54,15 @@ export const getAllTentPairs = async (req: Request, res: Response) => {
const tents = await tent_service.getAllTents();
Ok(res, { data: tents });
} catch {
- Error(res, { msg: "Erreur lors de la récupération des binômes." });
+ Error(res, { msg: 'Erreur lors de la récupération des binômes.' });
}
};
export const toggleTentConfirmation = async (req: Request, res: Response) => {
const { userId1, userId2, confirmed } = req.body;
- if (!userId1 || !userId2 || typeof confirmed !== "boolean") {
- Error(res, { msg: "Paramètres manquants ou invalides." });
+ if (!userId1 || !userId2 || typeof confirmed !== 'boolean') {
+ Error(res, { msg: 'Paramètres manquants ou invalides.' });
}
try {
@@ -74,11 +74,11 @@ export const toggleTentConfirmation = async (req: Request, res: Response) => {
const user2 = await getUserById(userId2);
if (!user1 || !user2) {
- Error(res, { msg: "Impossible de récupérer les utilisateurs." });
+ Error(res, { msg: 'Impossible de récupérer les utilisateurs.' });
}
// Génération du contenu HTML
- const htmlEmail = generateEmailHtml("templateNotifyTentConfirmation", {
+ const htmlEmail = generateEmailHtml('templateNotifyTentConfirmation', {
user1: `${user1.firstName} ${user1.lastName}`,
user2: `${user2.firstName} ${user2.lastName}`,
confirmed,
@@ -88,10 +88,8 @@ export const toggleTentConfirmation = async (req: Request, res: Response) => {
const emailOptions = {
from: email_from,
to: [user1.email, user2.email],
- subject: confirmed
- ? "🎉 Votre tente a été validée !"
- : "⛺ Votre tente a été dévalidée",
- text: "", // optionnel
+ subject: confirmed ? '🎉 Votre tente a été validée !' : '⛺ Votre tente a été dévalidée',
+ text: '', // optionnel
html: htmlEmail,
};
@@ -99,9 +97,7 @@ export const toggleTentConfirmation = async (req: Request, res: Response) => {
await sendEmail(emailOptions);
Ok(res, {
- msg: confirmed
- ? "Tente validée et email envoyé."
- : "Tente dévalidée et email envoyé.",
+ msg: confirmed ? 'Tente validée et email envoyé.' : 'Tente dévalidée et email envoyé.',
});
} catch (err: any) {
console.error(err);
diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts
index 0e0bb3c..d7008dd 100644
--- a/backend/src/controllers/user.controller.ts
+++ b/backend/src/controllers/user.controller.ts
@@ -1,7 +1,7 @@
import bcrypt from 'bcryptjs';
-import { type Request, type Response } from "express";
+import { type Request, type Response } from 'express';
import * as randomstring from 'randomstring';
-import * as auth_service from "../services/auth.service";
+import * as auth_service from '../services/auth.service';
import * as user_service from '../services/user.service';
import { noSyncEmails } from '../utils/no_sync_list';
import { Error, Ok } from '../utils/responses';
@@ -14,7 +14,7 @@ export const getUsersAdmin = async (req: Request, res: Response) => {
return;
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: 'Erreur interne lors de la récupération des utilisateurs avec leurs rôles.' });
return;
}
};
@@ -26,13 +26,13 @@ export const getUsers = async (req: Request, res: Response) => {
return;
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: 'Erreur interne lors de la récupération des utilisateurs avec leurs rôles.' });
return;
}
};
export const getUsersByPermission = async (req: Request, res: Response) => {
- const { permission } = req.params
+ const { permission } = req.params;
try {
const users = await user_service.getUsersbyPermission(permission);
@@ -40,19 +40,18 @@ export const getUsersByPermission = async (req: Request, res: Response) => {
return;
} catch (error) {
console.error(error);
- Error(res, { msg: "Erreur interne lors de la récupération des utilisateurs avec leurs rôles." });
+ Error(res, { msg: 'Erreur interne lors de la récupération des utilisateurs avec leurs rôles.' });
return;
}
};
-
export const syncNewstudent = async (req: Request, res: Response) => {
const { date } = req.body;
try {
const token = await SIEP_Utils.getTokenUTTAPI();
const newStudents = await SIEP_Utils.getNewStudentsFromUTTAPI_NOPAGE(token, date);
- const newStudentfiltered = newStudents.filter((student: any) => !noSyncEmails.includes(student.email));//Nouveau à ne pas sync (Démissionnaires, etc)
+ const newStudentfiltered = newStudents.filter((student: any) => !noSyncEmails.includes(student.email)); //Nouveau à ne pas sync (Démissionnaires, etc)
for (const element of newStudentfiltered) {
const userInDb = await user_service.getUserByEmail(element.email.toLowerCase());
@@ -63,18 +62,19 @@ export const syncNewstudent = async (req: Request, res: Response) => {
element.nom,
element.email.toLowerCase(),
element.Majeur,
- "Nouveau",
- element.diplome === "MA" ? "Master" : element.specialite,
- tmpPassword);
+ 'Nouveau',
+ element.diplome === 'MA' ? 'Master' : element.specialite,
+ tmpPassword,
+ );
- await auth_service.createRegistrationToken(newUser.id)
+ await auth_service.createRegistrationToken(newUser.id);
}
}
- Ok(res, { msg: "All NewStudent created and synced" })
+ Ok(res, { msg: 'All NewStudent created and synced' });
} catch (error) {
- Error(res, { error })
+ Error(res, { error });
}
-}
+};
export const getCurrentUser = async (req: Request, res: Response) => {
const userId = req.user?.userId;
@@ -83,7 +83,7 @@ export const getCurrentUser = async (req: Request, res: Response) => {
const user = await user_service.getUserById(userId);
Ok(res, { data: user });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour du profil." });
+ Error(res, { msg: 'Erreur lors de la mise à jour du profil.' });
}
};
@@ -93,20 +93,19 @@ export const updateProfile = async (req: Request, res: Response) => {
try {
const result = await user_service.updateUserInfoByUserId(userId, branch, contact);
- Ok(res, { msg: "Profil mis à jour", data: result });
+ Ok(res, { msg: 'Profil mis à jour', data: result });
} catch {
- Error(res, { msg: "Erreur lors de la mise à jour du profil." });
+ Error(res, { msg: 'Erreur lors de la mise à jour du profil.' });
}
};
-
export const adminUpdateUser = async (req: Request, res: Response) => {
const { userId } = req.params;
const updates = req.body;
try {
const result = await user_service.updateUserByAdmin(parseInt(userId), updates);
- Ok(res, { msg: "Utilisateur mis à jour", data: result });
+ Ok(res, { msg: 'Utilisateur mis à jour', data: result });
} catch {
Error(res, { msg: "Erreur lors de la mise à jour de l'utilisateur." });
}
@@ -117,7 +116,7 @@ export const adminDeleteUser = async (req: Request, res: Response) => {
try {
const result = await user_service.deleteUserById(parseInt(userId));
- Ok(res, { msg: "Utilisateur supprimé", data: result });
+ Ok(res, { msg: 'Utilisateur supprimé', data: result });
} catch {
Error(res, { msg: "Erreur lors de la suppression de l'utilisateur." });
}
diff --git a/backend/src/email/email.preview-data.ts b/backend/src/email/email.preview-data.ts
index f2742b1..713bb8a 100644
--- a/backend/src/email/email.preview-data.ts
+++ b/backend/src/email/email.preview-data.ts
@@ -1,4 +1,4 @@
-import type { TemplateData } from "../../types/email";
+import type { TemplateData } from '../../types/email';
import { service_url } from '../utils/secret';
export const defaultPreviewData: Record = {
@@ -8,7 +8,7 @@ export const defaultPreviewData: Record = {
},
templateNotebook: {
notebook_fr: `${service_url}api/uploads/notebooks/fr.pdf`,
- notebook_en: `${service_url}api/uploads/notebooks/en.pdf`
+ notebook_en: `${service_url}api/uploads/notebooks/en.pdf`,
},
templateAttributionBus: {
bus: 'bus',
@@ -29,8 +29,6 @@ export const defaultPreviewData: Record = {
resetLink: `${service_url}resetpassword?token=preview-token`,
},
templateNotifyPermanenceReminder: {
- permanence: {
-
- }
- }
+ permanence: {},
+ },
};
diff --git a/backend/src/email/email.registry.ts b/backend/src/email/email.registry.ts
index a1f4972..6a4c461 100644
--- a/backend/src/email/email.registry.ts
+++ b/backend/src/email/email.registry.ts
@@ -1,4 +1,4 @@
-import type { PermanenceEmailData, TemplateRenderer } from "../../types/email";
+import type { PermanenceEmailData, TemplateRenderer } from '../../types/email';
export const templateResetPassword = 'reset-password.html';
const templateNotebook = 'notebook.html';
@@ -26,9 +26,9 @@ export const templateRenderers: Record = {
notebook_fr?: string;
notebook_en?: string;
};
- return {
+ return {
notebook_fr: typedData.notebook_fr,
- notebook_en: typedData.notebook_en
+ notebook_en: typedData.notebook_en,
};
},
},
@@ -77,5 +77,5 @@ export const templateRenderers: Record = {
const typedData = data as PermanenceEmailData;
return typedData;
},
- }
+ },
};
diff --git a/backend/src/email/email.renderer.ts b/backend/src/email/email.renderer.ts
index ec3fef5..be1eadf 100644
--- a/backend/src/email/email.renderer.ts
+++ b/backend/src/email/email.renderer.ts
@@ -1,6 +1,6 @@
-import fs from "fs";
-import Handlebars from "handlebars";
-import path from "path";
+import fs from 'fs';
+import Handlebars from 'handlebars';
+import path from 'path';
const templateCache = new Map();
@@ -8,10 +8,10 @@ const readTemplate = (templateFileName: string) => {
const cached = templateCache.get(templateFileName);
if (cached) return cached;
- const templatePath = path.join(path.resolve(__dirname, "./templates"), templateFileName);
+ const templatePath = path.join(path.resolve(__dirname, './templates'), templateFileName);
if (fs.existsSync(templatePath)) {
- const content = fs.readFileSync(templatePath, "utf8");
+ const content = fs.readFileSync(templatePath, 'utf8');
templateCache.set(templateFileName, content);
return content;
}
diff --git a/backend/src/email/templates/attribution-bus.html b/backend/src/email/templates/attribution-bus.html
index ffee593..ba13336 100644
--- a/backend/src/email/templates/attribution-bus.html
+++ b/backend/src/email/templates/attribution-bus.html
@@ -1,120 +1,279 @@
-
+
-
-
-
- Intégration UTT
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Le Week-End d'Intégration approhe à grands pas !
- Si tu reçois ce message c'est que tu pars au WEI (youhouu !), tu trouveras dans celui-ci le bus avec lequel tu vas te rendre sur le lieu pour ce week-end.
- Fais bien attention à ne pas être en retard sous peine de rater ton bus, ça serait embêtant à la fois pour toi et pour nous.
- Autre point très important : les essentiels pour le WEI. Tu trouveras ci-dessous un rappel des objets obligatoires à ramener pour passer un bon week-end. Il risque de pleuvoir alors prévoyez bien en conséquence !
-
- Duvet et matelas gonflable/tapis de sol 🛏️ (si vous n'avez pas de duvet, vous ne partirez pas)
- Gourde, Tupperware, couverts, gobby (=écocup) 🍴
- Vêtements : changes pour 2 jours, pull, maillot de bain 👙
- Manteau imperméable 🧥
- Affaires salissables : change complet & chaussures (à mettre dès le départ en bus) 🚌
- Produits d'hygiène : brosse à dent, serviette, nécessaire de toilette 🪥
- Tongues/crocs pour les douches 🩴
- Papiers importants : Carte d'identité, CB & liquide, autorisation parentale (pour les mineurs) 💳
- Ta place au WEI 📩
- Crème solaire & anti-moustique ☀️
- De quoi grignoter (prenez un pique-nique à manger avant de prendre le bus, pas dans le bus) 😋
-
- 🚫 Affaires interdites :
-
- Boissons autres que de l'eau
- Substances illicites
- Armes blanches
- Déodorant en spray
-
- Pour rappel, voici la vidéo des indispensables du WEI ici
- Concernant ton bus, tu as été attribué au bus {{bus}}
- Maintenant il faut que tu sois présent en amphi de verdure à l'UTT à {{time}}
- Voilà, toute l'équipe de l'intégration te souhaite un excellent WEI ;)
-
-
-
-
- Retrouve des informations utiles sur notre Instagram !
-
- Cordialement, L'équipe intégration UTT
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
- The Integration Weekend is just around the corner!
- If you are receiving this message, it means you're coming to the WEI (woohoo!). In this email, you'll find the bus you have been assigned to travel to the venue for the weekend.
- Please make sure not to be late , otherwise you may miss your bus, which would be inconvenient for both you and us.
- Another very important point: the WEI essentials. Below, you'll find a reminder of the mandatory items you need to bring to have a great weekend. Rain is possible, so make sure to pack accordingly!
-
- Sleeping bag and inflatable mattress/sleeping mat 🛏️ (if you do not have a sleeping bag, you will not be allowed to leave)
- Water bottle, Tupperware container, cutlery, gobby (= reusable cup) 🍴
- Clothing: changes of clothes for 2 days, sweater, swimsuit 👙
- Waterproof jacket 🧥
- Clothes you don't mind getting dirty: a full change of clothes & shoes (to be worn from the moment you board the bus) 🚌
- Toiletries: toothbrush, towel, personal hygiene essentials 🪥
- Flip-flops/Crocs for the showers 🩴
- Important documents: ID card, bank card & cash, parental authorization form (for minors) 💳
- Your WEI ticket 📩
- Sunscreen & mosquito repellent ☀️
- Something to snack on (bring a picnic to eat before boarding the bus, not on the bus) 😋
-
- 🚫 Prohibited items:
-
- Any drinks other than water
- Illegal substances
- Bladed weapons
- Spray deodorant
-
- As a reminder, you can find the WEI essentials video here
- Regarding your bus assignment, you have been assigned to {{bus}}
- You must now be present at the UTT Green Amphitheater at {{time}}
- That's it! The entire Integration Team wishes you an amazing WEI ;)
-
-
-
-
-
- Find useful updates on our Instagram!
-
- Regards, The UTT's Integration Team
- If you have any questions, feel free to contact us .
-
-
-
-
-
-
-
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+
+ Le Week-End d'Intégration approhe à grands pas !
+
+
+ Si tu reçois ce message c'est que tu pars au WEI (youhouu !), tu trouveras dans
+ celui-ci le bus avec lequel tu vas te rendre sur le lieu pour ce week-end.
+
+
+ Fais bien attention à ne pas être en retard sous peine de rater ton
+ bus, ça serait embêtant à la fois pour toi et pour nous.
+
+
+ Autre point très important : les essentiels pour le WEI. Tu trouveras ci-dessous un
+ rappel des objets obligatoires à ramener pour passer un bon week-end. Il risque de
+ pleuvoir alors prévoyez bien en conséquence !
+
+
+
+ Duvet et matelas gonflable/tapis de sol 🛏️ (si vous n'avez pas de duvet, vous ne
+ partirez pas)
+
+ Gourde, Tupperware, couverts, gobby (=écocup) 🍴
+
+ Vêtements : changes pour 2 jours, pull, maillot de bain 👙
+
+ Manteau imperméable 🧥
+
+ Affaires salissables : change complet & chaussures (à mettre dès le départ
+ en bus) 🚌
+
+
+ Produits d'hygiène : brosse à dent, serviette, nécessaire de toilette 🪥
+
+ Tongues/crocs pour les douches 🩴
+
+ Papiers importants : Carte d'identité, CB & liquide, autorisation parentale
+ (pour les mineurs) 💳
+
+ Ta place au WEI 📩
+ Crème solaire & anti-moustique ☀️
+
+ De quoi grignoter (prenez un pique-nique à manger avant de prendre le bus, pas
+ dans le bus) 😋
+
+
+
+ 🚫 Affaires interdites :
+
+
+ Boissons autres que de l'eau
+ Substances illicites
+ Armes blanches
+ Déodorant en spray
+
+
+ Pour rappel, voici la vidéo des indispensables du WEI
+ ici
+
+ Concernant ton bus, tu as été attribué au bus {{bus}}
+
+ Maintenant il faut que tu sois présent en amphi de verdure à l'UTT à
+ {{time}}
+
+ Voilà, toute l'équipe de l'intégration te souhaite un excellent WEI ;)
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
+
+ The Integration Weekend is just around the corner!
+
+
+ If you are receiving this message, it means you're coming to the WEI (woohoo!). In
+ this email, you'll find the bus you have been assigned to travel to the venue for
+ the weekend.
+
+
+ Please make sure not to be late , otherwise you may miss your bus,
+ which would be inconvenient for both you and us.
+
+
+ Another very important point: the WEI essentials. Below, you'll find a reminder of
+ the mandatory items you need to bring to have a great weekend. Rain is possible, so
+ make sure to pack accordingly!
+
+
+
+ Sleeping bag and inflatable mattress/sleeping mat 🛏️ (if you do not have a
+ sleeping bag, you will not be allowed to leave)
+
+
+ Water bottle, Tupperware container, cutlery, gobby (= reusable cup) 🍴
+
+
+ Clothing: changes of clothes for 2 days, sweater, swimsuit 👙
+
+ Waterproof jacket 🧥
+
+ Clothes you don't mind getting dirty: a full change of clothes & shoes (to
+ be worn from the moment you board the bus) 🚌
+
+
+ Toiletries: toothbrush, towel, personal hygiene essentials 🪥
+
+ Flip-flops/Crocs for the showers 🩴
+
+ Important documents: ID card, bank card & cash, parental authorization form
+ (for minors) 💳
+
+ Your WEI ticket 📩
+ Sunscreen & mosquito repellent ☀️
+
+ Something to snack on (bring a picnic to eat before boarding the bus, not on the
+ bus) 😋
+
+
+ 🚫 Prohibited items:
+
+ Any drinks other than water
+ Illegal substances
+ Bladed weapons
+ Spray deodorant
+
+
+ As a reminder, you can find the WEI essentials video
+ here
+
+ Regarding your bus assignment, you have been assigned to {{bus}}
+
+ You must now be present at the UTT Green Amphitheater at {{time}}
+
+ That's it! The entire Integration Team wishes you an amazing WEI ;)
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+
+ If you have any questions, feel free to
+ contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/email/templates/custom.html b/backend/src/email/templates/custom.html
index 3a16289..3262d94 100644
--- a/backend/src/email/templates/custom.html
+++ b/backend/src/email/templates/custom.html
@@ -1,46 +1,86 @@
-
+
-
-
-
- Réinitialisation de mot de passe
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- {{title}}
- {{content}}
-
-
-
-
- Retrouve des informations utiles sur notre Instagram !
-
- Cordialement, L'équipe intégration UTT
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ {{title}}
+ {{content}}
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
diff --git a/backend/src/email/templates/notebook.html b/backend/src/email/templates/notebook.html
index 5c3993b..a87f9c0 100644
--- a/backend/src/email/templates/notebook.html
+++ b/backend/src/email/templates/notebook.html
@@ -1,61 +1,162 @@
-
+
-
-
-
- Intégration UTT
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Un peu de travail...
- Si tu reçois ce mail, c'est que tu es sur le point de rejoindre l'UTT et de vivre tes premières années en école supérieure.
- Mais après toutes ces vacances, il est important de ne pas s'endormir et de vite se remettre au travail !
- C'est pourquoi l'intégration te propose un cahier de vacances qui te permettra de te remettre à niveau.
- Toutes les bases y sont revues, de la terminale… jusqu'au CP. À toi de nous prouver que tu en es capable ! Méthodologie et rigueur seront nécessaires pour en venir à bout (et pas mal d'humour également).
- Ce cahier sera examiné par un jury extrêmement talentueux : des ingénieurs hors pair, ayant déjà prouvé leur valeur lors d'un concours de Ricard sur la plage de Banyuls-sur-Mer.
- À toi de leur montrer que tu peux égaler leurs compétences ! Ce jury n'hésitera pas à te récompenser pour tes efforts si tu nous renvoies tes réponses à cette adresse mail.
- Alors si tu veux y participer, tu peux le télécharger juste ici et le renvoyer à clement.duranson@utt.fr avant le dimanche 30 août.
-
- Cahier de vacances !
-
- Nous serons présents sur les réseaux tout au long de l'été pour te tenir informé(e), te partager des astuces, et plein d'autres trucs trop cools ! Rejoins le site de l'intégration pour bien être informé des actus ! Tu as reçu dans le premier mail de notre part, un lien pour réinitialiser ton mot de passe et te connecter.
-
- Accéder au site
-
- Alors, bon courage à toi, nous sommes impatients de lire tes meilleures réponses.
- À très vite !
-
-
-
-
- Retrouve des informations utiles sur notre Instagram !
-
- Cordialement, L'équipe intégration UTT
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+
+ Un peu de travail...
+
+
+ Si tu reçois ce mail, c'est que tu es sur le point de rejoindre l'UTT et de vivre
+ tes premières années en école supérieure.
+
+
+ Mais après toutes ces vacances, il est important de ne pas s'endormir et de vite se
+ remettre au travail !
+
+
+ C'est pourquoi l'intégration te propose un cahier de vacances qui te permettra de te
+ remettre à niveau.
+
+
+ Toutes les bases y sont revues, de la terminale… jusqu'au CP. À toi de nous prouver
+ que tu en es capable ! Méthodologie et rigueur seront nécessaires pour en venir à
+ bout (et pas mal d'humour également).
+
+
+ Ce cahier sera examiné par un jury extrêmement talentueux : des ingénieurs hors
+ pair, ayant déjà prouvé leur valeur lors d'un concours de Ricard sur la plage de
+ Banyuls-sur-Mer.
+
+
+ À toi de leur montrer que tu peux égaler leurs compétences ! Ce jury n'hésitera pas
+ à te récompenser pour tes efforts si tu nous renvoies tes réponses à cette adresse
+ mail.
+
+
+ Alors si tu veux y participer, tu peux le télécharger juste ici et le renvoyer à
+ clement.duranson@utt.fr
+ avant le dimanche 30 août.
+
+
+ Cahier de vacances !
+
+
+ Nous serons présents sur les réseaux tout au long de l'été pour te tenir informé(e),
+ te partager des astuces, et plein d'autres trucs trop cools !
+ Rejoins le site de l'intégration pour bien être informé des actus !
+ Tu as reçu dans le premier mail de notre part, un lien pour réinitialiser ton mot de
+ passe et te connecter.
+
+
+ Accéder au site
+
+ Alors, bon courage à toi, nous sommes impatients de lire tes meilleures réponses.
+ À très vite !
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
diff --git a/backend/src/email/templates/notify-news.html b/backend/src/email/templates/notify-news.html
index 00b819e..a633f3b 100644
--- a/backend/src/email/templates/notify-news.html
+++ b/backend/src/email/templates/notify-news.html
@@ -1,77 +1,154 @@
-
+
-
-
-
- Intégration UTT
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Nouvelle actu !
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+ Nouvelle actu !
- {{title}}
- 👉 Rendez-vous sur le site de l'inté dans l'onglet News pour en savoir plus.
-
- Accéder au site
-
-
-
-
-
- Retrouve des informations utiles sur notre Instagram !
-
- Cordialement, L'équipe intégration UTT
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
- New news !
+ {{title}}
+
+ 👉 Rendez-vous sur le site de l'inté dans l'onglet News pour en
+ savoir plus.
+
+
+ Accéder au site
+
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
+ New news !
- {{title}}
- 👉 Visit the integration website in the News tab to find out more.
-
- Click here
-
-
-
-
-
- Find useful updates on our Instagram!
-
- Regards, The UTT's Integration Team
- If you have any questions, feel free to contact us .
-
-
-
-
-
-
-
+ {{title}}
+
+ 👉 Visit the integration website in the News tab to find out more.
+
+
+ Click here
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+
+ If you have any questions, feel free to
+ contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/email/templates/notify-permanence-reminder.html b/backend/src/email/templates/notify-permanence-reminder.html
index 3a3b6b9..31ca79a 100644
--- a/backend/src/email/templates/notify-permanence-reminder.html
+++ b/backend/src/email/templates/notify-permanence-reminder.html
@@ -1,78 +1,146 @@
-
+
-
-
-
- Intégration UTT
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Rappel de permanence
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+
+ Rappel de permanence
+
- {{permName}}
- Entre le {{permBeginDate}} à {{permBeginHour}} et le {{permEndDate}} à {{permEndHour}}
- Point de rendez-vous: {{permLocation}}
- Description de la permanence: {{permDescription}}
- Pour le bon déroulement des permanences, nous vous demandons de vous présenter 15 minutes à l'avance sur les lieux.
- Le ou la reponsable de permanence effectuera l'appel. Seules les permanences honorées seront comptabilisés pour le total de permanences des chefs et cheffes d'équipe.
-
-
-
-
- Retrouve des informations utiles sur notre Instagram !
-
- Cordialement, L'équipe intégration UTT
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
- Permanence reminder
+ {{permName}}
+
+ Entre le {{permBeginDate}} à {{permBeginHour}} et le {{permEndDate}} à
+ {{permEndHour}}
+
+ Point de rendez-vous: {{permLocation}}
+ Description de la permanence: {{permDescription}}
+
+ Pour le bon déroulement des permanences, nous vous demandons de vous présenter
+ 15 minutes à l'avance sur les lieux.
+
+
+ Le ou la reponsable de permanence effectuera l'appel. Seules les permanences
+ honorées seront comptabilisés pour le total de permanences des chefs et cheffes
+ d'équipe.
+
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
+
+ Permanence reminder
+
- {{permName}}
- From the {{permBeginDate}} at {{permBeginHour}} to the {{permEndDate}} at {{permEndHour}}
- Meeting point: {{permLocation}}
- Description: {{permDescription}}
- To ensure the smooth running of the permanence hours, we ask that you arrive 15 minutes early on the premises.
-
-
-
-
- Find useful updates on our Instagram!
-
- Regards, The UTT's Integration Team
- If you have any questions, feel free to contact us .
-
-
-
-
-
-
-
+ {{permName}}
+
+ From the {{permBeginDate}} at {{permBeginHour}} to the {{permEndDate}} at
+ {{permEndHour}}
+
+ Meeting point: {{permLocation}}
+ Description: {{permDescription}}
+
+ To ensure the smooth running of the permanence hours, we ask that you arrive
+ 15 minutes early on the premises.
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+
+ If you have any questions, feel free to
+ contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/email/templates/notify-tent-confirmation.html b/backend/src/email/templates/notify-tent-confirmation.html
index a77e7fe..ce5e619 100644
--- a/backend/src/email/templates/notify-tent-confirmation.html
+++ b/backend/src/email/templates/notify-tent-confirmation.html
@@ -1,27 +1,52 @@
-
+
-
-
-
- Intégration UTT
-
-
-
-
-
-
Intégration UTT
+
+
+
+
Intégration UTT
+
+
+
+
+
+
Intégration UTT
+
+
⛺ Mise à jour de ta tente
+
+ La tente entre {{user1}} et {{user2}} a été
+
+ {{#if confirmed}}validée{{else}}invalidée{{/if}} .
+
+
+ 👉 Tu peux consulter l'état de ta tente sur le site de l'inté dans l'onglet Tentes .
+
+
+ Accéder au site
+
-
⛺ Mise à jour de ta tente
-
- La tente entre {{user1}} et {{user2}} a été
-
- {{#if confirmed}}validée{{else}}invalidée{{/if}}
- .
-
-
👉 Tu peux consulter l'état de ta tente sur le site de l'inté dans l'onglet Tentes .
-
- Accéder au site
-
-
-
+
diff --git a/backend/src/email/templates/reset-password.html b/backend/src/email/templates/reset-password.html
index 0be8879..b36793a 100644
--- a/backend/src/email/templates/reset-password.html
+++ b/backend/src/email/templates/reset-password.html
@@ -1,79 +1,159 @@
-
+
-
-
-
-
Réinitialisation de mot de passe
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Réinitialisation de mot de passe
- Bonjour,
- Vous avez demandé à réinitialiser votre mot de passe. Cliquez sur le bouton ci-dessous pour choisir un nouveau mot de passe :
-
- Réinitialiser mon mot de passe
-
- Attention, le lien n'est valide que pendant 1h.
- Si vous n'avez pas demandé cette réinitialisation, veuillez ignorer cet e-mail.
-
-
-
-
- Retrouve des informations utiles sur notre Instagram !
-
- Cordialement, L'équipe intégration UTT
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
- Password Reset
- Hello,
- You requested to reset your password. Click the button below to choose a new password:
-
- Reset My Password
-
- Please note that this link is only valid for 1 hour.
- If you did not request this password reset, please ignore this email.
-
-
-
-
- Find useful updates on our Instagram!
-
- Regards, The UTT's Integration Team
- If you have any questions, feel free to contact us .
-
-
-
-
-
-
-
+
+
+
+
Réinitialisation de mot de passe
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+
+ Réinitialisation de mot de passe
+
+ Bonjour,
+
+ Vous avez demandé à réinitialiser votre mot de passe. Cliquez sur le bouton
+ ci-dessous pour choisir un nouveau mot de passe :
+
+
+ Réinitialiser mon mot de passe
+
+ Attention, le lien n'est valide que pendant 1h.
+ Si vous n'avez pas demandé cette réinitialisation, veuillez ignorer cet e-mail.
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
+ Password Reset
+ Hello,
+
+ You requested to reset your password. Click the button below to choose a new
+ password:
+
+
+ Reset My Password
+
+ Please note that this link is only valid for 1 hour.
+ If you did not request this password reset, please ignore this email.
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+
+ If you have any questions, feel free to
+ contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/email/templates/welcome.html b/backend/src/email/templates/welcome.html
index 030870b..11b6528 100644
--- a/backend/src/email/templates/welcome.html
+++ b/backend/src/email/templates/welcome.html
@@ -1,107 +1,239 @@
-
+
-
-
-
-
Intégration UTT
-
-
-
-
-
-
-
-
-
-
-
-
-
- INTEGRATION UTT
-
-
-
-
- Salut à toi jeune nouveau !
- Bravo pour ton admission à l'UTT ! Nous sommes l'équipe d'intégration, des étudiants bénévoles qui préparent minutieusement ton arrivée pour que celle-ci reste inoubliable.
- Un tas d'événements incroyables, dont la participation est basée sur le volontariat, t'attendent dès le Lundi 31 Août que tu arrives en 1ère année, en 3ème année, en master ou en Bachelor !
- Tout est fait pour que tu t'éclates et que tu rencontres les personnes qui feront de ton passage à l'UTT un moment inoubliable. Mais avant toute chose, il faut te préparer.
- Assure-toi de réaliser les tâches suivantes avant ton arrivée :
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+
+ Salut à toi jeune nouveau !
+
+
+ Bravo pour ton admission à l'UTT ! Nous sommes l'équipe d'intégration, des étudiants
+ bénévoles qui préparent minutieusement ton arrivée pour que celle-ci reste
+ inoubliable.
+
+
+ Un tas d'événements incroyables, dont la participation est basée sur le volontariat,
+ t'attendent dès le Lundi 31 Août que tu arrives en 1ère
+ année, en 3ème année, en master ou en Bachelor !
+
+
+ Tout est fait pour que tu t'éclates et que tu rencontres les personnes qui feront de
+ ton passage à l'UTT un moment inoubliable. Mais avant toute chose, il faut te
+ préparer.
+
+ Assure-toi de réaliser les tâches suivantes avant ton arrivée :
- 1. Se connecter sur le site de l'intégration
- Pour pouvoir te connecter au site de l'intégration il te suffit de changer ton mot de passe en cliquant sur ce lien suivant :
-
- Changer ton mot de passe
-
- Attention, ce lien est valable uniquement une fois !
- Une fois cela fait, tu pourras te connecter à ton compte et y retrouver toutes les informations relatives aux événements de la semaine via le lien suivant :
-
- https://integration.utt.fr
-
+ 1. Se connecter sur le site de l'intégration
+
+ Pour pouvoir te connecter au site de l'intégration il te suffit de changer ton mot
+ de passe en cliquant sur ce lien suivant :
+
+
+ Changer ton mot de passe
+
+
+ Attention, ce lien est valable uniquement une fois !
+
+
+ Une fois cela fait, tu pourras te connecter à ton compte et y retrouver toutes les
+ informations relatives aux événements de la semaine via le lien suivant :
+
+
+ https://integration.utt.fr
+
- 2. Parrainage
- Lorsque tu arrives à l'UTT, un.e étudiant.e plus ancien.ne devient ton parrain ou ta marraine. Il ou elle sera ton contact privilégié pour découvrir l'école mais aussi la vie étudiante troyenne et répondre à toutes tes questions que ce soit sur l'UTT, les logements, les cours, la vie à Troyes,...
- Pour t'attribuer quelqu'un qui te correspond au mieux on t'invite à remplir
- ce questionnaire
-
-
-
-
-
- Retrouve des informations utiles sur notre Instagram !
-
- Cordialement, L'équipe intégration UTT
- Si vous avez des questions, n'hésitez pas à nous contacter .
-
-
-
-
-
-
-
- Hello there, new arrival!
- Congratulations on being admitted to UTT! We are the integration team, a group of volunteer students who carefully prepare your arrival so that it becomes truly unforgettable.
- A lot of incredible events, all based on voluntary participation, are waiting for you starting on Monday, August 31st , whether you are entering your first year, third year, a master's program, or a bachelor's program!
- Everything is set up so you can have fun and meet the people who will make your time at UTT unforgettable. But first, you need to get ready.
- Make sure you complete the following tasks before you arrive:
+ 2. Parrainage
+
+ Lorsque tu arrives à l'UTT, un.e étudiant.e plus ancien.ne devient ton parrain ou ta
+ marraine. Il ou elle sera ton contact privilégié pour découvrir l'école mais aussi
+ la vie étudiante troyenne et répondre à toutes tes questions que ce soit sur l'UTT,
+ les logements, les cours, la vie à Troyes,...
+
+
+ Pour t'attribuer quelqu'un qui te correspond au mieux on t'invite à remplir
+ ce questionnaire
+
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
+
+ Hello there, new arrival!
+
+
+ Congratulations on being admitted to UTT! We are the integration team, a group of
+ volunteer students who carefully prepare your arrival so that it becomes truly
+ unforgettable.
+
+
+ A lot of incredible events, all based on voluntary participation, are waiting for
+ you starting on Monday, August 31st , whether you are entering your first year, third year, a master's program, or a
+ bachelor's program!
+
+
+ Everything is set up so you can have fun and meet the people who will make your time
+ at UTT unforgettable. But first, you need to get ready.
+
+ Make sure you complete the following tasks before you arrive:
- 1. Log in to the integration website
- To log in to the integration website, you simply need to change your password by clicking the following link:
-
- Change your password
-
- Warning: this link is valid only once!
- Once that is done, you will be able to log into your account and find all the information about the week's events through the following link:
-
- https://integration.utt.fr
-
+ 1. Log in to the integration website
+
+ To log in to the integration website, you simply need to change your password by
+ clicking the following link:
+
+
+ Change your password
+
+ Warning: this link is valid only once!
+
+ Once that is done, you will be able to log into your account and find all the
+ information about the week's events through the following link:
+
+
+ https://integration.utt.fr
+
- 2. Mentorship
- When you arrive at UTT, an older student will become your sponsor or mentor. They will be your main contact to help you discover the school as well as student life in Troyes, and to answer all your questions about UTT, housing, classes, and life in Troyes.
- To be matched with someone who suits you best, we invite you to fill out
- this questionnaire
-
-
-
-
-
- Find useful updates on our Instagram!
-
- Regards, The UTT's Integration Team
- If you have any questions, feel free to contact us .
-
-
-
-
-
-
-
+ 2. Mentorship
+
+ When you arrive at UTT, an older student will become your sponsor or mentor. They
+ will be your main contact to help you discover the school as well as student life in
+ Troyes, and to answer all your questions about UTT, housing, classes, and life in
+ Troyes.
+
+
+ To be matched with someone who suits you best, we invite you to fill out
+ this questionnaire
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+
+ If you have any questions, feel free to
+ contact us .
+
+
+
+
+
+
+
+
diff --git a/backend/src/errors/permanence.error.ts b/backend/src/errors/permanence.error.ts
index 1092c0f..61c1398 100644
--- a/backend/src/errors/permanence.error.ts
+++ b/backend/src/errors/permanence.error.ts
@@ -1,7 +1,7 @@
-export class UnauthorizedError extends Error { }
-export class AlreadyRegisteredError extends Error { }
-export class PermanenceNotFoundError extends Error { }
-export class PermanenceClosedError extends Error { }
-export class PermanenceFullError extends Error { }
-export class UnregisterDeadlineError extends Error { }
-export class RegisterDeadlineError extends Error { }
+export class UnauthorizedError extends Error {}
+export class AlreadyRegisteredError extends Error {}
+export class PermanenceNotFoundError extends Error {}
+export class PermanenceClosedError extends Error {}
+export class PermanenceFullError extends Error {}
+export class UnregisterDeadlineError extends Error {}
+export class RegisterDeadlineError extends Error {}
diff --git a/backend/src/middlewares/automation.middleware.ts b/backend/src/middlewares/automation.middleware.ts
index d3a41bf..606e75d 100644
--- a/backend/src/middlewares/automation.middleware.ts
+++ b/backend/src/middlewares/automation.middleware.ts
@@ -1,22 +1,21 @@
-import { type NextFunction, type Request, type Response } from "express";
-import { Unauthorized } from "../utils/responses"; // Assurez-vous que cette fonction est bien définie
-import { automation_token } from "../utils/secret";
+import { type NextFunction, type Request, type Response } from 'express';
+import { Unauthorized } from '../utils/responses'; // Assurez-vous que cette fonction est bien définie
+import { automation_token } from '../utils/secret';
export const authenticateAutomation = (req: Request, res: Response, next: NextFunction) => {
try {
-
const { token } = req.body;
-
+
if (!token) {
- return Unauthorized(res, { msg: "Unauthorized: Missing or malformed token" });
+ return Unauthorized(res, { msg: 'Unauthorized: Missing or malformed token' });
}
if (token !== automation_token) {
- return Unauthorized(res, { msg: "Unauthorized: Invalid token" })
+ return Unauthorized(res, { msg: 'Unauthorized: Invalid token' });
}
next();
} catch {
- return Unauthorized(res, { msg: "Unauthorized: Invalid token" });
+ return Unauthorized(res, { msg: 'Unauthorized: Invalid token' });
}
};
diff --git a/backend/src/routes/automation.routes.ts b/backend/src/routes/automation.routes.ts
index 6fdec9a..214f4a4 100644
--- a/backend/src/routes/automation.routes.ts
+++ b/backend/src/routes/automation.routes.ts
@@ -1,11 +1,10 @@
-import express from "express";
-import * as permanenceController from "../controllers/permanence.controller";
-
+import express from 'express';
+import * as permanenceController from '../controllers/permanence.controller';
const automationRoutes = express.Router();
// Permanences routes
-automationRoutes.post("/permanence/notification/hourly", permanenceController.sendHourlyNotificationToUsers);
-automationRoutes.post("/permanence/notification/daily", permanenceController.sendDailyNotificationToUsers);
+automationRoutes.post('/permanence/notification/hourly', permanenceController.sendHourlyNotificationToUsers);
+automationRoutes.post('/permanence/notification/daily', permanenceController.sendDailyNotificationToUsers);
export default automationRoutes;
diff --git a/backend/src/routes/email.routes.ts b/backend/src/routes/email.routes.ts
index ea994be..cb45b5e 100644
--- a/backend/src/routes/email.routes.ts
+++ b/backend/src/routes/email.routes.ts
@@ -4,7 +4,7 @@ import { checkRole } from '../middlewares/user.middleware';
const emailRouter = express.Router();
-emailRouter.post('/admin/sendemail', checkRole("Admin", []), emailController.handleSendEmail);
-emailRouter.post('/admin/previewemail', checkRole("Admin", []), emailController.handlePreviewEmail);
+emailRouter.post('/admin/sendemail', checkRole('Admin', []), emailController.handleSendEmail);
+emailRouter.post('/admin/previewemail', checkRole('Admin', []), emailController.handlePreviewEmail);
export default emailRouter;
diff --git a/backend/src/services/email.service.ts b/backend/src/services/email.service.ts
index 53f9abc..a345b5b 100644
--- a/backend/src/services/email.service.ts
+++ b/backend/src/services/email.service.ts
@@ -1,21 +1,36 @@
import nodemailer from 'nodemailer';
-import type { EmailOptions, TemplateData } from "../../types/email";
-import { templateRenderers } from "../email/email.registry";
-import { compileTemplate } from "../email/email.renderer";
+import type { EmailOptions, TemplateData } from '../../types/email';
+import { templateRenderers } from '../email/email.registry';
+import { compileTemplate } from '../email/email.renderer';
+import * as user_service from '../services/user.service';
import { email_from, email_host, email_password, email_user } from '../utils/secret';
-export const generateEmailHtml = (
- templateName: string,
- data: TemplateData
-) => {
+export const getRecipients = async (
+ recipientsGroups: string[] | undefined,
+ sendTo: string[] | undefined,
+): Promise
=> {
+ if (recipientsGroups?.length) {
+ const groupsEmails = await Promise.all(
+ recipientsGroups.map(async (recipientGroup) => {
+ const users = await user_service.getUsersbyPermission(recipientGroup);
+ return users.map((user) => user.email);
+ }),
+ );
+
+ const flatEmails = groupsEmails.flat();
+
+ return [...new Set(flatEmails)];
+ }
+
+ return [...new Set(sendTo || [])];
+};
+
+export const generateEmailHtml = (templateName: string, data: TemplateData) => {
const renderer = templateRenderers[templateName];
if (!renderer) return null;
- return compileTemplate(
- renderer.buildData(data),
- renderer.fileName
- );
+ return compileTemplate(renderer.buildData(data), renderer.fileName);
};
export const sendEmail = async (options: EmailOptions): Promise => {
@@ -45,7 +60,7 @@ export const sendEmail = async (options: EmailOptions): Promise => {
await transporter.sendMail(mailOptions);
} catch (error) {
- console.log(error)
- throw new Error('Erreur lors de l\'envoi de l\'email:');
+ console.log(error);
+ throw new Error("Erreur lors de l'envoi de l'email:");
}
};
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
new file mode 100644
index 0000000..b7db128
--- /dev/null
+++ b/frontend/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "semi": true,
+ "trailingComma": "all",
+ "singleQuote": true,
+ "printWidth": 120,
+ "tabWidth": 4,
+ "arrowParens": "always",
+ "endOfLine": "lf",
+ "bracketSameLine": true
+}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b167825..64e433a 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,118 +1,328 @@
-import React from 'react';
-import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
+import React from "react";
+import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
-import AdminRoute from './components/utils/adminroute';
-import PrivateRoute from './components/utils/privateroute';
-import ProtectedRoute from './components/utils/protectedroute';
+import AdminRoute from "./components/utils/adminroute";
+import PrivateRoute from "./components/utils/privateroute";
+import ProtectedRoute from "./components/utils/protectedroute";
import {
- AdminPageBus,
- AdminPageChall,
- AdminPageEmail,
- AdminPageEvents,
- AdminPageExport,
- AdminPageFaction,
- AdminPageGames,
- AdminPageNews,
- AdminPagePerm,
- AdminPageRole,
- AdminPageShotgun,
- AdminPageTeam,
- AdminPageTent,
- AdminPageUser
-} from './pages/admin';
-import LoginPage from './pages/auth';
-import ChallPage from './pages/challenge';
-import DiscordPage from './pages/discord';
-import FoodPage from './pages/food';
-import GamesPage from './pages/games';
-import HomePage from './pages/home';
-import LegalsPage from './pages/legals';
-import NewsPage from './pages/news';
-import NotFoundPage from './pages/notFound';
-import ParrainagePage from './pages/parrainage';
-import { AvailablePermanencesPage, MyPermanencesPage, RespoCallPage } from './pages/perm';
-import PlanningsPage from './pages/plannings';
-import PrivacyPage from './pages/privacy';
-import ProfilPage from './pages/profil';
-import RegisterPage from './pages/register';
-import ResetPasswordPage from './pages/resetPassword';
-import Roadbook from './pages/roadbook';
-import SdiPage from './pages/sdi';
-import ShotgunPage from './pages/shotgun';
-import WeiPage from './pages/wei';
+ AdminPageBus,
+ AdminPageChall,
+ AdminPageEmail,
+ AdminPageEvents,
+ AdminPageExport,
+ AdminPageFaction,
+ AdminPageGames,
+ AdminPageNews,
+ AdminPagePerm,
+ AdminPageRole,
+ AdminPageShotgun,
+ AdminPageTeam,
+ AdminPageTent,
+ AdminPageUser,
+} from "./pages/admin";
+import LoginPage from "./pages/auth";
+import ChallPage from "./pages/challenge";
+import DiscordPage from "./pages/discord";
+import FoodPage from "./pages/food";
+import GamesPage from "./pages/games";
+import HomePage from "./pages/home";
+import LegalsPage from "./pages/legals";
+import NewsPage from "./pages/news";
+import NotFoundPage from "./pages/notFound";
+import ParrainagePage from "./pages/parrainage";
+import {
+ AvailablePermanencesPage,
+ MyPermanencesPage,
+ RespoCallPage,
+} from "./pages/perm";
+import PlanningsPage from "./pages/plannings";
+import PrivacyPage from "./pages/privacy";
+import ProfilPage from "./pages/profil";
+import RegisterPage from "./pages/register";
+import ResetPasswordPage from "./pages/resetPassword";
+import Roadbook from "./pages/roadbook";
+import SdiPage from "./pages/sdi";
+import ShotgunPage from "./pages/shotgun";
+import WeiPage from "./pages/wei";
const App: React.FC = () => {
- const VITE_ANALYTICS_WEBSITE_ID = import.meta.env.VITE_ANALYTICS_WEBSITE_ID;
+ const VITE_ANALYTICS_WEBSITE_ID = import.meta.env.VITE_ANALYTICS_WEBSITE_ID;
- React.useEffect(() => {
- const script = document.createElement('script');
- script.src = "https://analytics.uttnetgroup.fr/script.js";
- script.defer = true;
- if (VITE_ANALYTICS_WEBSITE_ID) {
- script.setAttribute('data-website-id', VITE_ANALYTICS_WEBSITE_ID);
- }
- document.body.appendChild(script);
- return () => {
- document.body.removeChild(script);
- };
- }, [VITE_ANALYTICS_WEBSITE_ID]);
+ React.useEffect(() => {
+ const script = document.createElement("script");
+ script.src = "https://analytics.uttnetgroup.fr/script.js";
+ script.defer = true;
+ if (VITE_ANALYTICS_WEBSITE_ID) {
+ script.setAttribute("data-website-id", VITE_ANALYTICS_WEBSITE_ID);
+ }
+ document.body.appendChild(script);
+ return () => {
+ document.body.removeChild(script);
+ };
+ }, [VITE_ANALYTICS_WEBSITE_ID]);
- return (
-
-
- {/* Public */}
- } />
- } />
- } />
- } />
- } />
- } />
+ return (
+
+
+ {/* Public */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- {/* Utilisateurs connectés */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ {/* Utilisateurs connectés */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* Étudiant et Admin */}
- } />
- } />
- } />
- } />
- } />
+ {/* Étudiant et Admin */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* ResposCE et Admin */}
- } />
- } />
- } />
- } />
+ {/* ResposCE et Admin */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* ResposCE et Admin */}
- } />
+ {/* ResposCE et Admin */}
+
+
+
+ }
+ />
- {/* Arbitre et Admin*/}
- } />
- {/* Admin uniquement */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ {/* Arbitre et Admin*/}
+
+
+
+ }
+ />
+ {/* Admin uniquement */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* Fallback */}
- } />
-
-
- );
+ {/* Fallback */}
+ } />
+
+
+ );
};
export default App;
diff --git a/frontend/src/components/Admin/adminEmail.tsx b/frontend/src/components/Admin/adminEmail.tsx
index 8737caa..f719afb 100644
--- a/frontend/src/components/Admin/adminEmail.tsx
+++ b/frontend/src/components/Admin/adminEmail.tsx
@@ -6,7 +6,9 @@ import { type User } from '../../interfaces/user.interface';
import { emailPreview, sendEmail } from '../../services/requests/email.service';
import { getUsers } from '../../services/requests/user.service';
import { Button } from '../ui/button';
-import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
+import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
+import { HorizontalMultipleSelect } from '../ui/horizontalMultipleSelect';
+import { HorizontalSingleSelect } from '../ui/horizontalSingleSelect';
import { Input } from '../ui/input';
type SelectOption = {
@@ -16,44 +18,50 @@ type SelectOption = {
export const AdminEmail = () => {
const [subject, setSubject] = useState('');
- const [templateName, setTemplateName] = useState('');
+ const [templateName, setTemplateName] = useState('custom');
const [format] = useState<'html' | 'txt'>('html');
- const [isCustom, setIsCustom] = useState(false);
const [customTitle, setCustomTitle] = useState('');
const [customContent, setCustomContent] = useState('');
- const [permission, setPermission] = useState(null);
+ const [recipientsGroups, setRecipientsGroups] = useState([]);
const [sendTo, setSendTo] = useState([]);
const [preview, setPreview] = useState('');
const [users, setUsers] = useState([]);
- const permissionOptions = [
- { value: 'Nouveau', label: 'Nouveau' },
- { value: 'RespoCE', label: 'RespoCE' },
- { value: 'Admin', label: 'Admin' },
- { value: 'Student', label: 'Student' },
+ const recipientsOptions = [
+ { name: 'Nouveau', value: 'Nouveau' },
+ { name: 'CE', value: 'Student' },
+ { name: 'RespoCE', value: 'RespoCE' },
+ { name: 'Admin', value: 'Admin' },
];
const templateOptions = [
- { value: 'templateWelcome', label: 'Template Welcome' },
- { value: 'templateNotebook', label: 'Template Cahier de Vacances' },
+ { name: 'Personnalisé', value: 'custom' },
+ { name: 'Welcome', value: 'templateWelcome' },
+ { name: 'Cahier de Vacances', value: 'templateNotebook' },
];
useEffect(() => {
fetchData();
}, []);
+ useEffect(() => {
+ setPreview('');
+ }, [templateName, customTitle, customContent]);
+
+ const isCustom = () => templateName === 'custom';
+
const fetchData = async () => {
try {
const usersRes = await getUsers();
setUsers(usersRes);
} catch (err) {
- console.error("Erreur lors du chargement des données", err);
+ console.error('Erreur lors du chargement des données', err);
}
};
const handlePreview = async () => {
try {
- if (isCustom) {
+ if (isCustom()) {
const html = await emailPreview({
templateName: 'custom',
title: customTitle || subject,
@@ -70,19 +78,17 @@ export const AdminEmail = () => {
};
const handleSend = async () => {
- // On mappe toujours pour avoir un tableau de string
-
const emails = sendTo.map((u) => u.value);
const payload = {
subject,
- templateName: isCustom ? 'custom' : templateName,
+ templateName: isCustom() ? 'custom' : templateName,
format,
- permission,
- sendTo: permission ? null : emails,
- title: isCustom ? customTitle || subject : undefined,
- content: isCustom ? customContent : undefined,
- html: isCustom ? customContent : undefined,
+ recipientsGroups,
+ sendTo: recipientsGroups.length ? null : emails,
+ title: isCustom() ? customTitle || subject : undefined,
+ content: isCustom() ? customContent : undefined,
+ html: isCustom() ? customContent : undefined,
};
try {
@@ -92,18 +98,27 @@ export const AdminEmail = () => {
title: 'Email envoyé',
text: res.message,
});
- } catch(error) {
+ } catch (error) {
Swal.fire({
- title: "Erreur ❌",
- text: error?.response?.data?.message || "Une erreur est survenue.",
- icon: "error",
+ title: 'Erreur ❌',
+ text: error?.response?.data?.message || 'Une erreur est survenue.',
+ icon: 'error',
});
}
};
const confirmSend = async () => {
+ if (!subject) {
+ Swal.fire({
+ title: 'Objet vide',
+ text: "Impossible d'envoyer un email sans objet.",
+ icon: 'error',
+ });
+ return;
+ }
+
const result = await Swal.fire({
- title: 'Confirmer l\'envoi',
+ title: "Confirmer l'envoi",
text: 'Êtes-vous sûr de vouloir envoyer cet email ?',
icon: 'warning',
showCancelButton: true,
@@ -118,69 +133,76 @@ export const AdminEmail = () => {
}
};
-
-
return (
-
- 📬 Envoi d'e-mail
-
+ 📬 Envoi d'e-mail
- setSubject(e.target.value)} />
-
- {
- setIsCustom(e.target.checked);
- if (e.target.checked) setTemplateName('');
- }}
- />
- ✏️ Rédiger un mail personnalisé
-
- {isCustom && (
- setCustomTitle(e.target.value)}
- />
- )}
- {!isCustom ? (
- setTemplateName(opt?.value || '')}
- />
- ) : (
-
diff --git a/frontend/src/components/Admin/adminEvent.tsx b/frontend/src/components/Admin/adminEvent.tsx
index 9ea20e1..2e85f79 100644
--- a/frontend/src/components/Admin/adminEvent.tsx
+++ b/frontend/src/components/Admin/adminEvent.tsx
@@ -1,6 +1,6 @@
-import { CheckCircle, Loader2, XCircle } from "lucide-react";
-import { useEffect, useState } from "react";
-import Swal from "sweetalert2";
+import { CheckCircle, Loader2, XCircle } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import Swal from 'sweetalert2';
import {
checkChallengeStatus,
@@ -15,9 +15,9 @@ import {
toggleSDI,
toggleShotgun,
toggleWEI,
-} from "../../services/requests/event.service";
-import { Button } from "../ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
+} from '../../services/requests/event.service';
+import { Button } from '../ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
export const AdminEvents = () => {
const [loading, setLoading] = useState(false);
@@ -42,7 +42,7 @@ export const AdminEvents = () => {
checkSDIStatus(),
checkWEIStatus(),
checkFoodStatus(),
- checkChallengeStatus()
+ checkChallengeStatus(),
]);
setStatuses({
@@ -55,9 +55,9 @@ export const AdminEvents = () => {
});
} catch {
Swal.fire({
- icon: "error",
- title: "Erreur",
- text: "Impossible de récupérer les statuts.",
+ icon: 'error',
+ title: 'Erreur',
+ text: 'Impossible de récupérer les statuts.',
});
} finally {
setLoadingStatuses(false);
@@ -70,24 +70,24 @@ export const AdminEvents = () => {
const handleToggle = async (
key: keyof typeof statuses,
toggleFn: (value: boolean) => Promise,
- successMsg: string
+ successMsg: string,
) => {
setLoading(true);
try {
await toggleFn(!statuses[key]);
setStatuses((prev) => ({ ...prev, [key]: !prev[key] }));
Swal.fire({
- icon: "success",
- title: "Succès",
+ icon: 'success',
+ title: 'Succès',
text: successMsg,
timer: 1500,
showConfirmButton: false,
});
} catch (error: any) {
Swal.fire({
- icon: "error",
- title: "Erreur",
- text: error.response?.data?.message || "Une erreur est survenue",
+ icon: 'error',
+ title: 'Erreur',
+ text: error.response?.data?.message || 'Une erreur est survenue',
});
} finally {
setLoading(false);
@@ -97,33 +97,33 @@ export const AdminEvents = () => {
// Configuration des événements
const events = [
{
- key: "preRegistration" as const,
- label: "Pré-inscription",
+ key: 'preRegistration' as const,
+ label: 'Pré-inscription',
toggleFn: togglePreRegistration,
},
{
- key: "shotgun" as const,
- label: "Shotgun",
+ key: 'shotgun' as const,
+ label: 'Shotgun',
toggleFn: toggleShotgun,
},
{
- key: "sdi" as const,
- label: "SDI (Billetterie)",
+ key: 'sdi' as const,
+ label: 'SDI (Billetterie)',
toggleFn: toggleSDI,
},
{
- key: "wei" as const,
- label: "WEI (Billetterie + Tentes)",
+ key: 'wei' as const,
+ label: 'WEI (Billetterie + Tentes)',
toggleFn: toggleWEI,
},
{
- key: "food" as const,
- label: "Nourriture (Billetterie)",
+ key: 'food' as const,
+ label: 'Nourriture (Billetterie)',
toggleFn: toggleFood,
},
{
- key: "chall" as const,
- label: "Challenges (Affichage des challenges)",
+ key: 'chall' as const,
+ label: 'Challenges (Affichage des challenges)',
toggleFn: toggleChallenge,
},
];
@@ -132,9 +132,7 @@ export const AdminEvents = () => {
return (
-
- Chargement des statuts...
-
+ Chargement des statuts...
);
}
@@ -152,8 +150,7 @@ export const AdminEvents = () => {
return (
+ className="flex items-center justify-between p-4 bg-gray-50 rounded-xl border-2 border-gray-200">
{isActive ? (
@@ -164,21 +161,19 @@ export const AdminEvents = () => {
- handleToggle(key, toggleFn, `${label} mis à jour !`)
- }
+ onClick={() => handleToggle(key, toggleFn, `${label} mis à jour !`)}
disabled={loading}
- className={`transition-colors duration-300 ${isActive
- ? "bg-red-600 text-white hover:bg-red-700"
- : "bg-blue-500 text-white hover:bg-blue-600"
- } p-2 rounded-lg min-w-[110px] flex items-center justify-center`}
- >
+ className={`transition-colors duration-300 ${
+ isActive
+ ? 'bg-red-600 text-white hover:bg-red-700'
+ : 'bg-blue-500 text-white hover:bg-blue-600'
+ } p-2 rounded-lg min-w-[110px] flex items-center justify-center`}>
{loading ? (
) : isActive ? (
- "Désactiver"
+ 'Désactiver'
) : (
- "Activer"
+ 'Activer'
)}
diff --git a/frontend/src/components/ui/horizontalMultipleSelect.tsx b/frontend/src/components/ui/horizontalMultipleSelect.tsx
new file mode 100644
index 0000000..e9c22e4
--- /dev/null
+++ b/frontend/src/components/ui/horizontalMultipleSelect.tsx
@@ -0,0 +1,71 @@
+import type { Dispatch, SetStateAction } from 'react';
+
+type HorizontalSelectOption = {
+ name: string;
+ value: string;
+};
+
+type HorizontalSelectProps = {
+ options: HorizontalSelectOption[];
+ value: string[];
+ setValue: Dispatch>;
+};
+
+function HorizontalMultipleSelect(props: HorizontalSelectProps) {
+ if (!props.options || !props.value || !props.setValue) return null;
+
+ const toggleCheckbox = (newValue: string) => {
+ props.setValue((prev) => {
+ const current = prev ?? [];
+
+ return current.includes(newValue) ? current.filter((g) => g !== newValue) : [...current, newValue];
+ });
+ };
+
+ return (
+
+
+ {props.options.map((option) => (
+
+
+
+
toggleCheckbox(option.value)}
+ className="peer h-5 w-5 cursor-pointer transition-all appearance-none rounded shadow hover:shadow-md border border-slate-300 checked:bg-slate-800 checked:border-slate-800"
+ />
+
+
+
+
+
+
+
+
+ {option.name}
+
+
+ ))}
+
+
+ );
+}
+
+export { HorizontalMultipleSelect };
diff --git a/frontend/src/components/ui/horizontalSingleSelect.tsx b/frontend/src/components/ui/horizontalSingleSelect.tsx
new file mode 100644
index 0000000..81ea47a
--- /dev/null
+++ b/frontend/src/components/ui/horizontalSingleSelect.tsx
@@ -0,0 +1,51 @@
+import type { Dispatch, SetStateAction } from 'react';
+
+type HorizontalSelectOption = {
+ name: string;
+ value: string;
+};
+
+type HorizontalSelectProps = {
+ options: HorizontalSelectOption[];
+ value: string;
+ setValue: Dispatch>;
+};
+
+function HorizontalSingleSelect(props: HorizontalSelectProps) {
+ // if (!props.options || !props.value || !props.setValue) return null;
+
+ return (
+
+
+ {props.options.map((option) => (
+
+
+
+ props.setValue(option.value)}
+ id={`radio-horizontal-list-${option.value}`}
+ />
+
+
+
+
+ {option.name}
+
+
+ ))}
+
+
+ );
+}
+
+export { HorizontalSingleSelect };
diff --git a/frontend/src/services/requests/email.service.ts b/frontend/src/services/requests/email.service.ts
index 2d53f34..0f746ba 100644
--- a/frontend/src/services/requests/email.service.ts
+++ b/frontend/src/services/requests/email.service.ts
@@ -1,10 +1,10 @@
-import api from "../api";
+import api from '../api';
type SendEmailPayload = {
subject: string;
templateName: string;
format?: 'html' | 'txt';
- permission: string | null;
+ recipientsGroups: string[] | null;
sendTo: string[] | null;
title?: string;
content?: string;
@@ -24,5 +24,5 @@ export const sendEmail = async (payload: SendEmailPayload) => {
export const emailPreview = async (payload: PreviewEmailPayload) => {
const response = await api.post('/email/admin/previewemail', { payload });
- return response.data.data
-}
+ return response.data.data;
+};
From daca979468cb329ce32926953a7ed5f70afb3948 Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Tue, 30 Jun 2026 23:38:36 +0200
Subject: [PATCH 15/42] feat(email): mentorship reminder template
---
backend/src/email/email.preview-data.ts | 1 +
backend/src/email/email.registry.ts | 7 +
.../src/email/templates/attribution-bus.html | 8 +-
backend/src/email/templates/custom.html | 4 +-
.../src/email/templates/mentor-reminder.html | 193 ++++++
backend/src/email/templates/notebook.html | 4 +-
backend/src/email/templates/notify-news.html | 8 +-
.../templates/notify-permanence-reminder.html | 8 +-
.../src/email/templates/reset-password.html | 8 +-
backend/src/email/templates/welcome.html | 28 +-
frontend/src/App.tsx | 619 +++++++++---------
frontend/src/components/Admin/adminEmail.tsx | 1 +
frontend/src/pages/parrainage.tsx | 21 +-
13 files changed, 559 insertions(+), 351 deletions(-)
create mode 100644 backend/src/email/templates/mentor-reminder.html
diff --git a/backend/src/email/email.preview-data.ts b/backend/src/email/email.preview-data.ts
index 713bb8a..f319dd3 100644
--- a/backend/src/email/email.preview-data.ts
+++ b/backend/src/email/email.preview-data.ts
@@ -31,4 +31,5 @@ export const defaultPreviewData: Record = {
templateNotifyPermanenceReminder: {
permanence: {},
},
+ templateMentorReminder: {},
};
diff --git a/backend/src/email/email.registry.ts b/backend/src/email/email.registry.ts
index 6a4c461..60247f2 100644
--- a/backend/src/email/email.registry.ts
+++ b/backend/src/email/email.registry.ts
@@ -7,6 +7,7 @@ const templateWelcome = 'welcome.html';
const templateNotifyNews = 'notify-news.html';
const templateNotifyTentConfirmation = 'notify-tent-confirmation.html';
const templateNotifyPermanenceReminder = 'notify-permanence-reminder.html';
+const templateMentorReminder = 'mentor-reminder.html';
export const templateRenderers: Record = {
custom: {
@@ -78,4 +79,10 @@ export const templateRenderers: Record = {
return typedData;
},
},
+ templateMentorReminder: {
+ fileName: templateMentorReminder,
+ buildData: () => {
+ return {};
+ },
+ },
};
diff --git a/backend/src/email/templates/attribution-bus.html b/backend/src/email/templates/attribution-bus.html
index ba13336..7d2702b 100644
--- a/backend/src/email/templates/attribution-bus.html
+++ b/backend/src/email/templates/attribution-bus.html
@@ -154,7 +154,9 @@
Cordialement, L'équipe intégration UTT
Si vous avez des questions, n'hésitez pas à
- nous contacter .
@@ -265,7 +267,9 @@ Find useful updates on our Instagram!
Regards, The UTT's Integration Team
If you have any questions, feel free to
- contact us .
diff --git a/backend/src/email/templates/custom.html b/backend/src/email/templates/custom.html
index 3262d94..f5ef71d 100644
--- a/backend/src/email/templates/custom.html
+++ b/backend/src/email/templates/custom.html
@@ -72,7 +72,9 @@
Cordialement, L'équipe intégration UTT
Si vous avez des questions, n'hésitez pas à
- nous contacter .
diff --git a/backend/src/email/templates/mentor-reminder.html b/backend/src/email/templates/mentor-reminder.html
new file mode 100644
index 0000000..fc9f691
--- /dev/null
+++ b/backend/src/email/templates/mentor-reminder.html
@@ -0,0 +1,193 @@
+
+
+
+
+
+ Intégration UTT
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+
+ Tu veux un parrain ?
+
+
+
+ Ce mail te concerne seulement si tu n'as pas encore reçu de parain attribué !
+
+
+
+ Lorsque tu arrives à l'UTT, un.e étudiant.e plus ancien.ne devient ton parrain ou ta
+ marraine. Il ou elle sera ton contact privilégié pour découvrir l'école mais aussi
+ la vie étudiante troyenne et répondre à toutes tes questions que ce soit sur l'UTT,
+ les logements, les cours, la vie à Troyes,...
+
+
+
+ Pour t'attribuer quelqu'un qui te correspond au mieux on t'invite à remplir le
+ formulaire sur la page Parrainage du
+ site de l'Intégration !
+
+
+
+ https://integration.utt.fr
+
+
+
+ Tu as reçu un mail il y a quelques jours permettant de te connecter au site, si tu
+ ne le retrouves pas, contacte nous:
+ integration+site-support@utt.fr
+
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
+
+ Looking for a mentor?
+
+
+
+ This email only concerns you if you have not yet been assigned a mentor!
+
+
+
+ When you arrive at UTT, an older student becomes your mentor. They will be your main
+ point of contact to help you discover the school, student life in Troyes, and answer
+ any questions you may have about UTT, accommodation, courses, life in Troyes, and
+ much more.
+
+
+
+ To help us match you with the mentor who suits you best, we invite you to fill out
+ the form on the Mentorship page of the
+ Integration website !
+
+
+
+ https://integration.utt.fr
+
+
+
+ You received an email a few days ago containing instructions to log in to the
+ website. If you cannot find it, please contact us:
+ integration+site-support@utt.fr
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+
+ If you have any questions, feel free to
+ contact us .
+
+
+
+
+
+
+
+
+
diff --git a/backend/src/email/templates/notebook.html b/backend/src/email/templates/notebook.html
index a87f9c0..8130e66 100644
--- a/backend/src/email/templates/notebook.html
+++ b/backend/src/email/templates/notebook.html
@@ -148,7 +148,9 @@
Cordialement, L'équipe intégration UTT
Si vous avez des questions, n'hésitez pas à
- nous contacter .
diff --git a/backend/src/email/templates/notify-news.html b/backend/src/email/templates/notify-news.html
index a633f3b..8c50ea9 100644
--- a/backend/src/email/templates/notify-news.html
+++ b/backend/src/email/templates/notify-news.html
@@ -92,7 +92,9 @@
Cordialement, L'équipe intégration UTT
Si vous avez des questions, n'hésitez pas à
- nous contacter .
@@ -140,7 +142,9 @@ Find useful updates on our Instagram!
Regards, The UTT's Integration Team
If you have any questions, feel free to
- contact us .
diff --git a/backend/src/email/templates/notify-permanence-reminder.html b/backend/src/email/templates/notify-permanence-reminder.html
index 31ca79a..4fa9491 100644
--- a/backend/src/email/templates/notify-permanence-reminder.html
+++ b/backend/src/email/templates/notify-permanence-reminder.html
@@ -90,7 +90,9 @@
Cordialement, L'équipe intégration UTT
Si vous avez des questions, n'hésitez pas à
- nous contacter .
@@ -132,7 +134,9 @@ Find useful updates on our Instagram!
Regards, The UTT's Integration Team
If you have any questions, feel free to
- contact us .
diff --git a/backend/src/email/templates/reset-password.html b/backend/src/email/templates/reset-password.html
index b36793a..35b6437 100644
--- a/backend/src/email/templates/reset-password.html
+++ b/backend/src/email/templates/reset-password.html
@@ -95,7 +95,9 @@
Cordialement, L'équipe intégration UTT
Si vous avez des questions, n'hésitez pas à
- nous contacter .
@@ -145,7 +147,9 @@ Find useful updates on our Instagram!
Regards, The UTT's Integration Team
If you have any questions, feel free to
- contact us .
diff --git a/backend/src/email/templates/welcome.html b/backend/src/email/templates/welcome.html
index 11b6528..5de2b50 100644
--- a/backend/src/email/templates/welcome.html
+++ b/backend/src/email/templates/welcome.html
@@ -109,13 +109,9 @@ 2. Parrainage
les logements, les cours, la vie à Troyes,...
- Pour t'attribuer quelqu'un qui te correspond au mieux on t'invite à remplir
- ce questionnaire
+ Pour t'attribuer quelqu'un qui te correspond au mieux on t'invite à remplir le
+ formulaire sur la page Parrainage du
+ site de l'Intégration !
@@ -135,7 +131,9 @@
Cordialement, L'équipe intégration UTT
Si vous avez des questions, n'hésitez pas à
- nous contacter .
@@ -201,13 +199,9 @@ 2. Mentorship
Troyes.
- To be matched with someone who suits you best, we invite you to fill out
- this questionnaire
+ To match you with someone who is the best fit, we invite you to fill out the form on
+ the Parrainage page of the
+ Integration website !
@@ -225,7 +219,9 @@ Find useful updates on our Instagram!
Regards, The UTT's Integration Team
If you have any questions, feel free to
- contact us .
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 64e433a..bf8f1ec 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,328 +1,321 @@
-import React from "react";
-import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
+import React from 'react';
+import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
-import AdminRoute from "./components/utils/adminroute";
-import PrivateRoute from "./components/utils/privateroute";
-import ProtectedRoute from "./components/utils/protectedroute";
+import AdminRoute from './components/utils/adminroute';
+import PrivateRoute from './components/utils/privateroute';
+import ProtectedRoute from './components/utils/protectedroute';
import {
- AdminPageBus,
- AdminPageChall,
- AdminPageEmail,
- AdminPageEvents,
- AdminPageExport,
- AdminPageFaction,
- AdminPageGames,
- AdminPageNews,
- AdminPagePerm,
- AdminPageRole,
- AdminPageShotgun,
- AdminPageTeam,
- AdminPageTent,
- AdminPageUser,
-} from "./pages/admin";
-import LoginPage from "./pages/auth";
-import ChallPage from "./pages/challenge";
-import DiscordPage from "./pages/discord";
-import FoodPage from "./pages/food";
-import GamesPage from "./pages/games";
-import HomePage from "./pages/home";
-import LegalsPage from "./pages/legals";
-import NewsPage from "./pages/news";
-import NotFoundPage from "./pages/notFound";
-import ParrainagePage from "./pages/parrainage";
-import {
- AvailablePermanencesPage,
- MyPermanencesPage,
- RespoCallPage,
-} from "./pages/perm";
-import PlanningsPage from "./pages/plannings";
-import PrivacyPage from "./pages/privacy";
-import ProfilPage from "./pages/profil";
-import RegisterPage from "./pages/register";
-import ResetPasswordPage from "./pages/resetPassword";
-import Roadbook from "./pages/roadbook";
-import SdiPage from "./pages/sdi";
-import ShotgunPage from "./pages/shotgun";
-import WeiPage from "./pages/wei";
+ AdminPageBus,
+ AdminPageChall,
+ AdminPageEmail,
+ AdminPageEvents,
+ AdminPageExport,
+ AdminPageFaction,
+ AdminPageGames,
+ AdminPageNews,
+ AdminPagePerm,
+ AdminPageRole,
+ AdminPageShotgun,
+ AdminPageTeam,
+ AdminPageTent,
+ AdminPageUser,
+} from './pages/admin';
+import LoginPage from './pages/auth';
+import ChallPage from './pages/challenge';
+import DiscordPage from './pages/discord';
+import FoodPage from './pages/food';
+import GamesPage from './pages/games';
+import HomePage from './pages/home';
+import LegalsPage from './pages/legals';
+import NewsPage from './pages/news';
+import NotFoundPage from './pages/notFound';
+import ParrainagePage from './pages/parrainage';
+import { AvailablePermanencesPage, MyPermanencesPage, RespoCallPage } from './pages/perm';
+import PlanningsPage from './pages/plannings';
+import PrivacyPage from './pages/privacy';
+import ProfilPage from './pages/profil';
+import RegisterPage from './pages/register';
+import ResetPasswordPage from './pages/resetPassword';
+import Roadbook from './pages/roadbook';
+import SdiPage from './pages/sdi';
+import ShotgunPage from './pages/shotgun';
+import WeiPage from './pages/wei';
const App: React.FC = () => {
- const VITE_ANALYTICS_WEBSITE_ID = import.meta.env.VITE_ANALYTICS_WEBSITE_ID;
+ const VITE_ANALYTICS_WEBSITE_ID = import.meta.env.VITE_ANALYTICS_WEBSITE_ID;
- React.useEffect(() => {
- const script = document.createElement("script");
- script.src = "https://analytics.uttnetgroup.fr/script.js";
- script.defer = true;
- if (VITE_ANALYTICS_WEBSITE_ID) {
- script.setAttribute("data-website-id", VITE_ANALYTICS_WEBSITE_ID);
- }
- document.body.appendChild(script);
- return () => {
- document.body.removeChild(script);
- };
- }, [VITE_ANALYTICS_WEBSITE_ID]);
+ React.useEffect(() => {
+ const script = document.createElement('script');
+ script.src = 'https://analytics.uttnetgroup.fr/script.js';
+ script.defer = true;
+ if (VITE_ANALYTICS_WEBSITE_ID) {
+ script.setAttribute('data-website-id', VITE_ANALYTICS_WEBSITE_ID);
+ }
+ document.body.appendChild(script);
+ return () => {
+ document.body.removeChild(script);
+ };
+ }, [VITE_ANALYTICS_WEBSITE_ID]);
- return (
-
-
- {/* Public */}
- } />
- } />
- } />
- } />
- } />
- } />
+ return (
+
+
+ {/* Public */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- {/* Utilisateurs connectés */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Utilisateurs connectés */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* Étudiant et Admin */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Étudiant et Admin */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* ResposCE et Admin */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* ResposCE et Admin */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* ResposCE et Admin */}
-
-
-
- }
- />
+ {/* ResposCE et Admin */}
+
+
+
+ }
+ />
- {/* Arbitre et Admin*/}
-
-
-
- }
- />
- {/* Admin uniquement */}
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
+ {/* Arbitre et Admin*/}
+
+
+
+ }
+ />
+ {/* Admin uniquement */}
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
- {/* Fallback */}
- } />
-
-
- );
+ {/* Fallback */}
+ } />
+
+
+ );
};
export default App;
diff --git a/frontend/src/components/Admin/adminEmail.tsx b/frontend/src/components/Admin/adminEmail.tsx
index f719afb..ef3682b 100644
--- a/frontend/src/components/Admin/adminEmail.tsx
+++ b/frontend/src/components/Admin/adminEmail.tsx
@@ -38,6 +38,7 @@ export const AdminEmail = () => {
{ name: 'Personnalisé', value: 'custom' },
{ name: 'Welcome', value: 'templateWelcome' },
{ name: 'Cahier de Vacances', value: 'templateNotebook' },
+ { name: 'Rappel Parrainage', value: 'templateMentorReminder' },
];
useEffect(() => {
diff --git a/frontend/src/pages/parrainage.tsx b/frontend/src/pages/parrainage.tsx
index d552d86..afc6835 100644
--- a/frontend/src/pages/parrainage.tsx
+++ b/frontend/src/pages/parrainage.tsx
@@ -1,16 +1,16 @@
-import { useNavigate } from "react-router-dom";
+import { useNavigate } from 'react-router-dom';
-import { Footer } from "../components/footer";
-import { Navbar } from "../components/navbar";
-import { ParrainageNewStudent, ParrainageStudent } from "../components/Parrainnage/parrainageForm";
-import { getPermission } from "../services/requests/user.service";
+import { Footer } from '../components/footer';
+import { Navbar } from '../components/navbar';
+import { ParrainageNewStudent, ParrainageStudent } from '../components/Parrainnage/parrainageForm';
+import { getPermission } from '../services/requests/user.service';
const ParrainagePage = () => {
const navigate = useNavigate();
const permission = getPermission();
if (!permission) {
- navigate("/");
+ navigate('/');
return null;
}
@@ -19,17 +19,14 @@ const ParrainagePage = () => {
+ {(permission === 'Nouveau' || permission === 'Admin') &&
}
- {(permission === "Nouveau" || permission === "Admin") && (
-
)}
-
- {(permission === "Student" || permission === "Admin") && (
-
)}
+ {(permission === 'Student' || permission === 'Admin') &&
}
);
-}
+};
export default ParrainagePage;
From 45d1102daab48a52b3410d80c2ba40f02c1c6d7d Mon Sep 17 00:00:00 2001
From: Arthur Dodin
Date: Tue, 30 Jun 2026 23:42:45 +0200
Subject: [PATCH 16/42] refactor(email): tent template
---
.../templates/notify-tent-confirmation.html | 196 ++++++++++++++----
1 file changed, 159 insertions(+), 37 deletions(-)
diff --git a/backend/src/email/templates/notify-tent-confirmation.html b/backend/src/email/templates/notify-tent-confirmation.html
index ce5e619..d9c05ef 100644
--- a/backend/src/email/templates/notify-tent-confirmation.html
+++ b/backend/src/email/templates/notify-tent-confirmation.html
@@ -7,46 +7,168 @@
-
-
-
-
Intégration UTT
-
-
⛺ Mise à jour de ta tente
-
- La tente entre {{user1}} et {{user2}} a été
-
- {{#if confirmed}}validée{{else}}invalidée{{/if}} .
-
-
- 👉 Tu peux consulter l'état de ta tente sur le site de l'inté dans l'onglet Tentes .
-
-
- Accéder au site
-
-
+
+
+
+
+
+
+
+
+
+
+
+ INTEGRATION UTT
+
+
+
+
+
+ ⛺ Mise à jour de ta tente
+
+
+
+ La tente entre {{user1}} et {{user2}} a été
+
+ {{#if confirmed}}validée{{else}}invalidée{{/if}} .
+
+
+ 👉 Tu peux consulter l'état de ta tente sur le site de l'Intégration dans l'onglet
+ WEI .
+
+
+ Accéder au site
+
+
+
+
+
+
+ Retrouve des informations utiles sur notre Instagram !
+
+
+ Cordialement, L'équipe intégration UTT
+
+ Si vous avez des questions, n'hésitez pas à
+ nous contacter .
+
+
+
+
+
+
+
+
+ ⛺ Tent Update
+
+
+ The tent shared by {{user1}} and {{user2}} has
+ been
+
+ {{#if confirmed}}approved{{else}}rejected{{/if}} .
+
+
+
+ 👉 You can check the status of your tent on the Integration website under the
+ WEI tab.
+
+
+
+ Access the website
+
+
+
+
+
+ Find useful updates on our Instagram!
+
+ Regards, The UTT's Integration Team
+
+ If you have any questions, feel free to
+ contact us .
+
+
+
+
+
+
+