diff --git a/.github/workflows/_detect_changes.yml b/.github/workflows/_detect_changes.yml index 9e9b8a654..a4884e176 100644 --- a/.github/workflows/_detect_changes.yml +++ b/.github/workflows/_detect_changes.yml @@ -46,12 +46,12 @@ jobs: frontend: ${{steps.diff_check_frontend.outputs.frontend}} steps: - name: Check out PR target branch - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ github.base_ref }} - name: Check out source branch latest commit - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: clean: false diff --git a/.github/workflows/_node.yml b/.github/workflows/_node.yml index 525e8a199..5c81061e0 100644 --- a/.github/workflows/_node.yml +++ b/.github/workflows/_node.yml @@ -97,10 +97,10 @@ jobs: language: ['javascript'] steps: - name: Check out latest commit for current branch - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: ${{ matrix.node_version }} cache: 'npm' @@ -137,7 +137,7 @@ jobs: if: ${{ inputs.check_packages_licenses }} id: license_check_report continue-on-error: true - uses: pilosus/action-pip-license-checker@v2 + uses: pilosus/action-pip-license-checker@v3 with: requirements: ${{ inputs.requirements_path }} external: ${{ inputs.working_directory }}/licenses.csv diff --git a/.github/workflows/_python.yml b/.github/workflows/_python.yml index a7365c750..f7fafc513 100644 --- a/.github/workflows/_python.yml +++ b/.github/workflows/_python.yml @@ -258,11 +258,11 @@ jobs: env: ${{ fromJson(inputs.env) }} steps: - name: Check out latest commit - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Python id: setup_python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version-file: "pyproject.toml" @@ -460,7 +460,7 @@ jobs: - name: Upload coverage report as artifact if: inputs.use_coverage && inputs.upload_coverage - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-report-py${{ matrix.python_version }} path: ${{ inputs.working_directory }}/coverage.xml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 69f06cdc2..fb3eddf92 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v7 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -45,7 +45,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -70,4 +70,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/create_apt_cache.yaml b/.github/workflows/create_apt_cache.yaml index 9bd5fd730..ee1b2ec11 100644 --- a/.github/workflows/create_apt_cache.yaml +++ b/.github/workflows/create_apt_cache.yaml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out latest commit on current branch - uses: actions/checkout@v4 + uses: actions/checkout@v7 # Remember to set the same APT requirements file path set before! - name: Install APT dependencies diff --git a/.github/workflows/create_python_cache.yaml b/.github/workflows/create_python_cache.yaml index 15e541437..fc6562a04 100644 --- a/.github/workflows/create_python_cache.yaml +++ b/.github/workflows/create_python_cache.yaml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out latest commit - uses: actions/checkout@v4 + uses: actions/checkout@v7 # Uncomment only if necessary #- name: Install system dependencies required by Python packages @@ -34,7 +34,7 @@ jobs: - name: Set up Python id: setup_python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: "3.13" diff --git a/.github/workflows/dependency_review.yml b/.github/workflows/dependency_review.yml index 2e7df7389..affa53fda 100644 --- a/.github/workflows/dependency_review.yml +++ b/.github/workflows/dependency_review.yml @@ -11,6 +11,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v7 - name: 'Dependency Review' - uses: actions/dependency-review-action@v4 \ No newline at end of file + uses: actions/dependency-review-action@v5 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01d592a5b..9f60d73b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: match: ${{ steps.check-tag.outputs.match }} version: ${{ steps.check-tag.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Check version tag format id: check-tag @@ -43,7 +43,7 @@ jobs: - name: Create release if: steps.check-tag.outputs.match == 'true' - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ steps.check-tag.outputs.version }} name: Version ${{ steps.check-tag.outputs.version }} diff --git a/README.md b/README.md index a7ff51d44..4b14333e8 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ Thanks to [The Honeynet Project](https://www.honeynet.org) we are providing free #### Google Summer of Code GSoC logo -In 2026 we started participating in the [Google Summer of Code](https://summerofcode.withgoogle.com/) (GSoC)! +In 2026 we participated in the [Google Summer of Code](https://summerofcode.withgoogle.com/) and three projects were successfully implemented. -If you are interested in participating in the next Google Summer of Code, check all the info available in the [dedicated repository](https://github.com/intelowlproject/gsoc)! +If you are interested in participating in the next Google Summer of Code, check all the info available in the [wiki](https://github.com/GreedyBear-Project/GreedyBear/wiki/Google-Summer-of-Code)! ## Maintainers and Contributors @@ -45,8 +45,8 @@ This project was started as a personal Christmas project by [Matteo Lodi](https: Special thanks to: - [Tim Leonhard](https://github.com/regulartim) for having greatly improved the project and added Machine Learning Models during his master thesis. He's the current Principal Maintainer. - [Martina Carella](https://github.com/carellamartina) for having created the GUI during her master thesis. -- [Daniele Rosetti](https://github.com/drosetti) for helping maintaining the Frontend. -- and everyone who has contributed to GreedyBear! +- Our GSoC 2026 participants [Drona Raj Gyawali](https://github.com/drona-gyawali), [Krishna Awasthi](https://github.com/opbot-xd) and [Rachit Kumar Pandey](https://github.com/armoredvortex) for having built the [event collector API](https://www.honeynet.org/2026/08/11/11/), the [payload access feature](https://www.honeynet.org/2026/08/17/17/) and the [dashboard modularization](https://www.honeynet.org/2026/08/14/16/). +- and everyone else who has contributed to GreedyBear! GreedyBear contributors diff --git a/api/serializers/cowrie_session.py b/api/serializers/cowrie_session.py index a00b31505..a9e247c6a 100644 --- a/api/serializers/cowrie_session.py +++ b/api/serializers/cowrie_session.py @@ -1,14 +1,26 @@ +import re + from rest_framework import serializers +from greedybear.regex import REGEX_COWRIE_SESSION_ID + class CowrieSessionRequestSerializer(serializers.Serializer): query = serializers.CharField( + required=False, max_length=256, help_text=( "The search term, can be an IP address, the SHA-256 hash of a command sequence, or a password. " - 'SHA-256 hashes should match command sequences generated using Python\'s `"\n".join(sequence)` format.' + 'SHA-256 hashes should match command sequences generated using Python\'s `"\n".join(sequence)` format. ' + "Mutually exclusive with `id`." ), ) + id = serializers.CharField( + required=False, + max_length=16, + help_text=("Hex session ID, in the same format the payloads API returns. Mutually exclusive with `query`."), + ) + include_similar = serializers.BooleanField( required=False, default=False, @@ -25,6 +37,18 @@ class CowrieSessionRequestSerializer(serializers.Serializer): required=False, default=False, help_text="When `true`, includes detailed information about matching Cowrie sessions." ) + def validate_id(self, value: str) -> str: + if not re.fullmatch(REGEX_COWRIE_SESSION_ID, value): + raise serializers.ValidationError(f"Not a valid hex session ID: {value}") + return value + + def validate(self, data: dict) -> dict: + if data.get("query") and data.get("id"): + raise serializers.ValidationError("Provide either `query` or `id`, not both.") + if not data.get("query") and not data.get("id"): + raise serializers.ValidationError("Provide either `query` or `id`.") + return data + class SessionDetailSerializer(serializers.Serializer): """A single matching Cowrie session.""" @@ -40,7 +64,8 @@ class SessionDetailSerializer(serializers.Serializer): class CowrieSessionSerializer(serializers.Serializer): """Aggregated view of the sessions matching a query.""" - query = serializers.CharField(max_length=256, help_text="The query this result was produced for.") + query = serializers.CharField(required=False, max_length=256, help_text="The query this result was produced for. Present when searching by `query`.") + id = serializers.CharField(required=False, max_length=16, help_text="The session ID this result was produced for. Present when searching by `id`.") license = serializers.CharField(required=False, help_text="Present when a feed license is configured.") commands = serializers.ListField(child=serializers.CharField(), help_text="Unique command sequences, each newline-delimited.") sources = serializers.ListField(child=serializers.IPAddressField(), help_text="Unique source IP addresses.") diff --git a/api/serializers/events.py b/api/serializers/events.py index 2eda33fc4..e1f8a76d1 100644 --- a/api/serializers/events.py +++ b/api/serializers/events.py @@ -46,7 +46,9 @@ class Meta: "help_text": "IPv4 or IPv6 address of the sensor.", }, "honeypot_type": {"help_text": "Type of honeypot."}, - "honeypot_software": {"help_text": "Honeypot software name."}, + # capped to Honeypot.name's width: the value becomes a Honeypot record + # when the sensor's events are turned into IOCs + "honeypot_software": {"help_text": "Honeypot software name.", "max_length": 15}, "honeypot_description": {"help_text": "Description of the sensor."}, "group_label": {"help_text": "Group classification label."}, "country_code": {"help_text": "2-letter ISO country code."}, @@ -158,6 +160,7 @@ class Meta: "status", "ioc_count", "last_error", + "started_at", "processed_at", "created_at", ] diff --git a/api/serializers/feeds.py b/api/serializers/feeds.py index 5d993ad1d..92ebe97d0 100644 --- a/api/serializers/feeds.py +++ b/api/serializers/feeds.py @@ -409,6 +409,13 @@ class SimpleFeedResponseSerializer(serializers.Serializer): class AdvancedFeedResponseSerializer(SimpleFeedResponseSerializer): credential_count = serializers.IntegerField(min_value=0) sensors = SensorSerializer(many=True, required=False, default=list) + payload_hashes = serializers.ListField( + child=serializers.CharField(max_length=64), + allow_empty=True, + required=False, + default=list, + help_text="Lowercase SHA256 hashes of the payloads observed from this IOC. Only included when `verbose=true`.", + ) firehol_categories = serializers.ListField(child=serializers.CharField(max_length=64), allow_empty=True, required=False) destination_ports = serializers.ListField(child=serializers.IntegerField(min_value=1, max_value=65535), required=False) days_seen = serializers.ListField(child=serializers.DateField(format="%Y-%m-%d"), required=False) diff --git a/api/serializers/payloads.py b/api/serializers/payloads.py index 0b2b658d9..d4dfb8410 100644 --- a/api/serializers/payloads.py +++ b/api/serializers/payloads.py @@ -12,6 +12,13 @@ class HoneypotPayloadSerializer(serializers.ModelSerializer): slug_field="name", help_text="Names of the honeypots that captured this payload.", ) + iocs = serializers.SlugRelatedField( + many=True, + read_only=True, + slug_field="name", + help_text="Attacker IPs (or other IOCs) linked to this payload.", + ) + cowrie_sessions = serializers.SerializerMethodField(help_text="IDs of the Cowrie sessions that transferred this payload, as hex strings.") class Meta: model = HoneypotPayload @@ -23,6 +30,8 @@ class Meta: "mime_type", "size", "source_honeypots", + "iocs", + "cowrie_sessions", ] extra_kwargs = { "id": {"help_text": "Unique identifier of the payload."}, @@ -32,3 +41,6 @@ class Meta: "mime_type": {"help_text": "MIME type of the payload file."}, "size": {"help_text": "Size of the payload in bytes."}, } + + def get_cowrie_sessions(self, obj: HoneypotPayload) -> list[str]: + return [f"{session.session_id:x}" for session in obj.cowrie_sessions.all()] diff --git a/api/views/command_sequence.py b/api/views/command_sequence.py index 6d4251244..267f36098 100644 --- a/api/views/command_sequence.py +++ b/api/views/command_sequence.py @@ -91,7 +91,7 @@ def command_sequence_view(request): if is_sha256hash(observable): try: - seq = CommandSequence.objects.get(commands_hash=observable) + seq = CommandSequence.objects.get(commands_hash=observable.lower()) seqs = CommandSequence.objects.filter(cluster=seq.cluster) if include_similar and seq.cluster is not None else [seq] commands = ["\n".join(seq.commands) for seq in seqs] sessions = CowrieSession.objects.filter(commands__in=seqs, start_time__isnull=False) diff --git a/api/views/cowrie_session.py b/api/views/cowrie_session.py index 17a827fd9..44e95baff 100644 --- a/api/views/cowrie_session.py +++ b/api/views/cowrie_session.py @@ -27,12 +27,14 @@ "Retrieve Cowrie honeypot session data including command sequences, credentials, and session details. " "Queries can be performed using an IP address to find all sessions from that source, " "a SHA-256 hash to find sessions containing a specific command sequence, " - "or a password to find all sessions where that password was used." + "or a password to find all sessions where that password was used. " + "Alternatively, pass `id` to look up one specific session by its hex ID, " + "as returned by the payloads API." ), parameters=[CowrieSessionRequestSerializer], responses={ 200: CowrieSessionSerializer, - 400: OpenApiResponse(description="Missing or invalid `query` parameter."), + 400: OpenApiResponse(description="Missing or invalid `query`/`id` parameter, or both were given."), 401: OpenApiResponse(description="Authentication credentials were not provided or are invalid."), 404: OpenApiResponse(description="No matching sessions found."), }, @@ -47,8 +49,15 @@ def get(self, request: Request, *args, **kwargs): request_serializer.is_valid(raise_exception=True) save_request_source(request, ViewType.COWRIE_SESSION_VIEW.value) - observable = request_serializer.validated_data["query"] - if is_ip_address(observable): + session_id = request_serializer.validated_data.get("id") + observable = request_serializer.validated_data.get("query") + + if session_id is not None: + sessions = CowrieSession.objects.filter(session_id=int(session_id, 16), duration__gt=0).prefetch_related("source", "commands", "credentials") + if not sessions.exists(): + raise Http404(f"No session found with ID: {session_id}") + + elif is_ip_address(observable): sessions = CowrieSession.objects.filter(source__name=observable, duration__gt=0).prefetch_related("source", "commands", "credentials") if not sessions.exists(): raise Http404(f"No information found for IP: {observable}") @@ -72,9 +81,7 @@ def get(self, request: Request, *args, **kwargs): ) sessions = sessions.union(related_sessions) - data = { - "query": observable, - } + data = {"id": session_id} if session_id is not None else {"query": observable} if settings.FEEDS_LICENSE: data["license"] = settings.FEEDS_LICENSE diff --git a/api/views/feeds.py b/api/views/feeds.py index 8a706ec69..8e79dad1e 100644 --- a/api/views/feeds.py +++ b/api/views/feeds.py @@ -7,9 +7,10 @@ from certego_saas.apps.auth.backend import CookieTokenAuthentication from certego_saas.ext.pagination import CustomPageNumberPagination from django.contrib.postgres.aggregates import ArrayAgg +from django.contrib.postgres.expressions import ArraySubquery from django.core import signing -from django.db.models import Count, F, Q, QuerySet, Value -from django.db.models.functions import JSONObject +from django.db.models import Count, F, OuterRef, Q, QuerySet, Value +from django.db.models.functions import JSONObject, Lower from django.http import HttpResponseBase, StreamingHttpResponse from django.utils import timezone from drf_spectacular.types import OpenApiTypes @@ -50,7 +51,7 @@ from greedybear.consts import SHARE_TOKEN_SALT, TRENDING_FEEDS_DATA_VERSION_KEY from greedybear.cronjobs.repositories import TrendingBucketRepository from greedybear.cronjobs.trending import build_ranked_attackers -from greedybear.models import IOC, ShareToken, ViewType +from greedybear.models import IOC, HoneypotPayload, ShareToken, ViewType RENDERERS_BY_FORMAT = { "json": FeedJSONRenderer, @@ -303,8 +304,8 @@ class AdvancedFeedView(BaseFeedView): include_sensors = True def get_queryset(self) -> QuerySet: - """Overrides base class to include credential count - and sensor information.""" + """Overrides base class to include credential count, + sensor information and, when verbose, the hashes of the payloads seen from each IOC.""" iocs = super().get_queryset() iocs = iocs.annotate(credential_count=Count("credentials", distinct=True)) @@ -318,6 +319,15 @@ def get_queryset(self) -> QuerySet: distinct=True, ) ) + if self.request_params.get("verbose", False): + # Annotate payload hashes viasubquery instead of another ArrayAgg: + # The aggregates above already join honeypots, tags, sensors and credentials into one GROUP BY, + # and every additional multi-valued join multiplies the rows per IOC. + # ARRAY(subquery) also yields an empty array when an IOC has no payloads. + payload_hashes = ( + HoneypotPayload.objects.filter(iocs=OuterRef("pk")).annotate(sha256_lower=Lower("sha256")).order_by("sha256_lower").values("sha256_lower") + ) + iocs = iocs.annotate(payload_hashes=ArraySubquery(payload_hashes)) return iocs diff --git a/api/views/payloads.py b/api/views/payloads.py index 7a3c8ae85..d943bdd46 100644 --- a/api/views/payloads.py +++ b/api/views/payloads.py @@ -2,11 +2,13 @@ # See the file 'LICENSE' for copying permission. import logging +from django.db.models.functions import Lower from django.http import FileResponse from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view from rest_framework import status, viewsets from rest_framework.decorators import action +from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -49,11 +51,25 @@ class HoneypotPayloadViewSet(viewsets.ReadOnlyModelViewSet): :class:`~api.permissions.IsThreatResearcherOrAdmin`. """ - queryset = HoneypotPayload.objects.prefetch_related("source_honeypots").order_by("-id") + queryset = HoneypotPayload.objects.prefetch_related("source_honeypots", "iocs", "cowrie_sessions").order_by("-id") serializer_class = HoneypotPayloadSerializer permission_classes = [IsAuthenticated] lookup_field = "sha256" + def get_object(self) -> HoneypotPayload: + """ + Look up the payload by SHA256, ignoring case. + + The hash comes from the URL so it can be any case, while stored hashes + are lower-cased. Matching on Lower("sha256") also finds older rows that + were saved before that was true. + """ + queryset = self.filter_queryset(self.get_queryset()) + sha256 = self.kwargs[self.lookup_field].lower() + obj = get_object_or_404(queryset.annotate(sha256_lower=Lower("sha256")), sha256_lower=sha256) + self.check_object_permissions(self.request, obj) + return obj + @extend_schema( summary="Download payload binary", description="Streams the raw `.vir` quarantine file as an attachment. Restricted to staff or users in the `threat_researcher` group.", diff --git a/api/views/statistics.py b/api/views/statistics.py index 23b0a1276..2b7c90b57 100644 --- a/api/views/statistics.py +++ b/api/views/statistics.py @@ -172,7 +172,7 @@ def __aggregation_response_static_ioc(self, annotations: dict) -> Response: qs = ( IOC.objects.filter(last_seen__gte=delta) - .exclude(honeypots__active=False) + .filter(honeypots__active=True) .annotate(date=Trunc("last_seen", basis)) .values("date") .annotate(**annotations) diff --git a/api/views/utils.py b/api/views/utils.py index df0f40d53..1e7b6199c 100644 --- a/api/views/utils.py +++ b/api/views/utils.py @@ -116,7 +116,8 @@ def stream_ioc_objects(iocs, verbose=False, include_sensors=False): Args: iocs (QuerySet | list): Filtered IOCs to render. - verbose (bool): Include verbose fields (days_seen, destination_ports, firehol_categories). + verbose (bool): Include verbose fields (days_seen, destination_ports, firehol_categories) + and, when the `payload_hashes` annotation is present, the payload hashes. include_sensors (bool): Emit a `sensors` array when the `sensors_json` annotation is present. Yields: @@ -128,19 +129,25 @@ def stream_ioc_objects(iocs, verbose=False, include_sensors=False): # with the `tags` reverse FK on IOC. When the queryset comes from a repository method # that does not annotate `tags_json` (e.g. the ML scoring path), exclude the field. # `sensors_json` follows the same pattern and is only annotated for authenticated views. + # `payload_hashes` follows it too, and is only annotated when verbose is requested, + # since the list has no natural cap unlike tags/sensors. if isinstance(iocs, list): has_tags_annotation = bool(iocs) and hasattr(iocs[0], "tags_json") has_sensors_annotation = include_sensors and bool(iocs) and hasattr(iocs[0], "sensors_json") has_credential_count = bool(iocs) and hasattr(iocs[0], "credential_count") + has_payload_hashes = verbose and bool(iocs) and hasattr(iocs[0], "payload_hashes") else: annotations = getattr(getattr(iocs, "query", None), "annotations", {}) has_tags_annotation = "tags_json" in annotations has_sensors_annotation = include_sensors and "sensors_json" in annotations has_credential_count = "credential_count" in annotations + has_payload_hashes = verbose and "payload_hashes" in annotations required_fields = tuple(("tags_json" if f == "tags" else f) for f in required_fields if f != "tags" or has_tags_annotation) required_fields = tuple(f for f in required_fields if f != "credential_count" or has_credential_count) if has_sensors_annotation: required_fields = (*required_fields, "sensors_json") + if has_payload_hashes: + required_fields = (*required_fields, "payload_hashes") if isinstance(iocs, list): iocs_iter = (ioc_as_dict(ioc, set(required_fields)) for ioc in iocs) diff --git a/docker/Dockerfile_nginx b/docker/Dockerfile_nginx index 73bfacd8d..3485d78ad 100644 --- a/docker/Dockerfile_nginx +++ b/docker/Dockerfile_nginx @@ -1,4 +1,4 @@ -FROM library/nginx:1.31.4-alpine +FROM library/nginx:1.31.6-alpine ENV NGINX_LOG_DIR=/var/log/nginx diff --git a/frontend/README.md b/frontend/README.md index b97c7e21e..f56e29699 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,6 +1,10 @@ # GreedyBear - frontend -Built with [gb-ui](https://github.com/GreedyBear-Project/gb-ui). +## Shared UI components + +Shared UI components in `src/components/common/gb-ui/` were originally derived +from [certego-ui](https://github.com/certego/certego-ui) and are now maintained +locally in GreedyBear. ## Design thesis @@ -59,4 +63,3 @@ npm start - JS app main configs are available in `package.json` and `enviroments.js`. - diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4e3bf039f..12129edcd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,36 +8,40 @@ "name": "frontend", "version": "0.1.0", "dependencies": { - "@greedybear/gb-ui": "github:GreedyBear-Project/gb-ui#1.0.1", + "@tanstack/react-table": "^8.21.3", "@vnedyalk0v/react19-simple-maps": "^2.0.10", "axios": "^1.20.0", "axios-hooks": "^5.1.1", "bootstrap": ">=5.3.8", + "classnames": "^2.5.1", + "date-fns": "^2.30.0", "formik": "^2.4.9", "i18n-iso-countries": "^7.14.0", + "nanoid": "^3.3.19", "prop-types": "^15.8.1", - "react": "^19.2.8", - "react-dom": "^19.2.8", + "react": "^19.3.0", + "react-dom": "^19.3.0", "react-grid-layout": "^2.2.4", "react-icons": "^5.7.0", - "react-router-dom": "^7.18.3", + "react-router-dom": "^7.18.4", "react-use": "^17.6.1", "reactstrap": "^9.2.3", "recharts": "^3.10.1", - "sass": "^1.104.0", + "sass": "^1.105.0", "zustand": "^5.0.15" }, "devDependencies": { + "@playwright/test": "^1.63.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", "@testing-library/user-event": "^14.6.7", "@vitejs/plugin-react": "^6.1.1", "@vitest/coverage-v8": "^4.1.11", "eslint": "^9.39.5", - "jsdom": "^30.0.1", - "prettier": "^3.9.6", - "stylelint": "^17.14.1", - "vite": "^8.2.2", + "jsdom": "^30.1.1", + "prettier": "^3.9.9", + "stylelint": "^17.15.0", + "vite": "^8.3.0", "vitest": "^4.1.11" } }, @@ -49,42 +53,43 @@ "license": "MIT" }, "node_modules/@asamuzakjp/css-color": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.5.tgz", - "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.0.1.tgz", + "integrity": "sha512-C9duntabagkBZ1LebM7FKmphR4Q1pBclLxVbZETQV0akkFjV0ooFxo8FlvAQyKj9F6l8rEnmidgcTlpyxFYizg==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.2.1", - "@csstools/css-color-parser": "^4.1.9", + "@csstools/css-calc": "^3.4.0", + "@csstools/css-color-parser": "^4.2.3", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.5.2" + "@csstools/css-tokenizer": "^4.0.1", + "lru-cache": "^11.5.3" }, "engines": { - "node": "^22.13.0 || >=24.0.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.0.tgz", - "integrity": "sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.1.tgz", + "integrity": "sha512-NT4s3yZLjovPpliRpTvdsdzyPjqRqiCZj9MxnarBihbaY5MbAG7DWAJcrLlhmC7xKTNCXsTELcqB8YsqpLnUFQ==", "dev": true, "license": "MIT", "dependencies": { - "bidi-js": "^1.0.3", + "bidi-js": "^1.1.0", "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.5.2" + "lru-cache": "^11.5.3" }, "engines": { - "node": "^22.13.0 || >=24.0.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "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", @@ -95,48 +100,11 @@ "node": ">=6.9.0" } }, - "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==", - "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-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "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==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "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" @@ -146,6 +114,7 @@ "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" @@ -155,6 +124,7 @@ "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" @@ -175,42 +145,11 @@ "node": ">=6.9.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==", - "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==", - "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", @@ -305,9 +244,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", - "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", "dev": true, "funding": [ { @@ -325,9 +264,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", - "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.0.tgz", + "integrity": "sha512-XQKj5B7QiZcHiegCOCAzcAOJdhGgWOHbbu62h5e5mkHnn8lWcfiJhllkqWmxu5zWR9jucPHuo1iTB56P033hcg==", "dev": true, "funding": [ { @@ -349,9 +288,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", - "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.3.tgz", + "integrity": "sha512-y4LpL+lmpuyKDiEFq2PnZUVFdAjsoB/qQJod79yLNokXyW7jewi+/WJ69EfItj8A2unWtxXnGjw6LYXgXu5ZjA==", "dev": true, "funding": [ { @@ -365,8 +304,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.1.0", - "@csstools/css-calc": "^3.3.0" + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.4.0" }, "engines": { "node": ">=20.19.0" @@ -400,9 +339,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", - "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.14.tgz", + "integrity": "sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==", "dev": true, "funding": [ { @@ -425,9 +364,9 @@ } }, "node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.1.tgz", + "integrity": "sha512-bPlN9S9O1A0euCpEWE4qnvB5YDuyYVsUTrxSgmAM1Is0j4tICHoVyOVAXfWMP/kS9ZrjvyIXWV2PmomiAXXqOw==", "dev": true, "funding": [ { @@ -469,9 +408,9 @@ } }, "node_modules/@csstools/selector-resolve-nested": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.0.tgz", - "integrity": "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.1.tgz", + "integrity": "sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==", "dev": true, "funding": [ { @@ -514,126 +453,6 @@ "postcss-selector-parser": "^7.1.1" } }, - "node_modules/@emotion/babel-plugin": { - "version": "11.13.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", - "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/serialize": "^1.3.3", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/@emotion/cache": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", - "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.9.0", - "@emotion/sheet": "^1.4.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT" - }, - "node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, - "node_modules/@emotion/react": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", - "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/cache": "^11.14.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", - "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", - "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.2", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", - "license": "MIT" - }, - "node_modules/@emotion/unitless": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "license": "MIT" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", - "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", - "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", - "license": "MIT" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", - "license": "MIT" - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -796,85 +615,6 @@ } } }, - "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "license": "MIT" - }, - "node_modules/@greedybear/gb-ui": { - "version": "1.0.1", - "resolved": "git+ssh://git@github.com/GreedyBear-Project/gb-ui.git#72f95eee3dc48e4353933ce747d2e6488bcc363c", - "license": "MIT", - "dependencies": { - "@microlink/react-json-view": "^1.31.22", - "@tanstack/react-table": "^8.21.3", - "classnames": "^2.5.1", - "date-fns": "^2.30.0", - "match-sorter": "^6.4.0", - "nanoid": "^6.0.0", - "react-compound-slider": "^3.4.0", - "react-icons": "^5.7.0", - "react-infinite-scroll-component": "^7.2.1", - "react-json-editor-ajrm": "^2.5.14", - "react-select": "^5.10.2", - "react-share": "^5.3.0", - "react-top-loading-bar": "^3.0.2", - "react-use": "^17.6.1", - "recharts": "^3.9.2", - "zustand": "^5.0.14" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "axios": "^1.6.0", - "axios-hooks": "^5.1.1", - "bootstrap": "^5.2.1", - "formik": "^2.2.9", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-router-dom": "^6.3.0 || ^7.0.0", - "reactstrap": "^9.0.3" - } - }, - "node_modules/@greedybear/gb-ui/node_modules/nanoid": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-6.0.0.tgz", - "integrity": "sha512-mkUH+rPkwU2qPadJ0oJZOjeZ5Mxn8Q1UhevwkTRWNuUZzyia3h4rhzK39hxaHTk0o2OxB8W2SQ6A8k23ZDi1pQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^22 || ^24 || >=26" - } - }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -941,20 +681,11 @@ "url": "https://github.com/sponsors/nzakas" } }, - "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==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@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" @@ -970,6 +701,7 @@ "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -983,24 +715,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@microlink/react-json-view": { - "version": "1.31.22", - "resolved": "https://registry.npmjs.org/@microlink/react-json-view/-/react-json-view-1.31.22.tgz", - "integrity": "sha512-cfszidqClRqjnF5YQ5PSGeORo9MnMZvDv+csgq92G1LRZduWEBjeHXKLnulvhE7bX9+ORvjRCWcMQfwRlhPxmA==", - "license": "MIT", - "dependencies": { - "react-base16-styling": "~0.10.0", - "react-lifecycles-compat": "~3.0.4", - "react-textarea-autosize": "~8.5.9" - }, - "engines": { - "node": ">=17" - }, - "peerDependencies": { - "react": ">= 15", - "react-dom": ">= 15" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1044,7 +758,6 @@ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } @@ -1172,6 +885,9 @@ "cpu": [ "arm" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1192,6 +908,9 @@ "cpu": [ "arm" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1252,6 +971,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1272,6 +994,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1345,6 +1070,22 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1406,7 +1147,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" @@ -1423,7 +1163,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -1440,7 +1179,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -1457,7 +1195,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1474,7 +1211,6 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" @@ -1491,10 +1227,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -1511,10 +1243,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -1531,10 +1259,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -1551,10 +1275,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -1571,10 +1291,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -1591,10 +1307,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -1611,7 +1323,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openharmony" @@ -1628,7 +1339,6 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1645,7 +1355,6 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1809,7 +1518,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12", "npm": ">=6" @@ -1939,18 +1647,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "license": "MIT" - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1961,15 +1657,6 @@ "csstype": "^3.2.2" } }, - "node_modules/@types/react-transition-group": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", - "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*" - } - }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -1981,7 +1668,6 @@ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", "dev": true, - "license": "MIT", "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, @@ -2154,7 +1840,6 @@ "version": "2.0.10", "resolved": "https://registry.npmjs.org/@vnedyalk0v/react19-simple-maps/-/react19-simple-maps-2.0.10.tgz", "integrity": "sha512-tw8PYah/5c1UiwqhtB0k40p+jlYRW9QjdZZQ37CKONXErIZFHl0Sn8Ho03HNP/mXQs0YrogrBIeolln5mfbIsA==", - "license": "MIT", "dependencies": { "d3-color": "^3.1.0", "d3-geo": "^3.1.0", @@ -2357,42 +2042,6 @@ "node": ">=6.9.0" } }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-macros/node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2401,9 +2050,9 @@ "license": "MIT" }, "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -2430,9 +2079,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2494,6 +2143,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2556,23 +2206,11 @@ "node": ">=6" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2585,22 +2223,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz", + "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==", "dev": true, "license": "MIT" }, @@ -2649,40 +2278,15 @@ "url": "https://opencollective.com/express" } }, - "node_modules/copy-to-clipboard": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", - "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "license": "MIT", - "dependencies": { - "toggle-selection": "^1.0.6" - } - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3125,6 +2729,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -3205,6 +2810,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -3455,9 +3061,9 @@ "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { @@ -3488,9 +3094,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", "dev": true, "license": "ISC", "dependencies": { @@ -3541,12 +3147,6 @@ "node": ">=8" } }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3652,7 +3252,6 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -3671,9 +3270,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", "dev": true, "license": "MIT", "engines": { @@ -3788,9 +3387,9 @@ } }, "node_modules/globby": { - "version": "16.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.2.tgz", - "integrity": "sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.4.tgz", + "integrity": "sha512-c8B/VNLmxRcmqqenRA9t+9IyOjf9+V6lTxPaUJLqOCONdQkWZ0ETYgX0qbtJqPsgCNusT9MZ5Jeidw8Eb9tn2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3798,6 +3397,7 @@ "fast-glob": "^3.3.3", "ignore": "^7.0.5", "is-path-inside": "^4.0.0", + "micromatch": "^4.0.8", "slash": "^5.1.0", "unicorn-magic": "^0.4.0" }, @@ -3809,9 +3409,9 @@ } }, "node_modules/globby/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.10.tgz", + "integrity": "sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==", "dev": true, "license": "MIT", "engines": { @@ -3922,16 +3522,16 @@ "license": "MIT" }, "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-7.0.0.tgz", + "integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.6.0" + "@exodus/bytes": "^1.15.1" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" } }, "node_modules/html-escaper": { @@ -4015,6 +3615,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -4087,23 +3688,9 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, - "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4249,32 +3836,31 @@ } }, "node_modules/jsdom": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", - "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.1.tgz", + "integrity": "sha512-FahmoPK5vbPc+jxV1iErMHmAZypCZ942NHF4+qqaWAuvaKKTBZxawnmAtrbGWLU7MtlxfqIP0qw6aSI+aWGtLg==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^6.0.5", - "@asamuzakjp/dom-selector": "^8.3.0", + "@asamuzakjp/css-color": "^7.0.0", + "@asamuzakjp/dom-selector": "^9.2.1", "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@csstools/css-syntax-patches-for-csstree": "^1.1.13", "@exodus/bytes": "^1.15.1", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", + "html-encoding-sniffer": "^7.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.5.2", "parse5": "^8.0.1", "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.2", - "undici": "^8.9.0", - "w3c-xmlserializer": "^5.0.0", + "undici": "^8.10.2", + "w3c-xmlserializer": "^6.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^17.1.0", + "whatwg-url": "^17.1.1", "xml-name-validator": "^5.0.0" }, "engines": { @@ -4290,9 +3876,9 @@ } }, "node_modules/jsdom/node_modules/whatwg-url": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", - "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.2.tgz", + "integrity": "sha512-TEZA+Zqxin7Jjsm2cjRohCmen5awh+hT6Zi3VZdqZlNRk7zvOI/9WpBFg/DWlA56bWnzwm6DuB8NS0EsxQH9uQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4304,18 +3890,6 @@ "node": "^22.14.0 || >=24.0.0" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4327,6 +3901,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -4343,29 +3918,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsonp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", - "integrity": "sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==", - "dependencies": { - "debug": "^2.1.3" - } - }, - "node_modules/jsonp/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/jsonp/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4405,7 +3957,6 @@ "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" }, @@ -4438,7 +3989,6 @@ "arm64" ], "dev": true, - "license": "MPL-2.0", "optional": true, "os": [ "android" @@ -4459,7 +4009,6 @@ "arm64" ], "dev": true, - "license": "MPL-2.0", "optional": true, "os": [ "darwin" @@ -4480,7 +4029,6 @@ "x64" ], "dev": true, - "license": "MPL-2.0", "optional": true, "os": [ "darwin" @@ -4501,7 +4049,6 @@ "x64" ], "dev": true, - "license": "MPL-2.0", "optional": true, "os": [ "freebsd" @@ -4522,7 +4069,6 @@ "arm" ], "dev": true, - "license": "MPL-2.0", "optional": true, "os": [ "linux" @@ -4543,10 +4089,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", "optional": true, "os": [ "linux" @@ -4567,10 +4109,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", "optional": true, "os": [ "linux" @@ -4591,10 +4129,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", "optional": true, "os": [ "linux" @@ -4615,10 +4149,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", "optional": true, "os": [ "linux" @@ -4639,7 +4169,6 @@ "arm64" ], "dev": true, - "license": "MPL-2.0", "optional": true, "os": [ "win32" @@ -4660,7 +4189,6 @@ "x64" ], "dev": true, - "license": "MPL-2.0", "optional": true, "os": [ "win32" @@ -4677,6 +4205,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -4734,9 +4263,9 @@ } }, "node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -4804,17 +4333,6 @@ "node": ">=10" } }, - "node_modules/match-sorter": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.4.0.tgz", - "integrity": "sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q==", - "deprecated": "This was arguably a breaking change. Not in API, but more results can be returned. Upgrade to the next major when you are ready for that", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.8", - "remove-accents": "0.5.0" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4842,12 +4360,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, "node_modules/meow": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", @@ -5003,10 +4515,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "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, + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "funding": [ { "type": "github", @@ -5119,6 +4630,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -5131,6 +4643,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -5178,21 +4691,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -5204,12 +4702,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "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==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "devOptional": true, "license": "MIT", "engines": { @@ -5219,10 +4718,39 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -5240,7 +4768,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5276,9 +4804,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", "dev": true, "license": "MIT", "dependencies": { @@ -5307,9 +4835,9 @@ } }, "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "dev": true, "license": "MIT", "bin": { @@ -5438,65 +4966,24 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/react-base16-styling": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz", - "integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==", - "license": "MIT", - "dependencies": { - "@types/lodash": "^4.17.0", - "color": "^4.2.3", - "csstype": "^3.1.3", - "lodash-es": "^4.17.21" - } - }, - "node_modules/react-compound-slider": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/react-compound-slider/-/react-compound-slider-3.4.0.tgz", - "integrity": "sha512-KSje/rB0xSvvcb7YV0+82hkiXTV5ljSS7axKrNiXLf9AEO+rrr1Xq4MJWA+6v030YNNo/RoSoEB6D6fnoy+8ng==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "d3-array": "^2.8.0", - "warning": "^4.0.3" - }, - "peerDependencies": { - "react": ">=16.9" - } - }, - "node_modules/react-compound-slider/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/react-compound-slider/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, "node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "scheduler": "^0.28.0" }, "peerDependencies": { - "react": "^19.2.8" + "react": "^19.3.0" } }, "node_modules/react-draggable": { @@ -5523,7 +5010,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-2.2.4.tgz", "integrity": "sha512-Eb57FsgOMYOfsUGrMI1ku/FFR+dPNPrE8qo+3hwZubpqVSy4GO9v52DeX50Tl3JDYAlCypP4rmw7Vrqk/zOIvA==", - "license": "MIT", "dependencies": { "clsx": "^2.1.1", "fast-equals": "^4.0.3", @@ -5546,19 +5032,6 @@ "react": "*" } }, - "node_modules/react-infinite-scroll-component": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-7.2.1.tgz", - "integrity": "sha512-yPuEf6VHBJXFt+YXDxjlnAuhorUwiA2cKURx/P6a7WW3k+TvwY/mbx7KP1rQ1hxGzDcwreczRyLIv6eSd1sFIw==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, "node_modules/react-is": { "version": "19.2.6", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", @@ -5566,25 +5039,6 @@ "license": "MIT", "peer": true }, - "node_modules/react-json-editor-ajrm": { - "version": "2.5.14", - "resolved": "https://registry.npmjs.org/react-json-editor-ajrm/-/react-json-editor-ajrm-2.5.14.tgz", - "integrity": "sha512-z2035l5lnU0Wo73AUonj68d1Eh9vjVcLXEa8YogDEeFPcogzYkfT6R38fvYlLKDhP0oI+LyOO/IBWsFkXs4M0Q==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.0.0-rc.0" - }, - "peerDependencies": { - "react": ">=16.2.0", - "react-dom": ">=16.2.0" - } - }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "license": "MIT" - }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", @@ -5623,9 +5077,9 @@ } }, "node_modules/react-router": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", - "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz", + "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -5645,12 +5099,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", - "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz", + "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==", "license": "MIT", "dependencies": { - "react-router": "7.18.3" + "react-router": "7.18.4" }, "engines": { "node": ">=20.0.0" @@ -5660,69 +5114,6 @@ "react-dom": ">=18" } }, - "node_modules/react-select": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz", - "integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.8.1", - "@floating-ui/dom": "^1.0.1", - "@types/react-transition-group": "^4.4.0", - "memoize-one": "^6.0.0", - "prop-types": "^15.6.0", - "react-transition-group": "^4.3.0", - "use-isomorphic-layout-effect": "^1.2.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-share": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-share/-/react-share-5.3.0.tgz", - "integrity": "sha512-NU4TqizVi2TuyLpN93/ng2ux+uIGHK5b2dT15cHvZDwfi5b5NuzGych4htCGObN8lubygeamhRxUot4lZ0Pv6Q==", - "license": "MIT", - "dependencies": { - "classnames": "^2.3.2", - "jsonp": "^0.2.1" - }, - "peerDependencies": { - "react": "^17 || ^18 || ^19" - } - }, - "node_modules/react-textarea-autosize": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", - "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.13", - "use-composed-ref": "^1.3.0", - "use-latest": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-top-loading-bar": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/react-top-loading-bar/-/react-top-loading-bar-3.0.2.tgz", - "integrity": "sha512-hW0CHrHqKdBOBsVhms73ka0rgb9/aoiRfqo7jiS1vwIYDK7VkyMj52ypo5ewhshTQGHKE6mRvR99GZBZ+FuM/Q==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^16 || ^17 || ^18 || ^19" - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -5839,10 +5230,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", - "license": "MIT", - "workspaces": [ - "www" - ], "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", @@ -5900,12 +5287,6 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, - "node_modules/remove-accents": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", - "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", - "license": "MIT" - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5932,6 +5313,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -5953,7 +5335,6 @@ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", "dev": true, - "license": "MIT", "dependencies": { "@oxc-project/types": "=0.147.0", "@rolldown/pluginutils": "^1.0.0" @@ -6016,9 +5397,9 @@ } }, "node_modules/sass": { - "version": "1.104.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.104.0.tgz", - "integrity": "sha512-btHMApW2bgolvClhRW8AlQJzgI9lUB3pPSofBQQT+E46GWvf9o0TVQ13SYv5riWZVFyPN+JNz3TKW9XhBlc10w==", + "version": "1.105.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.105.0.tgz", + "integrity": "sha512-4EjSuRTyG8M4yRXXY1PT9OUV5zz23xBxJkbIAprohKHqf6HOU4MCpE3U3vAC+iO1gxobBjESNbcj8rYAQZWnyA==", "license": "MIT", "dependencies": { "chokidar": "^5.0.0", @@ -6049,9 +5430,9 @@ } }, "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, "node_modules/screenfull": { @@ -6124,21 +5505,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT" - }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -6170,15 +5536,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6248,9 +5605,9 @@ "license": "MIT" }, "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "dev": true, "license": "MIT", "dependencies": { @@ -6281,9 +5638,9 @@ } }, "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", "engines": { @@ -6320,9 +5677,9 @@ } }, "node_modules/stylelint": { - "version": "17.14.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.14.1.tgz", - "integrity": "sha512-xVQwyiuxALUBNB2fBe0tmNemg9KqLtdj3T64mioFDar79B2cU8LIyz+3KL6LdiHs9NkeNfwxpKSaIVOY8f112g==", + "version": "17.15.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.15.0.tgz", + "integrity": "sha512-mWIkesYQvQjf4Kvdeu9ns0IL8/K/wtjaGxPqsPd6DlLZvaQxGysJsocxUDrBcyHQ5VleAk4uSMat4yMrVED7PA==", "dev": true, "funding": [ { @@ -6336,14 +5693,14 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.2.1", + "@csstools/css-calc": "^3.3.0", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-syntax-patches-for-csstree": "^1.1.6", + "@csstools/css-syntax-patches-for-csstree": "^1.1.9", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", - "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-resolve-nested": "^4.0.1", "@csstools/selector-specificity": "^6.0.0", - "colord": "^2.9.3", + "colord": "^2.10.0", "cosmiconfig": "^9.0.2", "css-functions-list": "^3.3.3", "css-tree": "^3.2.1", @@ -6352,21 +5709,21 @@ "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.5", "global-modules": "^2.0.0", - "globby": "^16.2.1", + "globby": "^16.2.4", "globjoin": "^0.1.4", "html-tags": "^5.1.0", - "ignore": "^7.0.5", + "ignore": "^7.0.6", "import-meta-resolve": "^4.2.0", "mathml-tag-names": "^4.0.0", "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.5.16", + "postcss": "^8.5.26", "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.4", + "postcss-selector-parser": "^7.1.5", "postcss-value-parser": "^4.2.0", - "string-width": "^8.2.1", + "string-width": "^8.2.2", "supports-hyperlinks": "^4.5.0", "svg-tags": "^1.0.0", "table": "^6.9.0", @@ -6429,21 +5786,15 @@ } }, "node_modules/stylelint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.10.tgz", + "integrity": "sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==", "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6500,31 +5851,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, "node_modules/table": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", @@ -6762,9 +6094,9 @@ } }, "node_modules/undici": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", - "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", "dev": true, "license": "MIT", "engines": { @@ -6772,9 +6104,9 @@ } }, "node_modules/unicorn-magic": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", - "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.1.tgz", + "integrity": "sha512-lzlXPoVB0Uy/FvTaUgX39RbixYiSKoc3JSoSf01+0c2B6FR3qdYIPN7Qjo/AV7n6sqLCk0509cT3LRj1oSZ1+A==", "dev": true, "license": "MIT", "engines": { @@ -6794,51 +6126,6 @@ "punycode": "^2.1.0" } }, - "node_modules/use-composed-ref": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", - "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", - "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-latest": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", - "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", - "license": "MIT", - "dependencies": { - "use-isomorphic-layout-effect": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -6878,16 +6165,16 @@ } }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "bin": { @@ -6904,7 +6191,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7046,16 +6333,16 @@ } }, "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-6.0.0.tgz", + "integrity": "sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q==", "dev": true, "license": "MIT", "dependencies": { "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/warning": { diff --git a/frontend/package.json b/frontend/package.json index b5e464c92..952f83284 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,23 +9,26 @@ "prettier": "../.github/configurations/node_linters/prettier/.prettierrc.js" }, "dependencies": { - "@greedybear/gb-ui": "github:GreedyBear-Project/gb-ui#1.0.1", + "@tanstack/react-table": "^8.21.3", "@vnedyalk0v/react19-simple-maps": "^2.0.10", "axios": "^1.20.0", "axios-hooks": "^5.1.1", "bootstrap": ">=5.3.8", + "classnames": "^2.5.1", + "date-fns": "^2.30.0", "formik": "^2.4.9", "i18n-iso-countries": "^7.14.0", + "nanoid": "^3.3.19", "prop-types": "^15.8.1", - "react": "^19.2.8", - "react-dom": "^19.2.8", + "react": "^19.3.0", + "react-dom": "^19.3.0", "react-grid-layout": "^2.2.4", "react-icons": "^5.7.0", - "react-router-dom": "^7.18.3", + "react-router-dom": "^7.18.4", "react-use": "^17.6.1", "reactstrap": "^9.2.3", "recharts": "^3.10.1", - "sass": "^1.104.0", + "sass": "^1.105.0", "zustand": "^5.0.15" }, "scripts": { @@ -34,6 +37,9 @@ "preview": "vite preview", "test": "vitest", "test-coverage": "vitest run --coverage", + "start:visual": "vite --host 127.0.0.1 --port 3002", + "test:visual": "playwright test", + "test:visual:update": "playwright test --update-snapshots", "lint-config-install": "cd ../.github/configurations/node_linters/eslint && npm i", "lint": "eslint 'src/**/*.{js,jsx}' 'tests/**/*.{js,jsx}'", "lint-fix": "npm run lint -- --fix", @@ -47,16 +53,17 @@ "d3-color": "^3.1.0" }, "devDependencies": { + "@playwright/test": "^1.63.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", "@testing-library/user-event": "^14.6.7", "@vitejs/plugin-react": "^6.1.1", "@vitest/coverage-v8": "^4.1.11", "eslint": "^9.39.5", - "jsdom": "^30.0.1", - "prettier": "^3.9.6", - "stylelint": "^17.14.1", - "vite": "^8.2.2", + "jsdom": "^30.1.1", + "prettier": "^3.9.9", + "stylelint": "^17.15.0", + "vite": "^8.3.0", "vitest": "^4.1.11" } } diff --git a/frontend/playwright.config.js b/frontend/playwright.config.js new file mode 100644 index 000000000..a440a4e28 --- /dev/null +++ b/frontend/playwright.config.js @@ -0,0 +1,33 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests/visual", + fullyParallel: true, + reporter: "html", + snapshotPathTemplate: "{testDir}/{testFilePath}-snapshots/{arg}{ext}", + use: { + baseURL: "http://127.0.0.1:3002", + trace: "on-first-retry", + }, + projects: [ + { + name: "desktop-chromium", + use: { + ...devices["Desktop Chrome"], + viewport: { width: 1440, height: 1200 }, + }, + }, + { + name: "mobile-chromium", + use: { + ...devices["Pixel 7"], + }, + }, + ], + webServer: { + command: "npm run start:visual", + url: "http://127.0.0.1:3002/tests/visual.html", + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, +}); diff --git a/frontend/src/components/Routes.jsx b/frontend/src/components/Routes.jsx index 95fa3786e..820d1c58b 100644 --- a/frontend/src/components/Routes.jsx +++ b/frontend/src/components/Routes.jsx @@ -1,5 +1,5 @@ import React, { Suspense } from "react"; -import { FallBackLoading } from "@greedybear/gb-ui"; +import { FallBackLoading } from "./common/gb-ui/index"; import IfAuthRedirectGuard from "../wrappers/ifAuthRedirectGuard"; import AuthGuard from "../wrappers/AuthGuard"; diff --git a/frontend/src/components/auth/EmailVerification.jsx b/frontend/src/components/auth/EmailVerification.jsx index 371c659b4..b339ed203 100644 --- a/frontend/src/components/auth/EmailVerification.jsx +++ b/frontend/src/components/auth/EmailVerification.jsx @@ -1,7 +1,7 @@ import React from "react"; import { Navigate, useSearchParams } from "react-router-dom"; import { Spinner } from "reactstrap"; -import { ContentSection } from "@greedybear/gb-ui"; +import { ContentSection } from "../common/gb-ui/index"; import { verifyEmail } from "./api"; diff --git a/frontend/src/components/auth/Login.jsx b/frontend/src/components/auth/Login.jsx index ba488f4ca..bf4253a2b 100644 --- a/frontend/src/components/auth/Login.jsx +++ b/frontend/src/components/auth/Login.jsx @@ -12,7 +12,7 @@ import { import { MdInfoOutline } from "react-icons/md"; import { Form, Formik } from "formik"; -import { ContentSection } from "@greedybear/gb-ui"; +import { ContentSection } from "../common/gb-ui/index"; import { useAuthStore } from "../../stores"; import { diff --git a/frontend/src/components/auth/Logout.jsx b/frontend/src/components/auth/Logout.jsx index a3fd1d2f2..ab3d57a6e 100644 --- a/frontend/src/components/auth/Logout.jsx +++ b/frontend/src/components/auth/Logout.jsx @@ -1,7 +1,7 @@ import React from "react"; import { useShallow } from "zustand/shallow"; -import { FallBackLoading } from "@greedybear/gb-ui"; +import { FallBackLoading } from "../common/gb-ui/index"; import { useAuthStore } from "../../stores"; import { AUTHENTICATION_STATUSES } from "../../constants"; diff --git a/frontend/src/components/auth/Register.jsx b/frontend/src/components/auth/Register.jsx index 62265ae32..fc37a60cf 100644 --- a/frontend/src/components/auth/Register.jsx +++ b/frontend/src/components/auth/Register.jsx @@ -14,7 +14,7 @@ import { import { Form, Formik } from "formik"; import { useTitle } from "react-use"; -import { ContentSection, Select } from "@greedybear/gb-ui"; +import { ContentSection, Select } from "../common/gb-ui/index"; import { AfterRegistrationModalAlert, diff --git a/frontend/src/components/auth/ResetPassword.jsx b/frontend/src/components/auth/ResetPassword.jsx index 7f319bcd2..cdce34664 100644 --- a/frontend/src/components/auth/ResetPassword.jsx +++ b/frontend/src/components/auth/ResetPassword.jsx @@ -4,7 +4,7 @@ import { FormGroup, Label, Col, Input, Button, Spinner } from "reactstrap"; import { Form, Formik } from "formik"; import { useTitle } from "react-use"; -import { ContentSection } from "@greedybear/gb-ui"; +import { ContentSection } from "../common/gb-ui/index"; import { UUID_REGEX } from "../../constants/index"; import { resetPassword } from "./api"; diff --git a/frontend/src/components/auth/api.js b/frontend/src/components/auth/api.js index 9f732943d..8fe4f72a0 100644 --- a/frontend/src/components/auth/api.js +++ b/frontend/src/components/auth/api.js @@ -1,6 +1,6 @@ import axios from "axios"; -import { addToast } from "@greedybear/gb-ui"; +import { addToast } from "../common/gb-ui/index"; import { AUTH_BASE_URI } from "../../constants/api"; diff --git a/frontend/src/components/auth/utils/registration-buttons.jsx b/frontend/src/components/auth/utils/registration-buttons.jsx index fb67d4084..bc52c6a58 100644 --- a/frontend/src/components/auth/utils/registration-buttons.jsx +++ b/frontend/src/components/auth/utils/registration-buttons.jsx @@ -2,7 +2,7 @@ import React from "react"; // import PropTypes from "prop-types"; import { Alert, PopoverBody } from "reactstrap"; import { IoMail } from "react-icons/io5"; -import { PopupFormButton } from "@greedybear/gb-ui"; +import { PopupFormButton } from "../../common/gb-ui/index"; import EmailForm from "./EmailForm"; import { resendVerificationMail, requestPasswordReset } from "../api"; diff --git a/frontend/src/components/common/gb-ui/components/alerts/ErrorAlert.jsx b/frontend/src/components/common/gb-ui/components/alerts/ErrorAlert.jsx new file mode 100644 index 000000000..aa1d0e643 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/alerts/ErrorAlert.jsx @@ -0,0 +1,39 @@ +import React from "react"; +import classnames from "classnames"; +import { Alert, Col } from "reactstrap"; +import { MdError } from "react-icons/md"; + +const sizeToWidth = { + sm: 4, + md: 8, + lg: 10, + xl: 12, +}; + +function ErrorAlert({ error, size = "md", className = undefined, ...rest }) { + return ( + +

