From 37736293c857e594273a1ad895d327cf9af98f54 Mon Sep 17 00:00:00 2001 From: sebastienbinda Date: Sun, 23 Aug 2026 11:00:49 +0200 Subject: [PATCH] Ajout du formulaire de question/bug --- .github/workflows/ci.yml | 60 +- README.md | 2 +- backend/app.py | 3 + backend/controllers/__init__.py | 2 + backend/controllers/feedback_controller.py | 80 + backend/services/feedback/__init__.py | 17 + .../feedback/github_feedback_configuration.py | 84 + .../feedback/github_feedback_service.py | 163 ++ .../by_module/controllers/test_app_routes.py | 1 + .../controllers/test_feedback_routes.py | 175 ++ .../services/test_github_feedback_service.py | 82 + docker/docker-compose.local.yml | 4 + documentation/about.md | 6 + documentation/authentication.md | 1 + documentation/backend-api.md | 49 + documentation/backend-arch.md | 2 + documentation/ci.md | 38 +- documentation/deploy.md | 25 + documentation/frontend-arch.md | 4 + documentation/menu.md | 21 +- documentation/site-plan.md | 5 +- frontend/package-lock.json | 1795 +++++------------ frontend/package.json | 5 +- frontend/src/appRouting.js | 6 +- frontend/src/components/AboutView.jsx | 151 +- frontend/src/components/AddGameView.jsx | 2 + .../src/components/AdminLibraryImportView.jsx | 2 + frontend/src/components/AppViewSwitch.jsx | 8 + frontend/src/components/AuthView.jsx | 4 + .../CollectionShareManagementView.jsx | 1 + frontend/src/components/ConfigurationView.jsx | 2 + .../EmailVerificationResultView.jsx | 2 + frontend/src/components/FeedbackView.jsx | 189 ++ frontend/src/components/GameDetailView.jsx | 2 + .../src/components/GameDuplicateAdminView.jsx | 2 + frontend/src/components/HomeView.jsx | 2 + .../src/components/LibraryEntityListView.jsx | 2 + frontend/src/components/LibraryHomeView.jsx | 2 + .../components/LibraryPlatformDetailView.jsx | 2 + frontend/src/components/MainMenu.jsx | 31 +- frontend/src/components/PageLayout.jsx | 6 + .../src/components/PlatformDetailView.jsx | 2 + .../PlatformImageModerationView.jsx | 2 + .../UserCollectionOnboardingView.jsx | 2 + frontend/src/components/UsersView.jsx | 2 + frontend/src/components/WishlistView.jsx | 2 + .../hooks/app/useCloudCollectionViewModel.js | 1 + .../collection/useUserCollectionOnboarding.js | 1 + .../src/hooks/navigation/useAppNavigation.js | 2 + frontend/src/services/FeedbackApi.js | 54 + frontend/src/services/MainMenuAccessPolicy.js | 1 + frontend/src/styles/home.css | 222 +- frontend/src/styles/mobile-fixes.css | 24 +- frontend/tests/aboutFeedback.test.js | 81 + runtime/.env.local.example | 13 + runtime/.env.production.example | 11 + runtime/deploy.sh | 1 + runtime/docker-compose.online.yml | 7 + runtime/secure.sh | 2 + 59 files changed, 2082 insertions(+), 1388 deletions(-) create mode 100644 backend/controllers/feedback_controller.py create mode 100644 backend/services/feedback/__init__.py create mode 100644 backend/services/feedback/github_feedback_configuration.py create mode 100644 backend/services/feedback/github_feedback_service.py create mode 100644 backend/tests/by_module/controllers/test_feedback_routes.py create mode 100644 backend/tests/by_module/services/test_github_feedback_service.py create mode 100644 frontend/src/components/FeedbackView.jsx create mode 100644 frontend/src/services/FeedbackApi.js create mode 100644 frontend/tests/aboutFeedback.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 886e211..5e982ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,6 +121,29 @@ jobs: - name: Run backend tests run: ./scripts/test_backend.sh + backend-audit: + name: Backend dependency audit + runs-on: ubuntu-latest + needs: + - change-detection + if: needs.change-detection.outputs.backend == 'true' + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: backend/requirements.txt + + - name: Install pip-audit + run: python -m pip install --upgrade pip pip-audit + + - name: Audit backend dependencies + run: python -m pip_audit -r backend/requirements.txt --strict + frontend-tests: name: Frontend tests runs-on: ubuntu-latest @@ -146,6 +169,31 @@ jobs: working-directory: frontend run: npm test + frontend-audit: + name: Frontend dependency audit + runs-on: ubuntu-latest + needs: + - change-detection + if: needs.change-detection.outputs.frontend == 'true' + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 20 + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install frontend dependencies + working-directory: frontend + run: npm ci + + - name: Audit frontend dependencies + working-directory: frontend + run: npm audit --audit-level=low + frontend-build: name: Frontend build runs-on: ubuntu-latest @@ -177,9 +225,11 @@ jobs: needs: - change-detection - backend-tests + - backend-audit - frontend-tests + - frontend-audit - frontend-build - if: startsWith(github.ref, 'refs/tags/') && needs.change-detection.outputs.age_secrets_image == 'true' && needs.backend-tests.result == 'success' && needs.frontend-tests.result == 'success' && needs.frontend-build.result == 'success' + if: startsWith(github.ref, 'refs/tags/') && needs.change-detection.outputs.age_secrets_image == 'true' && needs.backend-tests.result == 'success' && needs.backend-audit.result == 'success' && needs.frontend-tests.result == 'success' && needs.frontend-audit.result == 'success' && needs.frontend-build.result == 'success' steps: - name: Checkout repository uses: actions/checkout@v5 @@ -215,9 +265,11 @@ jobs: runs-on: ubuntu-latest needs: - backend-tests + - backend-audit - frontend-tests + - frontend-audit - frontend-build - if: startsWith(github.ref, 'refs/tags/') && needs.backend-tests.result == 'success' && needs.frontend-tests.result == 'success' && needs.frontend-build.result == 'success' + if: startsWith(github.ref, 'refs/tags/') && needs.backend-tests.result == 'success' && needs.backend-audit.result == 'success' && needs.frontend-tests.result == 'success' && needs.frontend-audit.result == 'success' && needs.frontend-build.result == 'success' steps: - name: Checkout repository uses: actions/checkout@v5 @@ -268,10 +320,12 @@ jobs: docker-images: name: Build and publish Docker images runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') && needs.backend-tests.result == 'success' && needs.frontend-tests.result == 'success' && needs.frontend-build.result == 'success' + if: startsWith(github.ref, 'refs/tags/') && needs.backend-tests.result == 'success' && needs.backend-audit.result == 'success' && needs.frontend-tests.result == 'success' && needs.frontend-audit.result == 'success' && needs.frontend-build.result == 'success' needs: - backend-tests + - backend-audit - frontend-tests + - frontend-audit - frontend-build steps: - name: Checkout repository diff --git a/README.md b/README.md index 4d27190..7ac4410 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ fonctionnels et techniques sont dans `documentation/` : - [documentation/database.md](documentation/database.md) : schema PostgreSQL, migrations et persistance production. - [documentation/deploy.md](documentation/deploy.md) : deploiement, archive de - livraison, Docker Compose runtime et secrets production. + livraison, Docker Compose runtime, retours GitHub et secrets production. - [documentation/authentication.md](documentation/authentication.md) : authentification, profils et sessions. - [documentation/register.md](documentation/register.md) : inscription et diff --git a/backend/app.py b/backend/app.py index ed3fe66..aead184 100644 --- a/backend/app.py +++ b/backend/app.py @@ -20,6 +20,7 @@ AuthenticationController, CollectionController, CollectionShareController, + FeedbackController, GameController, LibraryController, PlatformController, @@ -110,6 +111,7 @@ auth_guard, collection_share_management_service, ) +feedback_controller = FeedbackController(auth_guard) user_controller = UserController(auth_guard) collection_controller = CollectionController(auth_guard) library_reset_job_coordinator = LibraryResetJobCoordinator() @@ -159,6 +161,7 @@ authentication_controller.register_routes(app) route_controller.register_routes(app) collection_share_controller.register_routes(app) +feedback_controller.register_routes(app) user_controller.register_routes(app) user_collection_import_controller.register_routes(app) user_collection_import_help_controller.register_routes(app) diff --git a/backend/controllers/__init__.py b/backend/controllers/__init__.py index 4fe5d0c..3d17773 100644 --- a/backend/controllers/__init__.py +++ b/backend/controllers/__init__.py @@ -14,6 +14,7 @@ from .authentication_controller import AuthenticationController from .collection_controller import CollectionController from .collection_share_controller import CollectionShareController +from .feedback_controller import FeedbackController from .game_controller import GameController from .library_controller import LibraryController from .platform_controller import PlatformController @@ -28,6 +29,7 @@ "AuthenticationController", "CollectionController", "CollectionShareController", + "FeedbackController", "GameController", "LibraryController", "PlatformController", diff --git a/backend/controllers/feedback_controller.py b/backend/controllers/feedback_controller.py new file mode 100644 index 0000000..e2d4624 --- /dev/null +++ b/backend/controllers/feedback_controller.py @@ -0,0 +1,80 @@ +# ____ _ _ ____ _ _ _ _ ___ +# / ___| | ___ _ _ __| |/ ___|___ | | | ___ ___| |_(_) ___ _ __ / _ \ _ __ _ __ +# | | | |/ _ \| | | |/ _` | | / _ \| | |/ _ \/ __| __| |/ _ \| `_ \| | | | `_ \| `_ | +# | |___| | (_) | |_| | (_| | |__| (_) | | | __/ (__| |_| | (_) | | | | |_| | |_) | |_) | +# \____|_|\___/ \__,_|\__,_|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|\___/| .__/| .__/ +# |_| |_| +# Projet : CloudCollectionApp +# Date de creation : 2026-08-23 +# Auteurs : OpenAI ChatGPT, Codex, Binda Sébastien +# Licence : Apache 2.0 +# +# Description : controleur HTTP des retours beta utilisateurs. + +from flask import Flask, current_app, jsonify, request + +from services import AuthGuard, UserProfile +from services.feedback import GitHubFeedbackService + + +class FeedbackController: + """Expose la route protegee d'envoi de retour beta vers GitHub.""" + + def __init__(self, auth_guard: AuthGuard, feedback_service_factory=None): + """Initialise le controleur des retours. + + Args: + auth_guard (AuthGuard): Garde d'authentification applicatif. + feedback_service_factory (Callable | None): Fabrique du service de retour. + + Returns: + None: Le constructeur ne retourne aucune valeur. + """ + + self.auth_guard = auth_guard + self.feedback_service_factory = feedback_service_factory or GitHubFeedbackService.from_environment + + def register_routes(self, flask_app: Flask) -> None: + """Enregistre la route de retour beta dans Flask. + + Args: + flask_app (Flask): Application Flask cible. + + Returns: + None: La methode ne retourne aucune valeur. + """ + + flask_app.add_url_rule( + "/api/feedback", + endpoint="submit_feedback", + view_func=self.auth_guard.require_profile(UserProfile.USER.value)(self.submit_feedback), + methods=["POST"], + ) + + def submit_feedback(self): + """Cree une issue GitHub depuis un retour utilisateur connecte. + + Args: + Aucun. + + Returns: + tuple[flask.Response, int]: Issue creee ou erreur JSON. + """ + + payload = request.get_json(silent=True) or {} + if not isinstance(payload, dict): + return jsonify({"error": "Le format du retour est invalide."}), 400 + + requester_subject = str( + self.auth_guard.get_current_token_payload().get("sub") or "" + ).strip().lower() + try: + feedback = self.feedback_service_factory().submit_feedback(payload, requester_subject) + return jsonify({"feedback": feedback}), 201 + except ValueError as exc: + return jsonify({"error": str(exc)}), 400 + except RuntimeError as exc: + return jsonify({"error": str(exc)}), 503 + except Exception: + current_app.logger.exception("Erreur pendant l'envoi d'un retour beta.") + return jsonify({"error": "Unable to send feedback."}), 500 diff --git a/backend/services/feedback/__init__.py b/backend/services/feedback/__init__.py new file mode 100644 index 0000000..d129fb0 --- /dev/null +++ b/backend/services/feedback/__init__.py @@ -0,0 +1,17 @@ +# ____ _ _ ____ _ _ _ _ ___ +# / ___| | ___ _ _ __| |/ ___|___ | | | ___ ___| |_(_) ___ _ __ / _ \ _ __ _ __ +# | | | |/ _ \| | | |/ _` | | / _ \| | |/ _ \/ __| __| |/ _ \| `_ \| | | | `_ \| `_ | +# | |___| | (_) | |_| | (_| | |__| (_) | | | __/ (__| |_| | (_) | | | | |_| | |_) | |_) | +# \____|_|\___/ \__,_|\__,_|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|\___/| .__/| .__/ +# |_| |_| +# Projet : CloudCollectionApp +# Date de creation : 2026-08-23 +# Auteurs : OpenAI ChatGPT, Codex, Binda Sébastien +# Licence : Apache 2.0 +# +# Description : exports du domaine des retours utilisateurs. + +from .github_feedback_configuration import GitHubFeedbackConfiguration +from .github_feedback_service import GitHubFeedbackService + +__all__ = ["GitHubFeedbackConfiguration", "GitHubFeedbackService"] diff --git a/backend/services/feedback/github_feedback_configuration.py b/backend/services/feedback/github_feedback_configuration.py new file mode 100644 index 0000000..404c58b --- /dev/null +++ b/backend/services/feedback/github_feedback_configuration.py @@ -0,0 +1,84 @@ +# ____ _ _ ____ _ _ _ _ ___ +# / ___| | ___ _ _ __| |/ ___|___ | | | ___ ___| |_(_) ___ _ __ / _ \ _ __ _ __ +# | | | |/ _ \| | | |/ _` | | / _ \| | |/ _ \/ __| __| |/ _ \| `_ \| | | | `_ \| `_ | +# | |___| | (_) | |_| | (_| | |__| (_) | | | __/ (__| |_| | (_) | | | | |_| | |_) | |_) | +# \____|_|\___/ \__,_|\__,_|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|\___/| .__/| .__/ +# |_| |_| +# Projet : CloudCollectionApp +# Date de creation : 2026-08-23 +# Auteurs : OpenAI ChatGPT, Codex, Binda Sébastien +# Licence : Apache 2.0 +# +# Description : configuration GitHub utilisee pour les retours beta. + +from dataclasses import dataclass +import os + +from services.security import EnvironmentSecretReader + + +@dataclass(frozen=True) +class GitHubFeedbackConfiguration: + """Decrit la configuration de creation d'issues GitHub pour les retours beta.""" + + repository: str + token: str + labels: tuple[str, ...] + title_prefix: str + + DEFAULT_LABELS = ("feedback", "remarque") + DEFAULT_TITLE_PREFIX = "[Retour utilisateur]" + + @classmethod + def from_environment(cls) -> "GitHubFeedbackConfiguration": + """Construit la configuration depuis les variables d'environnement. + + Args: + Aucun. + + Returns: + GitHubFeedbackConfiguration: Configuration GitHub lue et nettoyee. + + Raises: + ValueError: Si une valeur configuree est invalide. + """ + + labels = cls._parse_labels(os.getenv("GITHUB_FEEDBACK_LABELS", "feedback,remarque")) + configuration = cls( + repository=(os.getenv("GITHUB_FEEDBACK_REPOSITORY") or "").strip(), + token=(EnvironmentSecretReader.read("GITHUB_FEEDBACK_TOKEN") or "").strip(), + labels=labels, + title_prefix=( + os.getenv("GITHUB_FEEDBACK_TITLE_PREFIX", cls.DEFAULT_TITLE_PREFIX).strip() + or cls.DEFAULT_TITLE_PREFIX + ), + ) + configuration.validate() + return configuration + + def validate(self) -> None: + """Valide la coherence de la configuration GitHub. + + Args: + Aucun. + + Returns: + None: La methode ne retourne aucune valeur. + + Raises: + ValueError: Si le depot ou le token est invalide. + """ + + if "/" not in self.repository or len(self.repository.split("/")) != 2: + raise ValueError("GITHUB_FEEDBACK_REPOSITORY doit utiliser le format owner/repository.") + if not self.token: + raise ValueError("GITHUB_FEEDBACK_TOKEN est requis pour creer une issue GitHub.") + + @classmethod + def _parse_labels(cls, raw_labels: str) -> tuple[str, ...]: + labels = tuple( + label.strip() + for label in str(raw_labels or "").split(",") + if label.strip() + ) + return labels or cls.DEFAULT_LABELS diff --git a/backend/services/feedback/github_feedback_service.py b/backend/services/feedback/github_feedback_service.py new file mode 100644 index 0000000..19b3c0c --- /dev/null +++ b/backend/services/feedback/github_feedback_service.py @@ -0,0 +1,163 @@ +# ____ _ _ ____ _ _ _ _ ___ +# / ___| | ___ _ _ __| |/ ___|___ | | | ___ ___| |_(_) ___ _ __ / _ \ _ __ _ __ +# | | | |/ _ \| | | |/ _` | | / _ \| | |/ _ \/ __| __| |/ _ \| `_ \| | | | `_ \| `_ | +# | |___| | (_) | |_| | (_| | |__| (_) | | | __/ (__| |_| | (_) | | | | |_| | |_) | |_) | +# \____|_|\___/ \__,_|\__,_|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|\___/| .__/| .__/ +# |_| |_| +# Projet : CloudCollectionApp +# Date de creation : 2026-08-23 +# Auteurs : OpenAI ChatGPT, Codex, Binda Sébastien +# Licence : Apache 2.0 +# +# Description : service de creation d'issues GitHub depuis les retours beta. + +import json +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + +from .github_feedback_configuration import GitHubFeedbackConfiguration + + +class GitHubFeedbackService: + """Cree une issue GitHub a partir d'un retour utilisateur authentifie.""" + + CATEGORY_LABELS = { + "bug": "Bug", + "idea": "Idee", + "usability": "Utilisation", + "other": "Retour", + } + MAX_TITLE_LENGTH = 120 + MAX_MESSAGE_LENGTH = 4000 + MIN_MESSAGE_LENGTH = 10 + + def __init__(self, configuration: GitHubFeedbackConfiguration, http_post=None): + """Initialise le service de retour beta. + + Args: + configuration (GitHubFeedbackConfiguration): Configuration GitHub. + http_post (Callable | None): Fonction HTTP injectable pour les tests. + + Returns: + None: Le constructeur ne retourne aucune valeur. + """ + + self.configuration = configuration + self.http_post = http_post or self._post_json + + @classmethod + def from_environment(cls) -> "GitHubFeedbackService": + """Construit le service depuis l'environnement. + + Args: + Aucun. + + Returns: + GitHubFeedbackService: Service configure. + + Raises: + RuntimeError: Si la configuration est invalide. + """ + + try: + return cls(GitHubFeedbackConfiguration.from_environment()) + except ValueError as exc: + raise RuntimeError("Le service de retours beta n'est pas configure.") from exc + + def submit_feedback(self, payload: dict, requester_subject: str) -> dict: + """Valide un retour utilisateur et cree l'issue GitHub correspondante. + + Args: + payload (dict): Donnees du formulaire de retour. + requester_subject (str): Sujet authentifie ayant envoye le retour. + + Returns: + dict: Numero et URL publique de l'issue creee. + + Raises: + ValueError: Si les donnees utilisateur sont invalides. + RuntimeError: Si GitHub refuse ou ne peut pas traiter la creation. + """ + + feedback = self._normalize_payload(payload) + github_payload = { + "title": self._build_issue_title(feedback), + "body": self._build_issue_body(feedback, requester_subject), + } + if self.configuration.labels: + github_payload["labels"] = list(self.configuration.labels) + + issue = self.http_post(self._issues_url(), github_payload, self.configuration.token) + return { + "issue_number": int(issue.get("number", 0)), + "issue_url": str(issue.get("html_url") or ""), + } + + def _normalize_payload(self, payload: dict) -> dict: + title = str(payload.get("title") or "").strip() + category = str(payload.get("category") or "other").strip().lower() + message = str(payload.get("message") or "").strip() + page_url = str(payload.get("page_url") or "").strip() + user_agent = str(payload.get("user_agent") or "").strip() + + if category not in self.CATEGORY_LABELS: + raise ValueError("Le type de retour est invalide.") + if len(message) < self.MIN_MESSAGE_LENGTH: + raise ValueError("Le retour doit contenir au moins 10 caracteres.") + if len(message) > self.MAX_MESSAGE_LENGTH: + raise ValueError("Le retour est trop long.") + if len(title) > self.MAX_TITLE_LENGTH: + raise ValueError("Le titre du retour est trop long.") + + return { + "title": title, + "category": category, + "message": message, + "page_url": page_url[:500], + "user_agent": user_agent[:500], + } + + def _build_issue_title(self, feedback: dict) -> str: + title = feedback["title"] or feedback["message"].splitlines()[0] + normalized_title = " ".join(title.split())[: self.MAX_TITLE_LENGTH] + category_label = self.CATEGORY_LABELS[feedback["category"]] + return f"{self.configuration.title_prefix} {category_label} - {normalized_title}" + + def _build_issue_body(self, feedback: dict, requester_subject: str) -> str: + return "\n".join([ + "## Retour utilisateur", + "", + feedback["message"], + "", + "## Contexte", + "", + f"- Type : {self.CATEGORY_LABELS[feedback['category']]}", + f"- Utilisateur applicatif : {requester_subject or 'inconnu'}", + f"- Page : {feedback['page_url'] or 'non fournie'}", + f"- Navigateur : {feedback['user_agent'] or 'non fourni'}", + ]) + + def _issues_url(self) -> str: + return f"https://api.github.com/repos/{self.configuration.repository}/issues" + + @staticmethod + def _post_json(url: str, payload: dict, token: str) -> dict: + request = Request( + url, + data=json.dumps(payload).encode("utf-8"), + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "User-Agent": "CloudCollectionApp", + "X-GitHub-Api-Version": "2022-11-28", + }, + method="POST", + ) + try: + with urlopen(request, timeout=10) as response: + return json.loads(response.read().decode("utf-8")) + except HTTPError as exc: + raise RuntimeError("GitHub a refuse la creation du retour.") from exc + except (OSError, URLError, json.JSONDecodeError) as exc: + raise RuntimeError("Le service GitHub est temporairement indisponible.") from exc diff --git a/backend/tests/by_module/controllers/test_app_routes.py b/backend/tests/by_module/controllers/test_app_routes.py index 080a397..707a60e 100644 --- a/backend/tests/by_module/controllers/test_app_routes.py +++ b/backend/tests/by_module/controllers/test_app_routes.py @@ -95,6 +95,7 @@ def test_routes_route_lists_public_and_protected_routes(self): self.assertTrue( routes_by_key[("/api/users/collection/reinit", ("POST",))]["requires_auth"] ) + self.assertTrue(routes_by_key[("/api/feedback", ("POST",))]["requires_auth"]) self.assertEqual( ["Bearer"], routes_by_key[("/collections/videogames/games", ("POST",))]["auth_schemes"], diff --git a/backend/tests/by_module/controllers/test_feedback_routes.py b/backend/tests/by_module/controllers/test_feedback_routes.py new file mode 100644 index 0000000..0f09584 --- /dev/null +++ b/backend/tests/by_module/controllers/test_feedback_routes.py @@ -0,0 +1,175 @@ +# ____ _ _ ____ _ _ _ _ ___ +# / ___| | ___ _ _ __| |/ ___|___ | | | ___ ___| |_(_) ___ _ __ / _ \ _ __ _ __ +# | | | |/ _ \| | | |/ _` | | / _ \| | |/ _ \/ __| __| |/ _ \| `_ \| | | | `_ \| `_ | +# | |___| | (_) | |_| | (_| | |__| (_) | | | __/ (__| |_| | (_) | | | | |_| | |_) | |_) | +# \____|_|\___/ \__,_|\__,_|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|\___/| .__/| .__/ +# |_| |_| +# Projet : CloudCollectionApp +# Date de creation : 2026-08-23 +# Auteurs : OpenAI ChatGPT, Codex, Binda Sébastien +# Licence : Apache 2.0 +# +# Description : tests de la route HTTP de retours beta. + +import app as app_module + +try: + from tests.support.route_test_support import BaseAppRoutesTest +except ModuleNotFoundError: + from tests.support.route_test_support import BaseAppRoutesTest + + +class FakeFeedbackRouteService: + """Service de retour factice pour les tests HTTP.""" + + last_call = None + next_error = None + + def submit_feedback(self, payload, requester_subject): + """Memorise l'appel et retourne une issue factice. + + Args: + payload (dict): Payload recu. + requester_subject (str): Sujet authentifie. + + Returns: + dict: Issue factice. + + Raises: + Exception: Erreur configuree pour le test. + """ + + self.__class__.last_call = (payload, requester_subject) + if self.next_error: + raise self.next_error + return {"issue_number": 7, "issue_url": "https://github.com/acme/app/issues/7"} + + +class FeedbackRoutesTest(BaseAppRoutesTest): + """Valide le contrat HTTP d'envoi de retour beta.""" + + def setUp(self): + """Prepare les fakes de route. + + Args: + Aucun. + + Returns: + None: Le client Flask est configure. + """ + + super().setUp() + self.original_feedback_service_factory = app_module.feedback_controller.feedback_service_factory + app_module.feedback_controller.feedback_service_factory = FakeFeedbackRouteService + FakeFeedbackRouteService.last_call = None + FakeFeedbackRouteService.next_error = None + + def tearDown(self): + """Restaure la fabrique de service. + + Args: + Aucun. + + Returns: + None: Les fakes sont retires. + """ + + app_module.feedback_controller.feedback_service_factory = self.original_feedback_service_factory + super().tearDown() + + def test_submit_feedback_requires_authentication(self): + """Verifie que la route est protegee. + + Args: + Aucun. + + Returns: + None: Les assertions valident le statut. + """ + + response = self.client.post("/api/feedback", json={"message": "Retour utilisateur."}) + + self.assertEqual(403, response.status_code) + + def test_submit_feedback_creates_issue_for_user(self): + """Verifie la creation d'un retour avec le sujet connecte. + + Args: + Aucun. + + Returns: + None: Les assertions valident le payload. + """ + + response = self.client.post( + "/api/feedback", + json={"category": "idea", "message": "Ajouter un mode sombre serait utile."}, + headers=self.get_user_auth_headers(), + ) + + self.assertEqual(201, response.status_code) + self.assertEqual(7, response.get_json()["feedback"]["issue_number"]) + self.assertEqual("user@example.com", FakeFeedbackRouteService.last_call[1]) + + def test_submit_feedback_returns_400_for_invalid_payload(self): + """Verifie la conversion des erreurs de validation. + + Args: + Aucun. + + Returns: + None: Les assertions valident le statut. + """ + + FakeFeedbackRouteService.next_error = ValueError("Le retour est invalide.") + + response = self.client.post( + "/api/feedback", + json={"message": "Court"}, + headers=self.get_user_auth_headers(), + ) + + self.assertEqual(400, response.status_code) + + def test_submit_feedback_returns_400_for_non_object_payload(self): + """Verifie le rejet d'un JSON qui n'est pas un objet. + + Args: + Aucun. + + Returns: + None: Les assertions valident le statut. + """ + + response = self.client.post( + "/api/feedback", + json=["message invalide"], + headers=self.get_user_auth_headers(), + ) + + self.assertEqual(400, response.status_code) + self.assertIsNone(FakeFeedbackRouteService.last_call) + + def test_submit_feedback_returns_503_when_github_is_unavailable(self): + """Verifie la conversion des erreurs GitHub. + + Args: + Aucun. + + Returns: + None: Les assertions valident le statut. + """ + + FakeFeedbackRouteService.next_error = RuntimeError("GitHub indisponible.") + + response = self.client.post( + "/api/feedback", + json={"message": "Le formulaire fonctionne mal sur mobile."}, + headers=self.get_user_auth_headers(), + ) + + self.assertEqual(503, response.status_code) + + +if __name__ == "__main__": + unittest.main() diff --git a/backend/tests/by_module/services/test_github_feedback_service.py b/backend/tests/by_module/services/test_github_feedback_service.py new file mode 100644 index 0000000..29705a8 --- /dev/null +++ b/backend/tests/by_module/services/test_github_feedback_service.py @@ -0,0 +1,82 @@ +# ____ _ _ ____ _ _ _ _ ___ +# / ___| | ___ _ _ __| |/ ___|___ | | | ___ ___| |_(_) ___ _ __ / _ \ _ __ _ __ +# | | | |/ _ \| | | |/ _` | | / _ \| | |/ _ \/ __| __| |/ _ \| `_ \| | | | `_ \| `_ | +# | |___| | (_) | |_| | (_| | |__| (_) | | | __/ (__| |_| | (_) | | | | |_| | |_) | |_) | +# \____|_|\___/ \__,_|\__,_|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|\___/| .__/| .__/ +# |_| |_| +# Projet : CloudCollectionApp +# Date de creation : 2026-08-23 +# Auteurs : OpenAI ChatGPT, Codex, Binda Sébastien +# Licence : Apache 2.0 +# +# Description : tests du service de retours beta GitHub. + +import unittest + +from services.feedback import GitHubFeedbackConfiguration, GitHubFeedbackService + + +class GitHubFeedbackServiceTest(unittest.TestCase): + """Valide la creation d'issues GitHub depuis les retours utilisateur.""" + + def test_submit_feedback_creates_issue_payload(self): + """Verifie le payload transmis a GitHub. + + Args: + Aucun. + + Returns: + None: Les assertions valident le titre et le corps. + """ + + calls = [] + + def fake_post(url, payload, token): + calls.append((url, payload, token)) + return {"number": 42, "html_url": "https://github.com/acme/app/issues/42"} + + service = GitHubFeedbackService( + GitHubFeedbackConfiguration("acme/app", "token", ("feedback",), "[Beta]"), + http_post=fake_post, + ) + + result = service.submit_feedback( + { + "category": "bug", + "title": "Le bouton ne reagit pas", + "message": "Le bouton de partage ne reagit pas sur mobile.", + "page_url": "http://localhost/about", + "user_agent": "Firefox", + }, + "user@example.com", + ) + + self.assertEqual(42, result["issue_number"]) + self.assertEqual("https://github.com/acme/app/issues/42", result["issue_url"]) + self.assertEqual("https://api.github.com/repos/acme/app/issues", calls[0][0]) + self.assertEqual("token", calls[0][2]) + self.assertEqual("[Beta] Bug - Le bouton ne reagit pas", calls[0][1]["title"]) + self.assertIn("user@example.com", calls[0][1]["body"]) + self.assertEqual(["feedback"], calls[0][1]["labels"]) + + def test_submit_feedback_rejects_short_message(self): + """Verifie la validation de la taille minimale du message. + + Args: + Aucun. + + Returns: + None: Les assertions valident l'erreur. + """ + + service = GitHubFeedbackService( + GitHubFeedbackConfiguration("acme/app", "token", ("feedback",), "[Beta]"), + http_post=lambda url, payload, token: {}, + ) + + with self.assertRaises(ValueError): + service.submit_feedback({"category": "bug", "message": "Court"}, "user@example.com") + + +if __name__ == "__main__": + unittest.main() diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml index 1a4a373..2e090ae 100644 --- a/docker/docker-compose.local.yml +++ b/docker/docker-compose.local.yml @@ -58,6 +58,10 @@ services: SMTP_USERNAME: ${LOCAL_SMTP_USERNAME:-} SMTP_PASSWORD: ${LOCAL_SMTP_PASSWORD:-} SMTP_USE_TLS: ${LOCAL_SMTP_USE_TLS:-false} + GITHUB_FEEDBACK_REPOSITORY: ${GITHUB_FEEDBACK_REPOSITORY:-} + GITHUB_FEEDBACK_TOKEN: ${LOCAL_GITHUB_FEEDBACK_TOKEN:-${GITHUB_FEEDBACK_TOKEN:-}} + GITHUB_FEEDBACK_LABELS: ${GITHUB_FEEDBACK_LABELS:-feedback,remarque} + GITHUB_FEEDBACK_TITLE_PREFIX: ${GITHUB_FEEDBACK_TITLE_PREFIX:-[Retour utilisateur]} volumes: - ${USERS_WORKSPACE:-${APPLICATION_WORKDIR:-../runtime-data}/users-workspace}:/users/workspace - ${BACKEND_IMG_HOST_DIR:-${APPLICATION_WORKDIR:-../runtime-data}/images}:/images diff --git a/documentation/about.md b/documentation/about.md index 2786dd0..4761d05 100644 --- a/documentation/about.md +++ b/documentation/about.md @@ -22,6 +22,12 @@ available. - The content must remain descriptive and non-technical. - The text must describe use cases: importing a collection, exploring owned games, viewing the authenticated home page, and maintaining account data. +- The page must expose a single in-progress application notice with a link to + `/feedback`; it must not embed the feedback form directly. +- The dedicated `/feedback` page may expose a feedback form only to + authenticated non-GUEST application users. The form must call the protected + backend endpoint so users can send feedback without a GitHub account, while + unauthenticated visitors are invited to sign in first. - Navigation must go through `MainMenu`. - Actions reserved for an authenticated session must remain disabled through the menu when the user is not signed in. diff --git a/documentation/authentication.md b/documentation/authentication.md index e0df960..a5d60aa 100644 --- a/documentation/authentication.md +++ b/documentation/authentication.md @@ -130,6 +130,7 @@ right even when its frontend does not expose collection ownership): - `POST /api/collection-shares` - `GET /api/collection-shares` - `DELETE /api/collection-shares/` +- `POST /api/feedback` - `GET /api/users/me/collection` - `POST /api/users/import/file/` - `POST /api/users/import/analyze/` diff --git a/documentation/backend-api.md b/documentation/backend-api.md index b7fa4b7..694f519 100644 --- a/documentation/backend-api.md +++ b/documentation/backend-api.md @@ -208,6 +208,55 @@ Returns the backend route catalog, including: This route is protected and explicitly accepts `GUEST`, `USER` and `ADMIN`. +## User Feedback + +### Submit User Feedback + +```http +POST /api/feedback +Content-Type: application/json +Authorization: Bearer +``` + +This route requires at least profile `USER`. It lets an authenticated +application user send feedback without owning a GitHub account. The backend +creates a GitHub issue with a server-side token; the token is never exposed to +the frontend. + +Request: + +```json +{ + "category": "idea", + "title": "Ameliorer le partage", + "message": "Le bouton de partage pourrait etre plus visible.", + "page_url": "https://example.com/about", + "user_agent": "Mozilla/5.0 ..." +} +``` + +`category` accepts `bug`, `idea`, `usability` or `other`. `title` is optional. +`message` is mandatory and must contain at least 10 characters. + +Successful response: + +```json +{ + "feedback": { + "issue_number": 42, + "issue_url": "https://github.com/owner/repository/issues/42" + } +} +``` + +Errors use: + +- `400` for invalid feedback payload; +- `403` for missing Bearer token or insufficient profile; +- `503` when GitHub feedback configuration is missing or GitHub cannot create + the issue; +- `500` for unexpected failures. + ## Collection Share Management These routes require a Bearer profile with at least `USER`. The share owner is diff --git a/documentation/backend-arch.md b/documentation/backend-arch.md index 26815dc..2fd305d 100644 --- a/documentation/backend-arch.md +++ b/documentation/backend-arch.md @@ -82,6 +82,7 @@ Use one controller per functional area when possible, for example: future game actions and raw user ODS download; - `CollectionShareController` for connected-owner share creation, listing and revocation HTTP contracts; +- `FeedbackController` for authenticated feedback submission to GitHub; - `RouteController` for `/api/routes`; - `PlatformController`, `StudioController` and `GameController` for public Bibliotheque reads of global platforms, studios and games. @@ -99,6 +100,7 @@ Use domain folders under `backend/services/`: initialization. The platform catalog seed/update services also live there because they own SQL synchronization from backend CSV resources; - `email/`: email configuration and sending; +- `feedback/`: feedback validation and GitHub issue creation; - `formatting/`: value formatting helpers; - `collection/`: connected-user SQL collection consultation and query contracts; detailed collection statistics; `collection/imports/` also owns format-independent import diff --git a/documentation/ci.md b/documentation/ci.md index 17f9f3a..5446fc9 100644 --- a/documentation/ci.md +++ b/documentation/ci.md @@ -11,8 +11,14 @@ pushed Git tag. - Backend tests run only when backend-related files change, when the workflow file changes, and on every pushed Git tag. +- Backend dependency audit runs only when backend-related files change, when + the workflow file changes, and on every pushed Git tag. Any Python dependency + vulnerability reported by `pip-audit` fails the job. - Frontend tests run only when frontend-related files change, when the workflow file changes, and on every pushed Git tag. +- Frontend dependency audit runs only when frontend-related files change, when + the workflow file changes, and on every pushed Git tag. Any reported npm + vulnerability fails the job. - The frontend production build runs only when frontend-related files change, when the workflow file changes, and on every pushed Git tag. - Application Docker images are published only when a Git tag matching `X.Y.Z` @@ -36,8 +42,12 @@ jobs: - `change-detection`: detects which validation and publication jobs are needed from the changed files. - `backend-tests`: runs `./scripts/test_backend.sh`. +- `backend-audit`: installs `pip-audit` and runs + `python -m pip_audit -r backend/requirements.txt --strict`. - `frontend-tests`: installs frontend dependencies with `npm ci` and runs `npm test`. +- `frontend-audit`: installs frontend dependencies with `npm ci` and runs + `npm audit --audit-level=low`. - `frontend-build`: installs frontend dependencies with `npm ci` and runs `npm run build`. - `deploy-archive`: for Git tags only, builds @@ -50,11 +60,14 @@ jobs: On pull requests and branch pushes, backend tests run for every added, modified or removed path prefixed with `backend/`, for `scripts/test_backend.sh`, for `docker/backend.Dockerfile`, for `docker/backend.Dockerfile.dockerignore`, or -for `.github/workflows/ci.yml`. Frontend tests and the frontend build run for -every added, modified or removed path prefixed with `frontend/`, for +for `.github/workflows/ci.yml`. The backend dependency audit runs for the same +backend-related changes. Frontend tests, the frontend dependency audit and the +frontend build run for every added, modified or removed path prefixed with +`frontend/`, for `docker/frontend.Dockerfile`, for `docker/frontend.Dockerfile.dockerignore`, or -for `.github/workflows/ci.yml`. On Git tags, both validations always run before -Docker publication. +for `.github/workflows/ci.yml`. On Git tags, backend tests, backend dependency +audit, frontend tests, frontend dependency audit and frontend build always run +before Docker publication. For branch push events, the workflow reads GitHub's event payload first so that file deletions and multi-commit branch pushes are detected reliably. It falls @@ -62,18 +75,27 @@ back to Git diff commands when the payload does not contain changed paths. For release tags, the workflow may compare the tagged commit with the previous release tag when a publication job needs changed-file decisions. -The `docker-images` and `deploy-archive` jobs depend on backend tests, frontend -tests and the frontend build. Docker images and the deployment archive must not -be published if tests or frontend build fail. Branch pushes never publish -Docker images or deployment archives. +The `docker-images` and `deploy-archive` jobs depend on backend tests, backend +dependency audit, frontend tests, frontend dependency audit and the frontend +build. Docker images and the deployment archive must not be published if tests, +audit or frontend build fail. Branch pushes never publish Docker images or +deployment archives. Backend tests run through `./scripts/test_backend.sh`, which prepares the Python environment and then executes the backend test suite. ODS fixtures are now loaded directly by import tests when needed. +Backend dependency audit runs through `pip-audit` against +`backend/requirements.txt`. The job uses Python `3.12`, installs `pip-audit` +inside the CI runner, and fails for any vulnerability or audit-service failure +because it runs with `--strict`. + Frontend tests run through `npm test` in `frontend/`, using Node.js' native test runner against `frontend/tests/*.test.js`. +Frontend dependency audit runs through `npm audit --audit-level=low` in +`frontend/`. The job must fail for any vulnerability reported by npm audit. + The deploy archive is built by `scripts/create_deploy_archive.sh` and named `cloud-application-deploy-.zip`, where `` is the release tag. Archive content and runtime usage are documented in `documentation/deploy.md`. diff --git a/documentation/deploy.md b/documentation/deploy.md index 3041a99..0446d40 100644 --- a/documentation/deploy.md +++ b/documentation/deploy.md @@ -105,6 +105,7 @@ Required secrets inside the archive: - `AUTH_SECRET_KEY_ENCRYPTED` - `POSTGRES_PASSWORD` - `SMTP_PASSWORD` +- `GITHUB_FEEDBACK_TOKEN` The production Compose file consumes: @@ -113,6 +114,7 @@ The production Compose file consumes: - `AUTH_ENV_ENCRYPTION_KEY_FILE`, `AUTH_PASSWORD_ENCRYPTED_FILE` and `AUTH_SECRET_KEY_ENCRYPTED_FILE` for backend authentication. - `SMTP_PASSWORD_FILE` for backend email delivery. +- `GITHUB_FEEDBACK_TOKEN_FILE` for authenticated feedback issue creation. By default, the temporary decrypted secret directory is created under `/tmp` if Docker can bind-mount a test file from there. Set `PRODUCTION_SECRETS_TMP_PARENT` @@ -216,6 +218,29 @@ To test email delivery against the production Compose stack: ./scripts/test_email.sh -p --to destinataire@example.com ``` +## GitHub Feedback Runtime Configuration + +Authenticated feedback creates issues in GitHub from the backend. Users do not +need a GitHub account; the backend uses the server-side +`GITHUB_FEEDBACK_TOKEN` secret. + +Required configuration: + +```text +GITHUB_FEEDBACK_REPOSITORY=owner/repository +GITHUB_FEEDBACK_TOKEN +``` + +Optional non-secret variables: + +```text +GITHUB_FEEDBACK_LABELS=feedback,remarque +GITHUB_FEEDBACK_TITLE_PREFIX=[Retour utilisateur] +``` + +The GitHub token must be scoped only to the target repository and allowed to +create issues. Store it in the encrypted age secret archive, not in `.env`. + ## Compose Contract `docker-compose.online.yml` must run published images instead of building local diff --git a/documentation/frontend-arch.md b/documentation/frontend-arch.md index 55d83c9..a7828d3 100644 --- a/documentation/frontend-arch.md +++ b/documentation/frontend-arch.md @@ -247,6 +247,10 @@ Use the following domain folders for new or modified hooks: automatic request loops. - Do not put React state in services. - Do not duplicate token logic outside existing auth/API services. +- Keep beta feedback submission HTTP details in `FeedbackApi`. The dedicated + `FeedbackView` page may own the small local form state, but the service must + attach the Bearer token and call only the backend feedback endpoint; GitHub + tokens must never be present in frontend code. - Keep public link exchange isolated in `CollectionShareSessionApi`: it must not attach an existing Authorization header. Keep owner management HTTP calls in `CollectionSharesApi` with normal Bearer headers. diff --git a/documentation/menu.md b/documentation/menu.md index 406d8f7..73b799e 100644 --- a/documentation/menu.md +++ b/documentation/menu.md @@ -42,17 +42,17 @@ session state received through props. bar: `Connexion` for anonymous visitors, `Deconnexion` for authenticated users. - For authenticated users on desktop, the main navigation order is - `Ma collection`, `Liste de souhaits`, `Statistiques`, `Bibliotheque`, `Configuration`, then - `A propos`; `Deconnexion` remains the last action on the right side of the - navigation bar. + `Ma collection`, `Liste de souhaits`, `Statistiques`, `Bibliotheque`, + `Configuration`, `Faire un retour`, then `A propos`; `Deconnexion` remains + the last action on the right side of the navigation bar. - For authenticated users on mobile, `Collection`, `Souhaits`, `Stats`, `Biblio` and `Plus` are the primary dock entries, in that order. - Anonymous visitors see only public/session entries: `Bibliotheque`, - `Connexion` and `A propos` directly in the mobile dock; the `Plus` entry is - not rendered when no secondary action is available. -- On mobile, authenticated users see `Configuration`, `A propos` and - `Deconnexion` as secondary actions opened from `Plus`, with `Deconnexion` - last. + `Connexion`, `Faire un retour` and `A propos` directly in the mobile dock; the + `Plus` entry is not rendered when no secondary action is available. +- On mobile, authenticated users see `Configuration`, `Faire un retour`, + `A propos` and `Deconnexion` as secondary actions opened from `Plus`, with + `Deconnexion` last. - The secondary mobile panel closes when the mouse pointer leaves it. - On mobile and touch devices, pointer leave must not cause accidental closing; filter events by `pointerType`. @@ -63,11 +63,14 @@ session state received through props. shortcuts. - A GUEST sees only the permitted Collection/Wishlist primary entries, followed by Bibliotheque and `Plus`; the dock grid adapts to the resulting item count. - `Plus` contains About and Logout, never Configuration. + `Plus` contains Faire un retour, About and Logout, never Configuration. ## Access Constraints - `A propos` always remains accessible and opens `/about`. +- `Faire un retour` always remains accessible and opens `/feedback`; the page + itself decides whether the connected session can submit a protected feedback + request. - `Bibliotheque` always remains accessible. When an `ADMIN` session has Library games waiting for validation, the menu may show a prop-driven badge on this entry. The menu must not fetch the summary diff --git a/documentation/site-plan.md b/documentation/site-plan.md index e01a6b2..c254d64 100644 --- a/documentation/site-plan.md +++ b/documentation/site-plan.md @@ -8,6 +8,9 @@ ## Public Routes - `/about`: public About page for unauthenticated visitors. +- `/feedback`: public feedback page. It displays the feedback form only for + authenticated non-GUEST application users; anonymous visitors are invited to + sign in, and the backend remains responsible for creating the GitHub issue. - `/auth`: sign-in page. When opened from an activation email with `email=
`, it redirects to `/about` if that account is already connected, or asks the connected user to sign out before reconnecting with @@ -107,7 +110,7 @@ claims: - `/collection/jeux/` is available only when backend confirms that the game, optionally selected with `?region=`, belongs to a shared category; -- `/bibliotheque/**`, `/about` and Logout remain available; +- `/bibliotheque/**`, `/about`, `/feedback` and Logout remain available; - `/configuration`, every `/configuration/**` subroute, `/users`, `/add-game` and `/collection/import` are unavailable. Direct navigation redirects to the shared Collection first, otherwise Wishlist, otherwise About. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d8f24c1..1d064ad 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,317 +9,35 @@ "version": "0.1.0", "dependencies": { "chart.js": "^4.5.1", + "lucide-react": "^1.33.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@vitejs/plugin-react": "^4.3.1", - "vite": "^5.4.0" + "@vitejs/plugin-react": "^6.1.0", + "vite": "^8.2.2" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "node_modules/@oxc-project/types": { + "version": "0.146.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", + "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "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.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@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-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", + "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==", "cpu": [ "arm" ], @@ -330,13 +48,13 @@ "android" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz", + "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==", "cpu": [ "arm64" ], @@ -347,30 +65,13 @@ "android" ], "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz", + "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==", "cpu": [ "arm64" ], @@ -381,13 +82,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz", + "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==", "cpu": [ "x64" ], @@ -398,30 +99,13 @@ "darwin" ], "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz", + "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==", "cpu": [ "x64" ], @@ -432,13 +116,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz", + "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==", "cpu": [ "arm" ], @@ -449,200 +133,150 @@ "linux" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz", + "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz", + "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==", "cpu": [ - "loong64" + "arm64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" + "libc": [ + "musl" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz", + "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz", + "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz", + "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz", + "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" + "libc": [ + "musl" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "linux" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz", + "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "sunos" + "openharmony" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz", + "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==", "cpu": [ "arm64" ], @@ -653,15 +287,15 @@ "win32" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz", + "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", @@ -670,618 +304,45 @@ "win32" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz", + "integrity": "sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", - "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true } - ], - "license": "CC-BY-4.0" + } }, "node_modules/chart.js": { "version": "4.5.1", @@ -1295,142 +356,326 @@ "pnpm": ">=8" } }, - "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==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "supports-color": { + "picomatch": { "optional": true } } }, - "node_modules/electron-to-chromium": { - "version": "1.5.348", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.348.tgz", - "integrity": "sha512-QC2X59nRlycQQMc4ZXjSVBX+tSgJfgRtcrYHbIZLgOV2dCvefoQGegLR7lLXKgpPpSuVmJU19LMzGrSa2C7k3Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], "dev": true, - "hasInstallScript": true, - "license": "MIT", + "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "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==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/loose-envify": { @@ -1445,27 +690,19 @@ "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, + "node_modules/lucide-react": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.33.0.tgz", + "integrity": "sha512-MTRwMy0ZlL8Ur/vOAiJ9XGHE+kFPC7brq6MxAm0GiGXEBj0qy0jA/pG4N675oSzciO/UCdX8T+5yUQdmDeTLxg==", "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -1481,13 +718,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1495,10 +725,23 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", - "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -1516,7 +759,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1549,59 +792,38 @@ "react": "^18.3.1" } }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "node_modules/rolldown": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz", + "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.146.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm-eabi": "1.2.5", + "@rolldown/binding-android-arm64": "1.2.5", + "@rolldown/binding-darwin-arm64": "1.2.5", + "@rolldown/binding-darwin-x64": "1.2.5", + "@rolldown/binding-freebsd-x64": "1.2.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", + "@rolldown/binding-linux-arm64-gnu": "1.2.5", + "@rolldown/binding-linux-arm64-musl": "1.2.5", + "@rolldown/binding-linux-ppc64-gnu": "1.2.5", + "@rolldown/binding-linux-s390x-gnu": "1.2.5", + "@rolldown/binding-linux-x64-gnu": "1.2.5", + "@rolldown/binding-linux-x64-musl": "1.2.5", + "@rolldown/binding-openharmony-arm64": "1.2.5", + "@rolldown/binding-win32-arm64-msvc": "1.2.5", + "@rolldown/binding-win32-x64-msvc": "1.2.5" } }, "node_modules/scheduler": { @@ -1613,16 +835,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1633,53 +845,41 @@ "node": ">=0.10.0" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, - "bin": { - "update-browserslist-db": "cli.js" + "engines": { + "node": ">=12.0.0" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -1688,23 +888,33 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { @@ -1721,15 +931,14 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" } } } diff --git a/frontend/package.json b/frontend/package.json index 85e7417..dd1b20e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,11 +11,12 @@ }, "dependencies": { "chart.js": "^4.5.1", + "lucide-react": "^1.33.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@vitejs/plugin-react": "^4.3.1", - "vite": "^5.4.0" + "@vitejs/plugin-react": "^6.1.0", + "vite": "^8.2.2" } } diff --git a/frontend/src/appRouting.js b/frontend/src/appRouting.js index eae5efd..351385d 100644 --- a/frontend/src/appRouting.js +++ b/frontend/src/appRouting.js @@ -158,6 +158,7 @@ class AppRouting { } return [ "/about", + "/feedback", "/auth", "/auth/verify-email", "/bibliotheque", @@ -171,7 +172,7 @@ class AppRouting { * Deduit la vue active depuis le chemin et les parametres d'URL. * * @param {void} Aucun - Utilise `window.location`. - * @returns {"about"|"home"|"games"|"statistics"|"wishlist"|"addGame"|"configuration"|"auth"|"emailVerificationResult"|"users"|"adminLibraryImport"|"platformImageModeration"|"gameDuplicateAdmin"|"collectionOnboarding"|"library"|"libraryPlatforms"|"libraryPlatformDetail"|"libraryStudios"|"libraryGames"|"gameDetail"} Identifiant de vue. + * @returns {"about"|"feedback"|"home"|"games"|"statistics"|"wishlist"|"addGame"|"configuration"|"auth"|"emailVerificationResult"|"users"|"adminLibraryImport"|"platformImageModeration"|"gameDuplicateAdmin"|"collectionOnboarding"|"library"|"libraryPlatforms"|"libraryPlatformDetail"|"libraryStudios"|"libraryGames"|"gameDetail"} Identifiant de vue. */ static getViewFromUrl() { if (/^\/collection\/share\/[^/]+$/.test(window.location.pathname)) { @@ -186,6 +187,9 @@ class AppRouting { if (window.location.pathname === "/about") { return "about"; } + if (window.location.pathname === "/feedback") { + return "feedback"; + } if (window.location.pathname === "/auth") { return "auth"; } diff --git a/frontend/src/components/AboutView.jsx b/frontend/src/components/AboutView.jsx index 473142a..cb7fddf 100644 --- a/frontend/src/components/AboutView.jsx +++ b/frontend/src/components/AboutView.jsx @@ -12,6 +12,16 @@ * * Description : page publique de presentation fonctionnelle de l'application. */ +import { + ArrowRight, + Code2, + HeartHandshake, + LockKeyhole, + Search, + Share2, + Smartphone, + UploadCloud, +} from "lucide-react"; import PageLayout from "./PageLayout"; /** @@ -34,6 +44,7 @@ function AboutView({ onOpenAuth, onOpenHome, onOpenLibrary, + onOpenFeedback, onOpenWishlist, onOpenStatistics, onOpenConfiguration, @@ -52,9 +63,9 @@ function AboutView({ /> )} eyebrow="CloudCollectionApp" - title="CloudApplicationApp" + title="Votre collection de jeux, toujours sous la main" subtitle={ - "Transformez votre fichier de collection personnel en site en ligne, disponible a tout moment, avec un simple import. Votre collection reste privee, la base de jeux s'enrichit avec la communaute." + "Importez votre fichier, consultez vos jeux partout, gardez vos donnees privees et partagez seulement ce que vous choisissez." } isAuthenticated={isAuthenticated} canUseCollectionViews={canUseCollectionViews} @@ -68,6 +79,7 @@ function AboutView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenStatistics={onOpenStatistics} onOpenConfiguration={onOpenConfiguration} @@ -75,88 +87,107 @@ function AboutView({ > {error ?

{error}

: null}
-
-

Ce que permet l'application

+
+

Un espace simple pour ne plus perdre le fil

- CloudCollectionApp transforme un tableur local en espace web personnel. Apres - inscription, importez votre fichier de collection et retrouvez votre collection depuis - n'importe quel appareil. Vos jeux restent rattaches a votre compte, pendant que le - catalogue commun des plateformes, studios et jeux progresse grace aux imports de tous - les utilisateurs. + CloudCollectionApp transforme votre fichier de collection en espace personnel en ligne. + Vous retrouvez rapidement ce que vous possedez, ce que vous cherchez encore et les + informations utiles quand vous etes chez vous, en boutique ou en salon.

-
-

Du fichier au site

+
+ +

Disponible partout

- Importez votre fichier de collection une seule fois et accedez ensuite a vos - plateformes, jeux et indicateurs depuis une interface en ligne claire. Vous pouvez - ensuite telecharger un fichier mis a jour avec les modifications faites sur le site. + Votre collection vous suit sur ordinateur, tablette ou mobile. Plus besoin d'avoir + le bon fichier sous la main pour verifier un jeu ou une plateforme.

-
-

Collection privee

-

- Votre collection personnelle reste associee a votre compte. Elle n'est pas exposee aux - autres utilisateurs et les acces passent par votre session connectee. -

-
-
-

Base commune

+
+ +

Import rapide

- Chaque import aide a enrichir le referentiel commun des jeux, plateformes et studios, - pour rendre la recherche et les futures collections plus utiles. + Repartez de votre tableur existant, importez-le, puis ajoutez de nouveaux fichiers + quand votre collection evolue.

-
- -
-

Les points cles au quotidien

-

- Une fois la collection importee, l'application devient un tableau de bord personnel - pour consulter, suivre et faire evoluer votre collection sans revenir au fichier source - pour chaque action. -

-
- -
-
-

Explorer la collection

+
+ +

Recherche claire

- Parcourez vos jeux par plateforme et retrouvez rapidement une entree grace aux vues de - detail, aux filtres et a la recherche. + Retrouvez vos jeux par plateforme, consultez les details importants et gardez une + liste d'envies separee de votre collection.

-
-

Suivre la liste des envies

-

- Gardez une liste de souhaits separee pour preparer vos prochains ajouts et suivre les - jeux qui vous interessent. -

-
-
-

Piloter les mises a jour

+
+ +

Collection privee

- Ajoutez, modifiez ou transferez des jeux avec les actions autorisees par votre profil - et recuperez ensuite un fichier de collection coherent avec vos changements. + Vos jeux restent rattaches a votre compte. Vous pouvez garder votre collection pour + vous ou partager un acces controle quand vous le decidez.

-
-

Afficher les statistiques

+
+ +

Esprit communautaire

- Consultez les indicateurs de collection pour garder une vision claire des plateformes, - volumes et informations importantes. + Le catalogue commun des jeux, plateformes et studios s'ameliore avec les imports et + les validations, au benefice de tous les collectionneurs.

-
-

Libre et open source

+
+ +

Open source

- Profitez d'une application gratuite, libre et open source, pensee pour rester - transparente et evoluer avec sa communaute. + Le projet est libre et transparent. Il peut evoluer avec les besoins reels des + utilisateurs, sans enfermer votre collection dans une boite noire.

+ +
+

Application en evolution

+

+ L'application est encore en cours de travail. Des fonctionnalites peuvent evoluer, et + les retours des utilisateurs aident a prioriser les prochaines ameliorations. +

+ +
+ +
+ +

Pour commencer

+

+ Creez un compte, importez votre fichier, puis consultez votre collection en ligne. Vos + donnees restent privees par defaut, votre espace connecte garde vos parametres a jour + et les fonctions de partage restent sous votre controle. +

+ {!isAuthenticated ? ( + + ) : null} +
); diff --git a/frontend/src/components/AddGameView.jsx b/frontend/src/components/AddGameView.jsx index 5139546..6d040f6 100644 --- a/frontend/src/components/AddGameView.jsx +++ b/frontend/src/components/AddGameView.jsx @@ -38,6 +38,7 @@ function AddGameView({ onOpenAuth, onOpenHome, onOpenLibrary, + onOpenFeedback, onOpenWishlist, onOpenStatistics, onOpenConfiguration, @@ -68,6 +69,7 @@ function AddGameView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenStatistics={onOpenStatistics} onOpenConfiguration={onOpenConfiguration} diff --git a/frontend/src/components/AdminLibraryImportView.jsx b/frontend/src/components/AdminLibraryImportView.jsx index c9a879a..6361fea 100644 --- a/frontend/src/components/AdminLibraryImportView.jsx +++ b/frontend/src/components/AdminLibraryImportView.jsx @@ -43,6 +43,7 @@ function AdminLibraryImportView({ onOpenConfiguration, onOpenHome, onOpenLibrary, + onOpenFeedback, onOpenStatistics, onOpenWishlist, onPrepareNewImportAfterRefusal, @@ -77,6 +78,7 @@ function AdminLibraryImportView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenStatistics={onOpenStatistics} onOpenConfiguration={onOpenConfiguration} diff --git a/frontend/src/components/AppViewSwitch.jsx b/frontend/src/components/AppViewSwitch.jsx index df644c8..52fc38a 100644 --- a/frontend/src/components/AppViewSwitch.jsx +++ b/frontend/src/components/AppViewSwitch.jsx @@ -16,6 +16,7 @@ import AddGameView from "./AddGameView"; import AboutView from "./AboutView"; import AuthView from "./AuthView"; import EmailVerificationResultView from "./EmailVerificationResultView"; +import FeedbackView from "./FeedbackView"; import GameDetailView from "./GameDetailView"; import GameDuplicateAdminView from "./GameDuplicateAdminView"; import LibraryEntityListView from "./LibraryEntityListView"; @@ -50,6 +51,9 @@ class AppViewSwitch { if (props.currentView === "about") { return "about"; } + if (props.currentView === "feedback") { + return "feedback"; + } if ([ "configuration", "adminLibraryImport", @@ -108,6 +112,7 @@ class AppViewSwitch { onOpenHome: props.goHome, onOpenStatistics: props.openStatistics, onOpenLibrary: props.openLibrary, + onOpenFeedback: props.openFeedback, onOpenWishlist: props.openWishlist, onOpenConfiguration: props.openConfiguration, onLogout: props.logout, @@ -138,6 +143,9 @@ class AppViewSwitch { return this.renderAbout(props); } + if (props.currentView === "feedback") { + return ; + } if (props.currentView === "addGame") { return this.renderAddGame(props); } diff --git a/frontend/src/components/AuthView.jsx b/frontend/src/components/AuthView.jsx index c0fcf05..028d971 100644 --- a/frontend/src/components/AuthView.jsx +++ b/frontend/src/components/AuthView.jsx @@ -31,6 +31,7 @@ function AuthView({ onOpenAuth, onOpenHome, onOpenLibrary, + onOpenFeedback, onOpenWishlist, onOpenConfiguration, onLogout, @@ -255,6 +256,7 @@ function AuthView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenConfiguration={onOpenConfiguration} onLogout={onLogout} @@ -279,6 +281,7 @@ function AuthView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenConfiguration={onOpenConfiguration} onLogout={onLogout} @@ -316,6 +319,7 @@ function AuthView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenConfiguration={onOpenConfiguration} onLogout={onLogout} diff --git a/frontend/src/components/CollectionShareManagementView.jsx b/frontend/src/components/CollectionShareManagementView.jsx index 3a114ca..d3e0d11 100644 --- a/frontend/src/components/CollectionShareManagementView.jsx +++ b/frontend/src/components/CollectionShareManagementView.jsx @@ -82,6 +82,7 @@ function CollectionShareManagementView(props) { onOpenAuth={props.onOpenAuth} onOpenHome={props.onOpenHome} onOpenLibrary={props.onOpenLibrary} + onOpenFeedback={props.onOpenFeedback} onOpenWishlist={props.onOpenWishlist} onOpenStatistics={props.onOpenStatistics} onOpenConfiguration={props.onOpenConfiguration} diff --git a/frontend/src/components/ConfigurationView.jsx b/frontend/src/components/ConfigurationView.jsx index dd2ef4b..309446b 100644 --- a/frontend/src/components/ConfigurationView.jsx +++ b/frontend/src/components/ConfigurationView.jsx @@ -55,6 +55,7 @@ function ConfigurationView({ onOpenAuth, onOpenHome, onOpenLibrary, + onOpenFeedback, onOpenWishlist, onOpenStatistics, onOpenUsers, @@ -91,6 +92,7 @@ function ConfigurationView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenStatistics={onOpenStatistics} onOpenConfiguration={onOpenConfiguration} diff --git a/frontend/src/components/EmailVerificationResultView.jsx b/frontend/src/components/EmailVerificationResultView.jsx index 196a8aa..4cc280a 100644 --- a/frontend/src/components/EmailVerificationResultView.jsx +++ b/frontend/src/components/EmailVerificationResultView.jsx @@ -74,6 +74,7 @@ function EmailVerificationResultView({ onOpenAuth, onOpenHome, onOpenLibrary, + onOpenFeedback, onOpenWishlist, onOpenConfiguration, onLogout, @@ -94,6 +95,7 @@ function EmailVerificationResultView({ onOpenAuth={onOpenAuth} onOpenHome={onOpenHome} onOpenLibrary={onOpenLibrary} + onOpenFeedback={onOpenFeedback} onOpenWishlist={onOpenWishlist} onOpenConfiguration={onOpenConfiguration} onLogout={onLogout} diff --git a/frontend/src/components/FeedbackView.jsx b/frontend/src/components/FeedbackView.jsx new file mode 100644 index 0000000..c9cee89 --- /dev/null +++ b/frontend/src/components/FeedbackView.jsx @@ -0,0 +1,189 @@ +/* + * ____ _ _ ____ _ _ _ _ ___ + * / ___| | ___ _ _ __| |/ ___|___ | | | ___ ___| |_(_) ___ _ __ / _ \ _ __ _ __ + * | | | |/ _ \| | | |/ _` | | / _ \| | |/ _ \/ __| __| |/ _ \| `_ \| | | | `_ \| `_ | + * | |___| | (_) | |_| | (_| | |__| (_) | |__| (_) | | | | __/ (__| |_| | (_) | | | | |_| | |_) | |_) | + * \____|_|\___/ \__,_|\__,_|\____\___/|_|_|\___|\___|\__|_|\___/|_| |_|\___/| .__/| .__/ + * |_| |_| + * Projet : CloudCollectionApp + * Date de creation : 2026-08-23 + * Auteurs : OpenAI ChatGPT, Codex, Binda Sébastien + * Licence : Apache 2.0 + * + * Description : page dediee d'envoi de retours utilisateurs. + */ +import { useState } from "react"; +import { ArrowRight, MessageSquare, Send } from "lucide-react"; +import FeedbackApi from "../services/FeedbackApi"; +import PageLayout from "./PageLayout"; + +/** + * Affiche le formulaire de retour utilisateur envoye vers GitHub par le backend. + * + * @param {Object} props - Etat de session et callbacks de navigation. + * @returns {import("react").JSX.Element} Page de retour utilisateur. + */ +function FeedbackView({ + isAuthenticated, + isGuest, + canUseCollectionViews, + canViewCollection, + canViewWishlist, + canViewStatistics, + canAccessConfiguration, + authenticatedUsername, + authenticatedProfile, + onOpenAbout, + onOpenAuth, + onOpenHome, + onOpenLibrary, + onOpenFeedback, + onOpenWishlist, + onOpenStatistics, + onOpenConfiguration, + onLogout, +}) { + const [feedbackCategory, setFeedbackCategory] = useState("idea"); + const [feedbackTitle, setFeedbackTitle] = useState(""); + const [feedbackMessage, setFeedbackMessage] = useState(""); + const [feedbackStatus, setFeedbackStatus] = useState(""); + const [feedbackIssueUrl, setFeedbackIssueUrl] = useState(""); + const [feedbackError, setFeedbackError] = useState(""); + const [isSendingFeedback, setIsSendingFeedback] = useState(false); + const canSubmitFeedback = isAuthenticated && !isGuest; + + const submitFeedback = async (event) => { + event.preventDefault(); + setFeedbackStatus(""); + setFeedbackIssueUrl(""); + setFeedbackError(""); + setIsSendingFeedback(true); + try { + const feedback = await FeedbackApi.submitFeedback({ + category: feedbackCategory, + title: feedbackTitle, + message: feedbackMessage, + page_url: window.location.href, + user_agent: window.navigator.userAgent, + }); + setFeedbackMessage(""); + setFeedbackTitle(""); + setFeedbackIssueUrl(feedback.issue_url || ""); + setFeedbackStatus("Merci, votre remarque a ete envoyee."); + } catch (submitError) { + setFeedbackError(submitError.message || "Impossible d'envoyer le retour."); + } finally { + setIsSendingFeedback(false); + } + }; + + return ( + +
+
+ +

Envoyer une remarque

+

+ Decrivez simplement ce qui pose probleme ou ce qui pourrait etre ameliore. Une fois + la remarque envoyee, un lien de suivi GitHub vous sera fourni. +

+ {canSubmitFeedback ? ( +
+ + +