diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 0000000..addf53d --- /dev/null +++ b/messages/en.json @@ -0,0 +1,321 @@ +{ + "common": { + "loading": "Loading...", + "save": "Save", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit", + "create": "Create", + "close": "Close", + "confirm": "Confirm", + "back": "Back", + "next": "Next", + "submit": "Submit", + "search": "Search", + "filter": "Filter", + "export": "Export", + "import": "Import", + "refresh": "Refresh", + "actions": "Actions", + "status": "Status", + "name": "Name", + "email": "Email", + "role": "Role", + "date": "Date", + "description": "Description", + "settings": "Settings", + "yes": "Yes", + "no": "No", + "all": "All", + "none": "None", + "noResults": "No results found", + "error": "Error", + "success": "Success", + "warning": "Warning", + "info": "Info", + "required": "Required", + "optional": "Optional", + "total": "Total", + "page": "Page", + "of": "of", + "showing": "Showing", + "entries": "entries", + "perPage": "per page", + "language": "Language", + "english": "English", + "french": "French" + }, + "auth": { + "signIn": "Sign In", + "signOut": "Sign Out", + "signUp": "Sign Up", + "email": "Email Address", + "password": "Password", + "forgotPassword": "Forgot Password?", + "resetPassword": "Reset Password", + "newPassword": "New Password", + "confirmPassword": "Confirm Password", + "currentPassword": "Current Password", + "magicLink": "Sign in with Magic Link", + "magicLinkSent": "Check your email for a sign-in link", + "invalidCredentials": "Invalid email or password", + "accountLocked": "Account locked. Try again later.", + "setPassword": "Set Password", + "passwordRequirements": "Password must be at least 8 characters", + "passwordsDoNotMatch": "Passwords do not match", + "welcomeBack": "Welcome back", + "signInDescription": "Enter your email to sign in to your account", + "orContinueWith": "Or continue with" + }, + "nav": { + "dashboard": "Dashboard", + "programs": "Programs", + "rounds": "Rounds", + "projects": "Projects", + "users": "Users", + "evaluations": "Evaluations", + "assignments": "Assignments", + "analytics": "Analytics", + "settings": "Settings", + "audit": "Audit Log", + "myProjects": "My Projects", + "myEvaluations": "My Evaluations", + "profile": "Profile", + "help": "Help", + "notifications": "Notifications", + "mentoring": "Mentoring", + "liveVoting": "Live Voting", + "applications": "Applications", + "messages": "Messages" + }, + "dashboard": { + "title": "Dashboard", + "welcome": "Welcome, {name}", + "overview": "Overview", + "recentActivity": "Recent Activity", + "pendingEvaluations": "Pending Evaluations", + "completedEvaluations": "Completed Evaluations", + "totalProjects": "Total Projects", + "activeRounds": "Active Rounds", + "assignedProjects": "Assigned Projects", + "upcomingDeadlines": "Upcoming Deadlines", + "quickActions": "Quick Actions", + "noActivity": "No recent activity" + }, + "programs": { + "title": "Programs", + "createProgram": "Create Program", + "editProgram": "Edit Program", + "programName": "Program Name", + "year": "Year", + "status": "Status", + "rounds": "Rounds", + "projects": "Projects", + "noPrograms": "No programs found", + "deleteConfirm": "Are you sure you want to delete this program?" + }, + "rounds": { + "title": "Rounds", + "createRound": "Create Round", + "editRound": "Edit Round", + "roundName": "Round Name", + "roundType": "Round Type", + "startDate": "Start Date", + "endDate": "End Date", + "votingWindow": "Voting Window", + "criteria": "Evaluation Criteria", + "status": "Status", + "active": "Active", + "closed": "Closed", + "upcoming": "Upcoming", + "noRounds": "No rounds found" + }, + "projects": { + "title": "Projects", + "createProject": "Create Project", + "editProject": "Edit Project", + "projectName": "Project Title", + "teamName": "Team Name", + "country": "Country", + "category": "Category", + "status": "Status", + "description": "Description", + "files": "Files", + "evaluations": "Evaluations", + "noProjects": "No projects found", + "importCsv": "Import CSV", + "bulkStatusUpdate": "Bulk Status Update", + "viewDetails": "View Details", + "assignMentor": "Assign Mentor", + "oceanIssue": "Ocean Issue" + }, + "evaluations": { + "title": "Evaluations", + "submitEvaluation": "Submit Evaluation", + "draft": "Draft", + "submitted": "Submitted", + "score": "Score", + "feedback": "Feedback", + "criteria": "Criteria", + "globalScore": "Global Score", + "decision": "Decision", + "recommend": "Recommend", + "doNotRecommend": "Do Not Recommend", + "saveAsDraft": "Save as Draft", + "finalSubmit": "Final Submit", + "confirmSubmit": "Are you sure? This action cannot be undone.", + "progress": "Progress", + "completionRate": "Completion Rate", + "noEvaluations": "No evaluations yet", + "evaluationSummary": "Evaluation Summary", + "strengths": "Strengths", + "weaknesses": "Weaknesses", + "overallAssessment": "Overall Assessment" + }, + "users": { + "title": "Users", + "createUser": "Create User", + "editUser": "Edit User", + "inviteUser": "Invite User", + "bulkImport": "Bulk Import", + "sendInvitation": "Send Invitation", + "resendInvitation": "Resend Invitation", + "role": "Role", + "status": "Status", + "active": "Active", + "invited": "Invited", + "suspended": "Suspended", + "noUsers": "No users found", + "expertiseTags": "Expertise Tags", + "maxAssignments": "Max Assignments", + "lastLogin": "Last Login", + "deleteConfirm": "Are you sure you want to delete this user?" + }, + "assignments": { + "title": "Assignments", + "assign": "Assign", + "unassign": "Unassign", + "bulkAssign": "Bulk Assign", + "smartAssign": "Smart Assignment", + "manual": "Manual", + "algorithm": "Algorithm", + "aiAuto": "AI Auto", + "noAssignments": "No assignments", + "assignedTo": "Assigned to", + "assignedBy": "Assigned by" + }, + "files": { + "title": "Files", + "upload": "Upload File", + "download": "Download", + "delete": "Delete File", + "fileName": "File Name", + "fileType": "File Type", + "fileSize": "File Size", + "uploadDate": "Upload Date", + "noFiles": "No files uploaded", + "dragAndDrop": "Drag and drop files here", + "maxSize": "Maximum file size: {size}", + "version": "Version", + "versionHistory": "Version History", + "replaceFile": "Replace File", + "bulkDownload": "Bulk Download" + }, + "settings": { + "title": "Settings", + "general": "General", + "branding": "Branding", + "email": "Email", + "security": "Security", + "ai": "AI Configuration", + "storage": "Storage", + "language": "Language", + "defaultLanguage": "Default Language", + "availableLanguages": "Available Languages", + "languageDescription": "Configure the platform's language settings", + "saved": "Settings saved successfully", + "saveFailed": "Failed to save settings" + }, + "liveVoting": { + "title": "Live Voting", + "session": "Session", + "startVoting": "Start Voting", + "stopVoting": "Stop Voting", + "endSession": "End Session", + "timeRemaining": "Time Remaining", + "castVote": "Cast Your Vote", + "voteSubmitted": "Vote submitted", + "results": "Results", + "juryScore": "Jury Score", + "audienceScore": "Audience Score", + "weightedTotal": "Weighted Total", + "noVotes": "No votes yet", + "votingClosed": "Voting has closed", + "presentationSettings": "Presentation Settings", + "audienceVoting": "Audience Voting" + }, + "mentor": { + "title": "Mentoring", + "myMentees": "My Mentees", + "projectDetails": "Project Details", + "sendMessage": "Send Message", + "notes": "Notes", + "addNote": "Add Note", + "milestones": "Milestones", + "completeMilestone": "Mark Complete", + "activity": "Activity", + "lastViewed": "Last Viewed", + "noMentees": "No mentees assigned" + }, + "profile": { + "title": "Profile", + "editProfile": "Edit Profile", + "name": "Full Name", + "email": "Email", + "phone": "Phone Number", + "country": "Country", + "bio": "Bio", + "expertise": "Areas of Expertise", + "notifications": "Notification Preferences", + "digestFrequency": "Digest Frequency", + "availability": "Availability", + "workload": "Preferred Workload", + "changePassword": "Change Password", + "deleteAccount": "Delete Account", + "deleteAccountConfirm": "This action cannot be undone. All your data will be permanently deleted." + }, + "onboarding": { + "welcome": "Welcome to MOPC", + "setupProfile": "Let's set up your profile", + "step1": "Personal Information", + "step2": "Expertise & Preferences", + "step3": "Review & Complete", + "complete": "Complete Setup", + "skip": "Skip for now" + }, + "errors": { + "generic": "Something went wrong. Please try again.", + "notFound": "Page not found", + "unauthorized": "You are not authorized to access this page", + "forbidden": "Access denied", + "serverError": "Internal server error", + "networkError": "Network error. Please check your connection.", + "sessionExpired": "Your session has expired. Please sign in again.", + "validationError": "Please check the form for errors" + }, + "notifications": { + "title": "Notifications", + "markAllRead": "Mark all as read", + "noNotifications": "No notifications", + "viewAll": "View all notifications" + }, + "coi": { + "title": "Conflict of Interest", + "declaration": "COI Declaration", + "declareConflict": "Declare Conflict of Interest", + "noConflict": "No conflict of interest", + "hasConflict": "Conflict declared", + "reason": "Reason for conflict", + "confirmDeclaration": "I confirm this declaration is accurate" + } +} diff --git a/messages/fr.json b/messages/fr.json new file mode 100644 index 0000000..fd7600e --- /dev/null +++ b/messages/fr.json @@ -0,0 +1,321 @@ +{ + "common": { + "loading": "Chargement...", + "save": "Enregistrer", + "cancel": "Annuler", + "delete": "Supprimer", + "edit": "Modifier", + "create": "Cr\u00e9er", + "close": "Fermer", + "confirm": "Confirmer", + "back": "Retour", + "next": "Suivant", + "submit": "Soumettre", + "search": "Rechercher", + "filter": "Filtrer", + "export": "Exporter", + "import": "Importer", + "refresh": "Actualiser", + "actions": "Actions", + "status": "Statut", + "name": "Nom", + "email": "E-mail", + "role": "R\u00f4le", + "date": "Date", + "description": "Description", + "settings": "Param\u00e8tres", + "yes": "Oui", + "no": "Non", + "all": "Tous", + "none": "Aucun", + "noResults": "Aucun r\u00e9sultat trouv\u00e9", + "error": "Erreur", + "success": "Succ\u00e8s", + "warning": "Avertissement", + "info": "Information", + "required": "Obligatoire", + "optional": "Facultatif", + "total": "Total", + "page": "Page", + "of": "de", + "showing": "Affichage de", + "entries": "entr\u00e9es", + "perPage": "par page", + "language": "Langue", + "english": "Anglais", + "french": "Fran\u00e7ais" + }, + "auth": { + "signIn": "Se connecter", + "signOut": "Se d\u00e9connecter", + "signUp": "S'inscrire", + "email": "Adresse e-mail", + "password": "Mot de passe", + "forgotPassword": "Mot de passe oubli\u00e9 ?", + "resetPassword": "R\u00e9initialiser le mot de passe", + "newPassword": "Nouveau mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "currentPassword": "Mot de passe actuel", + "magicLink": "Se connecter avec un lien magique", + "magicLinkSent": "V\u00e9rifiez votre e-mail pour un lien de connexion", + "invalidCredentials": "E-mail ou mot de passe invalide", + "accountLocked": "Compte verrouill\u00e9. R\u00e9essayez plus tard.", + "setPassword": "D\u00e9finir le mot de passe", + "passwordRequirements": "Le mot de passe doit contenir au moins 8 caract\u00e8res", + "passwordsDoNotMatch": "Les mots de passe ne correspondent pas", + "welcomeBack": "Bon retour", + "signInDescription": "Entrez votre e-mail pour vous connecter \u00e0 votre compte", + "orContinueWith": "Ou continuer avec" + }, + "nav": { + "dashboard": "Tableau de bord", + "programs": "Programmes", + "rounds": "Tours", + "projects": "Projets", + "users": "Utilisateurs", + "evaluations": "\u00c9valuations", + "assignments": "Affectations", + "analytics": "Analytique", + "settings": "Param\u00e8tres", + "audit": "Journal d'audit", + "myProjects": "Mes projets", + "myEvaluations": "Mes \u00e9valuations", + "profile": "Profil", + "help": "Aide", + "notifications": "Notifications", + "mentoring": "Mentorat", + "liveVoting": "Vote en direct", + "applications": "Candidatures", + "messages": "Messages" + }, + "dashboard": { + "title": "Tableau de bord", + "welcome": "Bienvenue, {name}", + "overview": "Aper\u00e7u", + "recentActivity": "Activit\u00e9 r\u00e9cente", + "pendingEvaluations": "\u00c9valuations en attente", + "completedEvaluations": "\u00c9valuations termin\u00e9es", + "totalProjects": "Total des projets", + "activeRounds": "Tours actifs", + "assignedProjects": "Projets assign\u00e9s", + "upcomingDeadlines": "\u00c9ch\u00e9ances \u00e0 venir", + "quickActions": "Actions rapides", + "noActivity": "Aucune activit\u00e9 r\u00e9cente" + }, + "programs": { + "title": "Programmes", + "createProgram": "Cr\u00e9er un programme", + "editProgram": "Modifier le programme", + "programName": "Nom du programme", + "year": "Ann\u00e9e", + "status": "Statut", + "rounds": "Tours", + "projects": "Projets", + "noPrograms": "Aucun programme trouv\u00e9", + "deleteConfirm": "\u00cates-vous s\u00fbr de vouloir supprimer ce programme ?" + }, + "rounds": { + "title": "Tours", + "createRound": "Cr\u00e9er un tour", + "editRound": "Modifier le tour", + "roundName": "Nom du tour", + "roundType": "Type de tour", + "startDate": "Date de d\u00e9but", + "endDate": "Date de fin", + "votingWindow": "Fen\u00eatre de vote", + "criteria": "Crit\u00e8res d'\u00e9valuation", + "status": "Statut", + "active": "Actif", + "closed": "Cl\u00f4tur\u00e9", + "upcoming": "\u00c0 venir", + "noRounds": "Aucun tour trouv\u00e9" + }, + "projects": { + "title": "Projets", + "createProject": "Cr\u00e9er un projet", + "editProject": "Modifier le projet", + "projectName": "Titre du projet", + "teamName": "Nom de l'\u00e9quipe", + "country": "Pays", + "category": "Cat\u00e9gorie", + "status": "Statut", + "description": "Description", + "files": "Fichiers", + "evaluations": "\u00c9valuations", + "noProjects": "Aucun projet trouv\u00e9", + "importCsv": "Importer CSV", + "bulkStatusUpdate": "Mise \u00e0 jour en masse du statut", + "viewDetails": "Voir les d\u00e9tails", + "assignMentor": "Assigner un mentor", + "oceanIssue": "Probl\u00e9matique oc\u00e9anique" + }, + "evaluations": { + "title": "\u00c9valuations", + "submitEvaluation": "Soumettre l'\u00e9valuation", + "draft": "Brouillon", + "submitted": "Soumis", + "score": "Note", + "feedback": "Commentaires", + "criteria": "Crit\u00e8res", + "globalScore": "Note globale", + "decision": "D\u00e9cision", + "recommend": "Recommander", + "doNotRecommend": "Ne pas recommander", + "saveAsDraft": "Enregistrer comme brouillon", + "finalSubmit": "Soumission finale", + "confirmSubmit": "\u00cates-vous s\u00fbr ? Cette action est irr\u00e9versible.", + "progress": "Progression", + "completionRate": "Taux de compl\u00e9tion", + "noEvaluations": "Aucune \u00e9valuation pour le moment", + "evaluationSummary": "R\u00e9sum\u00e9 de l'\u00e9valuation", + "strengths": "Points forts", + "weaknesses": "Points faibles", + "overallAssessment": "\u00c9valuation globale" + }, + "users": { + "title": "Utilisateurs", + "createUser": "Cr\u00e9er un utilisateur", + "editUser": "Modifier l'utilisateur", + "inviteUser": "Inviter un utilisateur", + "bulkImport": "Importation en masse", + "sendInvitation": "Envoyer l'invitation", + "resendInvitation": "Renvoyer l'invitation", + "role": "R\u00f4le", + "status": "Statut", + "active": "Actif", + "invited": "Invit\u00e9", + "suspended": "Suspendu", + "noUsers": "Aucun utilisateur trouv\u00e9", + "expertiseTags": "Tags d'expertise", + "maxAssignments": "Affectations max.", + "lastLogin": "Derni\u00e8re connexion", + "deleteConfirm": "\u00cates-vous s\u00fbr de vouloir supprimer cet utilisateur ?" + }, + "assignments": { + "title": "Affectations", + "assign": "Affecter", + "unassign": "D\u00e9saffecter", + "bulkAssign": "Affectation en masse", + "smartAssign": "Affectation intelligente", + "manual": "Manuel", + "algorithm": "Algorithme", + "aiAuto": "IA automatique", + "noAssignments": "Aucune affectation", + "assignedTo": "Affect\u00e9 \u00e0", + "assignedBy": "Affect\u00e9 par" + }, + "files": { + "title": "Fichiers", + "upload": "T\u00e9l\u00e9charger un fichier", + "download": "T\u00e9l\u00e9charger", + "delete": "Supprimer le fichier", + "fileName": "Nom du fichier", + "fileType": "Type de fichier", + "fileSize": "Taille du fichier", + "uploadDate": "Date de t\u00e9l\u00e9chargement", + "noFiles": "Aucun fichier t\u00e9l\u00e9charg\u00e9", + "dragAndDrop": "Glissez-d\u00e9posez les fichiers ici", + "maxSize": "Taille maximale du fichier : {size}", + "version": "Version", + "versionHistory": "Historique des versions", + "replaceFile": "Remplacer le fichier", + "bulkDownload": "T\u00e9l\u00e9chargement en masse" + }, + "settings": { + "title": "Param\u00e8tres", + "general": "G\u00e9n\u00e9ral", + "branding": "Image de marque", + "email": "E-mail", + "security": "S\u00e9curit\u00e9", + "ai": "Configuration IA", + "storage": "Stockage", + "language": "Langue", + "defaultLanguage": "Langue par d\u00e9faut", + "availableLanguages": "Langues disponibles", + "languageDescription": "Configurer les param\u00e8tres de langue de la plateforme", + "saved": "Param\u00e8tres enregistr\u00e9s avec succ\u00e8s", + "saveFailed": "\u00c9chec de l'enregistrement des param\u00e8tres" + }, + "liveVoting": { + "title": "Vote en direct", + "session": "Session", + "startVoting": "D\u00e9marrer le vote", + "stopVoting": "Arr\u00eater le vote", + "endSession": "Terminer la session", + "timeRemaining": "Temps restant", + "castVote": "Voter", + "voteSubmitted": "Vote soumis", + "results": "R\u00e9sultats", + "juryScore": "Note du jury", + "audienceScore": "Note du public", + "weightedTotal": "Total pond\u00e9r\u00e9", + "noVotes": "Aucun vote pour le moment", + "votingClosed": "Le vote est clos", + "presentationSettings": "Param\u00e8tres de pr\u00e9sentation", + "audienceVoting": "Vote du public" + }, + "mentor": { + "title": "Mentorat", + "myMentees": "Mes mentees", + "projectDetails": "D\u00e9tails du projet", + "sendMessage": "Envoyer un message", + "notes": "Notes", + "addNote": "Ajouter une note", + "milestones": "Jalons", + "completeMilestone": "Marquer comme termin\u00e9", + "activity": "Activit\u00e9", + "lastViewed": "Derni\u00e8re consultation", + "noMentees": "Aucun mentee assign\u00e9" + }, + "profile": { + "title": "Profil", + "editProfile": "Modifier le profil", + "name": "Nom complet", + "email": "E-mail", + "phone": "Num\u00e9ro de t\u00e9l\u00e9phone", + "country": "Pays", + "bio": "Biographie", + "expertise": "Domaines d'expertise", + "notifications": "Pr\u00e9f\u00e9rences de notification", + "digestFrequency": "Fr\u00e9quence du digest", + "availability": "Disponibilit\u00e9", + "workload": "Charge de travail pr\u00e9f\u00e9r\u00e9e", + "changePassword": "Changer le mot de passe", + "deleteAccount": "Supprimer le compte", + "deleteAccountConfirm": "Cette action est irr\u00e9versible. Toutes vos donn\u00e9es seront d\u00e9finitivement supprim\u00e9es." + }, + "onboarding": { + "welcome": "Bienvenue sur MOPC", + "setupProfile": "Configurons votre profil", + "step1": "Informations personnelles", + "step2": "Expertise et pr\u00e9f\u00e9rences", + "step3": "V\u00e9rification et finalisation", + "complete": "Terminer la configuration", + "skip": "Passer pour le moment" + }, + "errors": { + "generic": "Une erreur s'est produite. Veuillez r\u00e9essayer.", + "notFound": "Page non trouv\u00e9e", + "unauthorized": "Vous n'\u00eates pas autoris\u00e9 \u00e0 acc\u00e9der \u00e0 cette page", + "forbidden": "Acc\u00e8s refus\u00e9", + "serverError": "Erreur interne du serveur", + "networkError": "Erreur r\u00e9seau. V\u00e9rifiez votre connexion.", + "sessionExpired": "Votre session a expir\u00e9. Veuillez vous reconnecter.", + "validationError": "Veuillez v\u00e9rifier le formulaire pour les erreurs" + }, + "notifications": { + "title": "Notifications", + "markAllRead": "Tout marquer comme lu", + "noNotifications": "Aucune notification", + "viewAll": "Voir toutes les notifications" + }, + "coi": { + "title": "Conflit d'int\u00e9r\u00eats", + "declaration": "D\u00e9claration de COI", + "declareConflict": "D\u00e9clarer un conflit d'int\u00e9r\u00eats", + "noConflict": "Aucun conflit d'int\u00e9r\u00eats", + "hasConflict": "Conflit d\u00e9clar\u00e9", + "reason": "Raison du conflit", + "confirmDeclaration": "Je confirme que cette d\u00e9claration est exacte" + } +} diff --git a/next.config.ts b/next.config.ts index 0078638..becc049 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,4 +1,5 @@ import type { NextConfig } from 'next' +import createNextIntlPlugin from 'next-intl/plugin' const nextConfig: NextConfig = { output: 'standalone', @@ -14,4 +15,6 @@ const nextConfig: NextConfig = { }, } -export default nextConfig +const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts') + +export default withNextIntl(nextConfig) diff --git a/package-lock.json b/package-lock.json index 89a9cee..ea1d947 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,6 @@ "@trpc/client": "^11.0.0-rc.678", "@trpc/react-query": "^11.0.0-rc.678", "@trpc/server": "^11.0.0-rc.678", - "@types/leaflet": "^1.9.21", "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -55,6 +54,7 @@ "motion": "^11.15.0", "next": "^15.1.0", "next-auth": "^5.0.0-beta.25", + "next-intl": "^4.8.2", "nodemailer": "^7.0.7", "openai": "^6.16.0", "papaparse": "^5.4.1", @@ -69,19 +69,18 @@ "sonner": "^2.0.7", "superjson": "^2.2.2", "tailwind-merge": "^3.4.0", - "twilio": "^5.4.0", "use-debounce": "^10.0.4", "zod": "^3.24.1" }, "devDependencies": { "@playwright/test": "^1.49.1", "@types/bcryptjs": "^2.4.6", + "@types/leaflet": "^1.9.21", "@types/node": "^25.0.10", "@types/nodemailer": "^7.0.9", "@types/papaparse": "^5.3.15", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", - "autoprefixer": "^10.4.20", "eslint": "^9.17.0", "eslint-config-next": "^15.1.0", "postcss": "^8.4.49", @@ -1001,6 +1000,67 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.1.tgz", + "integrity": "sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "3.1.0", + "@formatjs/intl-localematcher": "0.8.1", + "decimal.js": "^10.6.0", + "tslib": "^2.8.1" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.1.tgz", + "integrity": "sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "3.1.0", + "tslib": "^2.8.1" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz", + "integrity": "sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.1.tgz", + "integrity": "sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "3.1.1", + "@formatjs/icu-skeleton-parser": "2.1.1", + "tslib": "^2.8.1" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.1.tgz", + "integrity": "sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "3.1.1", + "tslib": "^2.8.1" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@handlewithcare/prosemirror-inputrules": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@handlewithcare/prosemirror-inputrules/-/prosemirror-inputrules-0.1.4.tgz", @@ -1852,6 +1912,301 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@playwright/test": { "version": "1.58.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.0.tgz", @@ -3721,6 +4076,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@shikijs/types": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz", @@ -3749,6 +4110,172 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", + "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", + "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", + "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", + "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.11.tgz", + "integrity": "sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", + "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.11.tgz", + "integrity": "sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", + "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", + "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", + "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -3758,6 +4285,15 @@ "tslib": "^2.8.0" } }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@tailwindcss/node": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", @@ -4468,6 +5004,7 @@ "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, "license": "MIT" }, "node_modules/@types/hast": { @@ -4497,6 +5034,7 @@ "version": "1.9.21", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "dev": true, "license": "MIT", "dependencies": { "@types/geojson": "*" @@ -5280,18 +5818,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "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/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5552,43 +6078,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5614,17 +6103,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -5652,16 +6130,6 @@ "dev": true, "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -5710,40 +6178,6 @@ "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==", "license": "MIT" }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/buffer-crc32": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", @@ -5753,12 +6187,6 @@ "node": ">=8.0.0" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, "node_modules/c12": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", @@ -6319,12 +6747,6 @@ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", "license": "MIT" }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -6342,6 +6764,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", @@ -6522,15 +6950,6 @@ "node": ">= 0.4" } }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/effect": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", @@ -6542,13 +6961,6 @@ "fast-check": "^3.23.1" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.279", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", - "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", - "dev": true, - "license": "ISC" - }, "node_modules/emoji-mart": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", @@ -6829,16 +7241,6 @@ "@esbuild/win32-x64": "0.27.2" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -7516,26 +7918,6 @@ "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -7567,20 +7949,6 @@ "node": ">= 6" } }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, "node_modules/framer-motion": { "version": "11.18.2", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", @@ -8205,17 +8573,19 @@ "url": "https://opencollective.com/unified" } }, - "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==", + "node_modules/icu-minify": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.8.2.tgz", + "integrity": "sha512-LHBQV+skKkjZSPd590pZ7ZAHftUgda3eFjeuNwA8/15L8T8loCNBktKQyTlkodAU86KovFXeg/9WntlAo5wA5A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" + "@formatjs/icu-messageformat-parser": "^3.4.0" } }, "node_modules/ignore": { @@ -8316,6 +8686,18 @@ "node": ">=12" } }, + "node_modules/intl-messageformat": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.1.2.tgz", + "integrity": "sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "3.1.1", + "@formatjs/fast-memoize": "3.1.0", + "@formatjs/icu-messageformat-parser": "3.5.1", + "tslib": "^2.8.1" + } + }, "node_modules/ipaddr.js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", @@ -8489,7 +8871,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8534,7 +8915,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -8876,28 +9256,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -8914,27 +9272,6 @@ "node": ">=4.0" } }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9298,54 +9635,12 @@ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -10407,6 +10702,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "15.5.10", "resolved": "https://registry.npmjs.org/next/-/next-15.5.10.tgz", @@ -10515,6 +10819,93 @@ } } }, + "node_modules/next-intl": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.8.2.tgz", + "integrity": "sha512-GuuwyvyEI49/oehQbBXEoY8KSIYCzmfMLhmIwhMXTb+yeBmly1PnJcpgph3KczQ+HTJMXwXCmkizgtT8jBMf3A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "@parcel/watcher": "^2.4.1", + "@swc/core": "^1.15.2", + "icu-minify": "^4.8.2", + "negotiator": "^1.0.0", + "next-intl-swc-plugin-extractor": "^4.8.2", + "po-parser": "^2.1.1", + "use-intl": "^4.8.2" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/next-intl-swc-plugin-extractor": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.8.2.tgz", + "integrity": "sha512-sHDs36L1VZmFHj3tPHsD+KZJtnsRudHlNvT0ieIe3iFVn5OpGLTxW3d/Zc/2LXSj5GpGuR6wQeikbhFjU9tMQQ==", + "license": "MIT" + }, + "node_modules/next-intl/node_modules/@swc/core": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", + "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.25" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.11", + "@swc/core-darwin-x64": "1.15.11", + "@swc/core-linux-arm-gnueabihf": "1.15.11", + "@swc/core-linux-arm64-gnu": "1.15.11", + "@swc/core-linux-arm64-musl": "1.15.11", + "@swc/core-linux-x64-gnu": "1.15.11", + "@swc/core-linux-x64-musl": "1.15.11", + "@swc/core-win32-arm64-msvc": "1.15.11", + "@swc/core-win32-ia32-msvc": "1.15.11", + "@swc/core-win32-x64-msvc": "1.15.11" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/next-intl/node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -10543,6 +10934,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -10570,13 +10967,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, "node_modules/nodemailer": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", @@ -10639,6 +11029,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10954,7 +11345,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11007,6 +11397,12 @@ "node": ">=18" } }, + "node_modules/po-parser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", + "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==", + "license": "MIT" + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -11044,13 +11440,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, "node_modules/preact": { "version": "10.24.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", @@ -11470,12 +11859,6 @@ "prosemirror-transform": "^1.1.0" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -11512,21 +11895,6 @@ ], "license": "MIT" }, - "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -12294,17 +12662,11 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, - "node_modules/scmp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", - "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", - "deprecated": "Just use Node.js's crypto.timingSafeEqual()", - "license": "BSD-3-Clause" - }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12433,6 +12795,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12452,6 +12815,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12468,6 +12832,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -12486,6 +12851,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13057,24 +13423,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/twilio": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.12.0.tgz", - "integrity": "sha512-ZAKnDKcWvJSb90xQS13QB5KQOeMJPzsRPHxZqju8i5ALg3D4hNwAF9bpytVTxTJV99BL4Rn6Un+ZtXjGeMpjvQ==", - "license": "MIT", - "dependencies": { - "axios": "^1.12.0", - "dayjs": "^1.11.9", - "https-proxy-agent": "^5.0.0", - "jsonwebtoken": "^9.0.2", - "qs": "^6.14.1", - "scmp": "^2.1.0", - "xmlbuilder": "^13.0.2" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -13358,37 +13706,6 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, - "node_modules/update-browserslist-db": { - "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==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -13446,6 +13763,27 @@ "react": "*" } }, + "node_modules/use-intl": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.8.2.tgz", + "integrity": "sha512-3VNXZgDnPFqhIYosQ9W1Hc6K5q+ZelMfawNbexdwL/dY7BTHbceLUBX5Eeex9lgogxTp0pf1SjHuhYNAjr9H3g==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^3.1.0", + "@schummar/icu-type-parser": "1.21.5", + "icu-minify": "^4.8.2", + "intl-messageformat": "^11.1.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", @@ -13965,15 +14303,6 @@ "node": ">=4.0" } }, - "node_modules/xmlbuilder": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", - "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, "node_modules/y-prosemirror": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.3.7.tgz", diff --git a/package.json b/package.json index 633a48c..2583a61 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "motion": "^11.15.0", "next": "^15.1.0", "next-auth": "^5.0.0-beta.25", + "next-intl": "^4.8.2", "nodemailer": "^7.0.7", "openai": "^6.16.0", "papaparse": "^5.4.1", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dac9731..2dd106d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -110,6 +110,12 @@ enum SettingCategory { SECURITY DEFAULTS WHATSAPP + DIGEST + ANALYTICS + AUDIT_CONFIG + INTEGRATIONS + LOCALIZATION + COMMUNICATION } enum NotificationChannel { @@ -210,6 +216,13 @@ model User { notificationPreference NotificationChannel @default(EMAIL) whatsappOptIn Boolean @default(false) + // Digest preferences (F1) + digestFrequency String @default("none") // none, daily, weekly + + // Availability & workload (F2) + availabilityJson Json? @db.JsonB // Array of { start, end } date ranges + preferredWorkload Int? // Preferred number of assignments + // Onboarding (Phase 2B) onboardingCompletedAt DateTime? @@ -269,6 +282,24 @@ model User { // Mentor messages mentorMessages MentorMessage[] @relation("MentorMessageSender") + // Digest logs (F1) + digestLogs DigestLog[] + + // Mentor notes & milestones (F8) + mentorNotesMade MentorNote[] @relation("MentorNoteAuthor") + milestoneCompletions MentorMilestoneCompletion[] @relation("MilestoneCompletedByUser") + + // Messages (F9) + sentMessages Message[] @relation("MessageSender") + messageRecipients MessageRecipient[] + + // Webhooks (F12) + createdWebhooks Webhook[] @relation("WebhookCreatedBy") + + // Discussion comments (F13) + discussionComments DiscussionComment[] + closedDiscussions EvaluationDiscussion[] @relation("DiscussionClosedBy") + // NextAuth relations accounts Account[] sessions Session[] @@ -338,6 +369,7 @@ model Program { partners Partner[] specialAwards SpecialAward[] taggingJobs TaggingJob[] + mentorMilestones MentorMilestone[] @@unique([name, year]) @@index([status]) @@ -475,6 +507,11 @@ model Project { logoKey String? // Storage key (e.g., "logos/project456/1234567890.png") logoProvider String? // Storage provider used: 's3' or 'local' + // Draft saving (F11) + isDraft Boolean @default(false) + draftDataJson Json? @db.JsonB + draftExpiresAt DateTime? + // Flexible fields tags String[] @default([]) // "Ocean Conservation", "Tech", etc. metadataJson Json? @db.JsonB // Custom fields from Typeform, etc. @@ -498,6 +535,7 @@ model Project { statusHistory ProjectStatusHistory[] mentorMessages MentorMessage[] evaluationSummaries EvaluationSummary[] + discussions EvaluationDiscussion[] @@index([roundId]) @@index([status]) @@ -526,6 +564,10 @@ model ProjectFile { isLate Boolean @default(false) // Uploaded after round deadline + // File versioning (F7) + version Int @default(1) + replacedById String? // Points to newer version of this file + createdAt DateTime @default(now()) // Relations @@ -675,6 +717,10 @@ model AuditLog { // Details detailsJson Json? @db.JsonB // Before/after values, additional context + // Audit enhancements (F14) + sessionId String? // Groups actions in same user session + previousDataJson Json? @db.JsonB // Snapshot of data before change + // Request info ipAddress String? userAgent String? @@ -951,6 +997,12 @@ model LiveVotingSession { votingEndsAt DateTime? projectOrderJson Json? @db.JsonB // Array of project IDs in presentation order + // Live voting UX enhancements (F5/F6) + presentationSettingsJson Json? @db.JsonB // theme, auto-advance, branding + allowAudienceVotes Boolean @default(false) + audienceVoteWeight Float @default(0) // 0-1 weight relative to jury + tieBreakerMethod String @default("admin_decides") // admin_decides, highest_individual, revote + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -962,12 +1014,13 @@ model LiveVotingSession { } model LiveVote { - id String @id @default(cuid()) - sessionId String - projectId String - userId String - score Int // 1-10 - votedAt DateTime @default(now()) + id String @id @default(cuid()) + sessionId String + projectId String + userId String + score Int // 1-10 + isAudienceVote Boolean @default(false) // F6: audience voting + votedAt DateTime @default(now()) // Relations session LiveVotingSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@ -977,6 +1030,7 @@ model LiveVote { @@index([sessionId]) @@index([projectId]) @@index([userId]) + @@index([isAudienceVote]) } // ============================================================================= @@ -1020,9 +1074,15 @@ model MentorAssignment { expertiseMatchScore Float? aiReasoning String? @db.Text + // Mentor dashboard enhancements (F8) + lastViewedAt DateTime? + completionStatus String @default("in_progress") // in_progress, completed, paused + // Relations - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) - mentor User @relation("MentorAssignments", fields: [mentorId], references: [id]) + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + mentor User @relation("MentorAssignments", fields: [mentorId], references: [id]) + notes MentorNote[] + milestoneCompletions MentorMilestoneCompletion[] @@index([mentorId]) @@index([method]) @@ -1427,3 +1487,242 @@ model MentorMessage { @@index([projectId, createdAt]) } + +// ============================================================================= +// DIGEST LOGS (F1: Email Digest) +// ============================================================================= + +model DigestLog { + id String @id @default(cuid()) + userId String + digestType String // "daily", "weekly" + contentJson Json @db.JsonB + sentAt DateTime @default(now()) + + // Relations + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) + @@index([sentAt]) +} + +// ============================================================================= +// ROUND TEMPLATES (F3) +// ============================================================================= + +model RoundTemplate { + id String @id @default(cuid()) + name String + description String? @db.Text + programId String? // null = global template + roundType RoundType @default(EVALUATION) + criteriaJson Json @db.JsonB + settingsJson Json? @db.JsonB + assignmentConfig Json? @db.JsonB + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([programId]) +} + +// ============================================================================= +// MENTOR NOTES & MILESTONES (F8) +// ============================================================================= + +model MentorNote { + id String @id @default(cuid()) + mentorAssignmentId String + authorId String + content String @db.Text + isVisibleToAdmin Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + mentorAssignment MentorAssignment @relation(fields: [mentorAssignmentId], references: [id], onDelete: Cascade) + author User @relation("MentorNoteAuthor", fields: [authorId], references: [id]) + + @@index([mentorAssignmentId]) +} + +model MentorMilestone { + id String @id @default(cuid()) + programId String + name String + description String? @db.Text + isRequired Boolean @default(false) + deadlineOffsetDays Int? + sortOrder Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + program Program @relation(fields: [programId], references: [id], onDelete: Cascade) + completions MentorMilestoneCompletion[] + + @@index([programId]) + @@index([sortOrder]) +} + +model MentorMilestoneCompletion { + id String @id @default(cuid()) + milestoneId String + mentorAssignmentId String + completedAt DateTime @default(now()) + completedById String + + // Relations + milestone MentorMilestone @relation(fields: [milestoneId], references: [id], onDelete: Cascade) + mentorAssignment MentorAssignment @relation(fields: [mentorAssignmentId], references: [id], onDelete: Cascade) + completedBy User @relation("MilestoneCompletedByUser", fields: [completedById], references: [id]) + + @@unique([milestoneId, mentorAssignmentId]) + @@index([mentorAssignmentId]) +} + +// ============================================================================= +// COMMUNICATION HUB (F9) +// ============================================================================= + +model Message { + id String @id @default(cuid()) + senderId String + recipientType String // USER, ROLE, ROUND_JURY, PROGRAM_TEAM, ALL + recipientFilter Json? @db.JsonB + roundId String? + templateId String? + subject String + body String @db.Text + deliveryChannels String[] + scheduledAt DateTime? + sentAt DateTime? + metadata Json? @db.JsonB + createdAt DateTime @default(now()) + + // Relations + sender User @relation("MessageSender", fields: [senderId], references: [id]) + template MessageTemplate? @relation(fields: [templateId], references: [id]) + recipients MessageRecipient[] + + @@index([senderId]) + @@index([sentAt]) + @@index([scheduledAt]) +} + +model MessageTemplate { + id String @id @default(cuid()) + name String + category String + subject String + body String @db.Text + variables Json? @db.JsonB + isActive Boolean @default(true) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + messages Message[] + + @@index([category]) + @@index([isActive]) +} + +model MessageRecipient { + id String @id @default(cuid()) + messageId String + userId String + channel String // EMAIL, IN_APP, WHATSAPP + isRead Boolean @default(false) + readAt DateTime? + deliveredAt DateTime? + + // Relations + message Message @relation(fields: [messageId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([messageId]) + @@index([userId, isRead]) +} + +// ============================================================================= +// WEBHOOKS (F12) +// ============================================================================= + +model Webhook { + id String @id @default(cuid()) + name String + url String + secret String // HMAC signing key + events String[] + headers Json? @db.JsonB + isActive Boolean @default(true) + maxRetries Int @default(3) + createdById String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + createdBy User @relation("WebhookCreatedBy", fields: [createdById], references: [id]) + deliveries WebhookDelivery[] + + @@index([isActive]) +} + +model WebhookDelivery { + id String @id @default(cuid()) + webhookId String + event String + payload Json @db.JsonB + responseStatus Int? + responseBody String? @db.Text + attempts Int @default(0) + lastAttemptAt DateTime? + status String @default("PENDING") // PENDING, DELIVERED, FAILED + createdAt DateTime @default(now()) + + // Relations + webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade) + + @@index([webhookId]) + @@index([status]) + @@index([createdAt]) +} + +// ============================================================================= +// PEER REVIEW / EVALUATION DISCUSSIONS (F13) +// ============================================================================= + +model EvaluationDiscussion { + id String @id @default(cuid()) + projectId String + roundId String + status String @default("open") // open, closed + createdAt DateTime @default(now()) + closedAt DateTime? + closedById String? + + // Relations + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + closedBy User? @relation("DiscussionClosedBy", fields: [closedById], references: [id]) + comments DiscussionComment[] + + @@unique([projectId, roundId]) + @@index([roundId]) + @@index([status]) +} + +model DiscussionComment { + id String @id @default(cuid()) + discussionId String + userId String + content String @db.Text + createdAt DateTime @default(now()) + + // Relations + discussion EvaluationDiscussion @relation(fields: [discussionId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id]) + + @@index([discussionId, createdAt]) +} diff --git a/src/app/(admin)/admin/audit/page.tsx b/src/app/(admin)/admin/audit/page.tsx index 747791b..4c1b5b7 100644 --- a/src/app/(admin)/admin/audit/page.tsx +++ b/src/app/(admin)/admin/audit/page.tsx @@ -48,7 +48,11 @@ import { ChevronRight, RefreshCw, RotateCcw, + AlertTriangle, + Layers, + ArrowLeftRight, } from 'lucide-react' +import { Switch } from '@/components/ui/switch' import { formatDate } from '@/lib/utils' import { cn } from '@/lib/utils' @@ -127,6 +131,7 @@ export default function AuditLogPage() { const [page, setPage] = useState(1) const [expandedRows, setExpandedRows] = useState>(new Set()) const [showFilters, setShowFilters] = useState(true) + const [groupBySession, setGroupBySession] = useState(false) // Build query input const queryInput = useMemo( @@ -153,6 +158,11 @@ export default function AuditLogPage() { perPage: 100, }) + // Fetch anomalies + const { data: anomalyData } = trpc.audit.getAnomalies.useQuery({}, { + retry: false, + }) + // Export mutation const exportLogs = trpc.export.auditLogs.useQuery( { @@ -384,6 +394,54 @@ export default function AuditLogPage() { + {/* Anomaly Alerts */} + {anomalyData && anomalyData.anomalies.length > 0 && ( + + + + + Anomaly Alerts ({anomalyData.anomalies.length}) + + + +
+ {anomalyData.anomalies.slice(0, 5).map((anomaly, i) => ( +
+ +
+

{anomaly.isRapid ? 'Rapid Activity' : 'Bulk Operations'}

+

{String(anomaly.actionCount)} actions in {String(anomaly.timeWindowMinutes)} min ({anomaly.actionsPerMinute.toFixed(1)}/min)

+ {anomaly.userId && ( +

+ User: {String(anomaly.user?.name || anomaly.userId)} +

+ )} +
+ + {String(anomaly.actionCount)} actions + +
+ ))} +
+
+
+ )} + + {/* Session Grouping Toggle */} +
+
+ + +
+
+ {/* Results */} {isLoading ? ( @@ -485,6 +543,28 @@ export default function AuditLogPage() { )} + {!!(log as Record).previousDataJson && ( +
+

+ + Changes (Before / After) +

+ ).previousDataJson} + after={log.detailsJson} + /> +
+ )} + {groupBySession && !!(log as Record).sessionId && ( +
+

+ Session ID +

+

+ {String((log as Record).sessionId)} +

+
+ )} @@ -625,6 +705,42 @@ export default function AuditLogPage() { ) } +function DiffViewer({ before, after }: { before: unknown; after: unknown }) { + const beforeObj = typeof before === 'object' && before !== null ? before as Record : {} + const afterObj = typeof after === 'object' && after !== null ? after as Record : {} + const allKeys = Array.from(new Set([...Object.keys(beforeObj), ...Object.keys(afterObj)])) + const changedKeys = allKeys.filter( + (key) => JSON.stringify(beforeObj[key]) !== JSON.stringify(afterObj[key]) + ) + + if (changedKeys.length === 0) { + return ( +

No differences detected

+ ) + } + + return ( +
+
+ Field + Before + After +
+ {changedKeys.map((key) => ( +
+ {key} + + {beforeObj[key] !== undefined ? JSON.stringify(beforeObj[key]) : '--'} + + + {afterObj[key] !== undefined ? JSON.stringify(afterObj[key]) : '--'} + +
+ ))} +
+ ) +} + function AuditLogSkeleton() { return ( diff --git a/src/app/(admin)/admin/messages/page.tsx b/src/app/(admin)/admin/messages/page.tsx new file mode 100644 index 0000000..a44a67a --- /dev/null +++ b/src/app/(admin)/admin/messages/page.tsx @@ -0,0 +1,551 @@ +'use client' + +import { useState } from 'react' +import Link from 'next/link' +import { trpc } from '@/lib/trpc/client' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Textarea } from '@/components/ui/textarea' +import { Badge } from '@/components/ui/badge' +import { Skeleton } from '@/components/ui/skeleton' +import { Checkbox } from '@/components/ui/checkbox' +import { Switch } from '@/components/ui/switch' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from '@/components/ui/tabs' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { + Send, + Mail, + Bell, + Clock, + Loader2, + LayoutTemplate, + AlertCircle, + Inbox, + CheckCircle2, +} from 'lucide-react' +import { toast } from 'sonner' +import { formatDate } from '@/lib/utils' + +type RecipientType = 'ALL' | 'ROLE' | 'ROUND_JURY' | 'PROGRAM_TEAM' | 'USER' + +const RECIPIENT_TYPE_OPTIONS: { value: RecipientType; label: string }[] = [ + { value: 'ALL', label: 'All Users' }, + { value: 'ROLE', label: 'By Role' }, + { value: 'ROUND_JURY', label: 'Round Jury' }, + { value: 'PROGRAM_TEAM', label: 'Program Team' }, + { value: 'USER', label: 'Specific User' }, +] + +const ROLES = ['JURY_MEMBER', 'MENTOR', 'OBSERVER', 'APPLICANT', 'PROGRAM_ADMIN'] + +export default function MessagesPage() { + const [recipientType, setRecipientType] = useState('ALL') + const [selectedRole, setSelectedRole] = useState('') + const [roundId, setRoundId] = useState('') + const [selectedProgramId, setSelectedProgramId] = useState('') + const [selectedUserId, setSelectedUserId] = useState('') + const [subject, setSubject] = useState('') + const [body, setBody] = useState('') + const [selectedTemplateId, setSelectedTemplateId] = useState('') + const [deliveryChannels, setDeliveryChannels] = useState(['EMAIL', 'IN_APP']) + const [isScheduled, setIsScheduled] = useState(false) + const [scheduledAt, setScheduledAt] = useState('') + + const utils = trpc.useUtils() + + // Fetch supporting data + const { data: rounds } = trpc.round.listAll.useQuery() + const { data: programs } = trpc.program.list.useQuery() + const { data: templates } = trpc.message.listTemplates.useQuery() + const { data: users } = trpc.user.list.useQuery( + { page: 1, perPage: 100 }, + { enabled: recipientType === 'USER' } + ) + + // Fetch sent messages for history + const { data: sentMessages, isLoading: loadingSent } = trpc.message.inbox.useQuery( + { page: 1, pageSize: 50 } + ) + + const sendMutation = trpc.message.send.useMutation({ + onSuccess: (data) => { + const count = (data as Record)?.recipientCount || '' + toast.success(`Message sent successfully${count ? ` to ${count} recipients` : ''}`) + resetForm() + utils.message.inbox.invalidate() + }, + onError: (e) => toast.error(e.message), + }) + + const resetForm = () => { + setSubject('') + setBody('') + setSelectedTemplateId('') + setSelectedRole('') + setRoundId('') + setSelectedProgramId('') + setSelectedUserId('') + setIsScheduled(false) + setScheduledAt('') + } + + const handleTemplateSelect = (templateId: string) => { + setSelectedTemplateId(templateId) + if (templateId && templateId !== '__none__' && templates) { + const template = (templates as Array>).find( + (t) => String(t.id) === templateId + ) + if (template) { + setSubject(String(template.subject || '')) + setBody(String(template.body || '')) + } + } + } + + const toggleChannel = (channel: string) => { + setDeliveryChannels((prev) => + prev.includes(channel) + ? prev.filter((c) => c !== channel) + : [...prev, channel] + ) + } + + const buildRecipientFilter = (): unknown => { + switch (recipientType) { + case 'ROLE': + return selectedRole ? { role: selectedRole } : undefined + case 'USER': + return selectedUserId ? { userId: selectedUserId } : undefined + case 'PROGRAM_TEAM': + return selectedProgramId ? { programId: selectedProgramId } : undefined + default: + return undefined + } + } + + const handleSend = () => { + if (!subject.trim()) { + toast.error('Subject is required') + return + } + if (!body.trim()) { + toast.error('Message body is required') + return + } + if (deliveryChannels.length === 0) { + toast.error('Select at least one delivery channel') + return + } + if (recipientType === 'ROLE' && !selectedRole) { + toast.error('Please select a role') + return + } + if (recipientType === 'ROUND_JURY' && !roundId) { + toast.error('Please select a round') + return + } + if (recipientType === 'PROGRAM_TEAM' && !selectedProgramId) { + toast.error('Please select a program') + return + } + if (recipientType === 'USER' && !selectedUserId) { + toast.error('Please select a user') + return + } + + sendMutation.mutate({ + recipientType, + recipientFilter: buildRecipientFilter(), + roundId: roundId || undefined, + subject: subject.trim(), + body: body.trim(), + deliveryChannels, + scheduledAt: isScheduled && scheduledAt ? new Date(scheduledAt).toISOString() : undefined, + templateId: selectedTemplateId && selectedTemplateId !== '__none__' ? selectedTemplateId : undefined, + }) + } + + return ( +
+ {/* Header */} +
+
+

Communication Hub

+

+ Send messages and notifications to platform users +

+
+ +
+ + + + + + Compose + + + + Sent History + + + + + {/* Compose Form */} + + + Compose Message + + Send a message via email, in-app notifications, or both + + + + {/* Recipient type */} +
+ + +
+ + {/* Conditional sub-filters */} + {recipientType === 'ROLE' && ( +
+ + +
+ )} + + {recipientType === 'ROUND_JURY' && ( +
+ + +
+ )} + + {recipientType === 'PROGRAM_TEAM' && ( +
+ + +
+ )} + + {recipientType === 'USER' && ( +
+ + +
+ )} + + {recipientType === 'ALL' && ( +
+ +

+ This message will be sent to all platform users. +

+
+ )} + + {/* Template selector */} + {templates && (templates as unknown[]).length > 0 && ( +
+ + +
+ )} + + {/* Subject */} +
+ + setSubject(e.target.value)} + /> +
+ + {/* Body */} +
+
+ + + Variables: {'{{projectName}}'}, {'{{userName}}'}, {'{{deadline}}'}, {'{{roundName}}'}, {'{{programName}}'} + +
+