+ +   Error! +

+
+
+ {[error?.response?.status, error?.response?.statusText] + .filter(Boolean) + .join(" | ")} +
+
+ {error?.parsedMsg?.toString()} +
+
+ ); +} + +export default ErrorAlert; diff --git a/frontend/src/components/common/gb-ui/components/buttons/CopyToClipboardButton.jsx b/frontend/src/components/common/gb-ui/components/buttons/CopyToClipboardButton.jsx new file mode 100644 index 000000000..4356f8b5b --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/buttons/CopyToClipboardButton.jsx @@ -0,0 +1,73 @@ +import React from "react"; +import { Tooltip, UncontrolledTooltip } from "reactstrap"; +import { HiClipboardCopy } from "react-icons/hi"; + +import { useCopyToClipboard } from "react-use"; + +function CopyToClipboardButton({ + id, + text, + children = null, + tooltip = null, + showOnHover = false, + ...rest +}) { + // props + + // local state + const [copied, setCopied] = React.useState(false); + const timeoutRef = React.useRef(null); + + const [, copyToClipboard] = useCopyToClipboard(); + + React.useEffect( + () => () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + }, + [], + ); + + // callbacks + const onCopy = () => { + copyToClipboard(text); + setCopied(true); + if (timeoutRef.current) clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout( + () => setCopied(false), + tooltip?.timeout || 1000, + ); + }; + + return ( + <> + + {children || } + + + {tooltip?.text || "Copied!"} + + {showOnHover && ( + + Click to copy + + )} + + ); +} + +export default CopyToClipboardButton; diff --git a/frontend/src/components/common/gb-ui/components/buttons/IconButton.jsx b/frontend/src/components/common/gb-ui/components/buttons/IconButton.jsx new file mode 100644 index 000000000..2ed4b827e --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/buttons/IconButton.jsx @@ -0,0 +1,33 @@ +import React from "react"; +import { Button, UncontrolledTooltip } from "reactstrap"; +import { nanoid } from "nanoid"; + +function IconButton({ + id = undefined, + title = undefined, + titlePlacement = "right-start", + Icon, + ...rest +}) { + // props + + // vars + const btnId = React.useMemo(() => id || `iconbtn-${nanoid(4)}`, [id]); + + return ( + + ); +} + +export default IconButton; diff --git a/frontend/src/components/common/gb-ui/components/buttons/PopupFormButton.jsx b/frontend/src/components/common/gb-ui/components/buttons/PopupFormButton.jsx new file mode 100644 index 000000000..4c69694b0 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/buttons/PopupFormButton.jsx @@ -0,0 +1,71 @@ +import React from "react"; +import { Button, Popover, UncontrolledTooltip, PopoverBody } from "reactstrap"; +import { nanoid } from "nanoid"; +import { IoMdClose } from "react-icons/io"; + +export default function PopupFormButton({ + id = undefined, + title = null, + titlePlacement = "right-start", + popOverPlacement = "right-start", + Icon, + Form, + onFormSuccess = () => null, + ...rest +}) { + // props + + // state + const [popoverOpen, setPopoverOpen] = React.useState(false); + const closeTimeoutRef = React.useRef(null); + + React.useEffect( + () => () => { + if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); + }, + [], + ); + + // callbacks + const onFormSubmit = React.useCallback(() => { + if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); + closeTimeoutRef.current = setTimeout(() => setPopoverOpen(false), 400); + onFormSuccess(); + }, [onFormSuccess]); + + // vars + const btnId = React.useMemo(() => id || `popover-btn-${nanoid(4)}`, [id]); + + return ( + <> + + {title && ( + + {title} + + )} + setPopoverOpen(!popoverOpen)} + > + +
+ + + + ); +} diff --git a/frontend/src/components/common/gb-ui/components/buttons/ScrollToTopButton.jsx b/frontend/src/components/common/gb-ui/components/buttons/ScrollToTopButton.jsx new file mode 100644 index 000000000..1c7810cbd --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/buttons/ScrollToTopButton.jsx @@ -0,0 +1,54 @@ +import React from "react"; +import { RiArrowUpSLine } from "react-icons/ri"; +import { Button } from "reactstrap"; + +export default function ScrollToTopButton({ + defaultVisible = false, + scrollYOffset = 300, + ...rest +}) { + // props + + // local state + const [isVisible, setIsVisible] = React.useState(defaultVisible); + + // Show button when page is scrolled upto given distance + const toggleVisibility = React.useCallback( + () => + window.pageYOffset > scrollYOffset + ? setIsVisible(true) + : setIsVisible(false), + [scrollYOffset, setIsVisible], + ); + + // Set the top cordinate to 0 + // make scrolling smooth + const scrollToTop = () => + window.scrollTo({ + top: 0, + behavior: "smooth", + }); + + React.useEffect(() => { + window.addEventListener("scroll", toggleVisibility); + toggleVisibility(); + + return () => { + window.removeEventListener("scroll", toggleVisibility); + }; + }, [toggleVisibility]); + + return ( +
+ {isVisible && ( + + )} +
+ ); +} diff --git a/frontend/src/components/common/gb-ui/components/containers/ContentSection.jsx b/frontend/src/components/common/gb-ui/components/containers/ContentSection.jsx new file mode 100644 index 000000000..0f883dd7f --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/containers/ContentSection.jsx @@ -0,0 +1,12 @@ +import React from "react"; +import classnames from "classnames"; + +function ContentSection({ className = null, children, ...rest }) { + return ( +
+ {children} +
+ ); +} + +export default ContentSection; diff --git a/frontend/src/components/common/gb-ui/components/containers/Loader.jsx b/frontend/src/components/common/gb-ui/components/containers/Loader.jsx new file mode 100644 index 000000000..85af15350 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/containers/Loader.jsx @@ -0,0 +1,27 @@ +import React from "react"; +import { Spinner } from "reactstrap"; + +import ErrorAlert from "../alerts/ErrorAlert"; + +function Loader({ + loading, + error = null, + render, + renderError = ErrorAlert, + size = "md", +}) { + return ( + <> + {loading && ( + + )} + {error && renderError({ error, size })} + {!(loading || error) && render()} + + ); +} + +export default Loader; diff --git a/frontend/src/components/common/gb-ui/components/containers/LoadingBoundary.jsx b/frontend/src/components/common/gb-ui/components/containers/LoadingBoundary.jsx new file mode 100644 index 000000000..6f187ddac --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/containers/LoadingBoundary.jsx @@ -0,0 +1,30 @@ +import React from "react"; +import { Spinner } from "reactstrap"; + +import ErrorAlert from "../alerts/ErrorAlert"; + +function LoadingBoundary({ + loading = false, + error = null, + size = "md", + render, + renderError = ErrorAlert, +}) { + const hasError = Boolean(error?.response || error?.message); + const RenderError = renderError; + + return ( + <> + {loading && ( + + )} + {hasError && } + {!loading && !hasError && render()} + + ); +} + +export default LoadingBoundary; diff --git a/frontend/src/components/common/gb-ui/components/containers/SmallInfoCard.jsx b/frontend/src/components/common/gb-ui/components/containers/SmallInfoCard.jsx new file mode 100644 index 000000000..85fd5d8d1 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/containers/SmallInfoCard.jsx @@ -0,0 +1,14 @@ +import React from "react"; +import classNames from "classnames"; +import { Card, CardBody, CardHeader } from "reactstrap"; + +function SmallInfoCard({ header, body, className = undefined, ...rest }) { + return ( + + {header} + {body} + + ); +} + +export default SmallInfoCard; diff --git a/frontend/src/components/common/gb-ui/components/form/Select.jsx b/frontend/src/components/common/gb-ui/components/form/Select.jsx new file mode 100644 index 000000000..a4d710b18 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/form/Select.jsx @@ -0,0 +1,20 @@ +import React from "react"; +import { Input } from "reactstrap"; + +export default function Select({ + choices, + allowEmpty = false, + emptyLabel = "", + ...rest +}) { + return ( + + {allowEmpty && } + {choices.map(({ label: text, value }) => ( + + ))} + + ); +} diff --git a/frontend/src/components/common/gb-ui/components/icons/ArrowToggleIcon.jsx b/frontend/src/components/common/gb-ui/components/icons/ArrowToggleIcon.jsx new file mode 100644 index 000000000..d59ef5e42 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/icons/ArrowToggleIcon.jsx @@ -0,0 +1,16 @@ +import React from "react"; + +import { MdExpandLess, MdExpandMore } from "react-icons/md"; +import classNames from "classnames"; + +function ArrowToggleIcon({ isExpanded, className = null, ...rest }) { + const cls = classNames("pointer bg-tertiary rounded-pill", className); + + return isExpanded ? ( + + ) : ( + + ); +} + +export default ArrowToggleIcon; diff --git a/frontend/src/components/common/gb-ui/components/icons/BooleanIcon.jsx b/frontend/src/components/common/gb-ui/components/icons/BooleanIcon.jsx new file mode 100644 index 000000000..39e44e25f --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/icons/BooleanIcon.jsx @@ -0,0 +1,30 @@ +import React from "react"; +import classnames from "classnames"; +import { MdCheck, MdCheckCircleOutline } from "react-icons/md"; +import { IoMdCloseCircleOutline, IoMdClose } from "react-icons/io"; + +export default function BooleanIcon({ + truthy, + withColors = false, + outline = true, + ...rest +}) { + const Check = outline ? MdCheckCircleOutline : MdCheck; + const Close = outline ? IoMdCloseCircleOutline : IoMdClose; + + return truthy ? ( + + ) : ( + + ); +} diff --git a/frontend/src/components/common/gb-ui/components/misc/FallbackLoading.jsx b/frontend/src/components/common/gb-ui/components/misc/FallbackLoading.jsx new file mode 100644 index 000000000..a082b694a --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/misc/FallbackLoading.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Spinner } from "reactstrap"; + +export default function FallBackLoading({ text = "Loading...", ...rest }) { + return ( +
+
+ +

{text}

+
+
+ ); +} diff --git a/frontend/src/components/common/gb-ui/components/misc/Toaster.jsx b/frontend/src/components/common/gb-ui/components/misc/Toaster.jsx new file mode 100644 index 000000000..86fd4072c --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/misc/Toaster.jsx @@ -0,0 +1,49 @@ +import React, { useState, useCallback } from "react"; +import { Toast, ToastBody, ToastHeader, Spinner } from "reactstrap"; +import { MdError, MdWarning, MdInfo, MdCheckCircle } from "react-icons/md"; + +function getIcon(color) { + const iconChoices = { + danger: MdError, + warning: MdWarning, + info: MdInfo, + success: MdCheckCircle, + spinner: Spinner, + }; + return iconChoices[color] ? iconChoices[color] : iconChoices.info; +} + +export default function Toaster({ + header, + body = null, + color = "info", + timeout = 4000, + showToggle = false, + ...props +}) { + // state + const [show, setShow] = useState(true); + const toggle = useCallback(() => setShow((v) => !v), [setShow]); + + React.useEffect(() => { + const timer = setTimeout(() => setShow(false), timeout); + return () => clearTimeout(timer); + }, [timeout]); + + const Icon = getIcon(color); + + const toastColor = color === "spinner" ? "info" : color; + + return ( + + + + {header} + + {body && {body}} + + ); +} diff --git a/frontend/src/components/common/gb-ui/components/misc/UserBubble.jsx b/frontend/src/components/common/gb-ui/components/misc/UserBubble.jsx new file mode 100644 index 000000000..14907659f --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/misc/UserBubble.jsx @@ -0,0 +1,42 @@ +import React from "react"; +import classnames from "classnames"; + +/** + * @type {component} + * @param props + */ +export default function UserBubble({ + userInfo, + size = "sm", + className = undefined, + ...elProps +}) { + const userInitials = + userInfo?.first_name?.charAt(0).concat(userInfo?.last_name?.charAt(0)) || + userInfo?.username?.slice(0, 2); + + const bubbleClassName = React.useMemo( + () => + classnames( + "chat-bubble", + { + "chat-bubble-sm": size === "sm", + "chat-bubble-xs": size === "xs", + highlight: true, + }, + className, + ), + [className, size], + ); + + return ( +
+ {userInitials} +
+ ); +} diff --git a/frontend/src/components/common/gb-ui/components/modals/ConfirmModal.jsx b/frontend/src/components/common/gb-ui/components/modals/ConfirmModal.jsx new file mode 100644 index 000000000..29ad0b88c --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/modals/ConfirmModal.jsx @@ -0,0 +1,69 @@ +import React, { Fragment } from "react"; +import { createRoot } from "react-dom/client"; +import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from "reactstrap"; + +function ConfirmModal({ + onClose, + message = "Are you sure?", + title = "Warning!", + confirmText = "Ok", + cancelText = "Cancel", + confirmColor = "primary", + cancelColor = "", + className = "", + buttonsComponent = null, + size = null, + bodyComponent = null, + modalProps = {}, +}) { + let buttonsContent = ( + + {cancelText && ( + + )}{" "} + + + ); + + if (buttonsComponent) { + const CustomComponent = buttonsComponent; + buttonsContent = ; + } + + const BodyComponent = bodyComponent; + + return ( + onClose(false)} + className={`reactstrap-confirm ${className}`} + {...modalProps} + > + {title && ( + onClose(false)}>{title || null} + )} + {bodyComponent ? : message} + {buttonsContent} + + ); +} + +export const confirm = (props) => + new Promise((resolve) => { + const el = document.createElement("div"); + document.body.appendChild(el); + const root = createRoot(el); + const handleResolve = (result) => { + root.unmount(); + el.remove(); + resolve(result); + }; + root.render(); + }); + +export default ConfirmModal; diff --git a/frontend/src/components/common/gb-ui/components/nav/DropdownNavLink.jsx b/frontend/src/components/common/gb-ui/components/nav/DropdownNavLink.jsx new file mode 100644 index 000000000..4a5cf9ff9 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/nav/DropdownNavLink.jsx @@ -0,0 +1,19 @@ +import React from "react"; +import { NavLink as RRNavLink } from "react-router-dom"; + +/** + * @type {component} + * @param props + */ +export default function DropdownNavLink(props) { + const { children, ...toPassProps } = props; + + return ( + `dropdown-item${isActive ? " active" : ""}`} + {...toPassProps} + > + {children} + + ); +} diff --git a/frontend/src/components/common/gb-ui/components/nav/NavLink.jsx b/frontend/src/components/common/gb-ui/components/nav/NavLink.jsx new file mode 100644 index 000000000..b7f1784de --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/nav/NavLink.jsx @@ -0,0 +1,37 @@ +import React from "react"; +import classnames from "classnames"; +import { NavLink as RRNavLink } from "react-router-dom"; + +// constants +const type2ClassnameMap = { + default: "", + primary: "link-primary", + primaryUl: "link-ul-primary", + muted: "link-muted", + mutedUl: "link-ul-muted", +}; + +// component +export default function NavLink({ + type = "default", + children, + className = null, + ...props +}) { + return ( + + classnames( + "nav-link", + "text-lg-center", + type2ClassnameMap[type], + className, + { active: isActive }, + ) + } + {...props} + > + {children} + + ); +} diff --git a/frontend/src/components/common/gb-ui/components/table/DataTable.jsx b/frontend/src/components/common/gb-ui/components/table/DataTable.jsx new file mode 100644 index 000000000..341899044 --- /dev/null +++ b/frontend/src/components/common/gb-ui/components/table/DataTable.jsx @@ -0,0 +1,570 @@ +/* eslint-disable no-nested-ternary */ +import React from "react"; +import classnames from "classnames"; +import { Table } from "reactstrap"; +import { + useReactTable, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + getPaginationRowModel, +} from "@tanstack/react-table"; +import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa"; + +import { makeTableArgs, defaultConfig, defaultInitialState } from "./utils"; +import Paginator from "./Paginator"; + +function getColumnId(column) { + return column.id || column.accessor; +} + +function getAccessorFn(column) { + if (typeof column.accessor === "function") return column.accessor; + if (typeof column.accessor === "string") + return (row) => row?.[column.accessor]; + return () => undefined; +} + +function normalizeColumn(column, index) { + const id = getColumnId(column) || `column_${index}`; + const accessorFn = getAccessorFn(column); + + return { + ...column, + id, + accessorFn, + }; +} + +function getLegacyRowValue(row, column) { + if (!column?.accessorFn) return undefined; + return column.accessorFn(row); +} + +function legacyDefaultFilterFn(row, columnId, filterValue) { + if (filterValue === undefined || filterValue === null || filterValue === "") + return true; + + const normalizedFilter = `${filterValue}`.toLowerCase(); + const rowValue = row.getValue(columnId); + + if (Array.isArray(rowValue)) { + return rowValue.some((value) => + `${value ?? ""}`.toLowerCase().includes(normalizedFilter), + ); + } + + return `${rowValue ?? ""}`.toLowerCase().includes(normalizedFilter); +} + +/** + * Suitable when data is already available client side. Thus, pagination/filtering/sorting can be performed client side too. + */ +function DataTable({ + config: userConfig = defaultConfig, + onSelectedRowChange = undefined, + SubComponent = undefined, + tableProps = undefined, + tableEmptyNode = "No Data", + TableBodyComponent = undefined, + columns = [], + data = [], + initialState = defaultInitialState, + stateReducer = undefined, + isRowSelectable = () => true, + customProps = undefined, + manualPagination = false, + manualFilters = false, + manualSortBy = false, + pageCount = undefined, +}) { + const config = React.useMemo( + () => ({ ...defaultConfig, ...userConfig }), + [userConfig], + ); + + const tableArgs = React.useMemo(() => makeTableArgs(config), [config]); + + const hookRegistry = React.useMemo(() => { + const registry = { visibleColumns: [], getRowProps: [] }; + tableArgs.forEach((hook) => { + if (typeof hook === "function") hook(registry); + }); + return registry; + }, [tableArgs]); + + const baseColumns = React.useMemo( + () => columns.map((column, index) => normalizeColumn(column, index)), + [columns], + ); + + const baseInstance = React.useMemo( + () => ({ customProps, isRowSelectable }), + [customProps, isRowSelectable], + ); + + const legacyColumns = React.useMemo(() => { + let transformedColumns = baseColumns; + hookRegistry.visibleColumns.forEach((transformer) => { + transformedColumns = transformer(transformedColumns, { + instance: baseInstance, + }); + }); + return transformedColumns; + }, [baseColumns, hookRegistry, baseInstance]); + + const tanstackColumns = React.useMemo( + () => + legacyColumns.map((column) => ({ + id: column.id, + accessorFn: column.accessorFn, + enableSorting: config.enableSortBy && !column.disableSortBy, + enableColumnFilter: config.enableFilters && !!column.Filter, + filterFn: column.filterFn || legacyDefaultFilterFn, + meta: { legacyColumn: column }, + })), + [legacyColumns, config.enableSortBy, config.enableFilters], + ); + + const legacyInitialState = React.useMemo( + () => ({ ...defaultInitialState, ...initialState }), + [initialState], + ); + + const [tableState, setTableState] = React.useState(() => ({ + pagination: { + pageIndex: legacyInitialState.pageIndex || 0, + pageSize: legacyInitialState.pageSize || 10, + }, + sorting: legacyInitialState.sortBy || [], + columnFilters: legacyInitialState.filters || [], + rowSelection: legacyInitialState.selectedRowIds || {}, + expanded: {}, + })); + + const reduceState = React.useCallback( + (nextState, action, prevState) => { + if (!stateReducer) return nextState; + return stateReducer(nextState, action, prevState) || nextState; + }, + [stateReducer], + ); + + const updateState = React.useCallback( + (producer, action) => { + setTableState((previousState) => { + const nextState = producer(previousState); + return reduceState(nextState, action, previousState); + }); + }, + [reduceState], + ); + + const toggleRowSelected = React.useCallback( + (rowId, selected) => { + updateState( + (previousState) => ({ + ...previousState, + rowSelection: { + ...previousState.rowSelection, + [rowId]: selected, + }, + }), + { type: "toggleRowSelected", id: rowId, value: selected }, + ); + }, + [updateState], + ); + + const table = useReactTable({ + data, + columns: tanstackColumns, + state: tableState, + pageCount, + manualPagination, + manualFiltering: manualFilters, + manualSorting: manualSortBy, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + enableRowSelection: (row) => + isRowSelectable({ + id: row.id, + original: row.original, + }), + onPaginationChange: (updater) => { + updateState( + (previousState) => { + const nextPagination = + typeof updater === "function" + ? updater(previousState.pagination) + : updater; + + return { + ...previousState, + pagination: nextPagination, + }; + }, + { + type: + typeof updater === "function" + ? "gotoPage" + : updater.pageIndex !== tableState.pagination.pageIndex + ? "gotoPage" + : "setPageSize", + pageIndex: + typeof updater === "function" + ? updater(tableState.pagination).pageIndex + : updater.pageIndex, + }, + ); + }, + onSortingChange: (updater) => { + updateState( + (previousState) => ({ + ...previousState, + sorting: + typeof updater === "function" + ? updater(previousState.sorting) + : updater, + }), + { type: "toggleSortBy" }, + ); + }, + onColumnFiltersChange: (updater) => { + updateState( + (previousState) => ({ + ...previousState, + columnFilters: + typeof updater === "function" + ? updater(previousState.columnFilters) + : updater, + }), + { type: "setFilter" }, + ); + }, + onRowSelectionChange: (updater) => { + updateState( + (previousState) => ({ + ...previousState, + rowSelection: + typeof updater === "function" + ? updater(previousState.rowSelection) + : updater, + }), + { type: "toggleRowSelected" }, + ); + }, + onExpandedChange: (updater) => { + updateState( + (previousState) => ({ + ...previousState, + expanded: + typeof updater === "function" + ? updater(previousState.expanded) + : updater, + }), + { type: "toggleRowExpanded" }, + ); + }, + }); + + const toLegacyRow = React.useCallback( + (row) => ({ + id: row.id, + original: row.original, + values: Object.fromEntries( + legacyColumns.map((column) => [ + column.id, + getLegacyRowValue(row.original, column), + ]), + ), + isSelected: row.getIsSelected(), + isExpanded: row.getIsExpanded(), + getToggleRowSelectedProps: () => ({ + checked: row.getIsSelected(), + onChange: (event) => row.toggleSelected(event.target.checked), + }), + getToggleRowExpandedProps: () => ({ + role: "button", + tabIndex: 0, + onClick: () => row.toggleExpanded(), + onKeyDown: (event) => { + if (event.key === "Enter" || event.key === " ") row.toggleExpanded(); + }, + }), + }), + [legacyColumns], + ); + + const pageRows = table.getRowModel().rows; + const legacyPageRows = React.useMemo( + () => pageRows.map((row) => toLegacyRow(row)), + [pageRows, toLegacyRow], + ); + + const selectedFlatRows = React.useMemo( + () => table.getSelectedRowModel().flatRows.map((row) => toLegacyRow(row)), + [table, toLegacyRow], + ); + + React.useEffect(() => { + if (!onSelectedRowChange) return; + onSelectedRowChange(selectedFlatRows.map((row) => row.original)); + }, [onSelectedRowChange, selectedFlatRows]); + + const tableInstanceContext = React.useMemo( + () => ({ + ...baseInstance, + page: legacyPageRows, + selectedFlatRows, + toggleRowSelected, + }), + [baseInstance, legacyPageRows, selectedFlatRows, toggleRowSelected], + ); + + const renderHeaderContent = React.useCallback( + (column) => { + const Header = column?.Header; + if (typeof Header === "function") { + return ( +
+ ); + } + return Header; + }, + [selectedFlatRows, toggleRowSelected, legacyPageRows], + ); + + const renderFooterContent = React.useCallback((column) => { + const Footer = column?.Footer; + if (typeof Footer === "function") return