diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 353b1f1..031bc7d 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -35,6 +35,13 @@ Corriger ou améliorer le projet N3WT-SCHOOL de manière minimaliste et fonction
## Exigences qualité
+Pour le front-end, les exigences de qualité sont les suivantes :
+
+- **Linting** : Utiliser ESLint pour le code JavaScript/TypeScript
+- **Formatage** : Utiliser Prettier pour le formatage du code
+- **Tests** : Utiliser Jest pour les tests unitaires et d'intégration
+- Référence : [frontend guideline](./instructions/frontend.instruction.md)
+
### Tests
- Tests unitaires obligatoires pour chaque nouvelle fonctionnalité
diff --git a/.github/instructions/frontend.instruction.md b/.github/instructions/frontend.instruction.md
new file mode 100644
index 0000000..e69de29
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index c9e09eb..0ce1ae6 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -1,13 +1,13 @@
{
- "version": "2.0.0",
- "tasks": [
- {
- "type": "npm",
- "script": "dev",
- "path": "Front-End",
- "problemMatcher": [],
- "label": "npm: dev - Front-End",
- "detail": "next dev"
- }
- ]
-}
\ No newline at end of file
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Start Frontend Dev Server",
+ "type": "shell",
+ "command": "npm run dev",
+ "group": "build",
+ "isBackground": true,
+ "problemMatcher": []
+ }
+ ]
+}
diff --git a/Back-End/Auth/templates/GestionLogin/login.html b/Back-End/Auth/templates/GestionLogin/login.html
deleted file mode 100644
index 46945c6..0000000
--- a/Back-End/Auth/templates/GestionLogin/login.html
+++ /dev/null
@@ -1,61 +0,0 @@
-{% load static %}
-
-
-
-
-
-
-
- Monteschool
-
-
-
-
-
-
-
-
Authentification
-
-
-
-
diff --git a/Back-End/Auth/templates/GestionLogin/new-password.html b/Back-End/Auth/templates/GestionLogin/new-password.html
deleted file mode 100644
index 0c84b92..0000000
--- a/Back-End/Auth/templates/GestionLogin/new-password.html
+++ /dev/null
@@ -1,64 +0,0 @@
-{% load static %}
-
-
-
-
-
-
- Monteschool
-
-
-
-
-
-
-
Nouveau Mot de Passe
-
-
-
-
\ No newline at end of file
diff --git a/Back-End/Auth/templates/GestionLogin/reset-password.html b/Back-End/Auth/templates/GestionLogin/reset-password.html
deleted file mode 100644
index 299e0de..0000000
--- a/Back-End/Auth/templates/GestionLogin/reset-password.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{% load static %}
-
-
-
-
-
-
-
- Monteschool
-
-
-
-
-
-
-
Réinitialiser Mot de Passe
-
-
-
-
\ No newline at end of file
diff --git a/Back-End/Auth/templates/GestionLogin/subscribe.html b/Back-End/Auth/templates/GestionLogin/subscribe.html
deleted file mode 100644
index a216d13..0000000
--- a/Back-End/Auth/templates/GestionLogin/subscribe.html
+++ /dev/null
@@ -1,64 +0,0 @@
-{% load static %}
-
-
-
-
-
-
- Monteschool
-
-
-
-
-
-
-
S'inscrire
-
-
-
-
\ No newline at end of file
diff --git a/Back-End/GestionEmail/__init__.py b/Back-End/GestionEmail/__init__.py
new file mode 100644
index 0000000..0eec8a6
--- /dev/null
+++ b/Back-End/GestionEmail/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'GestionEmail.apps.GestionEmailConfig'
diff --git a/Back-End/GestionEmail/apps.py b/Back-End/GestionEmail/apps.py
new file mode 100644
index 0000000..8ccc650
--- /dev/null
+++ b/Back-End/GestionEmail/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+class GestionEmailConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'GestionEmail'
diff --git a/Back-End/GestionEmail/urls.py b/Back-End/GestionEmail/urls.py
new file mode 100644
index 0000000..e9ffee7
--- /dev/null
+++ b/Back-End/GestionEmail/urls.py
@@ -0,0 +1,9 @@
+from django.urls import path
+from .views import (
+ SendEmailView, search_recipients
+)
+
+urlpatterns = [
+ path('send-email/', SendEmailView.as_view(), name='send_email'),
+ path('search-recipients/', search_recipients, name='search_recipients'),
+]
\ No newline at end of file
diff --git a/Back-End/GestionEmail/views.py b/Back-End/GestionEmail/views.py
new file mode 100644
index 0000000..8ccbabb
--- /dev/null
+++ b/Back-End/GestionEmail/views.py
@@ -0,0 +1,119 @@
+from django.http.response import JsonResponse
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from django.db.models import Q
+from Auth.models import Profile, ProfileRole
+
+import N3wtSchool.mailManager as mailer
+from N3wtSchool import bdd
+from drf_yasg.utils import swagger_auto_schema
+from drf_yasg import openapi
+from rest_framework.exceptions import NotFound
+import uuid
+import logging
+
+# Ajouter un logger pour debug
+logger = logging.getLogger(__name__)
+
+class SendEmailView(APIView):
+ """
+ API pour envoyer des emails aux parents et professeurs.
+ """
+ def post(self, request):
+ # Ajouter du debug
+ logger.info(f"Request data received: {request.data}")
+ logger.info(f"Request content type: {request.content_type}")
+
+ data = request.data
+ recipients = data.get('recipients', [])
+ cc = data.get('cc', [])
+ bcc = data.get('bcc', [])
+ subject = data.get('subject', 'Notification')
+ message = data.get('message', '')
+ establishment_id = data.get('establishment_id', '')
+
+ # Debug des données reçues
+ logger.info(f"Recipients: {recipients} (type: {type(recipients)})")
+ logger.info(f"CC: {cc} (type: {type(cc)})")
+ logger.info(f"BCC: {bcc} (type: {type(bcc)})")
+ logger.info(f"Subject: {subject}")
+ logger.info(f"Message length: {len(message) if message else 0}")
+ logger.info(f"Establishment ID: {establishment_id}")
+
+ if not recipients or not message:
+ logger.error("Recipients or message missing")
+ logger.error(f"Recipients empty: {not recipients}, Message empty: {not message}")
+ logger.error(f"Recipients value: '{recipients}', Message value: '{message}'")
+ return Response({'error': 'Les destinataires et le message sont requis.'}, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ # Récupérer la connexion SMTP
+ logger.info("Tentative de récupération de la connexion SMTP...")
+ connection = mailer.getConnection(establishment_id)
+ logger.info(f"Connexion SMTP récupérée: {connection}")
+
+ # Envoyer l'email
+ logger.info("Tentative d'envoi de l'email...")
+ result = mailer.sendMail(
+ subject=subject,
+ message=message,
+ recipients=recipients,
+ cc=cc,
+ bcc=bcc,
+ attachments=[],
+ connection=connection
+ )
+ logger.info(f"Email envoyé avec succès: {result}")
+ return result
+ except NotFound as e:
+ logger.error(f"NotFound error: {str(e)}")
+ return Response({'error': str(e)}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ logger.error(f"Exception during email sending: {str(e)}")
+ logger.error(f"Exception type: {type(e)}")
+ import traceback
+ logger.error(f"Traceback: {traceback.format_exc()}")
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+def search_recipients(request):
+ """
+ API pour rechercher des destinataires en fonction d'un terme de recherche et d'un établissement.
+ """
+ query = request.GET.get('q', '').strip() # Récupérer le terme de recherche depuis les paramètres GET
+ establishment_id = request.GET.get('establishment_id', None) # Récupérer l'ID de l'établissement
+
+ if not query:
+ return JsonResponse([], safe=False) # Retourner une liste vide si aucun terme n'est fourni
+
+ if not establishment_id:
+ return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
+
+ # Rechercher dans les champs pertinents (nom, prénom, email) et filtrer par establishment_id
+ profiles = Profile.objects.filter(
+ Q(first_name__icontains=query) |
+ Q(last_name__icontains=query) |
+ Q(email__icontains=query),
+ roles__establishment_id=establishment_id, # Utiliser 'roles' au lieu de 'profilerole'
+ roles__is_active=True # Filtrer uniquement les ProfileRole actifs
+ ).distinct()
+
+ # Construire la réponse avec les rôles associés
+ results = []
+ for profile in profiles:
+ profile_roles = ProfileRole.objects.filter(
+ profile=profile,
+ establishment_id=establishment_id,
+ is_active=True # Inclure uniquement les ProfileRole actifs
+ ).values(
+ 'id', 'role_type', 'establishment__name', 'is_active'
+ )
+ results.append({
+ 'id': profile.id,
+ 'first_name': profile.first_name,
+ 'last_name': profile.last_name,
+ 'email': profile.email,
+ 'roles': list(profile_roles) # Inclure tous les rôles actifs associés pour cet établissement
+ })
+
+ return JsonResponse(results, safe=False)
diff --git a/Back-End/GestionMessagerie/consumers.py b/Back-End/GestionMessagerie/consumers.py
new file mode 100644
index 0000000..ba32b84
--- /dev/null
+++ b/Back-End/GestionMessagerie/consumers.py
@@ -0,0 +1,627 @@
+import json
+import logging
+from uuid import UUID
+from decimal import Decimal
+from datetime import datetime
+from channels.generic.websocket import AsyncWebsocketConsumer
+from channels.db import database_sync_to_async
+from django.utils import timezone
+from .models import Conversation, ConversationParticipant, Message, UserPresence, MessageRead
+from .serializers import MessageSerializer, ConversationSerializer
+from Auth.models import Profile
+
+logger = logging.getLogger(__name__)
+
+def serialize_for_websocket(data):
+ """
+ Convertit récursivement les objets non-sérialisables en JSON en types sérialisables
+ """
+ if isinstance(data, dict):
+ return {key: serialize_for_websocket(value) for key, value in data.items()}
+ elif isinstance(data, list):
+ return [serialize_for_websocket(item) for item in data]
+ elif isinstance(data, UUID):
+ return str(data)
+ elif isinstance(data, Decimal):
+ return float(data)
+ elif isinstance(data, datetime):
+ return data.isoformat()
+ else:
+ return data
+
+class ChatConsumer(AsyncWebsocketConsumer):
+ """Consumer WebSocket pour la messagerie instantanée"""
+
+ async def connect(self):
+ self.user_id = self.scope['url_route']['kwargs']['user_id']
+ self.user_group_name = f'user_{self.user_id}'
+
+ # Vérifier si l'utilisateur est authentifié
+ user = self.scope.get('user')
+ if not user or user.is_anonymous:
+ logger.warning(f"Tentative de connexion WebSocket non authentifiée pour user_id: {self.user_id}")
+ await self.close()
+ return
+
+ # Vérifier que l'utilisateur connecté correspond à l'user_id de l'URL
+ if str(user.id) != str(self.user_id):
+ logger.warning(f"Tentative d'accès WebSocket avec user_id incorrect: {self.user_id} vs {user.id}")
+ await self.close()
+ return
+
+ self.user = user
+
+ # Rejoindre le groupe utilisateur
+ await self.channel_layer.group_add(
+ self.user_group_name,
+ self.channel_name
+ )
+
+ # Rejoindre les groupes des conversations de l'utilisateur
+ conversations = await self.get_user_conversations(self.user_id)
+ for conversation in conversations:
+ await self.channel_layer.group_add(
+ f'conversation_{conversation.id}',
+ self.channel_name
+ )
+
+ # Mettre à jour le statut de présence
+ presence = await self.update_user_presence(self.user_id, 'online')
+
+ # Notifier les autres utilisateurs du changement de statut
+ if presence:
+ await self.broadcast_presence_update(self.user_id, 'online')
+
+ # Envoyer les statuts de présence existants des autres utilisateurs connectés
+ await self.send_existing_user_presences()
+
+ await self.accept()
+
+ logger.info(f"User {self.user_id} connected to chat")
+
+ async def send_existing_user_presences(self):
+ """Envoyer les statuts de présence existants des autres utilisateurs connectés"""
+ try:
+ # Obtenir toutes les conversations de cet utilisateur
+ conversations = await self.get_user_conversations(self.user_id)
+
+ # Créer un set pour éviter les doublons d'utilisateurs
+ other_users = set()
+
+ # Pour chaque conversation, récupérer les participants
+ for conversation in conversations:
+ participants = await self.get_conversation_participants(conversation.id)
+ for participant in participants:
+ if participant.id != self.user_id:
+ other_users.add(participant.id)
+
+ # Envoyer le statut de présence pour chaque utilisateur
+ for user_id in other_users:
+ presence = await self.get_user_presence(user_id)
+ if presence:
+ await self.send(text_data=json.dumps({
+ 'type': 'presence_update',
+ 'user_id': str(user_id),
+ 'status': presence.status
+ }))
+
+ except Exception as e:
+ logger.error(f"Error sending existing user presences: {str(e)}")
+
+ async def disconnect(self, close_code):
+ # Quitter tous les groupes
+ await self.channel_layer.group_discard(
+ self.user_group_name,
+ self.channel_name
+ )
+
+ if hasattr(self, 'user'):
+ conversations = await self.get_user_conversations(self.user_id)
+ for conversation in conversations:
+ await self.channel_layer.group_discard(
+ f'conversation_{conversation.id}',
+ self.channel_name
+ )
+
+ # Mettre à jour le statut de présence
+ presence = await self.update_user_presence(self.user_id, 'offline')
+
+ # Notifier les autres utilisateurs du changement de statut
+ if presence:
+ await self.broadcast_presence_update(self.user_id, 'offline')
+
+ logger.info(f"User {self.user_id} disconnected from chat")
+
+ async def receive(self, text_data):
+ """Recevoir et traiter les messages du client"""
+ try:
+ text_data_json = json.loads(text_data)
+ message_type = text_data_json.get('type')
+
+ if message_type == 'send_message':
+ await self.handle_send_message(text_data_json)
+ elif message_type == 'typing_start':
+ await self.handle_typing_start(text_data_json)
+ elif message_type == 'typing_stop':
+ await self.handle_typing_stop(text_data_json)
+ elif message_type == 'mark_as_read':
+ await self.handle_mark_as_read(text_data_json)
+ elif message_type == 'join_conversation':
+ await self.handle_join_conversation(text_data_json)
+ elif message_type == 'leave_conversation':
+ await self.handle_leave_conversation(text_data_json)
+ elif message_type == 'presence_update':
+ await self.handle_presence_update(text_data_json)
+ else:
+ logger.warning(f"Unknown message type: {message_type}")
+ await self.send(text_data=json.dumps({
+ 'type': 'error',
+ 'message': f'Unknown message type: {message_type}'
+ }))
+
+ except json.JSONDecodeError:
+ await self.send(text_data=json.dumps({
+ 'type': 'error',
+ 'message': 'Invalid JSON format'
+ }))
+ except Exception as e:
+ logger.error(f"Error in receive: {str(e)}")
+ await self.send(text_data=json.dumps({
+ 'type': 'error',
+ 'message': 'Internal server error'
+ }))
+
+ async def handle_send_message(self, data):
+ """Gérer l'envoi d'un nouveau message"""
+ conversation_id = data.get('conversation_id')
+ content = data.get('content', '').strip()
+ message_type = data.get('message_type', 'text')
+ attachment = data.get('attachment')
+
+ # Vérifier qu'on a soit du contenu, soit un fichier
+ if not conversation_id or (not content and not attachment):
+ await self.send(text_data=json.dumps({
+ 'type': 'error',
+ 'message': 'Conversation ID and content or attachment are required'
+ }))
+ return
+
+ # Vérifier que l'utilisateur peut envoyer dans cette conversation
+ can_send = await self.can_user_send_message(self.user_id, conversation_id)
+ if not can_send:
+ await self.send(text_data=json.dumps({
+ 'type': 'error',
+ 'message': 'You cannot send messages to this conversation'
+ }))
+ return
+
+ # Créer le message avec ou sans fichier
+ message = await self.create_message(conversation_id, self.user_id, content, message_type, attachment)
+ if not message:
+ await self.send(text_data=json.dumps({
+ 'type': 'error',
+ 'message': 'Failed to create message'
+ }))
+ return
+
+ # Sérialiser le message
+ message_data = await self.serialize_message(message)
+
+ # Auto-marquer comme lu pour les utilisateurs connectés (présents dans la conversation)
+ await self.auto_mark_read_for_online_users(message, conversation_id)
+
+ # Envoyer le message à tous les participants de la conversation
+ await self.channel_layer.group_send(
+ f'conversation_{conversation_id}',
+ {
+ 'type': 'chat_message',
+ 'message': message_data
+ }
+ )
+
+ async def handle_typing_start(self, data):
+ """Gérer le début de frappe"""
+ conversation_id = data.get('conversation_id')
+ if conversation_id:
+ await self.update_typing_status(self.user_id, conversation_id, True)
+
+ # Récupérer le nom de l'utilisateur
+ user_name = await self.get_user_display_name(self.user_id)
+
+ await self.channel_layer.group_send(
+ f'conversation_{conversation_id}',
+ {
+ 'type': 'typing_status',
+ 'user_id': str(self.user_id),
+ 'user_name': user_name,
+ 'is_typing': True,
+ 'conversation_id': str(conversation_id)
+ }
+ )
+
+ async def handle_typing_stop(self, data):
+ """Gérer l'arrêt de frappe"""
+ conversation_id = data.get('conversation_id')
+ if conversation_id:
+ await self.update_typing_status(self.user_id, conversation_id, False)
+
+ # Récupérer le nom de l'utilisateur
+ user_name = await self.get_user_display_name(self.user_id)
+
+ await self.channel_layer.group_send(
+ f'conversation_{conversation_id}',
+ {
+ 'type': 'typing_status',
+ 'user_id': str(self.user_id),
+ 'user_name': user_name,
+ 'is_typing': False,
+ 'conversation_id': str(conversation_id)
+ }
+ )
+
+ async def handle_mark_as_read(self, data):
+ """Marquer les messages comme lus"""
+ conversation_id = data.get('conversation_id')
+ if conversation_id:
+ await self.mark_conversation_as_read(self.user_id, conversation_id)
+ await self.channel_layer.group_send(
+ f'conversation_{conversation_id}',
+ {
+ 'type': 'messages_read',
+ 'user_id': str(self.user_id),
+ 'conversation_id': str(conversation_id)
+ }
+ )
+
+ async def handle_join_conversation(self, data):
+ """Rejoindre une conversation"""
+ conversation_id = data.get('conversation_id')
+ if conversation_id:
+ await self.channel_layer.group_add(
+ f'conversation_{conversation_id}',
+ self.channel_name
+ )
+
+ async def handle_leave_conversation(self, data):
+ """Quitter une conversation"""
+ conversation_id = data.get('conversation_id')
+ if conversation_id:
+ await self.channel_layer.group_discard(
+ f'conversation_{conversation_id}',
+ self.channel_name
+ )
+
+ async def handle_presence_update(self, data):
+ """Gérer les mises à jour de présence"""
+ status = data.get('status', 'online')
+ if status in ['online', 'offline', 'away']:
+ await self.update_user_presence(self.user_id, status)
+ await self.broadcast_presence_update(self.user_id, status)
+
+ # Méthodes pour recevoir les messages des groupes
+ async def chat_message(self, event):
+ """Envoyer un message de chat au WebSocket"""
+ message_data = serialize_for_websocket(event['message'])
+ await self.send(text_data=json.dumps({
+ 'type': 'new_message',
+ 'message': message_data
+ }))
+
+ async def typing_status(self, event):
+ """Envoyer le statut de frappe"""
+ # Ne pas envoyer à l'expéditeur
+ if str(event['user_id']) != str(self.user_id):
+ await self.send(text_data=json.dumps({
+ 'type': 'typing_status',
+ 'user_id': str(event['user_id']),
+ 'user_name': event.get('user_name', ''),
+ 'is_typing': event['is_typing'],
+ 'conversation_id': str(event['conversation_id'])
+ }))
+
+ async def messages_read(self, event):
+ """Notifier que des messages ont été lus"""
+ if str(event['user_id']) != str(self.user_id):
+ await self.send(text_data=json.dumps({
+ 'type': 'messages_read',
+ 'user_id': str(event['user_id']),
+ 'conversation_id': str(event['conversation_id'])
+ }))
+
+ async def user_presence_update(self, event):
+ """Notifier d'un changement de présence"""
+ await self.send(text_data=json.dumps({
+ 'type': 'presence_update',
+ 'user_id': str(event['user_id']),
+ 'status': event['status']
+ }))
+
+ async def new_conversation_notification(self, event):
+ """Notifier d'une nouvelle conversation"""
+ conversation = serialize_for_websocket(event['conversation'])
+ conversation_id = conversation['id']
+
+ # Rejoindre automatiquement le groupe de la nouvelle conversation
+ await self.channel_layer.group_add(
+ f'conversation_{conversation_id}',
+ self.channel_name
+ )
+
+ # Envoyer la notification au client
+ await self.send(text_data=json.dumps({
+ 'type': 'new_conversation',
+ 'conversation': conversation
+ }))
+
+ # Diffuser les présences des participants de cette nouvelle conversation
+ try:
+ participants = await self.get_conversation_participants(conversation_id)
+ for participant in participants:
+ # Ne pas diffuser sa propre présence à soi-même
+ if participant.id != self.user_id:
+ presence = await self.get_user_presence(participant.id)
+ if presence:
+ await self.send(text_data=json.dumps({
+ 'type': 'presence_update',
+ 'user_id': str(participant.id),
+ 'status': presence.status
+ }))
+ except Exception as e:
+ logger.error(f"Error sending presence updates for new conversation: {str(e)}")
+
+ async def broadcast_presence_update(self, user_id, status):
+ """Diffuser un changement de statut de présence à tous les utilisateurs connectés"""
+ try:
+ # Obtenir tous les utilisateurs qui ont des conversations avec cet utilisateur
+ user_conversations = await self.get_user_conversations(user_id)
+
+ # Créer un set pour éviter les doublons d'utilisateurs
+ notified_users = set()
+
+ # Pour chaque conversation, notifier tous les participants
+ for conversation in user_conversations:
+ participants = await self.get_conversation_participants(conversation.id)
+ for participant in participants:
+ if participant.id != user_id and participant.id not in notified_users:
+ notified_users.add(participant.id)
+ # Envoyer la notification au groupe utilisateur
+ await self.channel_layer.group_send(
+ f'user_{participant.id}',
+ {
+ 'type': 'user_presence_update',
+ 'user_id': user_id,
+ 'status': status
+ }
+ )
+
+ logger.info(f"Broadcasted presence update for user {user_id} ({status}) to {len(notified_users)} users")
+
+ except Exception as e:
+ logger.error(f"Error broadcasting presence update: {str(e)}")
+
+ # Méthodes d'accès aux données (database_sync_to_async)
+ @database_sync_to_async
+ def get_user(self, user_id):
+ try:
+ return Profile.objects.get(id=user_id)
+ except Profile.DoesNotExist:
+ return None
+
+ @database_sync_to_async
+ def get_user_display_name(self, user_id):
+ """Obtenir le nom d'affichage d'un utilisateur"""
+ try:
+ user = Profile.objects.get(id=user_id)
+ if user.first_name and user.last_name:
+ return f"{user.first_name} {user.last_name}"
+ elif user.first_name:
+ return user.first_name
+ elif user.last_name:
+ return user.last_name
+ else:
+ return user.email or f"Utilisateur {user_id}"
+ except Profile.DoesNotExist:
+ return f"Utilisateur {user_id}"
+
+ @database_sync_to_async
+ def get_user_conversations(self, user_id):
+ return list(Conversation.objects.filter(
+ participants__participant_id=user_id,
+ participants__is_active=True,
+ is_active=True
+ ).distinct())
+
+ @database_sync_to_async
+ def get_conversation_participants(self, conversation_id):
+ """Obtenir tous les participants d'une conversation"""
+ return list(Profile.objects.filter(
+ conversation_participants__conversation_id=conversation_id,
+ conversation_participants__is_active=True
+ ))
+
+ @database_sync_to_async
+ def get_conversations_data(self, user_id):
+ try:
+ user = Profile.objects.get(id=user_id)
+ conversations = Conversation.objects.filter(
+ participants__participant=user,
+ participants__is_active=True,
+ is_active=True
+ ).distinct()
+
+ serializer = ConversationSerializer(conversations, many=True, context={'user': user})
+ return serializer.data
+ except Exception as e:
+ logger.error(f"Error getting conversations data: {str(e)}")
+ return []
+
+ @database_sync_to_async
+ def can_user_send_message(self, user_id, conversation_id):
+ return ConversationParticipant.objects.filter(
+ conversation_id=conversation_id,
+ participant_id=user_id,
+ is_active=True
+ ).exists()
+
+ @database_sync_to_async
+ def create_message(self, conversation_id, sender_id, content, message_type, attachment=None):
+ try:
+ conversation = Conversation.objects.get(id=conversation_id)
+ sender = Profile.objects.get(id=sender_id)
+
+ message_data = {
+ 'conversation': conversation,
+ 'sender': sender,
+ 'content': content,
+ 'message_type': message_type
+ }
+
+ # Ajouter les informations du fichier si présent
+ if attachment:
+ message_data.update({
+ 'file_url': attachment.get('fileUrl'),
+ 'file_name': attachment.get('fileName'),
+ 'file_size': attachment.get('fileSize'),
+ 'file_type': attachment.get('fileType'),
+ })
+ # Si c'est un fichier, s'assurer que le type de message est correct
+ if attachment.get('fileType', '').startswith('image/'):
+ message_data['message_type'] = 'image'
+ else:
+ message_data['message_type'] = 'file'
+
+ message = Message.objects.create(**message_data)
+
+ # Mettre à jour l'activité de la conversation
+ conversation.last_activity = message.created_at
+ conversation.save(update_fields=['last_activity'])
+
+ return message
+ except Exception as e:
+ logger.error(f"Error creating message: {str(e)}")
+ return None
+
+ @database_sync_to_async
+ def serialize_message(self, message):
+ serializer = MessageSerializer(message)
+ return serialize_for_websocket(serializer.data)
+
+ @database_sync_to_async
+ def get_user_presence(self, user_id):
+ """Récupérer la présence d'un utilisateur"""
+ try:
+ return UserPresence.objects.get(user_id=user_id)
+ except UserPresence.DoesNotExist:
+ return None
+
+ @database_sync_to_async
+ def update_user_presence(self, user_id, status):
+ try:
+ user = Profile.objects.get(id=user_id)
+ presence, created = UserPresence.objects.get_or_create(user=user)
+ old_status = presence.status
+ presence.status = status
+ presence.save()
+
+ # Si le statut a changé, notifier les autres utilisateurs
+ if old_status != status or created:
+ logger.info(f"User {user_id} presence changed from {old_status} to {status}")
+
+ return presence
+ except Exception as e:
+ logger.error(f"Error updating user presence: {str(e)}")
+ return None
+
+ @database_sync_to_async
+ def update_typing_status(self, user_id, conversation_id, is_typing):
+ try:
+ user = Profile.objects.get(id=user_id)
+ presence, created = UserPresence.objects.get_or_create(user=user)
+ if is_typing:
+ conversation = Conversation.objects.get(id=conversation_id)
+ presence.is_typing_in = conversation
+ else:
+ presence.is_typing_in = None
+ presence.save()
+ except Exception as e:
+ logger.error(f"Error updating typing status: {str(e)}")
+
+ @database_sync_to_async
+ def mark_conversation_as_read(self, user_id, conversation_id):
+ """Marquer tous les messages non lus d'une conversation comme lus"""
+ try:
+ # Mettre à jour le last_read_at du participant
+ participant = ConversationParticipant.objects.get(
+ conversation_id=conversation_id,
+ participant_id=user_id
+ )
+ current_time = timezone.now()
+ participant.last_read_at = current_time
+ participant.save(update_fields=['last_read_at'])
+
+ # Créer des enregistrements MessageRead pour tous les messages non lus
+ # que l'utilisateur n'a pas encore explicitement lus
+ unread_messages = Message.objects.filter(
+ conversation_id=conversation_id,
+ created_at__lte=current_time,
+ is_deleted=False
+ ).exclude(
+ sender_id=user_id # Exclure ses propres messages
+ ).exclude(
+ read_by__participant_id=user_id # Exclure les messages déjà marqués comme lus
+ )
+
+ # Créer les enregistrements MessageRead en batch
+ message_reads = [
+ MessageRead(message=message, participant_id=user_id, read_at=current_time)
+ for message in unread_messages
+ ]
+
+ if message_reads:
+ MessageRead.objects.bulk_create(message_reads, ignore_conflicts=True)
+ logger.info(f"Marked {len(message_reads)} messages as read for user {user_id} in conversation {conversation_id}")
+
+ except Exception as e:
+ logger.error(f"Error marking conversation as read: {str(e)}")
+
+ @database_sync_to_async
+ def auto_mark_read_for_online_users(self, message, conversation_id):
+ """Auto-marquer comme lu pour les utilisateurs en ligne dans la conversation"""
+ try:
+ # Obtenir tous les participants de la conversation (synchrone)
+ participants = ConversationParticipant.objects.filter(
+ conversation_id=conversation_id,
+ is_active=True
+ ).exclude(participant_id=message.sender.id)
+
+ # Obtenir l'heure de création du message
+ message_time = message.created_at
+
+ # Préparer les enregistrements MessageRead à créer
+ message_reads = []
+
+ for participant_obj in participants:
+ participant = participant_obj.participant
+
+ # Vérifier si l'utilisateur est en ligne (synchrone)
+ try:
+ presence = UserPresence.objects.filter(user=participant).first()
+ if presence and presence.status == 'online':
+ # Vérifier qu'il n'existe pas déjà un enregistrement MessageRead
+ if not MessageRead.objects.filter(message=message, participant=participant).exists():
+ message_reads.append(MessageRead(
+ message=message,
+ participant=participant,
+ read_at=message_time
+ ))
+ except:
+ # En cas d'erreur de présence, ne pas marquer comme lu
+ continue
+
+ # Créer les enregistrements MessageRead en batch
+ if message_reads:
+ MessageRead.objects.bulk_create(message_reads, ignore_conflicts=True)
+ logger.info(f"Auto-marked {len(message_reads)} messages as read for online users in conversation {conversation_id}")
+
+ except Exception as e:
+ logger.error(f"Error in auto_mark_read_for_online_users: {str(e)}")
diff --git a/Back-End/GestionMessagerie/middleware.py b/Back-End/GestionMessagerie/middleware.py
new file mode 100644
index 0000000..68c6f24
--- /dev/null
+++ b/Back-End/GestionMessagerie/middleware.py
@@ -0,0 +1,108 @@
+import jwt
+import logging
+from urllib.parse import parse_qs
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
+from channels.middleware import BaseMiddleware
+from channels.db import database_sync_to_async
+from Auth.models import Profile
+
+logger = logging.getLogger(__name__)
+
+@database_sync_to_async
+def get_user(user_id):
+ """Récupérer l'utilisateur de manière asynchrone"""
+ try:
+ return Profile.objects.get(id=user_id)
+ except Profile.DoesNotExist:
+ return AnonymousUser()
+
+class JWTAuthMiddleware(BaseMiddleware):
+ """Middleware pour l'authentification JWT dans les WebSockets"""
+
+ def __init__(self, inner):
+ super().__init__(inner)
+
+ def _check_cors_origin(self, scope):
+ """Vérifier si l'origine est autorisée pour les WebSockets"""
+ origin = None
+
+ # Récupérer l'origine depuis les headers
+ for name, value in scope.get('headers', []):
+ if name == b'origin':
+ origin = value.decode('latin1')
+ break
+
+ if not origin:
+ logger.warning("Aucune origine trouvée dans les headers WebSocket")
+ return False
+
+ # Récupérer les origines autorisées depuis la configuration CORS
+ allowed_origins = getattr(settings, 'CORS_ALLOWED_ORIGINS', [])
+
+ # Si CORS_ORIGIN_ALLOW_ALL est True, autoriser toutes les origines
+ if getattr(settings, 'CORS_ORIGIN_ALLOW_ALL', False):
+ logger.info(f"Origine WebSocket autorisée (CORS_ORIGIN_ALLOW_ALL): {origin}")
+ return True
+
+ # Vérifier si l'origine est dans la liste des origines autorisées
+ if origin in allowed_origins:
+ logger.info(f"Origine WebSocket autorisée: {origin}")
+ return True
+
+ logger.warning(f"Origine WebSocket non autorisée: {origin}. Origines autorisées: {allowed_origins}")
+ return False
+
+ async def __call__(self, scope, receive, send):
+ # Vérifier les CORS pour les WebSockets
+ if not self._check_cors_origin(scope):
+ logger.error("Connexion WebSocket refusée: origine non autorisée")
+ # Fermer la connexion WebSocket avec un code d'erreur
+ await send({
+ 'type': 'websocket.close',
+ 'code': 1008 # Policy Violation
+ })
+ return
+
+ # Extraire le token de l'URL
+ query_string = parse_qs(scope['query_string'].decode())
+ token = query_string.get('token')
+
+ if token:
+ token = token[0]
+ try:
+ # Décoder le token JWT
+ payload = jwt.decode(
+ token,
+ settings.SIMPLE_JWT['SIGNING_KEY'],
+ algorithms=[settings.SIMPLE_JWT['ALGORITHM']]
+ )
+
+ # Vérifier que c'est un token d'accès
+ if payload.get('type') != 'access':
+ logger.warning(f"Token type invalide: {payload.get('type')}")
+ scope['user'] = AnonymousUser()
+ else:
+ # Récupérer l'utilisateur
+ user_id = payload.get('user_id')
+ user = await get_user(user_id)
+ scope['user'] = user
+ logger.info(f"Utilisateur authentifié via JWT: {user.email if hasattr(user, 'email') else 'Unknown'}")
+
+ except jwt.ExpiredSignatureError:
+ logger.warning("Token JWT expiré")
+ scope['user'] = AnonymousUser()
+ except jwt.InvalidTokenError as e:
+ logger.warning(f"Token JWT invalide: {str(e)}")
+ scope['user'] = AnonymousUser()
+ except Exception as e:
+ logger.error(f"Erreur lors de l'authentification JWT: {str(e)}")
+ scope['user'] = AnonymousUser()
+ else:
+ scope['user'] = AnonymousUser()
+
+ return await super().__call__(scope, receive, send)
+
+def JWTAuthMiddlewareStack(inner):
+ """Stack middleware pour l'authentification JWT"""
+ return JWTAuthMiddleware(inner)
diff --git a/Back-End/GestionMessagerie/models.py b/Back-End/GestionMessagerie/models.py
index 6dec066..57e1a9c 100644
--- a/Back-End/GestionMessagerie/models.py
+++ b/Back-End/GestionMessagerie/models.py
@@ -1,7 +1,104 @@
from django.db import models
from Auth.models import Profile
from django.utils import timezone
+import uuid
+class Conversation(models.Model):
+ """Modèle pour gérer les conversations entre utilisateurs"""
+ CONVERSATION_TYPES = [
+ ('private', 'Privée'),
+ ('group', 'Groupe'),
+ ]
+
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+ name = models.CharField(max_length=255, blank=True, null=True) # Nom pour les groupes
+ conversation_type = models.CharField(max_length=10, choices=CONVERSATION_TYPES, default='private')
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ last_activity = models.DateTimeField(default=timezone.now)
+ is_active = models.BooleanField(default=True)
+
+ def __str__(self):
+ if self.name:
+ return f'Conversation: {self.name}'
+ return f'Conversation {self.id}'
+
+ def get_participants(self):
+ return Profile.objects.filter(conversation_participants__conversation=self)
+
+class ConversationParticipant(models.Model):
+ """Modèle pour gérer les participants d'une conversation"""
+ conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='participants')
+ participant = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='conversation_participants')
+ joined_at = models.DateTimeField(auto_now_add=True)
+ last_read_at = models.DateTimeField(default=timezone.now)
+ is_active = models.BooleanField(default=True)
+
+ class Meta:
+ unique_together = ('conversation', 'participant')
+
+ def __str__(self):
+ return f'{self.participant.email} in {self.conversation.id}'
+
+class Message(models.Model):
+ """Modèle pour les messages instantanés"""
+ MESSAGE_TYPES = [
+ ('text', 'Texte'),
+ ('file', 'Fichier'),
+ ('image', 'Image'),
+ ('system', 'Système'),
+ ]
+
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+ conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='messages')
+ sender = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='sent_messages')
+ content = models.TextField()
+ message_type = models.CharField(max_length=10, choices=MESSAGE_TYPES, default='text')
+ file_url = models.URLField(blank=True, null=True) # Pour les fichiers/images
+ file_name = models.CharField(max_length=255, blank=True, null=True) # Nom original du fichier
+ file_size = models.BigIntegerField(blank=True, null=True) # Taille en bytes
+ file_type = models.CharField(max_length=100, blank=True, null=True) # MIME type
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ is_edited = models.BooleanField(default=False)
+ is_deleted = models.BooleanField(default=False)
+
+ class Meta:
+ ordering = ['created_at']
+
+ def __str__(self):
+ return f'Message from {self.sender.email} at {self.created_at}'
+
+class MessageRead(models.Model):
+ """Modèle pour tracker les messages lus par chaque participant"""
+ message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name='read_by')
+ participant = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='read_messages')
+ read_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ unique_together = ('message', 'participant')
+
+ def __str__(self):
+ return f'{self.participant.email} read {self.message.id}'
+
+class UserPresence(models.Model):
+ """Modèle pour gérer la présence des utilisateurs"""
+ PRESENCE_STATUS = [
+ ('online', 'En ligne'),
+ ('away', 'Absent'),
+ ('busy', 'Occupé'),
+ ('offline', 'Hors ligne'),
+ ]
+
+ user = models.OneToOneField(Profile, on_delete=models.CASCADE, related_name='presence')
+ status = models.CharField(max_length=10, choices=PRESENCE_STATUS, default='offline')
+ last_seen = models.DateTimeField(default=timezone.now)
+ is_typing_in = models.ForeignKey(Conversation, on_delete=models.SET_NULL, null=True, blank=True, related_name='typing_users')
+
+ def __str__(self):
+ return f'{self.user.email} - {self.status}'
+
+# Ancien modèle conservé pour compatibilité
class Messagerie(models.Model):
id = models.AutoField(primary_key=True)
objet = models.CharField(max_length=200, default="", blank=True)
diff --git a/Back-End/GestionMessagerie/routing.py b/Back-End/GestionMessagerie/routing.py
new file mode 100644
index 0000000..ce4585b
--- /dev/null
+++ b/Back-End/GestionMessagerie/routing.py
@@ -0,0 +1,7 @@
+from django.urls import re_path
+from . import consumers
+
+websocket_urlpatterns = [
+ re_path(r'ws/chat/(?P\w+)/$', consumers.ChatConsumer.as_asgi()),
+ re_path(r'ws/chat/conversation/(?P[\w-]+)/$', consumers.ChatConsumer.as_asgi()),
+]
diff --git a/Back-End/GestionMessagerie/serializers.py b/Back-End/GestionMessagerie/serializers.py
index dc446d1..536c8dc 100644
--- a/Back-End/GestionMessagerie/serializers.py
+++ b/Back-End/GestionMessagerie/serializers.py
@@ -1,15 +1,266 @@
from rest_framework import serializers
from Auth.models import Profile
-from GestionMessagerie.models import Messagerie
+from GestionMessagerie.models import Messagerie, Conversation, ConversationParticipant, Message, MessageRead, UserPresence
+from channels.layers import get_channel_layer
+from asgiref.sync import async_to_sync
+
+class ProfileSimpleSerializer(serializers.ModelSerializer):
+ """Sérialiseur simple pour les profils utilisateur"""
+ class Meta:
+ model = Profile
+ fields = ['id', 'first_name', 'last_name', 'email']
+
+class UserPresenceSerializer(serializers.ModelSerializer):
+ """Sérialiseur pour la présence utilisateur"""
+ user = ProfileSimpleSerializer(read_only=True)
+
+ class Meta:
+ model = UserPresence
+ fields = ['user', 'status', 'last_seen', 'is_typing_in']
+
+class MessageReadSerializer(serializers.ModelSerializer):
+ """Sérialiseur pour les messages lus"""
+ participant = ProfileSimpleSerializer(read_only=True)
+
+ class Meta:
+ model = MessageRead
+ fields = ['participant', 'read_at']
class MessageSerializer(serializers.ModelSerializer):
+ """Sérialiseur pour les messages instantanés"""
+ sender = ProfileSimpleSerializer(read_only=True)
+ read_by = MessageReadSerializer(many=True, read_only=True)
+ attachment = serializers.SerializerMethodField()
+ is_read = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Message
+ fields = ['id', 'conversation', 'sender', 'content', 'message_type', 'file_url',
+ 'file_name', 'file_size', 'file_type', 'attachment',
+ 'created_at', 'updated_at', 'is_edited', 'is_deleted', 'read_by', 'is_read']
+ read_only_fields = ['id', 'created_at', 'updated_at']
+
+ def get_attachment(self, obj):
+ """Retourne les informations du fichier attaché sous forme d'objet"""
+ if obj.file_url:
+ return {
+ 'fileName': obj.file_name,
+ 'fileSize': obj.file_size,
+ 'fileType': obj.file_type,
+ 'fileUrl': obj.file_url,
+ }
+ return None
+
+ def get_is_read(self, obj):
+ """Détermine si le message est lu par l'utilisateur actuel"""
+ user = self.context.get('user')
+ if not user or not user.is_authenticated:
+ return False
+
+ # Si c'est le message de l'utilisateur lui-même, vérifier si quelqu'un d'autre l'a lu
+ if obj.sender == user:
+ # Pour les messages envoyés par l'utilisateur, vérifier si au moins un autre participant l'a explicitement lu
+ # Utiliser le modèle MessageRead pour une vérification précise
+ from .models import MessageRead
+ other_participants = obj.conversation.participants.exclude(participant=user).filter(is_active=True)
+
+ for participant in other_participants:
+ # Vérifier si ce participant a explicitement lu ce message
+ if MessageRead.objects.filter(message=obj, participant=participant.participant).exists():
+ return True
+
+ # Fallback: vérifier last_read_at seulement si l'utilisateur était en ligne récemment
+ # ou si last_read_at est postérieur à created_at (lecture explicite après réception)
+ if (participant.last_read_at and
+ participant.last_read_at > obj.created_at):
+
+ # Vérifier la présence de l'utilisateur pour s'assurer qu'il était en ligne
+ try:
+ from .models import UserPresence
+ user_presence = UserPresence.objects.filter(user=participant.participant).first()
+
+ # Si l'utilisateur était en ligne récemment (dans les 5 minutes suivant le message)
+ # ou si last_read_at est bien après created_at (lecture délibérée)
+ time_diff = participant.last_read_at - obj.created_at
+ if (user_presence and user_presence.last_seen and
+ user_presence.last_seen >= obj.created_at) or time_diff.total_seconds() > 10:
+ return True
+ except:
+ # En cas d'erreur, continuer avec la logique conservative
+ pass
+
+ return False
+ else:
+ # Pour les messages reçus, vérifier si l'utilisateur actuel l'a lu
+ # D'abord vérifier dans MessageRead pour une lecture explicite
+ from .models import MessageRead
+ if MessageRead.objects.filter(message=obj, participant=user).exists():
+ return True
+
+ # Fallback: vérifier last_read_at du participant
+ participant = obj.conversation.participants.filter(
+ participant=user,
+ is_active=True
+ ).first()
+
+ if participant and participant.last_read_at:
+ # Seulement considérer comme lu si last_read_at est postérieur à created_at
+ return participant.last_read_at > obj.created_at
+
+ return False
+
+class ConversationParticipantSerializer(serializers.ModelSerializer):
+ """Sérialiseur pour les participants d'une conversation"""
+ participant = ProfileSimpleSerializer(read_only=True)
+
+ class Meta:
+ model = ConversationParticipant
+ fields = ['participant', 'joined_at', 'last_read_at', 'is_active']
+
+class ConversationSerializer(serializers.ModelSerializer):
+ """Sérialiseur pour les conversations"""
+ participants = ConversationParticipantSerializer(many=True, read_only=True)
+ last_message = serializers.SerializerMethodField()
+ unread_count = serializers.SerializerMethodField()
+ interlocuteur = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Conversation
+ fields = ['id', 'name', 'conversation_type', 'created_at', 'updated_at',
+ 'last_activity', 'is_active', 'participants', 'last_message', 'unread_count', 'interlocuteur']
+ read_only_fields = ['id', 'created_at', 'updated_at']
+
+ def get_last_message(self, obj):
+ last_message = obj.messages.filter(is_deleted=False).last()
+ if last_message:
+ return MessageSerializer(last_message).data
+ return None
+
+ def get_unread_count(self, obj):
+ user = self.context.get('user')
+ if not user or not user.is_authenticated:
+ return 0
+
+ participant = obj.participants.filter(participant=user).first()
+ if not participant:
+ return 0
+
+ # Nouvelle logique : compter les messages qui ne sont PAS dans MessageRead
+ # et qui ont été créés après last_read_at (ou tous si last_read_at est None)
+
+ # Base query : messages de la conversation, excluant les propres messages et les supprimés
+ # ET ne comptant que les messages textuels
+ base_query = obj.messages.filter(
+ is_deleted=False,
+ message_type='text' # Ne compter que les messages textuels
+ ).exclude(sender=user)
+
+ # Si l'utilisateur n'a pas de last_read_at, tous les messages sont non lus
+ if not participant.last_read_at:
+ unread_from_timestamp = base_query
+ else:
+ # Messages créés après le dernier moment de lecture
+ unread_from_timestamp = base_query.filter(
+ created_at__gt=participant.last_read_at
+ )
+
+ # Soustraire les messages explicitement marqués comme lus dans MessageRead
+ from .models import MessageRead
+ read_message_ids = MessageRead.objects.filter(
+ participant=user,
+ message__conversation=obj
+ ).values_list('message_id', flat=True)
+
+ # Compter les messages non lus = messages après last_read_at MOINS ceux explicitement lus
+ unread_count = unread_from_timestamp.exclude(
+ id__in=read_message_ids
+ ).count()
+
+ return unread_count
+
+ def get_interlocuteur(self, obj):
+ """Pour les conversations privées, retourne l'autre participant"""
+ user = self.context.get('user')
+ if not user or not user.is_authenticated or obj.conversation_type != 'private':
+ return None
+
+ # Trouver l'autre participant (pas l'utilisateur actuel)
+ other_participant = obj.participants.filter(is_active=True).exclude(participant=user).first()
+ if other_participant:
+ return ProfileSimpleSerializer(other_participant.participant).data
+ return None
+
+class ConversationCreateSerializer(serializers.ModelSerializer):
+ """Sérialiseur pour créer une conversation"""
+ participant_ids = serializers.ListField(
+ child=serializers.IntegerField(),
+ write_only=True
+ )
+
+ class Meta:
+ model = Conversation
+ fields = ['name', 'conversation_type', 'participant_ids']
+
+ def create(self, validated_data):
+ participant_ids = validated_data.pop('participant_ids')
+ conversation_type = validated_data.get('conversation_type', 'private')
+
+ # Pour les conversations privées, ne pas utiliser de nom spécifique
+ # Le nom sera géré côté frontend en affichant le nom de l'interlocuteur
+ if conversation_type == 'private':
+ validated_data['name'] = None
+
+ conversation = super().create(validated_data)
+
+ # Ajouter les participants
+ participants = []
+ for participant_id in participant_ids:
+ try:
+ participant = Profile.objects.get(id=participant_id)
+ ConversationParticipant.objects.create(
+ conversation=conversation,
+ participant=participant
+ )
+ participants.append(participant)
+ except Profile.DoesNotExist:
+ continue
+
+ # Notifier les participants via WebSocket de la nouvelle conversation
+ try:
+ from channels.layers import get_channel_layer
+ from asgiref.sync import async_to_sync
+
+ channel_layer = get_channel_layer()
+ if channel_layer:
+ # Envoyer à chaque participant avec le bon contexte
+ for participant in participants:
+ # Sérialiser la conversation avec le contexte de ce participant
+ conversation_data = ConversationSerializer(conversation, context={'user': participant}).data
+
+ async_to_sync(channel_layer.group_send)(
+ f'user_{participant.id}',
+ {
+ 'type': 'new_conversation_notification',
+ 'conversation': conversation_data
+ }
+ )
+ except Exception as e:
+ # Log l'erreur mais ne pas interrompre la création de la conversation
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error(f"Erreur lors de la notification WebSocket de nouvelle conversation: {str(e)}")
+
+ return conversation
+
+# Ancien sérialiseur conservé pour compatibilité
+class MessageLegacySerializer(serializers.ModelSerializer):
destinataire_profil = serializers.SerializerMethodField()
emetteur_profil = serializers.SerializerMethodField()
class Meta:
model = Messagerie
fields = '__all__'
read_only_fields = ['date_envoi']
-
+
def get_destinataire_profil(self, obj):
return obj.destinataire.email
diff --git a/Back-End/GestionMessagerie/urls.py b/Back-End/GestionMessagerie/urls.py
index 31dc35a..45222f7 100644
--- a/Back-End/GestionMessagerie/urls.py
+++ b/Back-End/GestionMessagerie/urls.py
@@ -1,15 +1,22 @@
-from django.urls import path, re_path
-from .views import SendEmailView, search_recipients, ConversationListView, ConversationMessagesView, MarkAsReadView
-from GestionMessagerie.views import MessagerieView, MessageView, MessageSimpleView
+from django.urls import path
+from .views import (
+ InstantConversationListView, InstantConversationCreateView, InstantConversationDeleteView,
+ InstantMessageListView, InstantMessageCreateView,
+ InstantMarkAsReadView, FileUploadView,
+ InstantRecipientSearchView
+)
urlpatterns = [
- re_path(r'^messagerie/(?P[0-9]+)$', MessagerieView.as_view(), name="messagerie"),
- re_path(r'^messages$', MessageView.as_view(), name="messages"),
- re_path(r'^messages/(?P[0-9]+)$', MessageSimpleView.as_view(), name="messages"),
- path('send-email/', SendEmailView.as_view(), name='send_email'),
- path('search-recipients/', search_recipients, name='search_recipients'),
- # Endpoints pour le chat instantané
- path('conversations//', ConversationListView.as_view(), name='conversations'),
- path('conversations/messages//', ConversationMessagesView.as_view(), name='conversation_messages'),
- path('conversations/mark-as-read//', MarkAsReadView.as_view(), name='mark_as_read'),
+ # URLs pour messagerie instantanée
+ path('conversations/', InstantConversationListView.as_view(), name='conversations'),
+ path('create-conversation/', InstantConversationCreateView.as_view(), name='create_conversation'),
+ path('send-message/', InstantMessageCreateView.as_view(), name='send_message'),
+ path('conversations/mark-as-read/', InstantMarkAsReadView.as_view(), name='mark_as_read'),
+ path('search-recipients/', InstantRecipientSearchView.as_view(), name='search_recipients'),
+ path('upload-file/', FileUploadView.as_view(), name='upload_file'),
+
+ # URLs avec paramètres - doivent être après les URLs statiques
+ path('conversations/user//', InstantConversationListView.as_view(), name='conversations_by_user'),
+ path('conversations//', InstantConversationDeleteView.as_view(), name='delete_conversation'),
+ path('conversations//messages/', InstantMessageListView.as_view(), name='conversation_messages'),
]
\ No newline at end of file
diff --git a/Back-End/GestionMessagerie/views.py b/Back-End/GestionMessagerie/views.py
index f6bddb8..49be6d7 100644
--- a/Back-End/GestionMessagerie/views.py
+++ b/Back-End/GestionMessagerie/views.py
@@ -1,211 +1,455 @@
-from django.http.response import JsonResponse
from rest_framework.views import APIView
-from rest_framework.parsers import JSONParser
-from django.conf import settings
from rest_framework.response import Response
from rest_framework import status
-from django.db.models import Q
-from .models import Messagerie
+from rest_framework.parsers import MultiPartParser, FormParser
+from django.db import models
+from .models import Conversation, ConversationParticipant, Message, UserPresence
from Auth.models import Profile, ProfileRole
-from GestionMessagerie.serializers import MessageSerializer
-from School.models import Teacher
-
-from School.serializers import TeacherSerializer
-
-import N3wtSchool.mailManager as mailer
-from N3wtSchool import bdd
+from GestionMessagerie.serializers import (
+ ConversationSerializer, MessageSerializer,
+ ConversationCreateSerializer, UserPresenceSerializer,
+ ProfileSimpleSerializer
+)
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
-from rest_framework.exceptions import NotFound
+from django.utils import timezone
+import os
+import uuid
+import logging
+from django.core.files.storage import default_storage
+from django.core.files.base import ContentFile
+from django.db.models import Q
-class MessagerieView(APIView):
- def get(self, request, profile_id):
- messagesList = bdd.getObjects(_objectName=Messagerie, _columnName='destinataire__id', _value=profile_id)
- messages_serializer = MessageSerializer(messagesList, many=True)
- return JsonResponse(messages_serializer.data, safe=False)
+logger = logging.getLogger(__name__)
-class MessageView(APIView):
- def post(self, request):
- message_data=JSONParser().parse(request)
- message_serializer = MessageSerializer(data=message_data)
+# ====================== MESSAGERIE INSTANTANÉE ======================
- if message_serializer.is_valid():
- message_serializer.save()
-
- return JsonResponse('Nouveau Message ajouté', safe=False)
-
- return JsonResponse(message_serializer.errors, safe=False)
-
-class MessageSimpleView(APIView):
- def get(self, request, id):
- message=bdd.getObject(Messagerie, "id", id)
- message_serializer=MessageSerializer(message)
- return JsonResponse(message_serializer.data, safe=False)
-
-class SendEmailView(APIView):
+class InstantConversationListView(APIView):
"""
- API pour envoyer des emails aux parents et professeurs.
+ API pour lister les conversations instantanées d'un utilisateur
"""
- def post(self, request):
- data = request.data
- recipients = data.get('recipients', [])
- cc = data.get('cc', [])
- bcc = data.get('bcc', [])
- subject = data.get('subject', 'Notification')
- message = data.get('message', '')
- establishment_id = data.get('establishment_id', '')
-
- if not recipients or not message:
- return Response({'error': 'Les destinataires et le message sont requis.'}, status=status.HTTP_400_BAD_REQUEST)
-
+ @swagger_auto_schema(
+ operation_description="Liste les conversations instantanées d'un utilisateur",
+ responses={200: ConversationSerializer(many=True)}
+ )
+ def get(self, request, user_id=None):
try:
- # Récupérer la connexion SMTP
- connection = mailer.getConnection(establishment_id)
+ user = Profile.objects.get(id=user_id)
- # Envoyer l'email
- return mailer.sendMail(
- subject=subject,
- message=message,
- recipients=recipients,
- cc=cc,
- bcc=bcc,
- attachments=[],
- connection=connection
- )
- except NotFound as e:
- return Response({'error': str(e)}, status=status.HTTP_404_NOT_FOUND)
+ conversations = Conversation.objects.filter(
+ participants__participant=user,
+ participants__is_active=True,
+ is_active=True
+ ).distinct().order_by('-last_activity')
+
+ serializer = ConversationSerializer(conversations, many=True, context={'user': user})
+ return Response(serializer.data, status=status.HTTP_200_OK)
+ except Profile.DoesNotExist:
+ return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-class ContactsView(APIView):
+class InstantConversationCreateView(APIView):
"""
- API pour récupérer les contacts associés à un établissement.
- """
- def get(self, request, establishment_id):
- try:
- # Récupérer les enseignants associés à l'établissement
- teachers = Teacher.objects.filter(profile_role__establishment_id=establishment_id)
- teachers_serializer = TeacherSerializer(teachers, many=True)
-
- # Ajouter un contact pour l'administration
- admin_contact = {
- "id": "admin",
- "name": "Administration",
- "email": "admin@etablissement.com",
- "profilePic": "https://www.gravatar.com/avatar/admin"
- }
-
- contacts = [admin_contact] + teachers_serializer.data
- return Response(contacts, status=status.HTTP_200_OK)
- except Exception as e:
- return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-def search_recipients(request):
- """
- API pour rechercher des destinataires en fonction d'un terme de recherche et d'un établissement.
- """
- query = request.GET.get('q', '').strip() # Récupérer le terme de recherche depuis les paramètres GET
- establishment_id = request.GET.get('establishment_id', None) # Récupérer l'ID de l'établissement
-
- if not query:
- return JsonResponse([], safe=False) # Retourner une liste vide si aucun terme n'est fourni
-
- if not establishment_id:
- return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
-
- # Rechercher dans les champs pertinents (nom, prénom, email) et filtrer par establishment_id
- profiles = Profile.objects.filter(
- Q(first_name__icontains=query) |
- Q(last_name__icontains=query) |
- Q(email__icontains=query),
- roles__establishment_id=establishment_id, # Utiliser 'roles' au lieu de 'profilerole'
- roles__is_active=True # Filtrer uniquement les ProfileRole actifs
- ).distinct()
-
- # Construire la réponse avec les rôles associés
- results = []
- for profile in profiles:
- profile_roles = ProfileRole.objects.filter(
- profile=profile,
- establishment_id=establishment_id,
- is_active=True # Inclure uniquement les ProfileRole actifs
- ).values(
- 'id', 'role_type', 'establishment__name', 'is_active'
- )
- results.append({
- 'id': profile.id,
- 'first_name': profile.first_name,
- 'last_name': profile.last_name,
- 'email': profile.email,
- 'roles': list(profile_roles) # Inclure tous les rôles actifs associés pour cet établissement
- })
-
- return JsonResponse(results, safe=False)
-
-class ConversationListView(APIView):
- """
- Liste les conversations d'un utilisateur (parent ou enseignant).
- Retourne la liste des interlocuteurs et le dernier message échangé.
+ API pour créer une nouvelle conversation instantanée
"""
@swagger_auto_schema(
- operation_description="Liste les conversations d'un utilisateur (parent ou enseignant).",
- responses={200: openapi.Response('Liste des conversations')}
+ operation_description="Crée une nouvelle conversation instantanée",
+ request_body=ConversationCreateSerializer,
+ responses={201: ConversationSerializer}
)
- def get(self, request, profile_id):
- # Récupérer toutes les conversations où l'utilisateur est émetteur ou destinataire
- messages = Messagerie.objects.filter(Q(emetteur_id=profile_id) | Q(destinataire_id=profile_id))
- # Grouper par conversation_id
- conversations = {}
- for msg in messages.order_by('-date_envoi'):
- conv_id = msg.conversation_id or f"{min(msg.emetteur_id, msg.destinataire_id)}_{max(msg.emetteur_id, msg.destinataire_id)}"
- if conv_id not in conversations:
- conversations[conv_id] = msg
- # Préparer la réponse
- data = []
- for conv_id, last_msg in conversations.items():
- interlocuteur = last_msg.emetteur if last_msg.destinataire_id == int(profile_id) else last_msg.destinataire
- data.append({
- 'conversation_id': conv_id,
- 'last_message': MessageSerializer(last_msg).data,
- 'interlocuteur': {
- 'id': interlocuteur.id,
- 'first_name': interlocuteur.first_name,
- 'last_name': interlocuteur.last_name,
- 'email': interlocuteur.email,
- }
- })
- return Response(data, status=status.HTTP_200_OK)
+ def post(self, request):
+ serializer = ConversationCreateSerializer(data=request.data)
+ if serializer.is_valid():
+ conversation = serializer.save()
+ response_serializer = ConversationSerializer(conversation, context={'user': request.user})
+ return Response(response_serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
-class ConversationMessagesView(APIView):
+class InstantMessageListView(APIView):
"""
- Récupère tous les messages d'une conversation donnée.
+ API pour lister les messages d'une conversation
"""
@swagger_auto_schema(
- operation_description="Récupère tous les messages d'une conversation donnée.",
- responses={200: openapi.Response('Liste des messages')}
+ operation_description="Liste les messages d'une conversation",
+ responses={200: MessageSerializer(many=True)}
)
def get(self, request, conversation_id):
- messages = Messagerie.objects.filter(conversation_id=conversation_id).order_by('date_envoi')
- serializer = MessageSerializer(messages, many=True)
- return Response(serializer.data, status=status.HTTP_200_OK)
+ try:
+ conversation = Conversation.objects.get(id=conversation_id)
+ messages = conversation.messages.filter(is_deleted=False).order_by('created_at')
-class MarkAsReadView(APIView):
+ # Récupérer l'utilisateur actuel depuis les paramètres de requête
+ user_id = request.GET.get('user_id')
+ user = None
+ if user_id:
+ try:
+ user = Profile.objects.get(id=user_id)
+ except Profile.DoesNotExist:
+ pass
+
+ serializer = MessageSerializer(messages, many=True, context={'user': user})
+ return Response(serializer.data, status=status.HTTP_200_OK)
+ except Conversation.DoesNotExist:
+ return Response({'error': 'Conversation not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+class InstantMessageCreateView(APIView):
"""
- Marque tous les messages reçus dans une conversation comme lus pour l'utilisateur connecté.
+ API pour envoyer un nouveau message instantané
"""
@swagger_auto_schema(
- operation_description="Marque tous les messages reçus dans une conversation comme lus pour l'utilisateur connecté.",
+ operation_description="Envoie un nouveau message instantané",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
- 'profile_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='ID du profil utilisateur')
+ 'conversation_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID de la conversation'),
+ 'sender_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='ID de l\'expéditeur'),
+ 'content': openapi.Schema(type=openapi.TYPE_STRING, description='Contenu du message'),
+ 'message_type': openapi.Schema(type=openapi.TYPE_STRING, description='Type de message', default='text')
},
- required=['profile_id']
+ required=['conversation_id', 'sender_id', 'content']
),
- responses={200: openapi.Response('Statut OK')}
+ responses={201: MessageSerializer}
+ )
+ def post(self, request):
+ try:
+ conversation_id = request.data.get('conversation_id')
+ sender_id = request.data.get('sender_id')
+ content = request.data.get('content', '').strip()
+ message_type = request.data.get('message_type', 'text')
+
+ if not all([conversation_id, sender_id, content]):
+ return Response(
+ {'error': 'conversation_id, sender_id, and content are required'},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # Vérifier que la conversation existe
+ conversation = Conversation.objects.get(id=conversation_id)
+
+ # Vérifier que l'expéditeur existe et peut envoyer dans cette conversation
+ sender = Profile.objects.get(id=sender_id)
+ participant = ConversationParticipant.objects.filter(
+ conversation=conversation,
+ participant=sender,
+ is_active=True
+ ).first()
+
+ if not participant:
+ return Response(
+ {'error': 'You are not a participant in this conversation'},
+ status=status.HTTP_403_FORBIDDEN
+ )
+
+ # Récupérer les données de fichier si disponibles
+ file_url = request.data.get('file_url')
+ file_name = request.data.get('file_name')
+ file_type = request.data.get('file_type')
+ file_size = request.data.get('file_size')
+
+ # Créer le message
+ message = Message.objects.create(
+ conversation=conversation,
+ sender=sender,
+ content=content,
+ message_type=message_type,
+ file_url=file_url,
+ file_name=file_name,
+ file_type=file_type,
+ file_size=file_size
+ )
+
+ # Mettre à jour l'activité de la conversation
+ conversation.last_activity = message.created_at
+ conversation.save(update_fields=['last_activity'])
+
+ serializer = MessageSerializer(message)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+
+ except Conversation.DoesNotExist:
+ return Response({'error': 'Conversation not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Profile.DoesNotExist:
+ return Response({'error': 'Sender not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+class InstantMarkAsReadView(APIView):
+ """
+ API pour marquer une conversation comme lue
+ """
+ @swagger_auto_schema(
+ operation_description="Marque une conversation comme lue pour un utilisateur",
+ request_body=openapi.Schema(
+ type=openapi.TYPE_OBJECT,
+ properties={
+ 'user_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='ID de l\'utilisateur')
+ },
+ required=['user_id']
+ ),
+ responses={200: openapi.Response('Success')}
)
def post(self, request, conversation_id):
- profile_id = request.data.get('profile_id')
- Messagerie.objects.filter(conversation_id=conversation_id, destinataire_id=profile_id, is_read=False).update(is_read=True)
- return Response({'status': 'ok'}, status=status.HTTP_200_OK)
+ try:
+ user_id = request.data.get('user_id')
+ if not user_id:
+ return Response({'error': 'user_id is required'}, status=status.HTTP_400_BAD_REQUEST)
+
+ participant = ConversationParticipant.objects.get(
+ conversation_id=conversation_id,
+ participant_id=user_id,
+ is_active=True
+ )
+
+ participant.last_read_at = timezone.now()
+ participant.save(update_fields=['last_read_at'])
+
+ return Response({'status': 'success'}, status=status.HTTP_200_OK)
+
+ except ConversationParticipant.DoesNotExist:
+ return Response({'error': 'Participant not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+class UserPresenceView(APIView):
+ """
+ API pour gérer la présence des utilisateurs
+ """
+ @swagger_auto_schema(
+ operation_description="Met à jour le statut de présence d'un utilisateur",
+ request_body=openapi.Schema(
+ type=openapi.TYPE_OBJECT,
+ properties={
+ 'status': openapi.Schema(type=openapi.TYPE_STRING, description='Statut de présence')
+ },
+ required=['status']
+ ),
+ responses={200: UserPresenceSerializer}
+ )
+ def post(self, request, user_id):
+ try:
+ user = Profile.objects.get(id=user_id)
+ status_value = request.data.get('status')
+
+ if status_value not in ['online', 'away', 'busy', 'offline']:
+ return Response({'error': 'Invalid status'}, status=status.HTTP_400_BAD_REQUEST)
+
+ presence, created = UserPresence.objects.get_or_create(user=user)
+ presence.status = status_value
+ presence.last_seen = timezone.now()
+ presence.save()
+
+ serializer = UserPresenceSerializer(presence)
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ except Profile.DoesNotExist:
+ return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ @swagger_auto_schema(
+ operation_description="Récupère le statut de présence d'un utilisateur",
+ responses={200: UserPresenceSerializer}
+ )
+ def get(self, request, user_id):
+ try:
+ user = Profile.objects.get(id=user_id)
+ presence, created = UserPresence.objects.get_or_create(user=user)
+
+ if created:
+ presence.status = 'offline'
+ presence.save()
+
+ serializer = UserPresenceSerializer(presence)
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ except Profile.DoesNotExist:
+ return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+class FileUploadView(APIView):
+ """
+ API pour l'upload de fichiers dans la messagerie instantanée
+ """
+ parser_classes = (MultiPartParser, FormParser)
+
+ @swagger_auto_schema(
+ operation_description="Upload un fichier pour la messagerie",
+ manual_parameters=[
+ openapi.Parameter('file', openapi.IN_FORM, description="Fichier à uploader", type=openapi.TYPE_FILE, required=True),
+ openapi.Parameter('conversation_id', openapi.IN_FORM, description="ID de la conversation", type=openapi.TYPE_INTEGER, required=True),
+ openapi.Parameter('sender_id', openapi.IN_FORM, description="ID de l'expéditeur", type=openapi.TYPE_INTEGER, required=True),
+ ],
+ responses={
+ 200: openapi.Response('Success', openapi.Schema(
+ type=openapi.TYPE_OBJECT,
+ properties={
+ 'fileUrl': openapi.Schema(type=openapi.TYPE_STRING),
+ 'fileName': openapi.Schema(type=openapi.TYPE_STRING),
+ 'fileSize': openapi.Schema(type=openapi.TYPE_INTEGER),
+ 'fileType': openapi.Schema(type=openapi.TYPE_STRING),
+ }
+ )),
+ 400: 'Bad Request',
+ 413: 'File too large',
+ 415: 'Unsupported file type'
+ }
+ )
+ def post(self, request):
+ try:
+ file = request.FILES.get('file')
+ conversation_id = request.data.get('conversation_id')
+ sender_id = request.data.get('sender_id')
+
+ if not file:
+ return Response({'error': 'Aucun fichier fourni'}, status=status.HTTP_400_BAD_REQUEST)
+
+ if not conversation_id or not sender_id:
+ return Response({'error': 'conversation_id et sender_id requis'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # Vérifier que la conversation existe et que l'utilisateur y participe
+ try:
+ conversation = Conversation.objects.get(id=conversation_id)
+ sender = Profile.objects.get(id=sender_id)
+
+ # Vérifier que l'expéditeur participe à la conversation
+ if not ConversationParticipant.objects.filter(
+ conversation=conversation,
+ participant=sender,
+ is_active=True
+ ).exists():
+ return Response({'error': 'Accès non autorisé à cette conversation'}, status=status.HTTP_403_FORBIDDEN)
+
+ except (Conversation.DoesNotExist, Profile.DoesNotExist):
+ return Response({'error': 'Conversation ou utilisateur introuvable'}, status=status.HTTP_404_NOT_FOUND)
+
+ # Valider le type de fichier
+ allowed_types = [
+ 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp',
+ 'application/pdf',
+ 'application/msword',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.ms-excel',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'text/plain'
+ ]
+
+ if file.content_type not in allowed_types:
+ return Response({'error': 'Type de fichier non autorisé'}, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
+
+ # Valider la taille du fichier (10MB max)
+ max_size = 10 * 1024 * 1024 # 10MB
+ if file.size > max_size:
+ return Response({'error': 'Fichier trop volumineux (max 10MB)'}, status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE)
+
+ # Générer un nom de fichier unique
+ file_extension = os.path.splitext(file.name)[1]
+ unique_filename = f"{uuid.uuid4()}{file_extension}"
+
+ # Chemin de stockage : messagerie/conversation_id/
+ storage_path = f"messagerie/{conversation_id}/{unique_filename}"
+
+ # Sauvegarder le fichier
+ file_path = default_storage.save(storage_path, ContentFile(file.read()))
+
+ # Générer l'URL du fichier
+ file_url = default_storage.url(file_path)
+ if not file_url.startswith('http'):
+ # Construire l'URL complète si nécessaire
+ file_url = request.build_absolute_uri(file_url)
+
+ return Response({
+ 'fileUrl': file_url,
+ 'fileName': file.name,
+ 'fileSize': file.size,
+ 'fileType': file.content_type,
+ 'filePath': file_path
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ return Response({'error': f'Erreur lors de l\'upload: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+class InstantRecipientSearchView(APIView):
+ """
+ API pour rechercher des destinataires pour la messagerie instantanée
+ """
+ @swagger_auto_schema(
+ operation_description="Recherche des destinataires pour la messagerie instantanée",
+ manual_parameters=[
+ openapi.Parameter('establishment_id', openapi.IN_QUERY, description="ID de l'établissement", type=openapi.TYPE_INTEGER, required=True),
+ openapi.Parameter('q', openapi.IN_QUERY, description="Terme de recherche", type=openapi.TYPE_STRING, required=True)
+ ],
+ responses={200: ProfileSimpleSerializer(many=True)}
+ )
+ def get(self, request):
+ try:
+ establishment_id = request.query_params.get('establishment_id')
+ search_query = request.query_params.get('q', '').strip()
+
+ if not establishment_id:
+ return Response({'error': 'establishment_id is required'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # Récupérer les IDs des profils actifs dans l'établissement
+ profile_roles = ProfileRole.objects.filter(
+ establishment_id=establishment_id,
+ is_active=True
+ ).values_list('profile_id', flat=True)
+
+ # Rechercher les profils correspondants
+ users = Profile.objects.filter(id__in=profile_roles)
+
+ # Appliquer le filtre de recherche si un terme est fourni
+ if search_query:
+ users = users.filter(
+ Q(first_name__icontains=search_query) |
+ Q(last_name__icontains=search_query) |
+ Q(email__icontains=search_query)
+ )
+
+ # Exclure l'utilisateur actuel des résultats
+ if request.user.is_authenticated:
+ users = users.exclude(id=request.user.id)
+
+ serializer = ProfileSimpleSerializer(users[:10], many=True) # Limiter à 10 résultats
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+class InstantConversationDeleteView(APIView):
+ """
+ API pour supprimer (désactiver) une conversation instantanée
+ """
+ @swagger_auto_schema(
+ operation_description="Supprime une conversation instantanée (désactivation soft)",
+ responses={200: openapi.Schema(
+ type=openapi.TYPE_OBJECT,
+ properties={
+ 'success': openapi.Schema(type=openapi.TYPE_BOOLEAN),
+ 'message': openapi.Schema(type=openapi.TYPE_STRING)
+ }
+ )}
+ )
+ def delete(self, request, conversation_id):
+ try:
+ # Récupérer la conversation par son ID UUID
+ conversation = Conversation.objects.filter(id=conversation_id).first()
+
+ if not conversation:
+ return Response({'error': 'Conversation not found'}, status=status.HTTP_404_NOT_FOUND)
+
+ # Suppression simple : désactiver la conversation
+ conversation.is_active = False
+ conversation.save()
+
+ return Response({
+ 'success': True,
+ 'message': 'Conversation deleted successfully'
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ logger.error(f"Error deleting conversation: {str(e)}")
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
diff --git a/Back-End/N3wtSchool/asgi.py b/Back-End/N3wtSchool/asgi.py
index 6a4912d..29f2085 100644
--- a/Back-End/N3wtSchool/asgi.py
+++ b/Back-End/N3wtSchool/asgi.py
@@ -8,9 +8,40 @@ https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
-
from django.core.asgi import get_asgi_application
+from channels.routing import ProtocolTypeRouter, URLRouter
+from channels.security.websocket import AllowedHostsOriginValidator
+from django.urls import re_path
+from django.conf import settings
+from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'N3wtSchool.settings')
-application = get_asgi_application()
+# Initialize Django ASGI application early to ensure the AppRegistry
+# is populated before importing code that may import ORM models.
+django_asgi_app = get_asgi_application()
+
+# Import consumers after Django is initialized
+from GestionMessagerie.consumers import ChatConsumer
+from GestionMessagerie.middleware import JWTAuthMiddlewareStack
+
+# WebSocket URL patterns
+websocket_urlpatterns = [
+ re_path(r'ws/chat/(?P\w+)/$', ChatConsumer.as_asgi()),
+]
+
+# Créer l'application ASGI avec gestion des fichiers statiques
+if settings.DEBUG:
+ # En mode DEBUG, utiliser ASGIStaticFilesHandler pour servir les fichiers statiques
+ http_application = ASGIStaticFilesHandler(django_asgi_app)
+else:
+ http_application = django_asgi_app
+
+application = ProtocolTypeRouter({
+ "http": http_application,
+ "websocket": AllowedHostsOriginValidator(
+ JWTAuthMiddlewareStack(
+ URLRouter(websocket_urlpatterns)
+ )
+ ),
+})
diff --git a/Back-End/N3wtSchool/mailManager.py b/Back-End/N3wtSchool/mailManager.py
index 83108fd..6e36695 100644
--- a/Back-End/N3wtSchool/mailManager.py
+++ b/Back-End/N3wtSchool/mailManager.py
@@ -8,6 +8,10 @@ from rest_framework import status
from rest_framework.exceptions import NotFound
from Settings.models import SMTPSettings
from Establishment.models import Establishment # Importer le modèle Establishment
+import logging
+
+# Ajouter un logger pour debug
+logger = logging.getLogger(__name__)
def getConnection(id_establishement):
try:
@@ -53,6 +57,8 @@ def sendMail(subject, message, recipients, cc=[], bcc=[], attachments=[], connec
from_email = settings.EMAIL_HOST_USER
+ logger.info(f"From email: {from_email}")
+
email = EmailMultiAlternatives(
subject=subject,
body=plain_message,
@@ -67,10 +73,15 @@ def sendMail(subject, message, recipients, cc=[], bcc=[], attachments=[], connec
for attachment in attachments:
email.attach(*attachment)
+ logger.info("Tentative d'envoi de l'email...")
email.send(fail_silently=False)
+ logger.info("Email envoyé avec succès !")
return Response({'message': 'Email envoyé avec succès.'}, status=status.HTTP_200_OK)
except Exception as e:
- print(f"[DEBUG] Erreur lors de l'envoi de l'email : {e}")
+ logger.error(f"Erreur lors de l'envoi de l'email: {str(e)}")
+ logger.error(f"Type d'erreur: {type(e)}")
+ import traceback
+ logger.error(f"Traceback: {traceback.format_exc()}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def envoieReinitMotDePasse(recipients, code):
diff --git a/Back-End/N3wtSchool/settings.py b/Back-End/N3wtSchool/settings.py
index bd12a4e..73a97fd 100644
--- a/Back-End/N3wtSchool/settings.py
+++ b/Back-End/N3wtSchool/settings.py
@@ -46,6 +46,7 @@ INSTALLED_APPS = [
'Subscriptions.apps.GestioninscriptionsConfig',
'Auth.apps.GestionloginConfig',
'GestionMessagerie.apps.GestionMessagerieConfig',
+ 'GestionEmail.apps.GestionEmailConfig',
'GestionNotification.apps.GestionNotificationConfig',
'School.apps.SchoolConfig',
'Planning.apps.PlanningConfig',
@@ -62,14 +63,15 @@ INSTALLED_APPS = [
'django_celery_beat',
'N3wtSchool',
'drf_yasg',
- 'rest_framework_simplejwt'
+ 'rest_framework_simplejwt',
+ 'channels',
]
MIDDLEWARE = [
+ 'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware', # Déplacez ici, avant CorsMiddleware
- 'corsheaders.middleware.CorsMiddleware',
+ 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
@@ -161,6 +163,11 @@ LOGGING = {
"level": os.getenv("GESTION_MESSAGERIE_LOG_LEVEL", "INFO"),
"propagate": False,
},
+ "GestionEmail": {
+ "handlers": ["console"],
+ "level": os.getenv("GESTION_EMAIL_LOG_LEVEL", "INFO"),
+ "propagate": False,
+ },
"School": {
"handlers": ["console"],
"level": os.getenv("GESTION_ENSEIGNANTS_LOG_LEVEL", "INFO"),
@@ -250,18 +257,35 @@ else:
DOCUMENT_DIR = 'documents'
-CORS_ORIGIN_ALLOW_ALL = True
+# Configuration CORS temporaire pour debug
CORS_ALLOW_ALL_HEADERS = True
CORS_ALLOW_CREDENTIALS = True
+
+# Configuration CORS spécifique pour la production
+CORS_ALLOWED_ORIGINS = os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000,http://localhost:8080,http://127.0.0.1:3000,http://127.0.0.1:8080').split(',')
+
+
CORS_ALLOW_HEADERS = [
- 'content-type',
+ 'accept',
+ 'accept-encoding',
'authorization',
+ 'content-type',
+ 'dnt',
+ 'origin',
+ 'user-agent',
+ 'x-csrftoken',
+ 'x-requested-with',
'X-Auth-Token',
- 'x-csrftoken'
]
-CORS_ALLOWED_ORIGINS = [
- os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')
+# Méthodes HTTP autorisées
+CORS_ALLOWED_METHODS = [
+ 'DELETE',
+ 'GET',
+ 'OPTIONS',
+ 'PATCH',
+ 'POST',
+ 'PUT',
]
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://localhost:8080').split(',')
@@ -303,6 +327,7 @@ REST_FRAMEWORK = {
'PAGE_SIZE': NB_RESULT_SUBSCRIPTIONS_PER_PAGE,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
+ 'rest_framework.authentication.SessionAuthentication',
),
}
@@ -344,4 +369,16 @@ DOCUSEAL_JWT = {
'SIGNING_KEY': SECRET_KEY,
'EXPIRATION_DELTA': timedelta(hours=1),
'API_KEY': DOCUSEAL_API_KEY
+}
+
+# Django Channels Configuration
+ASGI_APPLICATION = 'N3wtSchool.asgi.application'
+
+CHANNEL_LAYERS = {
+ 'default': {
+ 'BACKEND': 'channels_redis.core.RedisChannelLayer',
+ 'CONFIG': {
+ "hosts": [('redis', 6379)],
+ },
+ },
}
\ No newline at end of file
diff --git a/Back-End/N3wtSchool/urls.py b/Back-End/N3wtSchool/urls.py
index 3681bf8..458d0b2 100644
--- a/Back-End/N3wtSchool/urls.py
+++ b/Back-End/N3wtSchool/urls.py
@@ -43,6 +43,7 @@ urlpatterns = [
path("Subscriptions/", include(("Subscriptions.urls", 'Subscriptions'), namespace='Subscriptions')),
path("Auth/", include(("Auth.urls", 'Auth'), namespace='Auth')),
path("GestionMessagerie/", include(("GestionMessagerie.urls", 'GestionMessagerie'), namespace='GestionMessagerie')),
+ path("GestionEmail/", include(("GestionEmail.urls", 'GestionEmail'), namespace='GestionEmail')),
path("GestionNotification/", include(("GestionNotification.urls", 'GestionNotification'), namespace='GestionNotification')),
path("School/", include(("School.urls", 'School'), namespace='School')),
path("DocuSeal/", include(("DocuSeal.urls", 'DocuSeal'), namespace='DocuSeal')),
diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py
index 528d447..cf794d4 100644
--- a/Back-End/Subscriptions/models.py
+++ b/Back-End/Subscriptions/models.py
@@ -305,7 +305,7 @@ class RegistrationSchoolFileMaster(models.Model):
class RegistrationParentFileMaster(models.Model):
groups = models.ManyToManyField(RegistrationFileGroup, related_name='parent_file_masters', blank=True)
name = models.CharField(max_length=255, default="")
- description = models.CharField(blank=True, null=True)
+ description = models.CharField(max_length=500, blank=True, null=True)
is_required = models.BooleanField(default=False)
############################################################
@@ -355,7 +355,7 @@ class StudentCompetency(models.Model):
class Meta:
unique_together = ('student', 'establishment_competency', 'period')
-
+
indexes = [
models.Index(fields=['student', 'establishment_competency', 'period']),
]
diff --git a/Back-End/clean_migration_dir.py b/Back-End/clean_migration_dir.py
index b04e17a..c097142 100644
--- a/Back-End/clean_migration_dir.py
+++ b/Back-End/clean_migration_dir.py
@@ -8,6 +8,7 @@ APPS = [
"Planning",
"GestionNotification",
"GestionMessagerie",
+ "GestionEmail",
"Auth",
"School",
"Common"
diff --git a/Back-End/db.sqlite3 b/Back-End/db.sqlite3
deleted file mode 100644
index bb7dd98..0000000
Binary files a/Back-End/db.sqlite3 and /dev/null differ
diff --git a/Back-End/requirements.txt b/Back-End/requirements.txt
index 68b0d90..c930b58 100644
--- a/Back-End/requirements.txt
+++ b/Back-End/requirements.txt
@@ -67,3 +67,6 @@ vine==5.1.0
wcwidth==0.2.13
webencodings==0.5.1
xhtml2pdf==0.2.16
+channels==4.0.0
+channels-redis==4.1.0
+daphne==4.1.0
diff --git a/Back-End/start.py b/Back-End/start.py
index a6b9def..570bdbf 100644
--- a/Back-End/start.py
+++ b/Back-End/start.py
@@ -14,13 +14,14 @@ test_mode = os.getenv('TEST_MODE', 'False') == 'True'
commands = [
["python", "manage.py", "collectstatic", "--noinput"],
- ["python", "manage.py", "flush", "--noinput"],
+ #["python", "manage.py", "flush", "--noinput"],
["python", "manage.py", "makemigrations", "Common", "--noinput"],
["python", "manage.py", "makemigrations", "Establishment", "--noinput"],
["python", "manage.py", "makemigrations", "Settings", "--noinput"],
["python", "manage.py", "makemigrations", "Subscriptions", "--noinput"],
["python", "manage.py", "makemigrations", "Planning", "--noinput"],
["python", "manage.py", "makemigrations", "GestionNotification", "--noinput"],
+ ["python", "manage.py", "makemigrations", "GestionEmail", "--noinput"],
["python", "manage.py", "makemigrations", "GestionMessagerie", "--noinput"],
["python", "manage.py", "makemigrations", "Auth", "--noinput"],
["python", "manage.py", "makemigrations", "School", "--noinput"],
@@ -35,14 +36,15 @@ for command in commands:
if run_command(command) != 0:
exit(1)
-if test_mode:
- for test_command in test_commands:
- if run_command(test_command) != 0:
- exit(1)
+#if test_mode:
+# for test_command in test_commands:
+# if run_command(test_command) != 0:
+# exit(1)
# Lancer les processus en parallèle
+
processes = [
- subprocess.Popen(["python", "manage.py", "runserver", "0.0.0.0:8080"]),
+ subprocess.Popen(["daphne", "-b", "0.0.0.0", "-p", "8080", "N3wtSchool.asgi:application"]),
subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"]),
subprocess.Popen(["celery", "-A", "N3wtSchool", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"])
]
diff --git a/Front-End/.env b/Front-End/.env
deleted file mode 100644
index 2c1e0f1..0000000
--- a/Front-End/.env
+++ /dev/null
@@ -1,5 +0,0 @@
-NEXT_PUBLIC_API_URL=http://localhost:8080
-NEXT_PUBLIC_USE_FAKE_DATA='false'
-AUTH_SECRET='false'
-NEXTAUTH_URL=http://localhost:3000
-DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg"
\ No newline at end of file
diff --git a/Front-End/docs/api-messagerie-technique.md b/Front-End/docs/api-messagerie-technique.md
new file mode 100644
index 0000000..101e250
--- /dev/null
+++ b/Front-End/docs/api-messagerie-technique.md
@@ -0,0 +1,340 @@
+# API Messagerie Instantanée - Guide Développeur
+
+## Vue d'ensemble
+
+Cette documentation technique présente l'implémentation du système de messagerie instantanée, incluant les APIs WebSocket et REST, l'architecture des composants React et les fonctions utilitaires.
+
+## API WebSocket
+
+### Connexion
+
+**URL de connexion :**
+
+```javascript
+// Développement
+ws://localhost:8000/ws/chat/{userId}/
+
+// Production
+wss://[domaine]/ws/chat/{userId}/
+```
+
+### Messages WebSocket
+
+#### Messages entrants (serveur → client)
+
+```javascript
+// Liste des conversations
+{
+ "type": "conversations_list",
+ "conversations": [...]
+}
+
+// Nouveau message reçu
+{
+ "type": "new_message",
+ "message": {
+ "id": 123,
+ "conversation_id": 456,
+ "sender_id": 789,
+ "content": "Contenu du message",
+ "timestamp": "2024-01-01T12:00:00Z"
+ }
+}
+
+// Utilisateur en train d'écrire
+{
+ "type": "typing_start",
+ "conversation_id": 456,
+ "user_id": 789
+}
+
+// Utilisateur a arrêté d'écrire
+{
+ "type": "typing_stop",
+ "conversation_id": 456,
+ "user_id": 789
+}
+```
+
+#### Messages sortants (client → serveur)
+
+```javascript
+// Envoyer un message
+{
+ "type": "chat_message",
+ "conversation_id": 456,
+ "message": "Contenu du message"
+}
+
+// Signaler début de frappe
+{
+ "type": "typing_start",
+ "conversation_id": 456
+}
+
+// Signaler fin de frappe
+{
+ "type": "typing_stop",
+ "conversation_id": 456
+}
+
+// Marquer comme lu
+{
+ "type": "mark_as_read",
+ "conversation_id": 456
+}
+
+// Rejoindre une conversation
+{
+ "type": "join_conversation",
+ "conversation_id": 456
+}
+```
+
+## API REST
+
+### Endpoints disponibles
+
+```javascript
+// Récupérer les conversations
+GET /api/messagerie/conversations/{userId}/
+Response: Array
+
+// Récupérer les messages d'une conversation
+GET /api/messagerie/messages/{conversationId}/
+Response: Array
+
+// Rechercher des destinataires
+GET /api/messagerie/search/{establishmentId}/?q={query}
+Response: Array
+
+// Créer une conversation
+POST /api/messagerie/conversations/create/
+Body: { "participants": [userId1, userId2] }
+Response: Conversation
+
+// Envoyer un email (séparé de la messagerie instantanée)
+POST /api/email/send/
+Body: { "recipients": [...], "subject": "...", "content": "..." }
+```
+
+## Composants React
+
+### InstantChat
+
+**Props :**
+
+```javascript
+{
+ userProfileId: number, // ID de l'utilisateur connecté
+ establishmentId: number // ID de l'établissement
+}
+```
+
+**États principaux :**
+
+- `conversations` : Liste des conversations
+- `selectedConversation` : Conversation active
+- `messages` : Messages de la conversation active
+- `searchQuery` : Terme de recherche
+- `searchResults` : Résultats de recherche de contacts
+
+### useWebSocket Hook
+
+**Paramètres :**
+
+```javascript
+useWebSocket(
+ userProfileId, // ID utilisateur
+ onMessage, // Callback pour messages reçus
+ onConnectionChange // Callback changement de connexion
+);
+```
+
+**Valeurs retournées :**
+
+```javascript
+{
+ isConnected: boolean,
+ connectionStatus: string,
+ sendChatMessage: (conversationId, content) => boolean,
+ sendTypingStart: (conversationId) => void,
+ sendTypingStop: (conversationId) => void,
+ markAsRead: (conversationId) => void,
+ joinConversation: (conversationId) => void,
+ reconnect: () => void
+}
+```
+
+## Actions Redux/State
+
+### messagerieAction.js
+
+```javascript
+// Récupérer les conversations
+fetchConversations(userId): Promise>
+
+// Récupérer les messages
+fetchMessages(conversationId): Promise>
+
+// Rechercher des destinataires
+searchMessagerieRecipients(establishmentId, query): Promise>
+
+// Créer une conversation
+createConversation(participants): Promise
+```
+
+### emailAction.js
+
+```javascript
+// Envoyer un email
+sendEmail(recipients, subject, content, csrfToken): Promise
+
+// Rechercher des destinataires email
+searchEmailRecipients(establishmentId, query): Promise>
+```
+
+## Modèles de Données
+
+### Conversation
+
+```javascript
+{
+ conversation_id: number,
+ participants: Array,
+ last_message: Message,
+ created_at: string,
+ updated_at: string
+}
+```
+
+### Message
+
+```javascript
+{
+ id: number,
+ conversation_id: number,
+ sender_id: number,
+ content: string,
+ timestamp: string,
+ is_read: boolean
+}
+```
+
+### User
+
+```javascript
+{
+ id: number,
+ first_name: string,
+ last_name: string,
+ email: string,
+ role: string
+}
+```
+
+## Gestion des Erreurs
+
+### WebSocket
+
+```javascript
+// Reconnexion automatique
+const reconnectWebSocket = () => {
+ setConnectionStatus('reconnecting');
+ // Logique de reconnexion avec backoff exponentiel
+};
+
+// Gestion des erreurs de connexion
+wsRef.current.onerror = (error) => {
+ logger.error('Erreur WebSocket:', error);
+ setIsConnected(false);
+};
+```
+
+### API REST
+
+```javascript
+// Wrapper avec gestion d'erreur
+const apiCall = async (url, options) => {
+ try {
+ const response = await fetch(url, options);
+ if (!response.ok) {
+ throw new Error(`Erreur ${response.status}`);
+ }
+ return await response.json();
+ } catch (error) {
+ logger.error('Erreur API:', error);
+ throw error;
+ }
+};
+```
+
+## Configuration des Tests
+
+### Jest Setup
+
+```javascript
+// jest.setup.js
+global.WebSocket = class MockWebSocket {
+ // Mock complet du WebSocket pour les tests
+};
+
+global.fetch = jest.fn(() =>
+ Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve([]),
+ })
+);
+```
+
+### Tests des Composants
+
+```javascript
+// Exemple de test
+test('renders InstantChat component', async () => {
+ await act(async () => {
+ render( );
+ });
+
+ expect(screen.getByText('Messages')).toBeInTheDocument();
+});
+```
+
+## Intégration Backend
+
+### Consumer Django
+
+```python
+# consumers.py
+class ChatConsumer(AsyncWebsocketConsumer):
+ async def connect(self):
+ # Logique de connexion
+
+ async def chat_message(self, event):
+ # Traitement des messages
+```
+
+### URLs Configuration
+
+```python
+# routing.py
+websocket_urlpatterns = [
+ re_path(r'ws/chat/(?P\w+)/$', ChatConsumer.as_asgi()),
+]
+```
+
+## Optimisations
+
+### Performance
+
+- Pagination des messages anciens (load on scroll)
+- Debounce pour la recherche de contacts (300ms)
+- Memoization des composants avec React.memo
+- Lazy loading des conversations
+
+### UX
+
+- Reconnexion automatique avec feedback visuel
+- Sauvegarde locale des messages en cours de frappe
+- Indicateurs de livraison des messages
+- Scrolling automatique vers les nouveaux messages
diff --git a/Front-End/docs/messagerie-instantanee.md b/Front-End/docs/messagerie-instantanee.md
new file mode 100644
index 0000000..dbfcf8a
--- /dev/null
+++ b/Front-End/docs/messagerie-instantanee.md
@@ -0,0 +1,126 @@
+# Système de Messagerie Instantanée
+
+## Présentation
+
+Le système de messagerie instantanée de N3WT-SCHOOL permet aux utilisateurs de l'établissement (administrateurs, professeurs, parents, étudiants) de communiquer en temps réel via une interface chat moderne et intuitive.
+
+## Fonctionnalités
+
+### Chat en Temps Réel
+
+- Envoi et réception de messages instantanés
+- Notification de statut de frappe (utilisateur en train d'écrire)
+- Indicateur de statut de connexion WebSocket
+- Reconnexion automatique en cas de perte de connexion
+
+### Gestion des Conversations
+
+- Liste des conversations existantes
+- Création de nouvelles conversations
+- Recherche de destinataires par nom ou email
+- Compteur de messages non lus
+
+### Interface Utilisateur
+
+- Interface moderne en deux panneaux (conversations + chat)
+- Bulles de messages différenciées (expéditeur/destinataire)
+- Indicateurs visuels de statut de connexion
+- Recherche temps réel de contacts
+
+## Utilisation
+
+### Accès au Chat
+
+Le système de messagerie est accessible via les pages suivantes :
+
+- **Parents** : `/[locale]/parents/messagerie`
+- **Administrateurs** : Intégré dans le panneau d'administration
+
+### Créer une Conversation
+
+1. Cliquer sur le bouton "+" en haut à droite de la liste des conversations
+2. Rechercher un contact en tapant son nom ou email
+3. Sélectionner le destinataire dans les résultats
+4. La conversation se crée automatiquement
+
+### Envoyer un Message
+
+1. Sélectionner une conversation dans la liste de gauche
+2. Taper le message dans le champ de saisie en bas
+3. Appuyer sur Entrée ou cliquer sur le bouton d'envoi
+
+## Architecture Technique
+
+### Frontend (React/Next.js)
+
+**Composants principaux :**
+
+- `InstantChat` : Composant principal du chat
+- `ConnectionStatus` : Affichage du statut de connexion
+- `ConversationItem` : Élément de liste de conversation
+- `MessageBubble` : Bulle de message individuelle
+- `MessageInput` : Zone de saisie de message
+- `TypingIndicator` : Indicateur de frappe
+
+**Hook personnalisé :**
+
+- `useWebSocket` : Gestion de la connexion WebSocket et des événements
+
+### Backend (Django)
+
+**Module GestionMessagerie :**
+
+- `consumers.py` : Consumer WebSocket pour la messagerie temps réel
+- `routing.py` : Configuration des routes WebSocket
+- `urls.py` : URLs API REST pour les conversations et messages
+
+**Module GestionEmail :**
+
+- `views.py` : Vues pour l'envoi d'emails classiques
+- `urls.py` : URLs pour les fonctions email
+
+### Communication
+
+- **WebSocket** : Communication bidirectionnelle temps réel
+- **REST API** : Chargement initial des données et recherche
+- **Channels** : Gestion des groupes de conversation Django
+
+## Configuration
+
+### URLs WebSocket
+
+Les URLs sont configurées automatiquement selon l'environnement :
+
+- **Développement** : `ws://localhost:8000/ws/chat/`
+- **Production** : `wss://[domaine]/ws/chat/`
+
+### Variables d'Environnement
+
+Le système utilise les configurations standard de l'application pour :
+
+- Base de données (conversations, messages, utilisateurs)
+- Authentification (sessions Django)
+- Établissements (filtrage par établissement)
+
+## Sécurité
+
+- Authentification requise pour accéder au chat
+- Filtrage des conversations par établissement
+- Validation côté serveur de tous les messages
+- Gestion des permissions selon le rôle utilisateur
+
+## Tests
+
+Le système dispose de tests unitaires Jest couvrant :
+
+- Rendu des composants
+- Gestion des connexions WebSocket
+- Recherche de contacts
+- Envoi de messages
+- Indicateurs de frappe
+
+Exécution des tests :
+
+```bash
+npm test
+```
diff --git a/Front-End/jest.config.js b/Front-End/jest.config.js
new file mode 100644
index 0000000..6fdad4c
--- /dev/null
+++ b/Front-End/jest.config.js
@@ -0,0 +1,33 @@
+const nextJest = require('next/jest');
+
+const createJestConfig = nextJest({
+ // Provide the path to your Next.js app to load next.config.js and .env files
+ dir: './',
+});
+
+// Add any custom config to be passed to Jest
+const customJestConfig = {
+ setupFilesAfterEnv: ['/jest.setup.js'],
+ testEnvironment: 'jest-environment-jsdom',
+ testPathIgnorePatterns: ['/.next/', '/node_modules/'],
+ moduleNameMapper: {
+ '^@/(.*)$': '/src/$1',
+ },
+ collectCoverageFrom: [
+ 'src/**/*.{js,jsx}',
+ '!src/**/*.stories.{js,jsx}',
+ '!src/pages/_app.js',
+ '!src/pages/_document.js',
+ ],
+ coverageThreshold: {
+ global: {
+ branches: 70,
+ functions: 70,
+ lines: 70,
+ statements: 70,
+ },
+ },
+};
+
+// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
+module.exports = createJestConfig(customJestConfig);
diff --git a/Front-End/jest.setup.js b/Front-End/jest.setup.js
new file mode 100644
index 0000000..7eaf3fb
--- /dev/null
+++ b/Front-End/jest.setup.js
@@ -0,0 +1,95 @@
+import '@testing-library/jest-dom';
+
+// Supprimer les avertissements React act() en environnement de test
+global.IS_REACT_ACT_ENVIRONMENT = true;
+
+// Mock window.matchMedia
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+});
+
+// Mock IntersectionObserver
+global.IntersectionObserver = class IntersectionObserver {
+ constructor() {}
+ observe() {
+ return null;
+ }
+ disconnect() {
+ return null;
+ }
+ unobserve() {
+ return null;
+ }
+};
+
+// Mock WebSocket
+global.WebSocket = class WebSocket {
+ constructor(url) {
+ this.url = url;
+ this.readyState = WebSocket.CONNECTING;
+ setTimeout(() => {
+ this.readyState = WebSocket.OPEN;
+ if (this.onopen) this.onopen();
+ }, 10);
+ }
+
+ send(data) {
+ // Mock send
+ }
+
+ close() {
+ this.readyState = WebSocket.CLOSED;
+ if (this.onclose) {
+ this.onclose({
+ code: 1000,
+ reason: 'Normal closure',
+ wasClean: true,
+ });
+ }
+ }
+
+ static get CONNECTING() {
+ return 0;
+ }
+ static get OPEN() {
+ return 1;
+ }
+ static get CLOSING() {
+ return 2;
+ }
+ static get CLOSED() {
+ return 3;
+ }
+};
+
+// Mock global pour fetch
+global.fetch = jest.fn(() =>
+ Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve([]),
+ })
+);
+
+// Mock ResizeObserver
+global.ResizeObserver = class ResizeObserver {
+ constructor() {}
+ observe() {
+ return null;
+ }
+ disconnect() {
+ return null;
+ }
+ unobserve() {
+ return null;
+ }
+};
diff --git a/Front-End/next.config.mjs b/Front-End/next.config.mjs
index caa5fc6..ffe8f35 100644
--- a/Front-End/next.config.mjs
+++ b/Front-End/next.config.mjs
@@ -24,6 +24,8 @@ const nextConfig = {
NEXT_PUBLIC_APP_VERSION: pkg.version,
NEXT_PUBLIC_API_URL:
process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
+ NEXT_PUBLIC_WSAPI_URL:
+ process.env.NEXT_PUBLIC_WSAPI_URL || 'ws://localhost:8080',
NEXT_PUBLIC_USE_FAKE_DATA: process.env.NEXT_PUBLIC_USE_FAKE_DATA || 'false',
AUTH_SECRET: process.env.AUTH_SECRET || 'false',
NEXTAUTH_URL: process.env.NEXTAUTH_URL || 'http://localhost:3000',
diff --git a/Front-End/package-lock.json b/Front-End/package-lock.json
index 32aaec3..c5f800b 100644
--- a/Front-End/package-lock.json
+++ b/Front-End/package-lock.json
@@ -34,13 +34,24 @@
"react-tooltip": "^5.28.0"
},
"devDependencies": {
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/react": "^13.4.0",
+ "@testing-library/user-event": "^14.4.3",
"autoprefixer": "^10.4.20",
"eslint": "^8",
"eslint-config-next": "14.2.11",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14"
}
},
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz",
+ "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==",
+ "dev": true
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -52,6 +63,420 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz",
+ "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
+ "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.1",
+ "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helpers": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/template": "^7.27.1",
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz",
+ "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.27.1",
+ "@babel/types": "^7.27.1",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz",
+ "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
+ "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
+ "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.27.1"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
@@ -60,6 +485,57 @@
"node": ">=6.9.0"
}
},
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz",
+ "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/template": "^7.27.1",
+ "@babel/types": "^7.27.1",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
+ "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true
+ },
"node_modules/@docuseal/react": {
"version": "1.0.66",
"resolved": "https://registry.npmjs.org/@docuseal/react/-/react-1.0.66.tgz",
@@ -303,6 +779,511 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/reporters": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.7.0",
+ "jest-config": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-resolve-dependencies": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/core/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@jest/core/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@jest/core/node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/core/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/@jest/environment": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "dev": true,
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
+ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -894,6 +1875,30 @@
"integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
"dev": true
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -919,11 +1924,299 @@
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
}
},
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "5.17.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
+ "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==",
+ "dev": true,
+ "dependencies": {
+ "@adobe/css-tools": "^4.0.1",
+ "@babel/runtime": "^7.9.2",
+ "@types/testing-library__jest-dom": "^5.9.1",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.5.6",
+ "lodash": "^4.17.15",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@testing-library/react": {
+ "version": "13.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz",
+ "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^8.5.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/@testing-library/dom": {
+ "version": "8.20.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
+ "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.1.3",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/aria-query": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+ "dev": true,
+ "dependencies": {
+ "deep-equal": "^2.0.5"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/deep-equal": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
+ "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.5",
+ "es-get-iterator": "^1.1.3",
+ "get-intrinsic": "^1.2.2",
+ "is-arguments": "^1.1.1",
+ "is-array-buffer": "^3.0.2",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.1",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/@testing-library/react/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
+ "node_modules/@types/graceful-fs": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
+ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
@@ -933,6 +2226,83 @@
"hoist-non-react-statics": "^3.3.0"
}
},
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "29.5.14",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
+ "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.0.0",
+ "pretty-format": "^29.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@types/jest/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/@types/jsdom": {
+ "version": "20.0.1",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
+ "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
+ }
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -945,6 +2315,20 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
+ "node_modules/@types/node": {
+ "version": "22.15.21",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
+ "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
+ "devOptional": true,
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
+ },
"node_modules/@types/quill": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
@@ -954,19 +2338,65 @@
}
},
"node_modules/@types/react": {
- "version": "19.1.3",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz",
- "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==",
+ "version": "18.3.22",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz",
+ "integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==",
"dependencies": {
+ "@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "devOptional": true,
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
"node_modules/@types/semver": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
"integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
"dev": true
},
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true
+ },
+ "node_modules/@types/testing-library__jest-dom": {
+ "version": "5.14.9",
+ "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
+ "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==",
+ "dev": true,
+ "dependencies": {
+ "@types/jest": "*"
+ }
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "dev": true
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.33",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz",
@@ -1236,6 +2666,13 @@
"win32"
]
},
+ "node_modules/abab": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+ "deprecated": "Use your platform's native atob() and btoa() methods instead",
+ "dev": true
+ },
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -1248,6 +2685,16 @@
"node": ">=0.4.0"
}
},
+ "node_modules/acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ }
+ },
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -1257,6 +2704,30 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.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==",
+ "dev": true,
+ "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",
@@ -1273,6 +2744,33 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -1517,6 +3015,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true
+ },
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
@@ -1595,6 +3099,116 @@
"node": ">= 0.4"
}
},
+ "node_modules/babel-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
+ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/transform": "^29.7.0",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.8.0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
+ "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "dev": true,
+ "dependencies": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1664,11 +3278,26 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "dependencies": {
+ "node-int64": "^0.4.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=="
},
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -1733,6 +3362,15 @@
"node": ">=6"
}
},
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -1776,6 +3414,15 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -1810,6 +3457,27 @@
"node": ">= 6"
}
},
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
+ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
+ "dev": true
+ },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
@@ -1820,6 +3488,57 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/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==",
+ "dev": true
+ },
+ "node_modules/cliui/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==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/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==",
+ "dev": true,
+ "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/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
@@ -1828,6 +3547,22 @@
"node": ">=0.8"
}
},
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "dev": true
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1844,6 +3579,18 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -1858,6 +3605,12 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
@@ -1866,6 +3619,125 @@
"node": ">= 0.6"
}
},
+ "node_modules/create-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "prompts": "^2.0.1"
+ },
+ "bin": {
+ "create-jest": "bin/create-jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/create-jest/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/create-jest/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/create-jest/node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/create-jest/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/create-jest/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1879,6 +3751,12 @@
"node": ">= 8"
}
},
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1890,6 +3768,30 @@
"node": ">=4"
}
},
+ "node_modules/cssom": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+ "dev": true
+ },
+ "node_modules/cssstyle": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "dev": true,
+ "dependencies": {
+ "cssom": "~0.3.6"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cssstyle/node_modules/cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+ "dev": true
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -1901,6 +3803,20 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "node_modules/data-urls": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "dev": true,
+ "dependencies": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -1989,6 +3905,20 @@
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="
},
+ "node_modules/dedent": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
+ "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
+ "dev": true,
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
@@ -2014,6 +3944,15 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -2046,6 +3985,34 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -2056,6 +4023,15 @@
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -2095,6 +4071,25 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true
+ },
+ "node_modules/domexception": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
+ "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "dev": true,
+ "dependencies": {
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2127,11 +4122,44 @@
"integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==",
"dev": true
},
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
"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=="
},
+ "node_modules/entities": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
+ "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.23.9",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
@@ -2213,6 +4241,26 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-get-iterator": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+ "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "is-arguments": "^1.1.1",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.7",
+ "isarray": "^2.0.5",
+ "stop-iteration-iterator": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/es-iterator-helpers": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
@@ -2316,6 +4364,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
"node_modules/eslint": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
@@ -2707,6 +4776,19 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -2754,6 +4836,60 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/expect-utils": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -2823,6 +4959,15 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -2912,6 +5057,21 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "dev": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -2957,6 +5117,19 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -2993,6 +5166,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -3024,6 +5215,15 @@
"node": ">=6"
}
},
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -3036,6 +5236,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
@@ -3119,6 +5331,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/globalthis": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
@@ -3268,6 +5489,72 @@
"react-is": "^16.7.0"
}
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-encoding": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dev": true,
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ics": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/ics/-/ics-3.8.1.tgz",
@@ -3303,6 +5590,25 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -3312,6 +5618,15 @@
"node": ">=0.8.19"
}
},
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -3386,6 +5701,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -3557,6 +5878,15 @@
"node": ">=8"
}
},
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/is-generator-function": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
@@ -3631,6 +5961,12 @@
"node": ">=8"
}
},
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -3675,6 +6011,18 @@
"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==",
+ "dev": true,
+ "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",
@@ -3777,6 +6125,84 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "dev": true,
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -3811,6 +6237,939 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.7.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
+ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
+ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.7.0",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.7.0",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-circus/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-cli": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
+ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "create-jest": "^29.7.0",
+ "exit": "^0.1.2",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-cli/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-cli/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-cli/node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-cli/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-cli/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-diff/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-docblock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "dev": true,
+ "dependencies": {
+ "detect-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
+ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-each/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-environment-jsdom": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz",
+ "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/jsdom": "^20.0.0",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jsdom": "^20.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
+ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
+ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.2"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
+ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-message-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+ "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-message-util/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-mock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
+ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "dev": true,
+ "dependencies": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-leak-detector": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-resolve": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/globals": "^29.7.0",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
+ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
+ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-validate/node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/jest-watcher": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.7.0",
+ "string-length": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
@@ -3844,12 +7203,75 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsdom": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+ "dev": true,
+ "dependencies": {
+ "abab": "^2.0.6",
+ "acorn": "^8.8.1",
+ "acorn-globals": "^7.0.0",
+ "cssom": "^0.5.0",
+ "cssstyle": "^2.3.0",
+ "data-urls": "^3.0.2",
+ "decimal.js": "^10.4.2",
+ "domexception": "^4.0.0",
+ "escodegen": "^2.0.0",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.2",
+ "parse5": "^7.1.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.2",
+ "w3c-xmlserializer": "^4.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0",
+ "ws": "^8.11.0",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -3862,6 +7284,18 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -3937,6 +7371,15 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -3955,6 +7398,15 @@
"node": ">=0.10"
}
},
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -4056,6 +7508,15 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
"node_modules/lucide-react": {
"version": "0.453.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.453.0.tgz",
@@ -4064,6 +7525,51 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -4072,6 +7578,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4092,6 +7604,45 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
@@ -4351,6 +7902,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -4374,6 +7931,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.20",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
+ "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+ "dev": true
+ },
"node_modules/oauth": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
@@ -4540,6 +8115,21 @@
"wrappy": "1"
}
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/openid-client": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz",
@@ -4634,6 +8224,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
@@ -4651,6 +8250,36 @@
"node": ">=6"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4777,6 +8406,70 @@
"node": ">= 6"
}
},
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"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",
@@ -4937,6 +8630,19 @@
}
]
},
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4953,6 +8659,18 @@
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
},
+ "node_modules/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -4962,6 +8680,28 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ]
+ },
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5227,6 +8967,19 @@
"node": ">= 12.13.0"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
@@ -5276,6 +9029,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -5295,6 +9063,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -5313,6 +9102,15 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/resolve.exports": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
+ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -5465,6 +9263,24 @@
"node": ">=10"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -5628,6 +9444,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -5645,6 +9467,15 @@
"atomic-sleep": "^1.0.0"
}
},
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5653,6 +9484,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -5661,12 +9502,52 @@
"node": ">= 10.x"
}
},
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
"node_modules/stable-hash": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
"integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
"dev": true
},
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -5675,6 +9556,19 @@
"node": ">=10.0.0"
}
},
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -5874,6 +9768,27 @@
"node": ">=4"
}
},
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -5952,6 +9867,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
+ },
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
@@ -6030,6 +9951,41 @@
}
}
},
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -6110,6 +10066,12 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -6126,6 +10088,33 @@
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
},
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "dev": true,
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
@@ -6184,6 +10173,15 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
@@ -6301,6 +10299,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "devOptional": true
+ },
"node_modules/universal-cookie": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz",
@@ -6310,6 +10314,15 @@
"cookie": "^0.7.2"
}
},
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/unrs-resolver": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz",
@@ -6381,6 +10394,16 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@@ -6447,6 +10470,84 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+ "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "dev": true,
+ "dependencies": {
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dev": true,
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6648,6 +10749,76 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
+ "node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/write-file-atomic/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/ws": {
+ "version": "8.18.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
+ "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
"node_modules/yaml": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
@@ -6659,6 +10830,53 @@
"node": ">= 14"
}
},
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/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==",
+ "dev": true
+ },
+ "node_modules/yargs/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==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -6684,16 +10902,357 @@
}
},
"dependencies": {
+ "@adobe/css-tools": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz",
+ "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==",
+ "dev": true
+ },
"@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="
},
+ "@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "@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==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz",
+ "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==",
+ "dev": true
+ },
+ "@babel/core": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
+ "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
+ "dev": true,
+ "requires": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.1",
+ "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helpers": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/template": "^7.27.1",
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz",
+ "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.27.1",
+ "@babel/types": "^7.27.1",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ }
+ },
+ "@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==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ }
+ },
+ "@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==",
+ "dev": true,
+ "requires": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz",
+ "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true
+ },
+ "@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==",
+ "dev": true
+ },
+ "@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==",
+ "dev": true
+ },
+ "@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==",
+ "dev": true
+ },
+ "@babel/helpers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
+ "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
+ "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.27.1"
+ }
+ },
+ "@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ }
+ },
+ "@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ }
+ },
+ "@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-jsx": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ }
+ },
+ "@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ }
+ },
"@babel/runtime": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog=="
},
+ "@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz",
+ "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/template": "^7.27.1",
+ "@babel/types": "^7.27.1",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
+ "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ }
+ },
+ "@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true
+ },
"@docuseal/react": {
"version": "1.0.66",
"resolved": "https://registry.npmjs.org/@docuseal/react/-/react-1.0.66.tgz",
@@ -6883,6 +11442,394 @@
}
}
},
+ "@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "dependencies": {
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true
+ }
+ }
+ },
+ "@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true
+ },
+ "@jest/console": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0"
+ }
+ },
+ "@jest/core": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^29.7.0",
+ "@jest/reporters": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.7.0",
+ "jest-config": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-resolve-dependencies": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ }
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "@jest/environment": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "dev": true,
+ "requires": {
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0"
+ }
+ },
+ "@jest/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "dev": true,
+ "requires": {
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
+ }
+ },
+ "@jest/expect-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "dev": true,
+ "requires": {
+ "jest-get-type": "^29.6.3"
+ }
+ },
+ "@jest/fake-timers": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ }
+ },
+ "@jest/globals": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
+ }
+ },
+ "@jest/reporters": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "dev": true,
+ "requires": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
+ }
+ },
+ "@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "requires": {
+ "@sinclair/typebox": "^0.27.8"
+ }
+ },
+ "@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ }
+ },
+ "@jest/test-result": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ }
+ },
+ "@jest/test-sequencer": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
+ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "dev": true,
+ "requires": {
+ "@jest/test-result": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "slash": "^3.0.0"
+ }
+ },
+ "@jest/transform": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ }
+ },
+ "@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ }
+ },
"@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -7197,6 +12144,30 @@
"integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
"dev": true
},
+ "@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
+ "@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "requires": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
"@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -7219,11 +12190,251 @@
"mini-svg-data-uri": "^1.2.3"
}
},
+ "@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "peer": true
+ },
+ "aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "peer": true,
+ "requires": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ }
+ },
+ "react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "peer": true
+ }
+ }
+ },
+ "@testing-library/jest-dom": {
+ "version": "5.17.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
+ "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==",
+ "dev": true,
+ "requires": {
+ "@adobe/css-tools": "^4.0.1",
+ "@babel/runtime": "^7.9.2",
+ "@types/testing-library__jest-dom": "^5.9.1",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.5.6",
+ "lodash": "^4.17.15",
+ "redent": "^3.0.0"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ }
+ }
+ },
+ "@testing-library/react": {
+ "version": "13.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz",
+ "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^8.5.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "dependencies": {
+ "@testing-library/dom": {
+ "version": "8.20.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
+ "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.1.3",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ }
+ },
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "aria-query": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
+ "dev": true,
+ "requires": {
+ "deep-equal": "^2.0.5"
+ }
+ },
+ "deep-equal": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
+ "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
+ "dev": true,
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.5",
+ "es-get-iterator": "^1.1.3",
+ "get-intrinsic": "^1.2.2",
+ "is-arguments": "^1.1.1",
+ "is-array-buffer": "^3.0.2",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.1",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.13"
+ }
+ },
+ "pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ }
+ },
+ "react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true
+ }
+ }
+ },
+ "@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "requires": {}
+ },
+ "@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "dev": true
+ },
+ "@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true
+ },
+ "@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@types/babel__traverse": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.20.7"
+ }
+ },
"@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
+ "@types/graceful-fs": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
+ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/hoist-non-react-statics": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
@@ -7233,6 +12444,76 @@
"hoist-non-react-statics": "^3.3.0"
}
},
+ "@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true
+ },
+ "@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "@types/jest": {
+ "version": "29.5.14",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
+ "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
+ "dev": true,
+ "requires": {
+ "expect": "^29.0.0",
+ "pretty-format": "^29.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "@types/jsdom": {
+ "version": "20.0.1",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
+ "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
+ }
+ },
"@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -7245,6 +12526,20 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
+ "@types/node": {
+ "version": "22.15.21",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
+ "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
+ "devOptional": true,
+ "requires": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
+ },
"@types/quill": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
@@ -7254,19 +12549,63 @@
}
},
"@types/react": {
- "version": "19.1.3",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz",
- "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==",
+ "version": "18.3.22",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz",
+ "integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==",
"requires": {
+ "@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
+ "@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "devOptional": true,
+ "requires": {}
+ },
"@types/semver": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
"integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
"dev": true
},
+ "@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true
+ },
+ "@types/testing-library__jest-dom": {
+ "version": "5.14.9",
+ "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
+ "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==",
+ "dev": true,
+ "requires": {
+ "@types/jest": "*"
+ }
+ },
+ "@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "dev": true
+ },
+ "@types/yargs": {
+ "version": "17.0.33",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "dev": true,
+ "requires": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true
+ },
"@typescript-eslint/eslint-plugin": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz",
@@ -7423,12 +12762,28 @@
"dev": true,
"optional": true
},
+ "abab": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+ "dev": true
+ },
"acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true
},
+ "acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ }
+ },
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -7436,6 +12791,24 @@
"dev": true,
"requires": {}
},
+ "acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.11.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "requires": {
+ "debug": "4"
+ }
+ },
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -7448,6 +12821,23 @@
"uri-js": "^4.2.2"
}
},
+ "ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.21.3"
+ },
+ "dependencies": {
+ "type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true
+ }
+ }
+ },
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -7623,6 +13013,12 @@
"integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
"dev": true
},
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true
+ },
"atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
@@ -7663,6 +13059,94 @@
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true
},
+ "babel-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
+ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "dev": true,
+ "requires": {
+ "@jest/transform": "^29.7.0",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ }
+ },
+ "babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "dependencies": {
+ "istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ }
+ }
+ }
+ },
+ "babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ }
+ },
+ "babel-preset-current-node-syntax": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
+ "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
+ "dev": true,
+ "requires": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ }
+ },
+ "babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "dev": true,
+ "requires": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ }
+ },
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -7703,11 +13187,26 @@
"update-browserslist-db": "^1.1.3"
}
},
+ "bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "requires": {
+ "node-int64": "^0.4.0"
+ }
+ },
"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=="
},
+ "buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
"busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -7751,6 +13250,12 @@
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
"camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -7771,6 +13276,12 @@
"supports-color": "^7.1.0"
}
},
+ "char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true
+ },
"chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -7796,6 +13307,18 @@
}
}
},
+ "ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true
+ },
+ "cjs-module-lexer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
+ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
+ "dev": true
+ },
"classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
@@ -7806,11 +13329,64 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
+ "cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "dependencies": {
+ "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==",
+ "dev": true
+ },
+ "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==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "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==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ }
+ }
+ },
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
},
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true
+ },
+ "collect-v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "dev": true
+ },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -7824,6 +13400,15 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
"commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -7835,11 +13420,101 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
"cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
},
+ "create-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "prompts": "^2.0.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ }
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
"cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -7850,11 +13525,40 @@
"which": "^2.0.1"
}
},
+ "css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
},
+ "cssom": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+ "dev": true
+ },
+ "cssstyle": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "dev": true,
+ "requires": {
+ "cssom": "~0.3.6"
+ },
+ "dependencies": {
+ "cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+ "dev": true
+ }
+ }
+ },
"csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -7866,6 +13570,17 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "data-urls": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "dev": true,
+ "requires": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0"
+ }
+ },
"data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -7923,6 +13638,13 @@
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="
},
+ "dedent": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
+ "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
+ "dev": true,
+ "requires": {}
+ },
"deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
@@ -7942,6 +13664,12 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true
+ },
"define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -7962,6 +13690,25 @@
"object-keys": "^1.1.1"
}
},
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true
+ },
+ "dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "peer": true
+ },
+ "detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true
+ },
"detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -7972,6 +13719,12 @@
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
+ "diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true
+ },
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -8005,6 +13758,21 @@
"esutils": "^2.0.2"
}
},
+ "dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true
+ },
+ "domexception": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
+ "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+ "dev": true,
+ "requires": {
+ "webidl-conversions": "^7.0.0"
+ }
+ },
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -8034,11 +13802,32 @@
"integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==",
"dev": true
},
+ "emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true
+ },
"emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
+ "entities": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
+ "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
+ "dev": true
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"es-abstract": {
"version": "1.23.9",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
@@ -8108,6 +13897,23 @@
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
},
+ "es-get-iterator": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+ "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "is-arguments": "^1.1.1",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.7",
+ "isarray": "^2.0.5",
+ "stop-iteration-iterator": "^1.0.0"
+ }
+ },
"es-iterator-helpers": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
@@ -8184,6 +13990,18 @@
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
+ "escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "requires": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2",
+ "source-map": "~0.6.1"
+ }
+ },
"eslint": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
@@ -8474,6 +14292,12 @@
"eslint-visitor-keys": "^3.4.1"
}
},
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
"esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -8509,6 +14333,50 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
+ "execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "dependencies": {
+ "signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ }
+ }
+ },
+ "exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true
+ },
+ "expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "dev": true,
+ "requires": {
+ "@jest/expect-utils": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0"
+ }
+ },
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -8571,6 +14439,15 @@
"reusify": "^1.0.4"
}
},
+ "fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "requires": {
+ "bser": "2.1.1"
+ }
+ },
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -8633,6 +14510,18 @@
"signal-exit": "^4.0.1"
}
},
+ "form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "dev": true,
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ }
+ },
"fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -8655,6 +14544,12 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "optional": true
+ },
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -8679,6 +14574,18 @@
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
},
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
"get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -8701,6 +14608,12 @@
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
},
+ "get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true
+ },
"get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -8710,6 +14623,12 @@
"es-object-atoms": "^1.0.0"
}
},
+ "get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true
+ },
"get-symbol-description": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
@@ -8768,6 +14687,12 @@
"is-glob": "^4.0.3"
}
},
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
"globalthis": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
@@ -8866,6 +14791,57 @@
"react-is": "^16.7.0"
}
},
+ "html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dev": true,
+ "requires": {
+ "whatwg-encoding": "^2.0.0"
+ }
+ },
+ "html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dev": true,
+ "requires": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ }
+ },
+ "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==",
+ "dev": true,
+ "requires": {
+ "agent-base": "6",
+ "debug": "4"
+ }
+ },
+ "human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true
+ },
+ "iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ }
+ },
"ics": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/ics/-/ics-3.8.1.tgz",
@@ -8892,12 +14868,28 @@
"resolve-from": "^4.0.0"
}
},
+ "import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "requires": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ }
+ },
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true
},
+ "indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true
+ },
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -8956,6 +14948,12 @@
"get-intrinsic": "^1.2.6"
}
},
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
"is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -9066,6 +15064,12 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
+ "is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true
+ },
"is-generator-function": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
@@ -9113,6 +15117,12 @@
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true
},
+ "is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true
+ },
"is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -9139,6 +15149,12 @@
"call-bound": "^1.0.3"
}
},
+ "is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true
+ },
"is-string": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
@@ -9205,6 +15221,65 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
+ "istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true
+ },
+ "istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true
+ }
+ }
+ },
+ "istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "requires": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ }
+ },
+ "istanbul-reports": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "dev": true,
+ "requires": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ }
+ },
"iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -9228,6 +15303,713 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "dev": true,
+ "requires": {
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.7.0"
+ }
+ },
+ "jest-changed-files": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
+ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "dev": true,
+ "requires": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0"
+ }
+ },
+ "jest-circus": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
+ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.7.0",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.7.0",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-cli": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
+ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "dev": true,
+ "requires": {
+ "@jest/core": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "create-jest": "^29.7.0",
+ "exit": "^0.1.2",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "yargs": "^17.3.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ }
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-docblock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "dev": true,
+ "requires": {
+ "detect-newline": "^3.0.0"
+ }
+ },
+ "jest-each": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
+ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-environment-jsdom": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz",
+ "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/jsdom": "^20.0.0",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jsdom": "^20.0.0"
+ }
+ },
+ "jest-environment-node": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
+ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ }
+ },
+ "jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true
+ },
+ "jest-haste-map": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
+ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "fsevents": "^2.3.2",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ }
+ },
+ "jest-leak-detector": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
+ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "dev": true,
+ "requires": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-message-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+ "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-mock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.7.0"
+ }
+ },
+ "jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "requires": {}
+ },
+ "jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "dev": true
+ },
+ "jest-resolve": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ }
+ },
+ "jest-resolve-dependencies": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
+ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "dev": true,
+ "requires": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
+ }
+ },
+ "jest-runner": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "dev": true,
+ "requires": {
+ "@jest/console": "^29.7.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-leak-detector": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-resolve": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ }
+ },
+ "jest-runtime": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "dev": true,
+ "requires": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/globals": "^29.7.0",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true
+ }
+ }
+ },
+ "jest-snapshot": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
+ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true
+ }
+ }
+ },
+ "jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ }
+ },
+ "jest-validate": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
+ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.7.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ }
+ }
+ },
+ "jest-watcher": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "dev": true,
+ "requires": {
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.7.0",
+ "string-length": "^4.0.1"
+ }
+ },
+ "jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
@@ -9252,12 +16034,58 @@
"argparse": "^2.0.1"
}
},
+ "jsdom": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+ "dev": true,
+ "requires": {
+ "abab": "^2.0.6",
+ "acorn": "^8.8.1",
+ "acorn-globals": "^7.0.0",
+ "cssom": "^0.5.0",
+ "cssstyle": "^2.3.0",
+ "data-urls": "^3.0.2",
+ "decimal.js": "^10.4.2",
+ "domexception": "^4.0.0",
+ "escodegen": "^2.0.0",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.2",
+ "parse5": "^7.1.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.2",
+ "w3c-xmlserializer": "^4.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0",
+ "ws": "^8.11.0",
+ "xml-name-validator": "^4.0.0"
+ }
+ },
+ "jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true
+ },
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
+ "json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -9270,6 +16098,12 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true
+ },
"jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -9334,6 +16168,12 @@
"json-buffer": "3.0.1"
}
},
+ "kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true
+ },
"language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -9349,6 +16189,12 @@
"language-subtag-registry": "^0.3.20"
}
},
+ "leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true
+ },
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -9432,17 +16278,64 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
+ }
+ },
"lucide-react": {
"version": "0.453.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.453.0.tgz",
"integrity": "sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==",
"requires": {}
},
+ "lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true
+ },
+ "make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "requires": {
+ "semver": "^7.5.3"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true
+ }
+ }
+ },
+ "makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "requires": {
+ "tmpl": "1.0.5"
+ }
+ },
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -9457,6 +16350,33 @@
"picomatch": "^2.3.1"
}
},
+ "mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.52.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true
+ },
+ "min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true
+ },
"mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
@@ -9601,6 +16521,12 @@
"lilconfig": "^3.1.2"
}
},
+ "node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true
+ },
"node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -9618,6 +16544,21 @@
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true
},
+ "npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.0.0"
+ }
+ },
+ "nwsapi": {
+ "version": "2.2.20",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
+ "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+ "dev": true
+ },
"oauth": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
@@ -9733,6 +16674,15 @@
"wrappy": "1"
}
},
+ "onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
"openid-client": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz",
@@ -9802,6 +16752,12 @@
"p-limit": "^3.0.2"
}
},
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
"parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
@@ -9816,6 +16772,27 @@
"callsites": "^3.0.0"
}
},
+ "parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ }
+ },
+ "parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "requires": {
+ "entities": "^6.0.0"
+ }
+ },
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -9911,6 +16888,54 @@
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="
},
+ "pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "requires": {
+ "find-up": "^4.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ }
+ }
+ },
"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",
@@ -9996,6 +17021,16 @@
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz",
"integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="
},
+ "prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "requires": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ }
+ },
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -10012,12 +17047,33 @@
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
},
+ "psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.3.1"
+ }
+ },
"punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true
},
+ "pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true
+ },
+ "querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true
+ },
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -10185,6 +17241,16 @@
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="
},
+ "redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "requires": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ }
+ },
"redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
@@ -10222,6 +17288,18 @@
"set-function-name": "^2.0.2"
}
},
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true
+ },
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true
+ },
"resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -10232,6 +17310,23 @@
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
+ "resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^5.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true
+ }
+ }
+ },
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -10244,6 +17339,12 @@
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true
},
+ "resolve.exports": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
+ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
+ "dev": true
+ },
"reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -10331,6 +17432,21 @@
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="
},
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "requires": {
+ "xmlchars": "^2.2.0"
+ }
+ },
"scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -10446,6 +17562,12 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
},
+ "sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true
+ },
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -10460,27 +17582,86 @@
"atomic-sleep": "^1.0.0"
}
},
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
"source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
+ "source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
},
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
"stable-hash": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
"integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
"dev": true
},
+ "stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true
+ }
+ }
+ },
+ "stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "requires": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ }
+ },
"streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
},
+ "string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "requires": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ }
+ },
"string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -10625,6 +17806,21 @@
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"dev": true
},
+ "strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true
+ },
+ "strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "requires": {
+ "min-indent": "^1.0.0"
+ }
+ },
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -10667,6 +17863,12 @@
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
+ "symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true
+ },
"tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
@@ -10712,6 +17914,33 @@
}
}
},
+ "test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "requires": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
+ }
+ },
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -10772,6 +18001,12 @@
}
}
},
+ "tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true
+ },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -10785,6 +18020,27 @@
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
},
+ "tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "dev": true,
+ "requires": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ }
+ },
+ "tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.1"
+ }
+ },
"ts-api-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
@@ -10834,6 +18090,12 @@
"prelude-ls": "^1.2.1"
}
},
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
"type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
@@ -10911,6 +18173,12 @@
"which-boxed-primitive": "^1.1.1"
}
},
+ "undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "devOptional": true
+ },
"universal-cookie": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz",
@@ -10920,6 +18188,12 @@
"cookie": "^0.7.2"
}
},
+ "universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true
+ },
"unrs-resolver": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz",
@@ -10965,6 +18239,16 @@
"punycode": "^2.1.0"
}
},
+ "url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "requires": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@@ -11001,6 +18285,66 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
+ "v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ }
+ },
+ "w3c-xmlserializer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+ "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "dev": true,
+ "requires": {
+ "xml-name-validator": "^4.0.0"
+ }
+ },
+ "walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "requires": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true
+ },
+ "whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dev": true,
+ "requires": {
+ "iconv-lite": "0.6.3"
+ }
+ },
+ "whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true
+ },
+ "whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dev": true,
+ "requires": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ }
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -11139,11 +18483,100 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
+ "write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "dependencies": {
+ "signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ }
+ }
+ },
+ "ws": {
+ "version": "8.18.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
+ "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true
+ },
+ "xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true
+ },
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
"yaml": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="
},
+ "yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "requires": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "dependencies": {
+ "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==",
+ "dev": true
+ },
+ "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==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true
+ },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/Front-End/package.json b/Front-End/package.json
index 46c0e26..537c959 100644
--- a/Front-End/package.json
+++ b/Front-End/package.json
@@ -8,7 +8,10 @@
"start": "next start",
"lint": "next lint",
"lint-light": "next lint --quiet",
- "check-strings": "node scripts/check-hardcoded-strings.js"
+ "check-strings": "node scripts/check-hardcoded-strings.js",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "test:coverage": "jest --coverage"
},
"dependencies": {
"@docuseal/react": "^1.0.56",
@@ -37,10 +40,15 @@
"react-tooltip": "^5.28.0"
},
"devDependencies": {
+ "@testing-library/react": "^13.4.0",
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/user-event": "^14.4.3",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
"autoprefixer": "^10.4.20",
"eslint": "^8",
"eslint-config-next": "14.2.11",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14"
}
-}
+}
\ No newline at end of file
diff --git a/Front-End/prod.env b/Front-End/prod.env
index 70fb338..99015de 100644
--- a/Front-End/prod.env
+++ b/Front-End/prod.env
@@ -1,4 +1,5 @@
NEXT_PUBLIC_API_URL=_NEXT_PUBLIC_API_URL_
+NEXT_PUBLIC_WSAPI_URL=_NEXT_PUBLIC_WSAPI_URL_
NEXT_PUBLIC_USE_FAKE_DATA=_NEXT_PUBLIC_USE_FAKE_DATA_
AUTH_SECRET=_AUTH_SECRET_
NEXTAUTH_URL=_NEXTAUTH_URL_
diff --git a/Front-End/src/app/[locale]/admin/layout.js b/Front-End/src/app/[locale]/admin/layout.js
index d454130..00763c5 100644
--- a/Front-End/src/app/[locale]/admin/layout.js
+++ b/Front-End/src/app/[locale]/admin/layout.js
@@ -3,7 +3,6 @@ import React, { useState, useEffect } from 'react';
import Sidebar from '@/components/Sidebar';
import { usePathname } from 'next/navigation';
import { useTranslations } from 'next-intl';
-import Image from 'next/image';
import {
LayoutDashboard,
FileText,
@@ -13,11 +12,8 @@ import {
Calendar,
Settings,
LogOut,
- Menu,
- X,
- Mail,
+ MessageSquare,
} from 'lucide-react';
-import DropdownMenu from '@/components/DropdownMenu';
import Popup from '@/components/Popup';
import {
@@ -86,7 +82,7 @@ export default function Layout({ children }) {
id: 'messagerie',
name: t('messagerie'),
url: FE_ADMIN_MESSAGERIE_URL,
- icon: Mail,
+ icon: MessageSquare,
},
settings: {
id: 'settings',
diff --git a/Front-End/src/app/[locale]/admin/messagerie/page.js b/Front-End/src/app/[locale]/admin/messagerie/page.js
index be3e460..b06dbc8 100644
--- a/Front-End/src/app/[locale]/admin/messagerie/page.js
+++ b/Front-End/src/app/[locale]/admin/messagerie/page.js
@@ -1,5 +1,5 @@
'use client';
-import React from 'react';
+import React, { useEffect } from 'react';
import SidebarTabs from '@/components/SidebarTabs';
import EmailSender from '@/components/Admin/EmailSender';
import InstantMessaging from '@/components/Admin/InstantMessaging';
@@ -26,11 +26,8 @@ export default function MessageriePage({ csrfToken }) {
];
return (
-
-
logger.debug(`Onglet actif : ${tabId}`)}
- />
+
+
);
}
diff --git a/Front-End/src/app/[locale]/parents/layout.js b/Front-End/src/app/[locale]/parents/layout.js
index 50e29de..a57944f 100644
--- a/Front-End/src/app/[locale]/parents/layout.js
+++ b/Front-End/src/app/[locale]/parents/layout.js
@@ -1,31 +1,64 @@
'use client';
// src/components/Layout.js
-import React, { useState } from 'react';
-import ProfileSelector from '@/components/ProfileSelector';
-import { useRouter } from 'next/navigation'; // Ajout de l'importation
-import { MessageSquare, LogOut, Settings, Home } from 'lucide-react'; // Ajout de l'importation de l'icône Home
+import React, { useState, useEffect } from 'react';
+import Sidebar from '@/components/Sidebar';
+import { useRouter, usePathname } from 'next/navigation';
+import { MessageSquare, Settings, Home, Menu } from 'lucide-react';
import {
FE_PARENTS_HOME_URL,
FE_PARENTS_MESSAGERIE_URL,
FE_PARENTS_SETTINGS_URL,
-} from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent
+} from '@/utils/Url';
import ProtectedRoute from '@/components/ProtectedRoute';
import { disconnect } from '@/app/actions/authAction';
import Popup from '@/components/Popup';
-import logger from '@/utils/logger';
-import { getRightStr, RIGHTS } from '@/utils/rights';
+import { RIGHTS } from '@/utils/rights';
import { useEstablishment } from '@/context/EstablishmentContext';
-
import Footer from '@/components/Footer';
export default function Layout({ children }) {
- const router = useRouter(); // Définition de router
- const [messages, setMessages] = useState([]);
+ const router = useRouter();
+ const pathname = usePathname();
const [isPopupVisible, setIsPopupVisible] = useState(false);
- const { profileRole, user, clearContext } = useEstablishment();
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+ const { clearContext } = useEstablishment();
const softwareName = 'N3WT School';
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
+ // Vérifier si on est sur la page messagerie
+ const isMessagingPage = pathname?.includes('/messagerie');
+
+ // Configuration des éléments de la sidebar pour les parents
+ const sidebarItems = [
+ {
+ id: 'home',
+ name: 'Accueil',
+ url: FE_PARENTS_HOME_URL,
+ icon: Home,
+ },
+ {
+ id: 'messagerie',
+ name: 'Messagerie',
+ url: FE_PARENTS_MESSAGERIE_URL,
+ icon: MessageSquare,
+ },
+ {
+ id: 'settings',
+ name: 'Paramètres',
+ url: FE_PARENTS_SETTINGS_URL,
+ icon: Settings,
+ },
+ ];
+
+ // Déterminer la page actuelle pour la sidebar
+ const getCurrentPage = () => {
+ if (pathname?.includes('/messagerie')) return 'messagerie';
+ if (pathname?.includes('/settings')) return 'settings';
+ return 'home';
+ };
+
+ const currentPage = getCurrentPage();
+
const handleDisconnect = () => {
setIsPopupVisible(true);
};
@@ -35,52 +68,63 @@ export default function Layout({ children }) {
disconnect();
clearContext();
};
+
+ const toggleSidebar = () => {
+ setIsSidebarOpen(!isSidebarOpen);
+ };
+
+ useEffect(() => {
+ // Fermer la sidebar quand on change de page sur mobile
+ setIsSidebarOpen(false);
+ }, [pathname]);
+
return (
-
- {/* Entête */}
-
-
- {/* Content */}
-
- {' '}
- {/* Ajout de flex-1 pour utiliser toute la hauteur disponible */}
- {children}
-
- {/* Footer responsive */}
-
+ {/* Sidebar */}
+
+
+
+ {/* Overlay for mobile */}
+ {isSidebarOpen && (
+
+ )}
+
+ {/* Main container */}
+
+ {children}
+
+
+ {/* Footer */}
+
+
+
setIsPopupVisible(false)}
+ />
);
}
diff --git a/Front-End/src/app/[locale]/parents/messagerie/page.js b/Front-End/src/app/[locale]/parents/messagerie/page.js
index d900ae3..69086e2 100644
--- a/Front-End/src/app/[locale]/parents/messagerie/page.js
+++ b/Front-End/src/app/[locale]/parents/messagerie/page.js
@@ -1,15 +1,28 @@
'use client';
-import React, { useEffect, useState } from 'react';
-import Chat from '@/components/Chat';
-
+import React from 'react';
+import InstantChat from '@/components/Chat/InstantChat';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function MessageriePage() {
const { user, selectedEstablishmentId } = useEstablishment();
- if (!user) return Chargement...
;
+ if (!user?.user_id || !selectedEstablishmentId) {
+ return (
+
+
+
+
Chargement de la messagerie...
+
+
+ );
+ }
return (
-
+
+
+
);
}
diff --git a/Front-End/src/app/actions/emailAction.js b/Front-End/src/app/actions/emailAction.js
new file mode 100644
index 0000000..45c4583
--- /dev/null
+++ b/Front-End/src/app/actions/emailAction.js
@@ -0,0 +1,34 @@
+import {
+ BE_GESTIONEMAIL_SEARCH_RECIPIENTS_URL,
+ BE_GESTIONEMAIL_SEND_EMAIL_URL,
+} from '@/utils/Url';
+import { errorHandler, requestResponseHandler } from './actionsHandlers';
+import { getCsrfToken } from '@/utils/getCsrfToken';
+
+// Recherche de destinataires pour email
+export const searchRecipients = (establishmentId, query) => {
+ const url = `${BE_GESTIONEMAIL_SEARCH_RECIPIENTS_URL}/?establishment_id=${establishmentId}&q=${encodeURIComponent(query)}`;
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(requestResponseHandler)
+ .catch(errorHandler);
+};
+// Envoyer un email
+export const sendEmail = async (messageData) => {
+ const csrfToken = getCsrfToken();
+ return fetch(BE_GESTIONEMAIL_SEND_EMAIL_URL, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': csrfToken,
+ },
+ credentials: 'include',
+ body: JSON.stringify(messageData),
+ })
+ .then(requestResponseHandler)
+ .catch(errorHandler);
+};
diff --git a/Front-End/src/app/actions/messagerieAction.js b/Front-End/src/app/actions/messagerieAction.js
index a2d0900..b73a41e 100644
--- a/Front-End/src/app/actions/messagerieAction.js
+++ b/Front-End/src/app/actions/messagerieAction.js
@@ -1,67 +1,251 @@
import {
BE_GESTIONMESSAGERIE_CONVERSATIONS_URL,
- BE_GESTIONMESSAGERIE_CONVERSATION_MESSAGES_URL,
- BE_GESTIONMESSAGERIE_MARK_AS_READ_URL,
- BE_GESTIONMESSAGERIE_MESSAGES_URL,
+ BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL,
+ BE_GESTIONMESSAGERIE_CREATE_CONVERSATION_URL,
BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL,
+ BE_GESTIONMESSAGERIE_MARK_AS_READ_URL,
+ BE_GESTIONMESSAGERIE_UPLOAD_FILE_URL,
+ BE_GESTIONMESSAGERIE_DELETE_CONVERSATION_URL,
} from '@/utils/Url';
import { errorHandler, requestResponseHandler } from './actionsHandlers';
+import logger from '@/utils/logger';
-export const fetchConversations = (profileId) => {
- return fetch(`${BE_GESTIONMESSAGERIE_CONVERSATIONS_URL}/${profileId}/`, {
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(requestResponseHandler)
- .catch(errorHandler);
+// Helper pour construire les en-têtes avec CSRF
+const buildHeaders = (csrfToken) => {
+ const headers = {
+ 'Content-Type': 'application/json',
+ };
+
+ // Ajouter le token CSRF
+ if (csrfToken) {
+ headers['X-CSRFToken'] = csrfToken;
+ }
+
+ return headers;
};
-export const fetchMessages = (conversationId) => {
- return fetch(
- `${BE_GESTIONMESSAGERIE_CONVERSATION_MESSAGES_URL}/${conversationId}/`,
- {
- headers: {
- 'Content-Type': 'application/json',
- },
+/**
+ * Récupère les conversations d'un utilisateur
+ */
+export const fetchConversations = async (userId, csrfToken) => {
+ try {
+ // Utiliser la nouvelle route avec user_id en paramètre d'URL
+ const url = `${BE_GESTIONMESSAGERIE_CONVERSATIONS_URL}/user/${userId}/`;
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: buildHeaders(csrfToken),
+ credentials: 'include',
+ });
+ return await requestResponseHandler(response);
+ } catch (error) {
+ logger.error('Erreur lors de la récupération des conversations:', error);
+ return errorHandler(error);
+ }
+};
+
+/**
+ * Récupère les messages d'une conversation
+ */
+export const fetchMessages = async (
+ conversationId,
+ page = 1,
+ limit = 50,
+ csrfToken,
+ userId = null
+) => {
+ try {
+ // Utiliser la nouvelle URL avec conversation_id en paramètre d'URL
+ let url = `${BE_GESTIONMESSAGERIE_CONVERSATIONS_URL}/${conversationId}/messages/?page=${page}&limit=${limit}`;
+
+ // Ajouter user_id si fourni pour calculer correctement is_read
+ if (userId) {
+ url += `&user_id=${userId}`;
}
- )
- .then(requestResponseHandler)
- .catch(errorHandler);
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: buildHeaders(csrfToken),
+ credentials: 'include',
+ });
+ return await requestResponseHandler(response);
+ } catch (error) {
+ logger.error('Erreur lors de la récupération des messages:', error);
+ return errorHandler(error);
+ }
};
-export const sendMessage = (data) => {
- return fetch(`${BE_GESTIONMESSAGERIE_MESSAGES_URL}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(data),
- })
- .then(requestResponseHandler)
- .catch(errorHandler);
+/**
+ * Envoie un message dans une conversation
+ */
+export const sendMessage = async (messageData, csrfToken) => {
+ try {
+ const response = await fetch(BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL, {
+ method: 'POST',
+ headers: buildHeaders(csrfToken),
+ credentials: 'include',
+ body: JSON.stringify(messageData),
+ });
+ return await requestResponseHandler(response);
+ } catch (error) {
+ logger.error("Erreur lors de l'envoi du message:", error);
+ return errorHandler(error);
+ }
};
-export const markAsRead = (conversationId, profileId) => {
- return fetch(`${BE_GESTIONMESSAGERIE_MARK_AS_READ_URL}/${conversationId}/`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ profile_id: profileId }),
- })
- .then(requestResponseHandler)
- .catch(errorHandler);
+/**
+ * Crée une nouvelle conversation
+ */
+export const createConversation = async (participantIds, csrfToken) => {
+ try {
+ const requestBody = {
+ participant_ids: participantIds, // Le backend attend "participant_ids"
+ conversation_type: 'private', // Spécifier le type de conversation
+ name: '', // Le nom sera généré côté backend
+ };
+
+ const response = await fetch(BE_GESTIONMESSAGERIE_CREATE_CONVERSATION_URL, {
+ method: 'POST',
+ headers: buildHeaders(csrfToken),
+ credentials: 'include',
+ body: JSON.stringify(requestBody),
+ });
+
+ return await requestResponseHandler(response);
+ } catch (error) {
+ logger.error('Erreur lors de la création de la conversation:', error);
+ return errorHandler(error);
+ }
};
-export const searchRecipients = (establishmentId, query) => {
- const url = `${BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL}/?establishment_id=${establishmentId}&q=${encodeURIComponent(query)}`;
- return fetch(url, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(requestResponseHandler)
- .catch(errorHandler);
+/**
+ * Recherche des destinataires pour la messagerie
+ */
+export const searchMessagerieRecipients = async (
+ establishmentId,
+ query,
+ csrfToken
+) => {
+ try {
+ const baseUrl = BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL.endsWith('/')
+ ? BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL
+ : BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL + '/';
+
+ const url = `${baseUrl}?establishment_id=${establishmentId}&q=${encodeURIComponent(query)}`;
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: buildHeaders(csrfToken),
+ credentials: 'include',
+ });
+
+ return await requestResponseHandler(response);
+ } catch (error) {
+ logger.error('Erreur lors de la recherche des destinataires:', error);
+ return errorHandler(error);
+ }
+};
+
+/**
+ * Marque des messages comme lus
+ */
+export const markAsRead = async (conversationId, userId, csrfToken) => {
+ try {
+ const response = await fetch(BE_GESTIONMESSAGERIE_MARK_AS_READ_URL, {
+ method: 'POST',
+ headers: buildHeaders(csrfToken),
+ credentials: 'include',
+ body: JSON.stringify({
+ conversation_id: conversationId,
+ user_id: userId,
+ }),
+ });
+ return await requestResponseHandler(response);
+ } catch (error) {
+ logger.error('Erreur lors du marquage des messages comme lus:', error);
+ return errorHandler(error);
+ }
+};
+
+/**
+ * Upload un fichier pour la messagerie
+ */
+export const uploadFile = async (
+ file,
+ conversationId,
+ senderId,
+ csrfToken,
+ onProgress = null
+) => {
+ const formData = new FormData();
+ formData.append('file', file);
+ formData.append('conversation_id', conversationId);
+ formData.append('sender_id', senderId);
+
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+
+ if (onProgress) {
+ xhr.upload.addEventListener('progress', (event) => {
+ if (event.lengthComputable) {
+ const percentComplete = (event.loaded / event.total) * 100;
+ onProgress(percentComplete);
+ }
+ });
+ }
+
+ xhr.addEventListener('load', () => {
+ if (xhr.status >= 200 && xhr.status < 300) {
+ try {
+ const response = JSON.parse(xhr.responseText);
+ resolve(response);
+ } catch (error) {
+ reject(new Error('Réponse invalide du serveur'));
+ }
+ } else {
+ try {
+ const errorResponse = JSON.parse(xhr.responseText);
+ reject(new Error(errorResponse.message || "Erreur lors de l'upload"));
+ } catch {
+ reject(new Error(`Erreur HTTP: ${xhr.status}`));
+ }
+ }
+ });
+
+ xhr.addEventListener('error', () => {
+ reject(new Error("Erreur réseau lors de l'upload"));
+ });
+
+ xhr.addEventListener('timeout', () => {
+ reject(new Error("Timeout lors de l'upload"));
+ });
+
+ xhr.open('POST', BE_GESTIONMESSAGERIE_UPLOAD_FILE_URL);
+ xhr.withCredentials = true;
+ xhr.timeout = 30000;
+
+ // Ajouter le header CSRF pour XMLHttpRequest
+ if (csrfToken) {
+ xhr.setRequestHeader('X-CSRFToken', csrfToken);
+ }
+
+ xhr.send(formData);
+ });
+};
+
+/**
+ * Supprime une conversation
+ */
+export const deleteConversation = async (conversationId, csrfToken) => {
+ try {
+ const url = `${BE_GESTIONMESSAGERIE_DELETE_CONVERSATION_URL}/${conversationId}/`;
+ const response = await fetch(url, {
+ method: 'DELETE',
+ headers: buildHeaders(csrfToken),
+ credentials: 'include',
+ });
+ return await requestResponseHandler(response);
+ } catch (error) {
+ logger.error('Erreur lors de la suppression de la conversation:', error);
+ return errorHandler(error);
+ }
};
diff --git a/Front-End/src/components/Admin/EmailSender.js b/Front-End/src/components/Admin/EmailSender.js
index 81cbf05..89ce8d4 100644
--- a/Front-End/src/components/Admin/EmailSender.js
+++ b/Front-End/src/components/Admin/EmailSender.js
@@ -2,7 +2,7 @@
import React, { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
-import { sendMessage, searchRecipients } from '@/app/actions/messagerieAction';
+import { sendEmail, searchRecipients } from '@/app/actions/emailAction';
import { fetchSmtpSettings } from '@/app/actions/settingsAction';
import { useNotification } from '@/context/NotificationContext';
import { useEstablishment } from '@/context/EstablishmentContext';
@@ -56,7 +56,7 @@ export default function EmailSender({ csrfToken }) {
};
try {
- await sendMessage(data);
+ await sendEmail(data);
showNotification('Email envoyé avec succès.', 'success', 'Succès');
// Réinitialiser les champs après succès
setRecipients([]);
diff --git a/Front-End/src/components/Admin/InstantMessaging.js b/Front-End/src/components/Admin/InstantMessaging.js
index b24bec4..fa039bb 100644
--- a/Front-End/src/components/Admin/InstantMessaging.js
+++ b/Front-End/src/components/Admin/InstantMessaging.js
@@ -1,88 +1,17 @@
-// filepath: d:\Dev\n3wt-innov\n3wt-school\Front-End\src\components\Admin\InstantMessaging.js
import React, { useEffect, useState } from 'react';
-import Chat from '@/components/Chat';
+import InstantChat from '@/components/Chat/InstantChat';
import { useEstablishment } from '@/context/EstablishmentContext';
-import {
- fetchConversations,
- sendMessage,
- searchRecipients,
-} from '@/app/actions/messagerieAction';
-import RecipientInput from '@/components/RecipientInput';
-import Modal from '@/components/Modal';
+import logger from '@/utils/logger';
export default function InstantMessaging({ csrfToken }) {
const { user, selectedEstablishmentId } = useEstablishment();
- const [discussions, setDiscussions] = useState([]);
- const [recipients, setRecipients] = useState([]); // Liste des correspondants sélectionnés
- const [showModal, setShowModal] = useState(false);
- const [firstMessage, setFirstMessage] = useState('');
-
- useEffect(() => {
- if (user) {
- fetchConversations(user.id).then(setDiscussions);
- }
- }, [user]);
-
- // Fonction pour ajouter une nouvelle discussion avec plusieurs correspondants
- const handleCreateDiscussion = async () => {
- if (!user || recipients.length === 0 || !firstMessage.trim()) return;
- for (const recipient of recipients) {
- await sendMessage({
- emetteur: user.id,
- destinataire: recipient.id,
- objet: '',
- corpus: firstMessage,
- conversation_id: undefined, // L'API générera un nouvel ID
- });
- }
- setRecipients([]);
- setFirstMessage('');
- setShowModal(false);
- fetchConversations(user.id).then(setDiscussions);
- };
-
- if (!user) return Chargement...
;
return (
-
);
}
diff --git a/Front-End/src/components/Chat.js b/Front-End/src/components/Chat.js
deleted file mode 100644
index 594bac3..0000000
--- a/Front-End/src/components/Chat.js
+++ /dev/null
@@ -1,175 +0,0 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { SendHorizontal, Plus } from 'lucide-react';
-import Image from 'next/image';
-import {
- fetchConversations,
- fetchMessages,
- sendMessage,
- markAsRead,
-} from '@/app/actions/messagerieAction';
-
-export default function Chat({
- userProfileId,
- establishmentId,
- discussions: discussionsProp,
- setDiscussions: setDiscussionsProp,
- onCreateDiscussion,
- onShowCreateDiscussion,
-}) {
- const [discussions, setDiscussions] = useState(discussionsProp || []);
- const [selectedDiscussion, setSelectedDiscussion] = useState(null);
- const [messages, setMessages] = useState([]);
- const [newMessage, setNewMessage] = useState('');
- const messagesEndRef = useRef(null);
-
- useEffect(() => {
- if (userProfileId) {
- fetchConversations(userProfileId).then(setDiscussions);
- }
- }, [userProfileId]);
-
- useEffect(() => {
- if (selectedDiscussion) {
- fetchMessages(selectedDiscussion.conversation_id).then(setMessages);
- // Marquer comme lu
- markAsRead(selectedDiscussion.conversation_id, userProfileId);
- }
- }, [selectedDiscussion, userProfileId]);
-
- const handleSendMessage = async () => {
- if (newMessage.trim() && selectedDiscussion) {
- await sendMessage({
- conversation_id: selectedDiscussion.conversation_id,
- emetteur: userProfileId,
- destinataire: selectedDiscussion.interlocuteur.id,
- corpus: newMessage,
- objet: '',
- });
- setNewMessage('');
- fetchMessages(selectedDiscussion.conversation_id).then(setMessages);
- fetchConversations(userProfileId).then(setDiscussions);
- }
- };
-
- const handleKeyPress = (event) => {
- if (event.key === 'Enter') {
- handleSendMessage();
- }
- };
-
- return (
-
- {/* Bandeau droit : Liste des discussions */}
-
-
-
- {discussions && discussions.length > 0 ? (
- discussions.map((discussion) => (
-
setSelectedDiscussion(discussion)}
- >
-
-
-
{discussion.name}
-
- {discussion.lastMessage}
-
-
-
- {discussion.lastMessageDate &&
- new Date(discussion.lastMessageDate).toLocaleTimeString()}
-
-
- ))
- ) : (
-
Aucune discussion disponible.
- )}
-
-
-
- {/* Zone de chat */}
-
- {/* En-tête du chat */}
- {selectedDiscussion && (
-
-
-
{selectedDiscussion.name}
-
- )}
- {/* Messages */}
-
- {selectedDiscussion &&
- messages.map((message) => (
-
-
-
{message.corpus}
-
- {message.date &&
- new Date(message.date).toLocaleTimeString()}
-
-
-
- ))}
-
-
- {/* Champ de saisie */}
- {selectedDiscussion && (
-
- setNewMessage(e.target.value)}
- className="flex-1 p-2 border border-gray-300 rounded-lg mr-2"
- placeholder="Écrire un message..."
- onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
- />
-
-
-
-
- )}
-
-
- );
-}
diff --git a/Front-End/src/components/Chat/ConnectionStatus.js b/Front-End/src/components/Chat/ConnectionStatus.js
new file mode 100644
index 0000000..39262d3
--- /dev/null
+++ b/Front-End/src/components/Chat/ConnectionStatus.js
@@ -0,0 +1,79 @@
+import React from 'react';
+import { Wifi, WifiOff, RotateCcw } from 'lucide-react';
+
+const ConnectionStatus = ({ status, onReconnect }) => {
+ const getStatusInfo = () => {
+ switch (status) {
+ case 'connected':
+ return {
+ icon: ,
+ text: 'Connecté',
+ className: 'text-green-600 bg-green-50 border-green-200',
+ };
+ case 'disconnected':
+ return {
+ icon: ,
+ text: 'Déconnecté',
+ className: 'text-red-600 bg-red-50 border-red-200',
+ };
+ case 'reconnecting':
+ return {
+ icon: ,
+ text: 'Reconnexion...',
+ className: 'text-yellow-600 bg-yellow-50 border-yellow-200',
+ };
+ case 'error':
+ return {
+ icon: ,
+ text: 'Erreur de connexion',
+ className: 'text-red-600 bg-red-50 border-red-200',
+ };
+ case 'failed':
+ return {
+ icon: ,
+ text: 'Connexion échouée',
+ className: 'text-red-600 bg-red-50 border-red-200',
+ };
+ default:
+ return {
+ icon: ,
+ text: 'Inconnu',
+ className: 'text-gray-600 bg-gray-50 border-gray-200',
+ };
+ }
+ };
+
+ if (status === 'connected') {
+ return (
+
+ );
+ }
+
+ const { icon, text, className } = getStatusInfo();
+
+ return (
+
+
+ {icon}
+ {text}
+
+ {(status === 'failed' || status === 'error') && onReconnect && (
+
+ Réessayer
+
+ )}
+
+ );
+};
+
+export default ConnectionStatus;
diff --git a/Front-End/src/components/Chat/ConversationItem.js b/Front-End/src/components/Chat/ConversationItem.js
new file mode 100644
index 0000000..8c2b105
--- /dev/null
+++ b/Front-End/src/components/Chat/ConversationItem.js
@@ -0,0 +1,196 @@
+import React from 'react';
+import { User, Trash2 } from 'lucide-react';
+import { getGravatarUrl } from '@/utils/gravatar';
+
+const ConversationItem = ({
+ conversation,
+ isSelected,
+ onClick,
+ onDelete, // Nouvelle prop pour la suppression
+ unreadCount = 0,
+ lastMessage,
+ isTyping = false,
+ userPresences = {}, // Nouveau prop pour les statuts de présence
+}) => {
+ const formatTime = (dateString) => {
+ if (!dateString) return '';
+ const date = new Date(dateString);
+ const now = new Date();
+ const diffInHours = (now - date) / (1000 * 60 * 60);
+
+ if (diffInHours < 24) {
+ return date.toLocaleTimeString('fr-FR', {
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ } else {
+ return date.toLocaleDateString('fr-FR', {
+ day: '2-digit',
+ month: '2-digit',
+ });
+ }
+ };
+
+ const getInterlocutorName = () => {
+ if (conversation.interlocuteur) {
+ // Si nous avons le nom et prénom, les utiliser
+ if (
+ conversation.interlocuteur.first_name &&
+ conversation.interlocuteur.last_name
+ ) {
+ return `${conversation.interlocuteur.first_name} ${conversation.interlocuteur.last_name}`;
+ }
+ // Sinon, utiliser l'email comme fallback
+ if (conversation.interlocuteur.email) {
+ return conversation.interlocuteur.email;
+ }
+ }
+ return conversation.name || 'Utilisateur inconnu';
+ };
+
+ const getLastMessageText = () => {
+ if (isTyping) {
+ return (
+ Tape un message...
+ );
+ }
+
+ if (lastMessage) {
+ return lastMessage.content || lastMessage.corpus || 'Message...';
+ }
+
+ if (conversation.last_message) {
+ return (
+ conversation.last_message.content ||
+ conversation.last_message.corpus ||
+ 'Message...'
+ );
+ }
+
+ return 'Aucun message';
+ };
+
+ const getLastMessageTime = () => {
+ if (lastMessage) {
+ return formatTime(lastMessage.created_at || lastMessage.date_envoi);
+ }
+
+ if (conversation.last_message) {
+ return formatTime(
+ conversation.last_message.created_at ||
+ conversation.last_message.date_envoi
+ );
+ }
+
+ return '';
+ };
+
+ const getUserPresenceStatus = () => {
+ if (conversation.interlocuteur?.id) {
+ const presence = userPresences[conversation.interlocuteur.id];
+ return presence?.status || 'offline';
+ }
+ return 'offline';
+ };
+
+ const getPresenceColor = (status) => {
+ switch (status) {
+ case 'online':
+ return 'bg-emerald-400';
+ case 'away':
+ return 'bg-yellow-400';
+ case 'busy':
+ return 'bg-red-400';
+ case 'offline':
+ default:
+ return 'bg-gray-400';
+ }
+ };
+
+ const getPresenceLabel = (status) => {
+ switch (status) {
+ case 'online':
+ return 'En ligne';
+ case 'away':
+ return 'Absent';
+ case 'busy':
+ return 'Occupé';
+ case 'offline':
+ default:
+ return 'Hors ligne';
+ }
+ };
+
+ const presenceStatus = getUserPresenceStatus();
+
+ return (
+
+ {/* Avatar */}
+
+
+
+ {/* Indicateur de statut en ligne */}
+
+
+
+ {/* Contenu de la conversation */}
+
+
+
+ {getInterlocutorName()}
+
+
+ {unreadCount > 0 && (
+
+ )}
+
+ {getLastMessageTime()}
+
+ {/* Bouton de suppression */}
+ {onDelete && (
+ {
+ e.stopPropagation(); // Empêcher la sélection de la conversation
+ onDelete();
+ }}
+ className="opacity-0 group-hover:opacity-100 hover:bg-red-100 p-1 rounded transition-all duration-200"
+ title="Supprimer la conversation"
+ >
+
+
+ )}
+
+
+
+
+ {getLastMessageText()}
+
+
+
+ );
+};
+
+export default ConversationItem;
diff --git a/Front-End/src/components/Chat/FileAttachment.js b/Front-End/src/components/Chat/FileAttachment.js
new file mode 100644
index 0000000..547933c
--- /dev/null
+++ b/Front-End/src/components/Chat/FileAttachment.js
@@ -0,0 +1,115 @@
+import React from 'react';
+import {
+ Download,
+ FileText,
+ Image,
+ Film,
+ Music,
+ Archive,
+ AlertCircle,
+} from 'lucide-react';
+
+const FileAttachment = ({
+ fileName,
+ fileSize,
+ fileType,
+ fileUrl,
+ onDownload = null,
+}) => {
+ // Obtenir l'icône en fonction du type de fichier
+ const getFileIcon = (type) => {
+ if (type.startsWith('image/')) {
+ return ;
+ }
+ if (type.startsWith('video/')) {
+ return ;
+ }
+ if (type.startsWith('audio/')) {
+ return ;
+ }
+ if (type.includes('pdf')) {
+ return ;
+ }
+ if (type.includes('zip') || type.includes('rar')) {
+ return ;
+ }
+ return ;
+ };
+
+ // Formater la taille du fichier
+ const formatFileSize = (bytes) => {
+ if (!bytes) return '';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ };
+
+ // Gérer le téléchargement
+ const handleDownload = () => {
+ if (onDownload) {
+ onDownload();
+ } else if (fileUrl) {
+ const link = document.createElement('a');
+ link.href = fileUrl;
+ link.download = fileName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+ };
+
+ // Vérifier si c'est une image pour afficher un aperçu
+ const isImage = fileType && fileType.startsWith('image/');
+
+ return (
+
+ {isImage && fileUrl ? (
+ // Affichage pour les images
+
+
window.open(fileUrl, '_blank')}
+ />
+
+
+
+
+
+ {fileName && (
+
{fileName}
+ )}
+
+ ) : (
+ // Affichage pour les autres fichiers
+
+
{getFileIcon(fileType)}
+
+
+ {fileName || 'Fichier sans nom'}
+
+ {fileSize && (
+
+ {formatFileSize(fileSize)}
+
+ )}
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default FileAttachment;
diff --git a/Front-End/src/components/Chat/FileUpload.js b/Front-End/src/components/Chat/FileUpload.js
new file mode 100644
index 0000000..99721fc
--- /dev/null
+++ b/Front-End/src/components/Chat/FileUpload.js
@@ -0,0 +1,179 @@
+import React, { useState } from 'react';
+import { X, Upload, FileText, Image, AlertCircle } from 'lucide-react';
+
+const FileUpload = ({
+ file,
+ onUpload,
+ onCancel,
+ conversationId,
+ senderId,
+ maxSize = 10 * 1024 * 1024, // 10MB par défaut
+}) => {
+ const [uploadProgress, setUploadProgress] = useState(0);
+ const [isUploading, setIsUploading] = useState(false);
+ const [error, setError] = useState(null);
+
+ // Vérifier le type de fichier et obtenir l'icône appropriée
+ const getFileIcon = (fileType) => {
+ if (fileType.startsWith('image/')) {
+ return ;
+ }
+ return ;
+ };
+
+ // Formater la taille du fichier
+ const formatFileSize = (bytes) => {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ };
+
+ // Vérifier si le fichier est valide
+ const isValidFile = () => {
+ const allowedTypes = [
+ 'image/jpeg',
+ 'image/jpg',
+ 'image/png',
+ 'image/gif',
+ 'image/webp',
+ 'application/pdf',
+ 'application/msword',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.ms-excel',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'text/plain',
+ ];
+
+ if (!allowedTypes.includes(file.type)) {
+ return false;
+ }
+
+ if (file.size > maxSize) {
+ return false;
+ }
+
+ return true;
+ };
+
+ // Gérer l'upload
+ const handleUpload = async () => {
+ if (!isValidFile()) {
+ setError('Type de fichier non autorisé ou fichier trop volumineux');
+ return;
+ }
+
+ setIsUploading(true);
+ setError(null);
+
+ try {
+ const result = await onUpload(
+ file,
+ conversationId,
+ senderId,
+ setUploadProgress
+ );
+ // L'upload s'est bien passé, le parent gère la suite
+ } catch (error) {
+ setError(error.message || "Erreur lors de l'upload");
+ setIsUploading(false);
+ }
+ };
+
+ if (!file) return null;
+
+ return (
+
+
+
+ {getFileIcon(file.type)}
+
+
+ {file.name}
+
+
{formatFileSize(file.size)}
+
+
+
+
+
+
+
+ {/* Prévisualisation pour les images */}
+ {file.type.startsWith('image/') && (
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+
+ )}
+
+ {/* Validation du fichier */}
+ {!isValidFile() && (
+
+
+
+ {file.size > maxSize
+ ? `Fichier trop volumineux (max ${formatFileSize(maxSize)})`
+ : 'Type de fichier non autorisé'}
+
+
+ )}
+
+ {/* Erreur d'upload */}
+ {error && (
+
+ )}
+
+ {/* Barre de progression */}
+ {isUploading && (
+
+
+ Upload en cours...
+
+ {Math.round(uploadProgress)}%
+
+
+
+
+ )}
+
+ {/* Boutons d'action */}
+
+
+ Annuler
+
+
+
+ {isUploading ? 'Upload...' : 'Envoyer'}
+
+
+
+ );
+};
+
+export default FileUpload;
diff --git a/Front-End/src/components/Chat/InstantChat.js b/Front-End/src/components/Chat/InstantChat.js
new file mode 100644
index 0000000..f2b9fb7
--- /dev/null
+++ b/Front-End/src/components/Chat/InstantChat.js
@@ -0,0 +1,1162 @@
+import React, { useState, useEffect, useRef, useCallback } from 'react';
+import { MessageSquare, Plus, Search, Trash2 } from 'lucide-react';
+import { useSession } from 'next-auth/react';
+import { useCsrfToken } from '@/context/CsrfContext';
+import { useNotification } from '@/context/NotificationContext';
+import { useChatConnection } from '@/context/ChatConnectionContext';
+import { useEstablishment } from '@/context/EstablishmentContext';
+import { format, isSameMinute } from 'date-fns';
+import { fr } from 'date-fns/locale';
+import ConnectionStatus from './ConnectionStatus';
+import ConversationItem from './ConversationItem';
+import MessageBubble from './MessageBubble';
+import MessageInput from './MessageInput';
+import TypingIndicator from './TypingIndicator';
+import logger from '@/utils/logger';
+import { getGravatarUrl } from '@/utils/gravatar';
+import Popup from '@/components/Popup';
+import {
+ fetchConversations,
+ fetchMessages,
+ searchMessagerieRecipients,
+ createConversation,
+ deleteConversation,
+} from '@/app/actions/messagerieAction';
+
+// Fonction pour grouper les messages consécutifs du même utilisateur dans la même minute
+const groupMessages = (messages) => {
+ if (!messages || messages.length === 0) return [];
+
+ const groups = [];
+ let currentGroup = null;
+
+ messages.forEach((message, index) => {
+ const messageTime = new Date(message.created_at || message.date_envoi);
+ const senderId =
+ message.sender?.id || message.sender_id || message.emetteur;
+ const senderInfo = message.sender || null;
+
+ // Vérifier si ce message peut être groupé avec le précédent
+ const canGroup =
+ currentGroup &&
+ currentGroup.senderId === senderId &&
+ isSameMinute(new Date(currentGroup.lastMessageTime), messageTime);
+
+ if (canGroup) {
+ // Ajouter au groupe existant
+ currentGroup.messages.push(message);
+ currentGroup.lastMessageTime = message.created_at || message.date_envoi;
+ } else {
+ // Créer un nouveau groupe
+ currentGroup = {
+ senderId,
+ senderInfo,
+ messages: [message],
+ firstMessageTime: message.created_at || message.date_envoi,
+ lastMessageTime: message.created_at || message.date_envoi,
+ isFirstInGroup: true,
+ };
+ groups.push(currentGroup);
+ }
+ });
+
+ return groups;
+};
+
+const InstantChat = ({ userProfileId, establishmentId }) => {
+ // Hook de notification
+ const { showNotification } = useNotification();
+
+ // Hook pour récupérer l'utilisateur connecté
+ const { user } = useEstablishment();
+
+ // Hook de connexion au chat
+ const {
+ isConnected,
+ connectionStatus,
+ connectToChat,
+ sendMessage: sendChatMessage,
+ getWebSocket,
+ addMessageCallback, // Ajouter le callback
+ userPresences, // Récupérer les présences du contexte
+ } = useChatConnection();
+
+ // États pour les conversations et messages
+ const [conversations, setConversations] = useState([]);
+ const csrfToken = useCsrfToken();
+ const [selectedConversation, setSelectedConversation] = useState(null);
+ const [messages, setMessages] = useState([]);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [searchResults, setSearchResults] = useState([]);
+ const [showSearch, setShowSearch] = useState(false);
+
+ // États pour l'interface utilisateur
+ const [typingUsers, setTypingUsers] = useState([]);
+ const [unreadCounts, setUnreadCounts] = useState({});
+ const [loading, setLoading] = useState(false);
+ const [processedMessages, setProcessedMessages] = useState(new Set()); // Éviter les doublons
+
+ // États pour la confirmation de suppression
+ const [confirmDeleteVisible, setConfirmDeleteVisible] = useState(false);
+ const [conversationToDelete, setConversationToDelete] = useState(null);
+
+ // Refs
+ const messagesEndRef = useRef(null);
+ const messagesContainerRef = useRef(null);
+
+ // Fonction pour faire défiler vers le bas
+ const scrollToBottom = useCallback(() => {
+ if (messagesEndRef.current) {
+ messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, []);
+
+ // Gestionnaire des messages WebSocket
+ const handleWebSocketMessage = useCallback(
+ (data) => {
+ // Debug : vérifier userProfileId à chaque message
+ logger.debug('🔍 handleWebSocketMessage appelé:', {
+ messageType: data.type,
+ currentUserProfileId: userProfileId,
+ userProfileIdType: typeof userProfileId,
+ });
+
+ switch (data.type) {
+ case 'conversations_list':
+ setConversations(data.conversations || []);
+ break;
+
+ case 'new_conversation':
+ // Nouvelle conversation créée
+ setConversations((prev) => {
+ const existingIndex = prev.findIndex(
+ (conv) =>
+ conv.conversation_id === data.conversation.conversation_id ||
+ conv.id === data.conversation.id
+ );
+ if (existingIndex >= 0) {
+ // Mettre à jour la conversation existante
+ const updated = [...prev];
+ updated[existingIndex] = data.conversation;
+ return updated;
+ } else {
+ // Ajouter la nouvelle conversation
+ return [data.conversation, ...prev];
+ }
+ });
+
+ // Lorsqu'une nouvelle conversation est reçue,
+ // les présences des participants devraient aussi être diffusées par le backend
+ break;
+
+ case 'new_message':
+ const newMessage = data.message;
+
+ logger.debug('🆕 NOUVEAU MESSAGE WebSocket reçu:', {
+ senderId: newMessage.sender?.id,
+ content: newMessage.content?.substring(0, 50),
+ conversationId:
+ newMessage.conversation || newMessage.conversation_id,
+ timestamp: new Date().toISOString(),
+ });
+
+ // Créer un identifiant unique pour le message
+ const conversationId =
+ newMessage.conversation || newMessage.conversation_id;
+ const messageId =
+ newMessage.id ||
+ newMessage.message_id ||
+ `${conversationId}_${newMessage.sender?.id}_${newMessage.content}_${newMessage.created_at}`;
+
+ // Vérifier si ce message a déjà été traité
+ if (processedMessages.has(messageId)) {
+ logger.debug('🔍 Message déjà traité, ignoré:', {
+ messageId,
+ processedCount: processedMessages.size,
+ });
+ break;
+ }
+
+ logger.debug('🆔 ID unique généré pour le message:', messageId);
+
+ // Marquer le message comme traité
+ setProcessedMessages((prev) => {
+ const newSet = new Set([...prev, messageId]);
+ // Nettoyage automatique si on dépasse 150 messages traités
+ if (newSet.size > 150) {
+ // Garder seulement les 100 plus récents (conversion en array, slice, puis reconversion)
+ const entries = Array.from(newSet);
+ return new Set(entries.slice(-100));
+ }
+ return newSet;
+ });
+
+ // Debug: vérifier le type de message reçu
+ logger.debug('🔍 Message reçu:', {
+ content: newMessage.content?.substring(0, 50),
+ message_type: newMessage.message_type,
+ sender_id: newMessage.sender_id,
+ userProfileId,
+ });
+
+ // Vérifier si le message appartient à la conversation sélectionnée
+ const messageConversationId =
+ newMessage.conversation_id || newMessage.conversation;
+ const selectedConversationId =
+ selectedConversation?.id || selectedConversation?.conversation_id;
+
+ if (messageConversationId === selectedConversationId) {
+ setMessages((prev) => {
+ // Vérifier si le message n'existe pas déjà
+ const messageExists = prev.some(
+ (msg) =>
+ (msg.id && msg.id === newMessage.id) ||
+ (msg.message_id && msg.message_id === newMessage.message_id)
+ );
+
+ if (!messageExists) {
+ return [...prev, newMessage];
+ }
+ return prev;
+ });
+ }
+
+ // Mettre à jour la liste des conversations
+ setConversations((prev) =>
+ prev.map((conv) => {
+ // Utiliser le même pattern que partout ailleurs dans le code
+ const convId = conv.id;
+ const messageConvId = newMessage.conversation;
+
+ if (convId === messageConvId) {
+ // Vérifier si le message doit incrémenter le compteur non lu
+ const isFromCurrentUser =
+ String(newMessage.sender.id) === String(userProfileId);
+
+ // Debug détaillé pour comprendre le problème d'incrémentation
+ logger.debug('🔍 Analyse du message pour compteur:', {
+ messageId: messageId,
+ senderId: newMessage.sender.id,
+ userProfileId: userProfileId,
+ senderIdType: typeof newMessage.sender.id,
+ userProfileIdType: typeof userProfileId,
+ isFromCurrentUser: isFromCurrentUser,
+ currentUnreadCount: conv.unread_count,
+ });
+
+ // Pour les messages WebSocket, is_read n'est pas fiable car le backend
+ // ne passe pas le contexte utilisateur au serializer.
+ // On considère que tous les nouveaux messages WebSocket sont non lus
+ // sauf s'ils viennent de l'utilisateur connecté
+ const shouldIncrementUnread = !isFromCurrentUser;
+
+ if (shouldIncrementUnread) {
+ logger.debug(
+ '🔺 INCRÉMENTATION du compteur non lu pour conversation:',
+ convId
+ );
+ } else {
+ logger.debug(
+ "➡️ Pas d'incrémentation (message de l'utilisateur connecté)"
+ );
+ }
+
+ return {
+ ...conv,
+ last_message: newMessage,
+ // Incrémenter le unread_count seulement si applicable
+ unread_count: shouldIncrementUnread
+ ? (conv.unread_count || 0) + 1
+ : conv.unread_count,
+ };
+ }
+ return conv;
+ })
+ );
+ break;
+
+ case 'typing_status':
+ const { user_id, is_typing, conversation_id, user_name } = data;
+ logger.debug('📝 Typing status reçu:', {
+ user_id,
+ is_typing,
+ conversation_id,
+ user_name,
+ });
+
+ // Identifier l'ID de la conversation sélectionnée (peut être .id ou .conversation_id)
+ const currentConversationId =
+ selectedConversation?.id || selectedConversation?.conversation_id;
+
+ if (
+ currentConversationId &&
+ String(currentConversationId) === String(conversation_id)
+ ) {
+ logger.debug(
+ '📝 Mise à jour typing pour conversation sélectionnée'
+ );
+ setTypingUsers((prev) => {
+ // Utiliser le nom de l'utilisateur s'il est disponible, sinon l'ID
+ const displayName = user_name || `Utilisateur ${user_id}`;
+ logger.debug(
+ '📝 Display name:',
+ displayName,
+ 'is_typing:',
+ is_typing
+ );
+
+ if (is_typing) {
+ const newUsers = prev.includes(displayName)
+ ? prev
+ : [...prev, displayName];
+ logger.debug(
+ '📝 Nouveaux utilisateurs en train de taper:',
+ newUsers
+ );
+ return newUsers;
+ } else {
+ const newUsers = prev.filter((name) => name !== displayName);
+ logger.debug(
+ '📝 Utilisateurs en train de taper après suppression:',
+ newUsers
+ );
+ return newUsers;
+ }
+ });
+ } else {
+ logger.debug('📝 Typing status pour une autre conversation:', {
+ selectedConversationId: currentConversationId,
+ messageConversationId: conversation_id,
+ });
+ }
+ break;
+
+ case 'messages_read':
+ // Marquer les messages comme lus
+ setMessages((prev) =>
+ prev.map((msg) =>
+ msg.conversation_id === data.conversation_id
+ ? { ...msg, is_read: true }
+ : msg
+ )
+ );
+ break;
+
+ case 'presence_update':
+ // Les mises à jour de présence sont maintenant gérées dans le contexte
+ break;
+
+ case 'error':
+ logger.error('Erreur WebSocket:', data.message);
+ break;
+
+ default:
+ // Message WebSocket non géré
+ }
+ },
+ [
+ selectedConversation,
+ userProfileId,
+ processedMessages,
+ setProcessedMessages,
+ ]
+ );
+
+ // Charger les conversations
+ const loadConversations = useCallback(async () => {
+ if (!userProfileId) return;
+
+ try {
+ setLoading(true);
+ const data = await fetchConversations(userProfileId, csrfToken);
+
+ // Vérifier la structure des données des conversations
+ if (data && Array.isArray(data)) {
+ // Structure validée
+ }
+
+ setConversations(data || []);
+ } catch (error) {
+ logger.error('❌ Erreur lors du chargement des conversations:', error);
+ setConversations([]);
+ } finally {
+ setLoading(false);
+ }
+ }, [userProfileId, csrfToken]);
+
+ // Effet pour défiler automatiquement vers le bas quand de nouveaux messages arrivent
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages, scrollToBottom]);
+
+ // Gestionnaire de changement de connexion - version stable
+ const handleConnectionChange = useCallback(
+ (isConnected) => {
+ if (isConnected && userProfileId) {
+ // Éviter de recharger si on est déjà en train de charger
+ loadConversations();
+ }
+ },
+ [userProfileId, loadConversations]
+ );
+
+ // Effet de chargement initial
+ useEffect(() => {
+ if (userProfileId) {
+ loadConversations();
+ }
+ }, [userProfileId, loadConversations]);
+
+ // Effet pour la gestion des messages WebSocket via le système de callbacks
+ useEffect(() => {
+ if (isConnected && addMessageCallback) {
+ // S'abonner aux messages WebSocket via le contexte
+ const unsubscribe = addMessageCallback(handleWebSocketMessage);
+
+ return () => {
+ // Se désabonner au démontage du composant
+ if (unsubscribe && typeof unsubscribe === 'function') {
+ unsubscribe();
+ }
+ };
+ }
+ }, [isConnected, addMessageCallback, handleWebSocketMessage]);
+
+ // Fonctions WebSocket déléguées
+ const sendTypingStart = useCallback(
+ (conversationId) => {
+ return sendChatMessage({
+ type: 'typing_start',
+ conversation_id: conversationId,
+ user_id: userProfileId,
+ });
+ },
+ [sendChatMessage, userProfileId]
+ );
+
+ const sendTypingStop = useCallback(
+ (conversationId) => {
+ return sendChatMessage({
+ type: 'typing_stop',
+ conversation_id: conversationId,
+ user_id: userProfileId,
+ });
+ },
+ [sendChatMessage, userProfileId]
+ );
+
+ const markAsRead = useCallback(
+ (conversationId, userId) => {
+ return sendChatMessage({
+ type: 'mark_as_read',
+ conversation_id: conversationId,
+ user_id: userId,
+ });
+ },
+ [sendChatMessage]
+ );
+
+ const joinConversation = useCallback(
+ (conversationId) => {
+ return sendChatMessage({
+ type: 'join_conversation',
+ conversation_id: conversationId,
+ user_id: userProfileId,
+ });
+ },
+ [sendChatMessage, userProfileId]
+ );
+
+ const reconnect = useCallback(() => {
+ connectToChat(userProfileId);
+ }, [connectToChat, userProfileId]);
+
+ // Charger les messages d'une conversation
+ const loadMessages = useCallback(
+ async (conversationId) => {
+ if (!conversationId) return;
+
+ try {
+ const data = await fetchMessages(
+ conversationId,
+ 1,
+ 50,
+ csrfToken,
+ userProfileId
+ );
+ setMessages(data || []);
+
+ // Marquer comme lu
+ if (userProfileId) {
+ await markAsRead(conversationId, userProfileId);
+ }
+
+ // Réinitialiser le compteur de messages non lus
+ setUnreadCounts((prev) => ({
+ ...prev,
+ [conversationId]: 0,
+ }));
+
+ // Mettre aussi à jour le unread_count dans les conversations
+ setConversations((prev) =>
+ prev.map((conv) =>
+ (conv.id || conv.conversation_id) === conversationId
+ ? { ...conv, unread_count: 0 }
+ : conv
+ )
+ );
+ } catch (error) {
+ logger.error('Erreur lors du chargement des messages:', error);
+ }
+ },
+ [userProfileId, markAsRead, csrfToken]
+ );
+
+ // Recherche de destinataires
+ const handleSearch = async (query) => {
+ setSearchQuery(query);
+
+ if (query.trim() && establishmentId) {
+ try {
+ const results = await searchMessagerieRecipients(
+ establishmentId,
+ query,
+ csrfToken
+ );
+ setSearchResults(results || []);
+ setShowSearch(true);
+ } catch (error) {
+ logger.error('Erreur lors de la recherche:', error);
+ setSearchResults([]);
+ }
+ } else {
+ setSearchResults([]);
+ setShowSearch(false);
+ }
+ };
+
+ // Sélectionner une conversation
+ const selectConversation = useCallback(
+ (conversation) => {
+ logger.debug('🔄 Sélection de la conversation:', conversation);
+ setSelectedConversation(conversation);
+ setTypingUsers([]);
+
+ // Utiliser id ou conversation_id selon ce qui est disponible
+ const conversationId = conversation.id || conversation.conversation_id;
+ logger.debug('🔄 ID de conversation extrait:', conversationId);
+
+ if (conversationId) {
+ logger.debug(
+ '🔄 Chargement des messages pour conversation:',
+ conversationId
+ );
+ loadMessages(conversationId);
+
+ logger.debug(
+ '🔄 Tentative de rejoindre la conversation:',
+ conversationId
+ );
+ const joinResult = joinConversation(conversationId);
+ logger.debug('🔄 Résultat joinConversation:', joinResult);
+ } else {
+ logger.error("❌ Impossible de trouver l'ID de conversation");
+ }
+ },
+ [loadMessages, joinConversation]
+ );
+
+ // Envoyer un message
+ const handleSendMessage = useCallback(
+ (content, attachment = null) => {
+ logger.debug('📤 handleSendMessage appelé:', {
+ content,
+ attachment,
+ selectedConversation,
+ });
+
+ if (!selectedConversation) {
+ logger.warn('❌ Aucune conversation sélectionnée');
+ return;
+ }
+
+ // Vérifier qu'on a soit du contenu, soit un fichier
+ if (!content.trim() && !attachment) {
+ logger.warn('❌ Aucun contenu ni fichier à envoyer');
+ return;
+ }
+
+ // Utiliser id ou conversation_id selon ce qui est disponible
+ const conversationId =
+ selectedConversation.id || selectedConversation.conversation_id;
+ if (!conversationId) {
+ logger.error("❌ Impossible de trouver l'ID de la conversation");
+ return;
+ }
+
+ // Construire le message selon le format attendu par le WebSocket
+ const messageData = {
+ type: 'send_message', // Utiliser 'send_message' au lieu de 'message'
+ conversation_id: conversationId,
+ content: content.trim() || '',
+ user_id: userProfileId,
+ };
+
+ if (attachment) {
+ messageData.attachment = attachment;
+ }
+
+ logger.debug('📤 Envoi du message via WebSocket:', messageData);
+ logger.debug('📤 Type de message:', messageData.type);
+ logger.debug('📤 État de la connexion:', {
+ isConnected,
+ connectionStatus,
+ });
+
+ const success = sendChatMessage(messageData);
+
+ logger.debug('📤 Résultat envoi message:', success);
+
+ if (!success) {
+ logger.error(
+ "❌ Impossible d'envoyer le message - WebSocket non connecté"
+ );
+ } else {
+ logger.debug('✅ Message envoyé avec succès');
+ }
+ },
+ [
+ selectedConversation,
+ sendChatMessage,
+ userProfileId,
+ isConnected,
+ connectionStatus,
+ ]
+ );
+
+ // Gestion des événements de frappe
+ const handleTypingStart = useCallback(() => {
+ if (selectedConversation) {
+ const conversationId =
+ selectedConversation.id || selectedConversation.conversation_id;
+ if (conversationId) {
+ sendTypingStart(conversationId);
+ }
+ }
+ }, [selectedConversation, sendTypingStart]);
+
+ const handleTypingStop = useCallback(() => {
+ if (selectedConversation) {
+ const conversationId =
+ selectedConversation.id || selectedConversation.conversation_id;
+ if (conversationId) {
+ sendTypingStop(conversationId);
+ }
+ }
+ }, [selectedConversation, sendTypingStop]);
+
+ // Créer une nouvelle conversation
+ const handleCreateConversation = useCallback(
+ async (recipientUser) => {
+ try {
+ setLoading(true);
+ logger.debug('Tentative de création de conversation avec:', {
+ userProfileId,
+ recipientId: recipientUser.id,
+ });
+
+ // Vérifier si une conversation existe déjà avec cet utilisateur
+ const existingConversation = conversations.find(
+ (conv) =>
+ conv.participants?.some(
+ (p) => p.participant?.id === recipientUser.id
+ ) || conv.interlocuteur?.id === recipientUser.id
+ );
+
+ if (existingConversation) {
+ logger.debug('Conversation existante trouvée:', existingConversation);
+ // Sélectionner la conversation existante
+ selectConversation(existingConversation);
+ setShowSearch(false);
+ setSearchQuery('');
+ setSearchResults([]);
+ return;
+ }
+
+ // Créer la conversation avec l'utilisateur sélectionné
+ const conversationData = await createConversation(
+ [userProfileId, recipientUser.id],
+ csrfToken
+ );
+
+ if (conversationData) {
+ logger.debug('Conversation créée avec succès:', conversationData);
+
+ // Recharger les conversations pour inclure la nouvelle
+ await loadConversations();
+
+ // Attendre un peu que les conversations se chargent puis sélectionner la nouvelle
+ setTimeout(() => {
+ const newConv = conversations.find(
+ (conv) =>
+ conv.id === conversationData.id ||
+ conv.conversation_id === conversationData.conversation_id
+ );
+ if (newConv) {
+ selectConversation(newConv);
+ } else {
+ // Si on ne trouve pas dans la liste, utiliser directement les données retournées
+ selectConversation(conversationData);
+ }
+ }, 500);
+
+ // Fermer la recherche
+ setShowSearch(false);
+ setSearchQuery('');
+ setSearchResults([]);
+ }
+ } catch (error) {
+ logger.error('Erreur lors de la création de la conversation:', error);
+ } finally {
+ setLoading(false);
+ }
+ },
+ [
+ userProfileId,
+ conversations,
+ loadConversations,
+ selectConversation,
+ csrfToken,
+ ]
+ );
+
+ // Demander la confirmation de suppression d'une conversation
+ const requestDeleteConversation = useCallback((conversation) => {
+ setConversationToDelete(conversation);
+ setConfirmDeleteVisible(true);
+ }, []);
+
+ // Supprimer une conversation (après confirmation)
+ const handleDeleteConversation = useCallback(async () => {
+ if (!conversationToDelete) return;
+
+ try {
+ setLoading(true);
+ logger.debug('Suppression de la conversation:', conversationToDelete);
+
+ const conversationId =
+ conversationToDelete.id || conversationToDelete.conversation_id;
+ const result = await deleteConversation(conversationId, csrfToken);
+
+ if (result.success) {
+ logger.debug('Conversation supprimée avec succès');
+
+ // Retirer la conversation de la liste
+ setConversations((prev) =>
+ prev.filter(
+ (conv) => (conv.id || conv.conversation_id) !== conversationId
+ )
+ );
+
+ // Si c'était la conversation sélectionnée, la désélectionner
+ if (
+ selectedConversation &&
+ (selectedConversation.id || selectedConversation.conversation_id) ===
+ conversationId
+ ) {
+ setSelectedConversation(null);
+ setMessages([]);
+ }
+
+ // Afficher un message de succès
+ showNotification(
+ 'Conversation supprimée avec succès',
+ 'success',
+ 'Suppression réussie'
+ );
+ } else {
+ logger.error('Erreur lors de la suppression:', result.error);
+ showNotification(
+ 'Erreur lors de la suppression de la conversation',
+ 'error',
+ 'Erreur de suppression'
+ );
+ }
+ } catch (error) {
+ logger.error('Erreur lors de la suppression de la conversation:', error);
+ showNotification(
+ 'Erreur lors de la suppression de la conversation',
+ 'error',
+ 'Erreur de suppression'
+ );
+ } finally {
+ setLoading(false);
+ setConfirmDeleteVisible(false);
+ setConversationToDelete(null);
+ }
+ }, [conversationToDelete, selectedConversation, csrfToken, showNotification]);
+
+ // Annuler la suppression
+ const handleCancelDelete = useCallback(() => {
+ setConfirmDeleteVisible(false);
+ setConversationToDelete(null);
+ }, []);
+
+ // Démarrer une nouvelle recherche de conversation
+ const handleStartNewConversation = useCallback(() => {
+ setShowSearch(true);
+ setSearchQuery('');
+ setSearchResults([]);
+ setSelectedConversation(null); // Désélectionner la conversation actuelle
+ // Focus sur la barre de recherche après un court délai
+ setTimeout(() => {
+ const searchInput = document.querySelector(
+ 'input[placeholder*="Rechercher"]'
+ );
+ if (searchInput) {
+ searchInput.focus();
+ }
+ }, 100);
+ }, []);
+
+ // Effet pour le scroll
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages, scrollToBottom]);
+
+ // Rendu
+ return (
+
+ {/* Sidebar des conversations */}
+
+ {/* En-tête */}
+
+
+
+ {/* Barre de recherche */}
+
+
+ handleSearch(e.target.value)}
+ className={`w-full pl-10 pr-4 py-2 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 ${
+ showSearch ? 'focus:ring-emerald-500' : 'focus:ring-emerald-500'
+ }`}
+ />
+ {showSearch && searchQuery && (
+ {
+ setShowSearch(false);
+ setSearchQuery('');
+ setSearchResults([]);
+ }}
+ className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
+ title="Annuler la recherche"
+ >
+ ×
+
+ )}
+
+
+ {/* Statut de connexion */}
+
+
+
+
+
+ {/* Liste des conversations */}
+
+ {loading ? (
+
+ Chargement des conversations...
+
+ ) : showSearch && searchResults.length > 0 ? (
+
+
+ Résultats de recherche
+
+ {searchResults.map((user) => (
+
handleCreateConversation(user)}
+ >
+
+
+ {/* Indicateur de statut en ligne pour les résultats de recherche */}
+
+
+
+
+ {user.first_name && user.last_name
+ ? `${user.first_name} ${user.last_name}`
+ : user.email}
+
+ {user.first_name && user.last_name && (
+
{user.email}
+ )}
+
+ {userPresences[user.id]?.status === 'online'
+ ? 'En ligne'
+ : 'Hors ligne'}
+
+
+
+ ))}
+
+ ) : showSearch ? (
+
+ Recherchez un contact pour démarrer une conversation
+
+ ) : conversations.length > 0 ? (
+
+ {conversations.map((conversation) => (
+ selectConversation(conversation)}
+ onDelete={() => requestDeleteConversation(conversation)}
+ unreadCount={
+ // Utiliser prioritairement le compteur du backend
+ conversation.unread_count !== undefined
+ ? conversation.unread_count
+ : unreadCounts[
+ conversation.id || conversation.conversation_id
+ ] || 0
+ }
+ lastMessage={conversation.last_message}
+ userPresences={userPresences}
+ />
+ ))}
+
+ ) : (
+
+ Aucune conversation disponible. Cliquez sur + pour en créer une.
+
+ )}
+
+
+
+ {/* Zone de chat principale */}
+
+ {selectedConversation ? (
+ <>
+ {/* En-tête de la conversation */}
+
+
+
+
+
+ {/* Indicateur de statut en ligne */}
+
+
+
+
+ {selectedConversation.interlocuteur
+ ? selectedConversation.interlocuteur.first_name &&
+ selectedConversation.interlocuteur.last_name
+ ? `${selectedConversation.interlocuteur.first_name} ${selectedConversation.interlocuteur.last_name}`
+ : selectedConversation.interlocuteur.email ||
+ 'Utilisateur'
+ : selectedConversation.name || 'Conversation'}
+
+
+ {selectedConversation.interlocuteur?.id &&
+ userPresences[selectedConversation.interlocuteur.id]
+ ?.status === 'online'
+ ? 'En ligne'
+ : 'Hors ligne'}
+
+
+
+
+
+
+ {/* Zone des messages */}
+
+ {groupMessages(messages).map((group, groupIndex) => {
+ const isOwnMessage = group.senderId === userProfileId;
+
+ // Utiliser les vraies données du sender au lieu de l'interlocuteur par défaut
+ let senderName = '';
+ let senderEmail = '';
+
+ if (isOwnMessage && user) {
+ // Pour l'utilisateur connecté, utiliser ses données
+ senderName = 'Moi';
+ senderEmail = user.email || '';
+ } else if (!isOwnMessage && group.senderInfo) {
+ // Utiliser les données du sender réel
+ senderName =
+ `${group.senderInfo.first_name || ''} ${group.senderInfo.last_name || ''}`.trim();
+ senderEmail = group.senderInfo.email || '';
+
+ // Fallback sur l'email si pas de nom
+ if (!senderName && senderEmail) {
+ senderName = senderEmail;
+ }
+ } else if (
+ !isOwnMessage &&
+ selectedConversation.interlocuteur
+ ) {
+ // Fallback sur l'interlocuteur de la conversation si pas de données sender
+ senderName =
+ `${selectedConversation.interlocuteur.first_name || ''} ${selectedConversation.interlocuteur.last_name || ''}`.trim();
+ senderEmail = selectedConversation.interlocuteur.email || '';
+
+ // Fallback sur l'email si pas de nom
+ if (!senderName && senderEmail) {
+ senderName = senderEmail;
+ }
+ }
+
+ // Rendre chaque message individuellement avec le nouveau style Discord
+ return group.messages.map((message, messageIndex) => (
+
+ ));
+ })}
+
+
+
+ {/* Indicateur de frappe */}
+
+
+ {/* Zone de saisie */}
+
+ >
+ ) : (
+
+
+
+
+
+
+ Sélectionnez une conversation
+
+
+ Choisissez une conversation dans la liste ou commencez-en une
+ nouvelle
+
+
+
+ )}
+
+
+ {/* Popup de confirmation de suppression */}
+ {confirmDeleteVisible && (
+
+ )}
+
+ );
+};
+
+export default InstantChat;
diff --git a/Front-End/src/components/Chat/MessageBubble.js b/Front-End/src/components/Chat/MessageBubble.js
new file mode 100644
index 0000000..4e60c2f
--- /dev/null
+++ b/Front-End/src/components/Chat/MessageBubble.js
@@ -0,0 +1,133 @@
+import React from 'react';
+import { format, isToday, isYesterday } from 'date-fns';
+import { fr } from 'date-fns/locale';
+import { Check, CheckCheck } from 'lucide-react';
+import FileAttachment from './FileAttachment';
+import { getGravatarUrl } from '@/utils/gravatar';
+
+const MessageBubble = ({
+ message,
+ isOwnMessage,
+ showAvatar = true,
+ isRead = false,
+ senderName = '',
+ senderEmail = '', // Nouveau prop pour l'email du sender
+ isFirstInGroup = true,
+ isLastInGroup = true,
+ showTime = true,
+}) => {
+ const formatMessageTime = (dateString) => {
+ if (!dateString) return '';
+
+ const date = new Date(dateString);
+
+ if (isToday(date)) {
+ return format(date, 'HH:mm', { locale: fr });
+ } else if (isYesterday(date)) {
+ return `Hier ${format(date, 'HH:mm', { locale: fr })}`;
+ } else {
+ return format(date, 'dd/MM HH:mm', { locale: fr });
+ }
+ };
+
+ const getMessageContent = () => {
+ return message.content || message.corpus || '';
+ };
+
+ const getMessageTime = () => {
+ return message.created_at || message.date_envoi;
+ };
+
+ const hasAttachment = () => {
+ return (
+ message.attachment &&
+ (message.attachment.fileName || message.attachment.fileUrl)
+ );
+ };
+
+ const isFileOnlyMessage = () => {
+ return hasAttachment() && !getMessageContent().trim();
+ };
+
+ return (
+ {
+ /* Peut ajouter des actions au hover */
+ }}
+ >
+
+ {/* Avatar - affiché seulement pour le premier message du groupe */}
+ {showAvatar && isFirstInGroup && (
+
+ )}
+
+ {/* Espace pour aligner avec l'avatar quand il n'est pas affiché */}
+ {(!showAvatar || !isFirstInGroup) && (
+
+ )}
+
+ {/* Contenu du message */}
+
+ {/* En-tête du message (nom + heure) - seulement pour le premier message du groupe */}
+ {isFirstInGroup && (
+
+
+ {senderName || (isOwnMessage ? 'Moi' : 'Utilisateur')}
+
+
+ {formatMessageTime(getMessageTime())}
+
+
+ )}
+
+ {/* Fichier attaché */}
+ {hasAttachment() && (
+
+
+
+ )}
+
+ {/* Contenu du message */}
+ {getMessageContent().trim() && (
+
+ {getMessageContent()}
+
+ )}
+
+ {/* Indicateurs de lecture et heure pour les messages non-groupés */}
+
+ {/* Heure pour les messages qui ne sont pas le premier du groupe */}
+ {!isFirstInGroup && (
+
+ {formatMessageTime(getMessageTime())}
+
+ )}
+
+ {/* Indicateurs de lecture (uniquement pour nos messages) */}
+ {isOwnMessage && (
+
+ {isRead ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+
+ );
+};
+
+export default MessageBubble;
diff --git a/Front-End/src/components/Chat/MessageInput.js b/Front-End/src/components/Chat/MessageInput.js
new file mode 100644
index 0000000..5e5dcc9
--- /dev/null
+++ b/Front-End/src/components/Chat/MessageInput.js
@@ -0,0 +1,233 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Send, Paperclip } from 'lucide-react';
+import FileUpload from './FileUpload';
+import { uploadFile } from '@/app/actions/messagerieAction';
+import logger from '@/utils/logger';
+
+const MessageInput = ({
+ onSendMessage,
+ onTypingStart,
+ onTypingStop,
+ disabled = false,
+ placeholder = 'Tapez votre message...',
+ conversationId = null,
+ senderId = null,
+}) => {
+ const [message, setMessage] = useState('');
+ const [isTyping, setIsTyping] = useState(false);
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [showFileUpload, setShowFileUpload] = useState(false);
+ const textareaRef = useRef(null);
+ const typingTimeoutRef = useRef(null);
+
+ // Ajuster la hauteur du textarea automatiquement
+ useEffect(() => {
+ if (textareaRef.current) {
+ textareaRef.current.style.height = 'auto';
+ textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 120)}px`;
+ }
+ }, [message]);
+
+ const handleInputChange = (e) => {
+ const value = e.target.value;
+ setMessage(value);
+
+ // Gestion du statut de frappe
+ if (value.trim() && !isTyping) {
+ setIsTyping(true);
+ onTypingStart?.();
+ }
+
+ // Réinitialiser le timeout de frappe
+ if (typingTimeoutRef.current) {
+ clearTimeout(typingTimeoutRef.current);
+ }
+
+ typingTimeoutRef.current = setTimeout(() => {
+ if (isTyping) {
+ setIsTyping(false);
+ onTypingStop?.();
+ }
+ }, 1000);
+ };
+
+ const handleKeyDown = (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSend();
+ }
+ };
+
+ const handleSend = () => {
+ const trimmedMessage = message.trim();
+ logger.debug('📝 MessageInput: handleSend appelé:', {
+ message,
+ trimmedMessage,
+ disabled,
+ });
+
+ if (!trimmedMessage || disabled) {
+ logger.debug('❌ MessageInput: Message vide ou désactivé');
+ return;
+ }
+
+ logger.debug(
+ '📤 MessageInput: Appel de onSendMessage avec:',
+ trimmedMessage
+ );
+ onSendMessage(trimmedMessage);
+ setMessage('');
+
+ // Arrêter le statut de frappe
+ if (isTyping) {
+ setIsTyping(false);
+ onTypingStop?.();
+ }
+
+ // Effacer le timeout
+ if (typingTimeoutRef.current) {
+ clearTimeout(typingTimeoutRef.current);
+ }
+ };
+
+ const handleFileSelect = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setSelectedFile(file);
+ setShowFileUpload(true);
+ }
+ // Réinitialiser l'input file
+ e.target.value = '';
+ };
+
+ const handleFileUpload = async (
+ file,
+ conversationId,
+ senderId,
+ onProgress
+ ) => {
+ try {
+ const result = await uploadFile(
+ file,
+ conversationId,
+ senderId,
+ onProgress
+ );
+
+ // Envoyer un message avec le fichier
+ onSendMessage('', {
+ type: 'file',
+ fileName: file.name,
+ fileSize: file.size,
+ fileType: file.type,
+ fileUrl: result.fileUrl,
+ });
+
+ // Réinitialiser l'état
+ setSelectedFile(null);
+ setShowFileUpload(false);
+
+ return result;
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ const handleCancelFileUpload = () => {
+ setSelectedFile(null);
+ setShowFileUpload(false);
+ };
+
+ // Nettoyage du timeout lors du démontage du composant
+ useEffect(() => {
+ return () => {
+ if (typingTimeoutRef.current) {
+ clearTimeout(typingTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ return (
+
+ {/* Aperçu d'upload de fichier */}
+ {showFileUpload && selectedFile && (
+
+
+
+ )}
+
+ {/* Zone de saisie */}
+
+
+ {/* Zone de saisie */}
+
+
+
+
+ {/* Boutons à droite (trombone au-dessus, envoi en dessous) */}
+
+ {/* Bouton d'ajout de fichier */}
+
+
+ {/* Bouton d'envoi */}
+
+
+
+
+
+
+ {/* Indicateur de limite de caractères (optionnel) */}
+ {message.length > 900 && (
+
+ 1000 ? 'text-red-500' : 'text-yellow-500'
+ }`}
+ >
+ {message.length}/1000
+
+
+ )}
+
+
+ );
+};
+
+export default MessageInput;
diff --git a/Front-End/src/components/Chat/TypingIndicator.js b/Front-End/src/components/Chat/TypingIndicator.js
new file mode 100644
index 0000000..ae5debc
--- /dev/null
+++ b/Front-End/src/components/Chat/TypingIndicator.js
@@ -0,0 +1,30 @@
+import React from 'react';
+
+const TypingIndicator = ({ typingUsers = [] }) => {
+ if (typingUsers.length === 0) return null;
+
+ const getTypingText = () => {
+ if (typingUsers.length === 1) {
+ return `${typingUsers[0]} tape un message...`;
+ } else if (typingUsers.length === 2) {
+ return `${typingUsers[0]} et ${typingUsers[1]} tapent un message...`;
+ } else {
+ return `${typingUsers[0]} et ${typingUsers.length - 1} autres tapent un message...`;
+ }
+ };
+
+ return (
+
+
+
+ {getTypingText()}
+
+
+ );
+};
+
+export default TypingIndicator;
diff --git a/Front-End/src/components/ProfileSelector.js b/Front-End/src/components/ProfileSelector.js
index 88a7d12..487d611 100644
--- a/Front-End/src/components/ProfileSelector.js
+++ b/Front-End/src/components/ProfileSelector.js
@@ -3,6 +3,7 @@ import { LogOut } from 'lucide-react';
import { disconnect } from '@/app/actions/authAction';
import { getGravatarUrl } from '@/utils/gravatar';
import { useEstablishment } from '@/context/EstablishmentContext';
+import { useChatConnection } from '@/context/ChatConnectionContext';
import DropdownMenu from '@/components/DropdownMenu';
import { usePopup } from '@/context/PopupContext';
import { getRightStr } from '@/utils/rights';
@@ -20,6 +21,7 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
setSelectedEstablishmentEvaluationFrequency,
setSelectedEstablishmentTotalCapacity,
} = useEstablishment();
+ const { isConnected, connectionStatus } = useChatConnection();
const [dropdownOpen, setDropdownOpen] = useState(false);
const { showPopup } = usePopup();
@@ -60,6 +62,36 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
(est) => est.role_id === selectedRoleId
);
+ // Fonction pour obtenir la couleur de la bulle de statut
+ const getStatusColor = () => {
+ switch (connectionStatus) {
+ case 'connected':
+ return 'bg-green-500';
+ case 'connecting':
+ return 'bg-yellow-500';
+ case 'error':
+ return 'bg-red-500';
+ case 'disconnected':
+ default:
+ return 'bg-gray-400';
+ }
+ };
+
+ // Fonction pour obtenir le titre de la bulle de statut
+ const getStatusTitle = () => {
+ switch (connectionStatus) {
+ case 'connected':
+ return 'Chat connecté';
+ case 'connecting':
+ return 'Connexion au chat...';
+ case 'error':
+ return 'Erreur de connexion au chat';
+ case 'disconnected':
+ default:
+ return 'Chat déconnecté';
+ }
+ };
+
// Suppression du tronquage JS, on utilise uniquement CSS
const isSingleRole = establishments && establishments.length === 1;
@@ -68,13 +100,20 @@ const ProfileSelector = ({ onRoleChange, className = '' }) => {
-
+
+
+ {/* Bulle de statut de connexion au chat */}
+
+
-
-
- {children}
-
-
+
+
+
+ {children}
+
+
+
diff --git a/Front-End/src/components/SidebarTabs.js b/Front-End/src/components/SidebarTabs.js
index c054823..9fcb1c5 100644
--- a/Front-End/src/components/SidebarTabs.js
+++ b/Front-End/src/components/SidebarTabs.js
@@ -31,7 +31,7 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
{/* Tabs Content */}
-
+
{tabs.map(
(tab) =>
@@ -42,7 +42,7 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
animate={{ opacity: 1, x: 0 }} // Animation visible
exit={{ opacity: 0, x: -50 }} // Animation de sortie
transition={{ duration: 0.3 }} // Durée des animations
- className="absolute w-full h-full"
+ className="flex-1 flex flex-col h-full min-h-0"
>
{tab.content}
diff --git a/Front-End/src/context/ChatConnectionContext.js b/Front-End/src/context/ChatConnectionContext.js
new file mode 100644
index 0000000..b03ff9a
--- /dev/null
+++ b/Front-End/src/context/ChatConnectionContext.js
@@ -0,0 +1,270 @@
+import React, {
+ createContext,
+ useContext,
+ useState,
+ useEffect,
+ useRef,
+ useCallback,
+} from 'react';
+import { useSession } from 'next-auth/react';
+import logger from '@/utils/logger';
+import { WS_CHAT_URL } from '@/utils/Url';
+
+const ChatConnectionContext = createContext();
+
+export const ChatConnectionProvider = ({ children }) => {
+ const { data: session, status } = useSession(); // Ajouter le hook useSession
+ const [isConnected, setIsConnected] = useState(false);
+ const [connectionStatus, setConnectionStatus] = useState('disconnected'); // 'disconnected', 'connecting', 'connected', 'error'
+ const [userPresences, setUserPresences] = useState({}); // Nouvel état pour les présences
+ const websocketRef = useRef(null);
+ const reconnectTimeoutRef = useRef(null);
+ const [reconnectAttempts, setReconnectAttempts] = useState(0);
+ const [currentUserId, setCurrentUserId] = useState(null);
+ const maxReconnectAttempts = 5;
+
+ // Système de callbacks pour les messages
+ const messageCallbacksRef = useRef(new Set());
+
+ // Fonctions pour gérer les callbacks de messages
+ const addMessageCallback = useCallback((callback) => {
+ messageCallbacksRef.current.add(callback);
+ return () => {
+ messageCallbacksRef.current.delete(callback);
+ };
+ }, []);
+
+ const notifyMessageCallbacks = useCallback((data) => {
+ messageCallbacksRef.current.forEach((callback) => {
+ try {
+ callback(data);
+ } catch (error) {
+ logger.error('ChatConnection: Error in message callback', error);
+ }
+ });
+ }, []);
+
+ // Gestion des présences utilisateur
+ const handlePresenceUpdate = useCallback((data) => {
+ const { user_id, status } = data;
+ setUserPresences((prev) => ({
+ ...prev,
+ [user_id]: { status },
+ }));
+ }, []);
+
+ // Configuration WebSocket
+ const getWebSocketUrl = (userId) => {
+ if (!userId) {
+ logger.warn('ChatConnection: No user ID provided for WebSocket URL');
+ return null;
+ }
+
+ // Récupérer le token d'authentification depuis NextAuth session
+ const token = session?.user?.token;
+
+ if (!token) {
+ logger.warn(
+ 'ChatConnection: No access token found for WebSocket connection'
+ );
+ return null;
+ }
+
+ // Construire l'URL WebSocket avec le token
+ const baseUrl = WS_CHAT_URL(userId);
+ const wsUrl = `${baseUrl}?token=${encodeURIComponent(token)}`;
+
+ return wsUrl;
+ };
+
+ // Connexion WebSocket
+ const connectToChat = (userId = null) => {
+ const userIdToUse = userId || currentUserId;
+
+ // Vérifier que la session est chargée
+ if (status === 'loading') {
+ setConnectionStatus('connecting');
+ return;
+ }
+
+ if (status === 'unauthenticated' || !session) {
+ logger.warn('ChatConnection: User not authenticated');
+ setConnectionStatus('error');
+ return;
+ }
+
+ if (!userIdToUse) {
+ logger.warn('ChatConnection: Cannot connect without user ID');
+ setConnectionStatus('error');
+ return;
+ }
+
+ if (websocketRef.current?.readyState === WebSocket.OPEN) {
+ return;
+ }
+
+ setCurrentUserId(userIdToUse);
+ setConnectionStatus('connecting');
+
+ try {
+ const wsUrl = getWebSocketUrl(userIdToUse);
+
+ if (!wsUrl) {
+ throw new Error(
+ 'Cannot generate WebSocket URL - missing token or user ID'
+ );
+ }
+
+ websocketRef.current = new WebSocket(wsUrl);
+
+ websocketRef.current.onopen = () => {
+ logger.info(
+ 'ChatConnection: Connected successfully for user:',
+ userIdToUse
+ );
+ setIsConnected(true);
+ setConnectionStatus('connected');
+ setReconnectAttempts(0);
+ };
+
+ websocketRef.current.onclose = (event) => {
+ setIsConnected(false);
+ setConnectionStatus('disconnected');
+
+ // Tentative de reconnexion automatique
+ if (reconnectAttempts < maxReconnectAttempts && !event.wasClean) {
+ const timeout = Math.min(
+ 1000 * Math.pow(2, reconnectAttempts),
+ 30000
+ );
+
+ reconnectTimeoutRef.current = setTimeout(() => {
+ setReconnectAttempts((prev) => prev + 1);
+ connectToChat();
+ }, timeout);
+ }
+ };
+
+ websocketRef.current.onerror = (error) => {
+ logger.error('ChatConnection: WebSocket error', error);
+ setConnectionStatus('error');
+ setIsConnected(false);
+ };
+
+ websocketRef.current.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data);
+
+ // Gérer les messages de présence
+ if (data.type === 'presence_update') {
+ handlePresenceUpdate(data);
+ }
+
+ // Notifier tous les callbacks enregistrés
+ notifyMessageCallbacks(data);
+ } catch (error) {
+ logger.error('ChatConnection: Error parsing message', error);
+ }
+ };
+ } catch (error) {
+ logger.error('ChatConnection: Error creating WebSocket', error);
+ setConnectionStatus('error');
+ setIsConnected(false);
+ }
+ };
+
+ // Déconnexion WebSocket
+ const disconnectFromChat = () => {
+ if (reconnectTimeoutRef.current) {
+ clearTimeout(reconnectTimeoutRef.current);
+ reconnectTimeoutRef.current = null;
+ }
+
+ if (websocketRef.current) {
+ websocketRef.current.close(1000, 'User disconnected');
+ websocketRef.current = null;
+ }
+
+ setIsConnected(false);
+ setConnectionStatus('disconnected');
+ setReconnectAttempts(0);
+ logger.info('ChatConnection: Disconnected by user');
+ };
+
+ // Envoi de message
+ const sendMessage = (message) => {
+ if (websocketRef.current?.readyState === WebSocket.OPEN) {
+ const messageStr = JSON.stringify(message);
+ websocketRef.current.send(messageStr);
+ return true;
+ } else {
+ logger.warn('ChatConnection: Cannot send message - not connected');
+ return false;
+ }
+ };
+
+ // Obtenir la référence WebSocket pour les composants qui en ont besoin
+ const getWebSocket = () => websocketRef.current;
+
+ // Effet pour la gestion de la session et connexion automatique
+ useEffect(() => {
+ // Si la session change vers authenticated et qu'on a un user_id, essayer de se connecter
+ if (status === 'authenticated' && session?.user?.user_id && !isConnected) {
+ connectToChat(session.user.user_id);
+ }
+
+ // Si la session devient unauthenticated, déconnecter
+ if (status === 'unauthenticated' && isConnected) {
+ disconnectFromChat();
+ }
+ }, [
+ status,
+ session?.user?.user_id,
+ isConnected,
+ connectToChat,
+ disconnectFromChat,
+ ]);
+
+ // Nettoyage à la destruction du composant
+ useEffect(() => {
+ return () => {
+ if (reconnectTimeoutRef.current) {
+ clearTimeout(reconnectTimeoutRef.current);
+ }
+ if (websocketRef.current) {
+ websocketRef.current.close();
+ }
+ };
+ }, []);
+
+ const value = {
+ isConnected,
+ connectionStatus,
+ userPresences, // Ajouter les présences utilisateur
+ connectToChat,
+ disconnectFromChat,
+ sendMessage,
+ getWebSocket,
+ reconnectAttempts,
+ maxReconnectAttempts,
+ addMessageCallback, // Ajouter cette fonction
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useChatConnection = () => {
+ const context = useContext(ChatConnectionContext);
+ if (!context) {
+ throw new Error(
+ 'useChatConnection must be used within a ChatConnectionProvider'
+ );
+ }
+ return context;
+};
+
+export default ChatConnectionContext;
diff --git a/Front-End/src/css/tailwind.css b/Front-End/src/css/tailwind.css
index b5c61c9..3501f39 100644
--- a/Front-End/src/css/tailwind.css
+++ b/Front-End/src/css/tailwind.css
@@ -1,3 +1,90 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+/* Animations pour le chat */
+
+@keyframes typing-bounce {
+ 0%,
+ 60%,
+ 100% {
+ transform: translateY(0);
+ opacity: 0.7;
+ }
+ 30% {
+ transform: translateY(-8px);
+ opacity: 1;
+ }
+}
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.typing-dot {
+ animation: typing-bounce 1.4s ease-in-out infinite;
+}
+
+.typing-dot:nth-child(1) {
+ animation-delay: 0s;
+}
+
+.typing-dot:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.typing-dot:nth-child(3) {
+ animation-delay: 0.4s;
+}
+
+.typing-indicator-enter {
+ animation: fade-in 0.3s ease-out;
+}
+
+/* Améliorations visuelles pour les accusés de lecture */
+.read-indicator {
+ transition: all 0.2s ease-in-out;
+}
+
+.read-indicator.read {
+ color: #60a5fa; /* Bleu plus visible pour les messages lus */
+}
+
+.read-indicator.sent {
+ color: #93c5fd; /* Bleu plus clair pour les messages envoyés */
+}
+
+/* Animation pour l'apparition des nouveaux messages */
+.message-appear {
+ animation: fade-in 0.3s ease-out;
+}
+
+/* Styles Discord-like pour les messages */
+.message-container:hover {
+ background-color: rgba(0, 0, 0, 0.02);
+}
+
+.message-container:hover .message-timestamp {
+ opacity: 1;
+}
+
+.message-container:hover .message-actions {
+ opacity: 1;
+}
+
+.message-timestamp {
+ opacity: 0;
+ transition: opacity 0.2s ease-in-out;
+}
+
+.message-actions {
+ opacity: 0;
+ transition: opacity 0.2s ease-in-out;
+}
diff --git a/Front-End/src/hooks/useWebSocket.js b/Front-End/src/hooks/useWebSocket.js
new file mode 100644
index 0000000..a9a1ed0
--- /dev/null
+++ b/Front-End/src/hooks/useWebSocket.js
@@ -0,0 +1,249 @@
+import { useState, useEffect, useRef, useCallback } from 'react';
+import { useSession } from 'next-auth/react';
+import { WS_CHAT_URL } from '@/utils/Url';
+import logger from '@/utils/logger';
+
+const useWebSocket = (userId, onMessage, onConnectionChange) => {
+ const [isConnected, setIsConnected] = useState(false);
+ const [connectionStatus, setConnectionStatus] = useState('disconnected');
+ const wsRef = useRef(null);
+ const reconnectTimeoutRef = useRef(null);
+ const reconnectAttemptsRef = useRef(0);
+ const isConnectingRef = useRef(false); // Empêcher les connexions multiples
+ const maxReconnectAttempts = 5;
+
+ // Récupération du token JWT
+ const { data: session } = useSession();
+ const authToken = session?.user?.token;
+
+ // Références stables pour les callbacks
+ const onMessageRef = useRef(onMessage);
+ const onConnectionChangeRef = useRef(onConnectionChange);
+
+ useEffect(() => {
+ onMessageRef.current = onMessage;
+ }, [onMessage]);
+
+ useEffect(() => {
+ onConnectionChangeRef.current = onConnectionChange;
+ }, [onConnectionChange]);
+
+ const connect = useCallback(() => {
+ if (!userId || !authToken) {
+ logger.warn('WebSocket: userId ou token manquant');
+ return;
+ }
+
+ // Empêcher les connexions multiples simultanées
+ if (
+ isConnectingRef.current ||
+ (wsRef.current && wsRef.current.readyState === WebSocket.CONNECTING)
+ ) {
+ logger.debug('WebSocket: connexion déjà en cours');
+ return;
+ }
+
+ // Fermer la connexion existante si elle existe
+ if (wsRef.current) {
+ wsRef.current.close();
+ wsRef.current = null;
+ }
+
+ isConnectingRef.current = true;
+
+ try {
+ // Ajouter le token à l'URL du WebSocket
+ const wsUrl = new URL(WS_CHAT_URL(userId));
+ wsUrl.searchParams.append('token', authToken);
+
+ wsRef.current = new WebSocket(wsUrl.toString());
+
+ wsRef.current.onopen = () => {
+ logger.debug('WebSocket connecté');
+ isConnectingRef.current = false;
+ setIsConnected(true);
+ setConnectionStatus('connected');
+ reconnectAttemptsRef.current = 0;
+ onConnectionChangeRef.current?.(true);
+ };
+
+ wsRef.current.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data);
+ onMessageRef.current?.(data);
+ } catch (error) {
+ logger.error('Erreur lors du parsing du message WebSocket:', error);
+ }
+ };
+
+ wsRef.current.onclose = (event) => {
+ logger.debug('WebSocket fermé:', event.code, event.reason);
+ isConnectingRef.current = false;
+ setIsConnected(false);
+ setConnectionStatus('disconnected');
+ onConnectionChangeRef.current?.(false);
+
+ // Tentative de reconnexion automatique seulement si la fermeture n'est pas intentionnelle
+ if (
+ event.code !== 1000 &&
+ reconnectAttemptsRef.current < maxReconnectAttempts
+ ) {
+ reconnectAttemptsRef.current++;
+ setConnectionStatus('reconnecting');
+
+ const delay = Math.min(
+ 1000 * Math.pow(2, reconnectAttemptsRef.current),
+ 30000
+ );
+ reconnectTimeoutRef.current = setTimeout(() => {
+ logger.debug(
+ `Tentative de reconnexion ${reconnectAttemptsRef.current}/${maxReconnectAttempts}`
+ );
+ connect();
+ }, delay);
+ } else {
+ setConnectionStatus('failed');
+ }
+ };
+
+ wsRef.current.onerror = (error) => {
+ logger.error('Erreur WebSocket:', error);
+ isConnectingRef.current = false;
+ setConnectionStatus('error');
+ };
+ } catch (error) {
+ logger.error('Erreur lors de la création du WebSocket:', error);
+ isConnectingRef.current = false;
+ setConnectionStatus('error');
+ }
+ }, [userId, authToken]);
+
+ const disconnect = useCallback(() => {
+ if (reconnectTimeoutRef.current) {
+ clearTimeout(reconnectTimeoutRef.current);
+ reconnectTimeoutRef.current = null;
+ }
+
+ if (wsRef.current) {
+ wsRef.current.close();
+ wsRef.current = null;
+ }
+
+ setIsConnected(false);
+ setConnectionStatus('disconnected');
+ }, []);
+
+ const sendMessage = useCallback(
+ (message) => {
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
+ // Ajouter le token à chaque message
+ const messageWithAuth = {
+ ...message,
+ token: authToken,
+ };
+ wsRef.current.send(JSON.stringify(messageWithAuth));
+ return true;
+ } else {
+ logger.warn("WebSocket non connecté, impossible d'envoyer le message");
+ return false;
+ }
+ },
+ [authToken]
+ );
+
+ const sendTypingStart = useCallback(
+ (conversationId) => {
+ sendMessage({
+ type: 'typing_start',
+ conversation_id: conversationId,
+ });
+ },
+ [sendMessage]
+ );
+
+ const sendTypingStop = useCallback(
+ (conversationId) => {
+ sendMessage({
+ type: 'typing_stop',
+ conversation_id: conversationId,
+ });
+ },
+ [sendMessage]
+ );
+
+ const markAsRead = useCallback(
+ (conversationId) => {
+ sendMessage({
+ type: 'mark_as_read',
+ conversation_id: conversationId,
+ });
+ },
+ [sendMessage]
+ );
+
+ const joinConversation = useCallback(
+ (conversationId) => {
+ sendMessage({
+ type: 'join_conversation',
+ conversation_id: conversationId,
+ });
+ },
+ [sendMessage]
+ );
+
+ const leaveConversation = useCallback(
+ (conversationId) => {
+ sendMessage({
+ type: 'leave_conversation',
+ conversation_id: conversationId,
+ });
+ },
+ [sendMessage]
+ );
+
+ const sendChatMessage = useCallback(
+ (conversationId, content, attachment = null) => {
+ const messageData = {
+ type: 'send_message',
+ conversation_id: conversationId,
+ content: content,
+ message_type: attachment ? 'file' : 'text',
+ };
+
+ // Ajouter les informations du fichier si présent
+ if (attachment) {
+ messageData.attachment = attachment;
+ }
+
+ return sendMessage(messageData);
+ },
+ [sendMessage]
+ );
+
+ useEffect(() => {
+ // Se connecter seulement si on a un userId et un token
+ if (userId && authToken) {
+ connect();
+ }
+
+ return () => {
+ disconnect();
+ };
+ }, [userId, authToken]); // Retirer connect et disconnect des dépendances
+
+ return {
+ isConnected,
+ connectionStatus,
+ sendMessage,
+ sendTypingStart,
+ sendTypingStop,
+ markAsRead,
+ joinConversation,
+ leaveConversation,
+ sendChatMessage,
+ reconnect: connect,
+ disconnect,
+ };
+};
+
+export default useWebSocket;
diff --git a/Front-End/src/styles/chat-animations.css b/Front-End/src/styles/chat-animations.css
new file mode 100644
index 0000000..6134129
--- /dev/null
+++ b/Front-End/src/styles/chat-animations.css
@@ -0,0 +1,63 @@
+/* Animations pour le chat */
+
+@keyframes typing-bounce {
+ 0%,
+ 60%,
+ 100% {
+ transform: translateY(0);
+ opacity: 0.7;
+ }
+ 30% {
+ transform: translateY(-8px);
+ opacity: 1;
+ }
+}
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.typing-dot {
+ animation: typing-bounce 1.4s ease-in-out infinite;
+}
+
+.typing-dot:nth-child(1) {
+ animation-delay: 0s;
+}
+
+.typing-dot:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.typing-dot:nth-child(3) {
+ animation-delay: 0.4s;
+}
+
+.typing-indicator-enter {
+ animation: fade-in 0.3s ease-out;
+}
+
+/* Améliorations visuelles pour les accusés de lecture */
+.read-indicator {
+ transition: all 0.2s ease-in-out;
+}
+
+.read-indicator.read {
+ color: #60a5fa; /* Bleu plus visible pour les messages lus */
+}
+
+.read-indicator.sent {
+ color: #93c5fd; /* Bleu plus clair pour les messages envoyés */
+}
+
+/* Animation pour l'apparition des nouveaux messages */
+.message-appear {
+ animation: fade-in 0.3s ease-out;
+}
diff --git a/Front-End/src/utils/Url.js b/Front-End/src/utils/Url.js
index 65756c2..cdac09e 100644
--- a/Front-End/src/utils/Url.js
+++ b/Front-End/src/utils/Url.js
@@ -1,5 +1,6 @@
import { RIGHTS } from '@/utils/rights';
export const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
+export const WS_BASE_URL = process.env.NEXT_PUBLIC_WSAPI_URL;
//URL-Back-End
@@ -53,13 +54,25 @@ export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/Establishment/establishm
export const BE_PLANNING_PLANNINGS_URL = `${BASE_URL}/Planning/plannings`;
export const BE_PLANNING_EVENTS_URL = `${BASE_URL}/Planning/events`;
+// GESTION EMAIL
+export const BE_GESTIONEMAIL_SEND_EMAIL_URL = `${BASE_URL}/GestionEmail/send-email/`;
+export const BE_GESTIONEMAIL_SEARCH_RECIPIENTS_URL = `${BASE_URL}/GestionEmail/search-recipients`;
+
// GESTION MESSAGERIE
export const BE_GESTIONMESSAGERIE_CONVERSATIONS_URL = `${BASE_URL}/GestionMessagerie/conversations`;
export const BE_GESTIONMESSAGERIE_CONVERSATION_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/conversations/messages`;
export const BE_GESTIONMESSAGERIE_MARK_AS_READ_URL = `${BASE_URL}/GestionMessagerie/conversations/mark-as-read`;
+export const BE_GESTIONMESSAGERIE_DELETE_CONVERSATION_URL = `${BASE_URL}/GestionMessagerie/conversations`;
export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messages`;
-export const BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL = `${BASE_URL}/GestionMessagerie/send-email/`;
+export const BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL = `${BASE_URL}/GestionMessagerie/send-message`;
+export const BE_GESTIONMESSAGERIE_CREATE_CONVERSATION_URL = `${BASE_URL}/GestionMessagerie/create-conversation/`;
export const BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL = `${BASE_URL}/GestionMessagerie/search-recipients`;
+export const BE_GESTIONMESSAGERIE_UPLOAD_FILE_URL = `${BASE_URL}/GestionMessagerie/upload-file/`;
+
+// WEBSOCKET MESSAGERIE
+export const WS_CHAT_URL = (userId) => {
+ return `${WS_BASE_URL}/ws/chat/${userId}/`;
+};
// SETTINGS
export const BE_SETTINGS_SMTP_URL = `${BASE_URL}/Settings/smtp-settings`;
diff --git a/README.md b/README.md
index 88104f7..d29f1fc 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,18 @@
# N3wt School
-Logiciel de gestion d'école
+Logiciel de gestion d'école avec système de messagerie instantanée intégré
+
+## ✨ Nouvelles Fonctionnalités
+
+### 📱 Messagerie Instantanée (Nouveau)
+
+- **Chat temps réel** avec WebSocket
+- **Upload de fichiers** avec prévisualisation
+- **Indicateurs de présence** (en ligne/hors ligne)
+- **Messages lus/non lus** avec compteurs
+- **Interface moderne** avec composants React
+
+Voir la [documentation complète](./docs/messagerie-instantanee.md) pour plus de détails.
## Maquette
diff --git a/debug.log b/debug.log
new file mode 100644
index 0000000..2715236
--- /dev/null
+++ b/debug.log
@@ -0,0 +1 @@
+[0526/220602.137:ERROR:third_party\crashpad\crashpad\client\crashpad_client_win.cc:811] not connected
diff --git a/docker-compose.yml b/docker-compose.yml
index b5af306..a8a3d99 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,6 @@
services:
redis:
- image: 'redis:latest'
+ image: "redis:latest"
ports:
- 6379:6379
@@ -8,7 +8,7 @@ services:
- TZ=Europe/Paris
database:
- image: 'postgres:latest'
+ image: "postgres:latest"
ports:
- 5432:5432
environment:
@@ -16,7 +16,7 @@ services:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: school
TZ: Europe/Paris
-
+
docuseal:
image: docuseal/docuseal:latest
depends_on:
@@ -36,8 +36,8 @@ services:
environment:
- TZ=Europe/Paris
- TEST_MODE=True
- - CORS_ALLOWED_ORIGINS=http://localhost:3000
- - CSRF_TRUSTED_ORIGINS=http://localhost:3000
+ - CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,http://localhost:8080,http://127.0.0.1:8080
+ - CSRF_TRUSTED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,http://localhost:8080,http://127.0.0.1:8080
links:
- "database:database"
- "redis:redis"
@@ -79,4 +79,3 @@ services:
# - TZ=Europe/Paris
# depends_on:
# - backend
-