diff --git a/.github/workflows/ci.yml b/.github/workflows/backend.yml similarity index 81% rename from .github/workflows/ci.yml rename to .github/workflows/backend.yml index 42038215..76d300a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/backend.yml @@ -1,16 +1,18 @@ -name: Build and Test +name: Backend on: pull_request: - types: [ opened, synchronize, reopened, closed ] + types: [opened, synchronize, reopened, closed] branches: - main + - prod paths: - - 'application/backend/**' + - "application/backend/**" push: branches: - main + - prod paths: - - 'application/backend/**' + - "application/backend/**" jobs: build-and-test: runs-on: ubuntu-latest diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml new file mode 100644 index 00000000..7061a1d3 --- /dev/null +++ b/.github/workflows/frontend.yml @@ -0,0 +1,33 @@ +name: Frontend +on: + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - main + - prod + paths: + - "application/frontend/**" + push: + branches: + - main + - prod + paths: + - "application/frontend/**" +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + - name: Set up Node 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "npm" + cache-dependency-path: application/frontend/package-lock.json + - name: Install dependencies + working-directory: application/frontend + run: npm ci + - name: Build + working-directory: application/frontend + run: npm run build:production diff --git a/.github/workflows/next-frontend.yml b/.github/workflows/next-frontend.yml new file mode 100644 index 00000000..c6ef12fb --- /dev/null +++ b/.github/workflows/next-frontend.yml @@ -0,0 +1,59 @@ +name: Next Frontend +on: + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - main + - prod + paths: + - "application/next-frontend/**" + push: + branches: + - main + - prod + paths: + - "application/next-frontend/**" + +jobs: + linting: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + - name: Set up Node 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "npm" + cache-dependency-path: application/next-frontend/package-lock.json + - name: Install dependencies + working-directory: application/next-frontend + run: npm ci + - name: Linting + working-directory: application/next-frontend + run: npm run lint + - name: Typechecking + working-directory: application/next-frontend + run: npm run typecheck + + # Test that the project compiles + test-build: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + - name: Set up Node 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "npm" + cache-dependency-path: application/next-frontend/package-lock.json + - name: Install dependencies + working-directory: application/next-frontend + run: npm ci + - name: Build + working-directory: application/next-frontend + run: npm run build + env: + # Prevents the build from failing, but doesn't do anything when testing build + NEXT_PUBLIC_BACKEND_URI: http://somerandomurl.test diff --git a/.github/workflows/versioning.yml b/.github/workflows/versioning.yml index 61d3cb7d..4094e941 100644 --- a/.github/workflows/versioning.yml +++ b/.github/workflows/versioning.yml @@ -5,50 +5,38 @@ on: - closed branches: - main + - prod paths: - application/** - infrastructure/** - .github/workflows/versioning.yml jobs: - build: + commit-version: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.sha }} - fetch-depth: '0' - - name: Set up Node.js - uses: actions/setup-node@v3 - - name: Install dependencies - run: npm install - working-directory: application/frontend - - name: Build bundle - run: npm run build:production - working-directory: application/frontend - env: - BACKEND_URI: https://api.bot.blank.pizza - - name: Remove build gitignore - run: rm public/.gitignore - working-directory: application/frontend - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Automated Build - branch: build - push_options: '--force' - create_branch: true + - uses: actions/checkout@v3 + with: + ref: ${{ github.sha }} + fetch-depth: "0" + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Automated Versioning + branch: build + push_options: "--force" + create_branch: true create-tag: - needs: build + needs: commit-version runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - with: - ref: build - fetch-depth: '0' + - uses: actions/checkout@v3 + with: + ref: build + fetch-depth: "0" - - name: Bump version and push tag - uses: anothrNick/github-tag-action@1.55.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WITH_V: true - DEFAULT_BRANCH: build + - name: Bump version and push tag + uses: anothrNick/github-tag-action@1.55.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WITH_V: true + DEFAULT_BRANCH: build diff --git a/README.md b/README.md index 0a23578b..77597cda 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![Infrastructure Diagram](https://github.com//blankoslo/Pizza.v2/blob/main/README/infrastructure.png?raw=true) ## Slack App Bot setup + 1. Go to `https://api.slack.com/apps/` 2. Click the `Create New App` button 3. Choose `from scratch` @@ -13,7 +14,7 @@ 8. Click `Event Subscriptions` 9. Turn on `Enable Events` 10. Open `Subscribe to bot events` -11. Add the events `file_shared` `message.channels` `message.im` `app_uninstalled` `tokens_revoked` +11. Add the events `file_shared` `message.channels` `message.im` `app_uninstalled` `tokens_revoked` `member_left_channel` 12. Click `OAuth & Permissions` in the menu 13. Go down to `Scopes` and click `Add an OAuth Scope` and add the scopes found in `/application/backend/api/slack`'s GET `/install` method. 14. Go to `App Home` @@ -25,29 +26,35 @@ 20. Click `Create new command` 21. Fill in `/set-pizza-channel` under `command`, fill in `This sets the current channel as the pizza channel` under `Short Description` 22. Click `save` -23. Click `Basic Information` in the menu -24. Under App Credentials, copy `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, `SLACK_SIGNING_SECRET` -25. You now have `SLACK_APP_TOKEN`, `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, `SLACK_SIGNING_SECRET` which is needed in terraform or docker-compose +23. Add another command `/pizzabot-admin-panel` under `command`, fill in `Link for administrating the pizzabot` under `Short Description` +24. Click `Basic Information` in the menu +25. Under App Credentials, copy `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, `SLACK_SIGNING_SECRET` +26. You now have `SLACK_APP_TOKEN`, `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, `SLACK_SIGNING_SECRET` which is needed in terraform or docker-compose ## How to run the system ### Development + The frontend, backend, bot, message queue and database can all be run with docker compose by running `docker-compose up`. Optionally you can do `docker-compose up -d [service]` to only start one or more service. During development all services run behind an nginx instance to simplify their interactions. The ports are 80 and 443. As we use Ouath2 for authentication we are forced to use https. Nginx needs valid ssl certificates, so you are gonna need to generate one with the command `openssl req -x509 -nodes -newkey rsa:4096 -keyout nginx-selfsigned.key -out nginx-selfsigned.crt -sha256 -days 365` and add it to `application/containers/development` -**NB:** You'll need to supply the docker-compose file with slack credentials as mention in the `Slack App Bot setup` section. You'll also need to supply cloudinary credentials if you want the uninstall handler to properly delete images, you should also update the `upload_preset` in `handle_file_share` in `bot/src/slack/__init__.py` to point to your own cloudinary account. +**NB:** You'll need to supply the docker-compose file with slack credentials as mention in the `Slack App Bot setup` section. These are defined in a `.env` file in `application/containers/development` folder of the project. This file is not commited to the repository, so you have to create it manually. You can use the `.env.example` file as a template inside `application/containers/development`. You'll also need to supply cloudinary credentials if you want the uninstall handler to properly delete images, you should also update the `upload_preset` in `handle_file_share` in `bot/src/slack/__init__.py` to point to your own cloudinary account. ### Good to know + Locales doesnt work properly in the alpine container used, meaning it's not a bug if stuff is localized wrong, such as the time string send in pizza event invites. ### Production + #### Terraform Cloud + This repository is connected to Terraform Cloud where it is automatically planned and then manually applied whenever a new tag is created. The branch used in Terraform Cloud is the `Build` branch, which gets created on every new version. This branch is the same as master, but it also contains the build files for the frontend application. A tag is automatically created through GitHub actions when a PR is merged into Main. #### Heroku + We are using terraform to describe the infrastructure, which can be found in the `/infrastructure` folder. In addition to this the backend/bot have `Procfile`, `runtime.txt`, and `.locales` files that describe the process, heroku runtime and additional locales to include. While the frontend have `.static` in the `public` folder to indicate the application folder for the nginx buildpacker, and a `.gitignore` file to keep the files and folder in git. 1. Go into the `infrastructure` folder and run `terraform apply` (not needed if using Terraform Cloud). @@ -56,21 +63,27 @@ We are using terraform to describe the infrastructure, which can be found in the 4. Create a CNAME record with the hostname specified in the main terraform file for both the frontend and the backend and point them to the `DNS TARGET`s from heroku. After a while routing and SSL should work flawlesly. Infrastructure: -* Backend-app: contains the database, papertrail instance, Rabbitmq instance, and backend application -* Bot-app: contains an attachement to the database, an attachement to the papertrail instance, an attachement to the Rabbitmq instance, the bot worker -* Frontend-app: contains a nginx instance with the build files from the `public` folder + +- Backend-app: contains the database, papertrail instance, Rabbitmq instance, and backend application +- Bot-app: contains an attachement to the database, an attachement to the papertrail instance, an attachement to the Rabbitmq instance, the bot worker +- Frontend-app: contains a nginx instance with the build files from the `public` folder ## Tests + ### Backend + The tests for this application were written using the pytest testing framework. The tests cover the blueprints, services, and broker handlers. To run the backend tests, navigate to the backend directory of the project and run the following command: + ``` python3 -m pytest tests ``` + Optionally you can add the options `-k [name of test suit or test function]` (to only run certain tests), `-s` (to show print statements) and `-v`/`-vv` (to make tests more verbose). ### Bot + TODO ## Contributing Guidelines diff --git a/application/backend/app/api/auth.py b/application/backend/app/api/auth.py index fa882486..70e0e1e8 100644 --- a/application/backend/app/api/auth.py +++ b/application/backend/app/api/auth.py @@ -5,7 +5,7 @@ from app.repositories.user_repository import UserRepository from app.models.user_schema import UserSchema from app.auth import auth -from flask_jwt_extended import create_access_token, create_refresh_token, jwt_required, get_jwt_identity +from flask_jwt_extended import create_access_token, create_refresh_token, jwt_required, get_jwt_identity, set_access_cookies, unset_jwt_cookies from app.services.slack_organization_service import SlackOrganizationService from app.services.injector import injector @@ -39,9 +39,11 @@ def get(self): slack_provider_cfg = get_slack_provider_cfg() authorization_endpoint = slack_provider_cfg["authorization_endpoint"] + base_url = current_app.config["FRONTEND_URI"] + base_url = base_url.rstrip("/") + # Use library to construct the request for Google login and provide # scopes that let you retrieve user's profile from Google - base_url = current_app.config["FRONTEND_URI"].rstrip('/') request_uri = auth.client.prepare_request_uri( authorization_endpoint, redirect_uri = f'{base_url}/login/callback', @@ -55,7 +57,10 @@ def get(self): @bp.route("/login/callback") class Auth(views.MethodView): def get(self): - base_url = current_app.config["FRONTEND_URI"].rstrip('/') + + base_url = current_app.config["FRONTEND_URI"] + base_url = base_url.rstrip("/") + code = request.args.get("code") authorization_response = f'{base_url}/login/callback?' for key in request.args.keys(): @@ -114,5 +119,15 @@ def get(self): } access_token = create_access_token(identity=user, additional_claims=additional_claims) refresh_token = create_refresh_token(identity=user, additional_claims=additional_claims) - return jsonify(access_token=access_token, refresh_token=refresh_token) + response = jsonify(access_token=access_token, refresh_token=refresh_token) + set_access_cookies(response, access_token) + return response return abort(401, message = "User email not available or not verified by Slack.") + + +@bp.route("/logout") +class Auth(views.MethodView): + def delete(self): + response = jsonify(msg="Successfully logged out") + unset_jwt_cookies(response) + return response diff --git a/application/backend/app/api/crud/slack_users.py b/application/backend/app/api/crud/slack_users.py index 582cf52b..842fe47a 100644 --- a/application/backend/app/api/crud/slack_users.py +++ b/application/backend/app/api/crud/slack_users.py @@ -1,9 +1,11 @@ -from flask import views +import requests +from flask import jsonify, views from flask_smorest import Blueprint, abort from app.models.slack_user_schema import SlackUserResponseSchema, SlackUserUpdateSchema, SlackUserQueryArgsSchema from flask_jwt_extended import jwt_required, current_user from app.services.injector import injector from app.services.slack_user_service import SlackUserService +from app.services.slack_organization_service import SlackOrganizationService bp = Blueprint("users", "users", url_prefix="/users", description="Operations on users") @@ -19,6 +21,45 @@ def get(self, args, pagination_parameters): total, slack_users = slack_user_service.get(filters=args, page=pagination_parameters.page, per_page=pagination_parameters.page_size, order_by_ascending=True, team_id=current_user.slack_organization_id) pagination_parameters.item_count = total return slack_users + +@bp.route("/current-channel") +class SlackChannel(views.MethodView): + @jwt_required() + def get(self): + """Get current channel info""" + slack_organization = injector.get(SlackOrganizationService) + org = slack_organization.get_by_id(team_id=current_user.slack_organization_id) + channel_id = org.channel_id + + if channel_id is None: + return jsonify({"channel_name": None, "channel_id": None, "users": [] }) + + # Request channel info from slack + request_url = f'https://slack.com/api/conversations.info?channel={channel_id}' + res = requests.get(request_url, headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {org.access_token}" + }).json() + + if not res["ok"]: + abort(500, message = "Error getting channel info") + + channel_name = res["channel"]["name"] + + slack_user_service = injector.get(SlackUserService) + count, slack_users = slack_user_service.get(filters={"slack_organization_id": current_user.slack_organization_id}) + returned_users = [ + { + "slack_id": user.slack_id, + "current_username": user.current_username, + "active": user.active, + "priority": user.priority, + "email": user.email, + "first_seen": user.first_seen, + } for user in slack_users + ] + + return jsonify({"channel_name": channel_name, "channel_id": channel_id, "users": returned_users }) @bp.route("/") class SlackUsersById(views.MethodView): diff --git a/application/backend/app/api/slack.py b/application/backend/app/api/slack.py index eb00a5f1..dabf3780 100644 --- a/application/backend/app/api/slack.py +++ b/application/backend/app/api/slack.py @@ -29,7 +29,10 @@ def get(self): 'users:read.email', 'commands' ] - frontend_base_url = current_app.config["FRONTEND_URI"].rstrip('/') + + frontend_base_url = current_app.config["FRONTEND_URI"] + frontend_base_url = frontend_base_url.rstrip("/") + callback_redirect_uri = f'{frontend_base_url}/slack/callback' client_id = current_app.config["SLACK_CLIENT_ID"] if client_id == None: @@ -55,7 +58,9 @@ def post(self): logger.warn("client_id or client_secret is None") return abort(500) - frontend_base_url = current_app.config["FRONTEND_URI"].rstrip('/') + frontend_base_url = current_app.config["FRONTEND_URI"] + frontend_base_url = frontend_base_url.rstrip("/") + callback_redirect_uri = f'{frontend_base_url}/slack/callback' data = { @@ -71,6 +76,7 @@ def post(self): logger.error(response["error"]) return abort(500) + if response['is_enterprise_install']: logger.warn("NOT SUPPORTED: User tried to install app into enterprise workspace.") return abort(501) @@ -85,12 +91,25 @@ def post(self): 'bot_user_id': response['bot_user_id'], 'access_token': response['access_token'] } + + is_reinstalled = slack_organization_service.get_by_id(schema_data['team_id']) is not None + slack_organization = schema.load(schema_data) slack_organization_service.upsert(slack_organization) BrokerService.publish("new_slack_organization_event", NewSlackOrganizationEventSchema().load({ 'team_id': schema_data['team_id'], - 'bot_token': schema_data['access_token'] + 'bot_token': schema_data['access_token'], + 'user_who_installed': response['authed_user']['id'] })) - return Response(status=200) + if is_reinstalled: + logger.info(f"Bot reinstalled for team: {schema_data['team_id']}") + return jsonify({ + "message": f"Bot has been re-added to the workspace '{schema_data['team_name']}'." + }) + + + return jsonify({ + "message": f"Bot has been added to the workspace '{schema_data['team_name']}'." + }) diff --git a/application/backend/app/application.py b/application/backend/app/application.py index 5a52dd4a..e2330480 100644 --- a/application/backend/app/application.py +++ b/application/backend/app/application.py @@ -6,7 +6,7 @@ from app.db import db, migrate from app.api import api, ma -from app.auth import auth, jwt +from app.auth import auth, jwt, refresh_cookie from app.services.broker import broker from app.services.injector import injector from app.services.invitation_service import InvitationService @@ -104,19 +104,24 @@ def add_headers(response): response.headers['Pragma'] = 'no-cache' response.headers['Expires'] = '0' response.headers['Content-Type'] = 'application/json; charset=utf-8' + response = refresh_cookie(response) return response + # Set up CORS if app.config["ENV"] == "production": origins = [FRONTEND_URI] resources_origins = {"origins": origins} - elif app.config["ENV"] == "development" or app.config["ENV"] == "test": - origins = ["https://localhost"] + elif app.config["ENV"] == "development": + origins = ["https://localhost:4434"] + resources_origins = {"origins": origins} + elif app.config["ENV"] == "test": + origins = ["https://localhost:4000"] resources_origins = {"origins": origins} CORS( app, resources={r"/api/*": resources_origins}, - allow_headers=["Authorization", "Content-Type"], + allow_headers=["Authorization", "Content-Type", "X-CSRF-TOKEN"], expose_headers=["X-Pagination"], methods=["OPTIONS", "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"], supports_credentials=True, diff --git a/application/backend/app/auth/__init__.py b/application/backend/app/auth/__init__.py index 2e100c5a..f3d43f9d 100644 --- a/application/backend/app/auth/__init__.py +++ b/application/backend/app/auth/__init__.py @@ -1,16 +1,22 @@ from oauthlib.oauth2 import WebApplicationClient -from flask_jwt_extended import JWTManager +from flask_jwt_extended import JWTManager, get_jwt, get_jwt_identity, set_access_cookies, create_access_token from app.repositories.user_repository import UserRepository +from datetime import datetime, timedelta, timezone + +from app.models.user_schema import UserSchema + class AuthClient(): - client: WebApplicationClient + client: WebApplicationClient + + def __init__(self, app=None, **kwargs): + if (app): + self.init_app(app, **kwargs) + + def init_app(self, app, **kwargs): + self.client = WebApplicationClient( + app.config["SLACK_CLIENT_ID"], kwargs=kwargs) - def __init__(self, app = None, **kwargs): - if (app): - self.init_app(app, **kwargs) - - def init_app(self, app, **kwargs): - self.client = WebApplicationClient(app.config["SLACK_CLIENT_ID"], kwargs=kwargs) auth = AuthClient() jwt = JWTManager() @@ -30,3 +36,25 @@ def user_lookup_callback(_jwt_header, jwt_data): identity = jwt_data["sub"] user = UserRepository.get_by_id(identity) return user + + +def refresh_cookie(response): + try: + exp_timestamp = get_jwt()["exp"] + now = datetime.now(timezone.utc) + target_timestamp = datetime.timestamp(now + timedelta(minutes=30)) + if target_timestamp > exp_timestamp: + identity = get_jwt_identity() + user = UserRepository.get_by_id(identity) + json_user = UserSchema().dump(user) + additional_claims = { + # TODO handle roles + "user": {**json_user, "roles": []} + } + access_token = create_access_token( + identity=user, additional_claims=additional_claims) + set_access_cookies(response, access_token) + return response + except (RuntimeError, KeyError): + # Case where there is not a valid JWT. Just return the original response + return response diff --git a/application/backend/app/config.py b/application/backend/app/config.py index efcdc501..1c376a7a 100644 --- a/application/backend/app/config.py +++ b/application/backend/app/config.py @@ -17,14 +17,16 @@ class Base(object): OPENAPI_SWAGGER_UI_PATH = "/swagger" # The following is equivalent to OPENAPI_SWAGGER_UI_VERSION = '3.19.5' OPENAPI_SWAGGER_UI_URL = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.19.5/" - # JWT - JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=30) + # JWT TODO: JWT_SECRET_KEY ? + JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=60) JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30) + JWT_TOKEN_LOCATION = ["headers", "cookies"] + JWT_COOKIE_SECURE = True class Test(Base): DEBUG = True - FRONTEND_URI = 'localhost' + FRONTEND_URI = 'https://localhost' SLACK_CLIENT_ID = 'dontCareSlackClientId' SLACK_CLIENT_SECRET = 'dontCareSlackClientSecret' MQ_EVENT_QUEUE = 'dontCareMqEventQueue' @@ -49,3 +51,4 @@ class Production(Base): CLOUDINARY_CLOUD_NAME = os.environ.get('CLOUDINARY_CLOUD_NAME') CLOUDINARY_API_KEY = os.environ.get('CLOUDINARY_API_KEY') CLOUDINARY_API_SECRET = os.environ.get('CLOUDINARY_API_SECRET') + JWT_COOKIE_DOMAIN = os.environ.get("DOMAIN") if 'DOMAIN' in os.environ else None diff --git a/application/backend/app/repositories/event_repository.py b/application/backend/app/repositories/event_repository.py index 81ba504f..5976d0a6 100644 --- a/application/backend/app/repositories/event_repository.py +++ b/application/backend/app/repositories/event_repository.py @@ -81,3 +81,17 @@ def get_event_by_id_if_ready_to_finalize(cls, event_id, session=db.session): .group_by(cls.id, cls.time, cls.restaurant_id) \ .having(func.count(cls.id) == cls.people_per_event) return query.first() + + @classmethod + def get_scheduled_events_for_user(cls, user_id, team_id, session=db.session): + query = session.query(cls.id, cls.restaurant_id, cls.time, Invitation.rsvp, cls.finalized, Invitation.slack_message_ts, Invitation.slack_message_channel) \ + .join(Invitation, cls.id == Invitation.event_id) \ + .filter( + and_( + Invitation.slack_id == user_id, + cls.slack_organization_id == team_id, + cls.time > datetime.now(), + ) + ) + + return query.all() diff --git a/application/backend/app/repositories/slack_organization_repository.py b/application/backend/app/repositories/slack_organization_repository.py index 1892ee43..bb63bedb 100644 --- a/application/backend/app/repositories/slack_organization_repository.py +++ b/application/backend/app/repositories/slack_organization_repository.py @@ -1,6 +1,23 @@ +from datetime import datetime + +from app.db import db + from app.models.slack_organization import SlackOrganization +from app.models.event import Event from app.models.mixins import CrudMixin - class SlackOrganizationRepository(SlackOrganization, CrudMixin): - pass + @classmethod + def get_scheduled_events(cls, team_id = None, session=db.session): + query = session.query(cls.team_id, Event.id, Event.time) \ + .outerjoin( + Event, + cls.team_id == Event.slack_organization_id + ) \ + .filter( + team_id == cls.team_id if team_id is not None else True, + Event.time > datetime.now() + ) + + res = query.count(), query.all() + return res diff --git a/application/backend/app/services/broker/handlers/action.py b/application/backend/app/services/broker/handlers/action.py index 17e8f3e1..56dfa5fd 100644 --- a/application/backend/app/services/broker/handlers/action.py +++ b/application/backend/app/services/broker/handlers/action.py @@ -103,8 +103,9 @@ def set_slack_channel(request: dict): success = True old_channel_id = None + scheduled_events_count = None try: - old_channel_id, slack_organization = slack_organization_service.set_channel(team_id=team_id, channel_id=channel_id) + old_channel_id, scheduled_events_count, slack_organization = slack_organization_service.set_channel(team_id=team_id, channel_id=channel_id) except Exception as e: logger.error(e) success = False @@ -112,6 +113,8 @@ def set_slack_channel(request: dict): response = {'success': success} if old_channel_id is not None: response['old_channel_id'] = old_channel_id + if scheduled_events_count: + response['scheduled_events_count'] = scheduled_events_count return response diff --git a/application/backend/app/services/broker/handlers/get.py b/application/backend/app/services/broker/handlers/get.py index b5bf1b61..eb44c321 100644 --- a/application/backend/app/services/broker/handlers/get.py +++ b/application/backend/app/services/broker/handlers/get.py @@ -2,12 +2,14 @@ from app.services.broker.schemas.get_unanswered_invitations import GetUnansweredInvitationsResponseSchema, GetUnansweredInvitationsDataSchema from app.services.broker.schemas.get_invited_unanswered_user_ids import GetInvitedUnansweredUserIdsResponseSchema +from app.services.broker.schemas.get_scheduled_events_for_user import GetScheduledEventsForUserRequestSchema, GetScheduledEventsForUserResponseSchema from app.services.broker.schemas.get_slack_installation import GetSlackInstallationRequestSchema, GetSlackInstallationResponseSchema from app.services.broker.schemas.get_slack_organizations import GetSlackOrganizationsResponseSchema from app.services.invitation_service import InvitationService from app.services.slack_organization_service import SlackOrganizationService from app.services.slack_user_service import SlackUserService +from app.services.event_service import EventService from app.services.injector import injector from app.models.enums import RSVP @@ -71,6 +73,25 @@ def get_invited_unanswered_user_ids(): return {'user_ids': response_data} +@MessageHandler.handle('get_scheduled_events_for_user', incoming_schema=GetScheduledEventsForUserRequestSchema, outgoing_schema = GetScheduledEventsForUserResponseSchema) +def get_scheduled_events_for_user(request: dict): + event_service = injector.get(EventService) + events = event_service.get_scheduled_events_for_user(request['user_id'], request['team_id']) + response_data = [] + for event in events: + data = { + "event_id": event.id, + "restaurant_id": event.restaurant_id, + "time": event.time.isoformat(), + "responded": event.rsvp, + "event_finalized": event.finalized, + "slack_message_channel": event.slack_message_channel, + "slack_message_ts": event.slack_message_ts, + } + response_data.append(data) + + return {'events': response_data} + @MessageHandler.handle('get_slack_installation', incoming_schema = GetSlackInstallationRequestSchema, outgoing_schema = GetSlackInstallationResponseSchema) def get_slack_installation(request: dict): @@ -81,7 +102,8 @@ def get_slack_installation(request: dict): 'team_id': slack_organization.team_id, 'app_id': slack_organization.app_id, 'bot_user_id': slack_organization.bot_user_id, - 'access_token': slack_organization.access_token + 'access_token': slack_organization.access_token, + 'channel_id': slack_organization.channel_id, } if slack_organization.team_name: diff --git a/application/backend/app/services/broker/handlers/update.py b/application/backend/app/services/broker/handlers/update.py index 01e5d920..0f525aa5 100644 --- a/application/backend/app/services/broker/handlers/update.py +++ b/application/backend/app/services/broker/handlers/update.py @@ -45,15 +45,21 @@ def update_slack_user(request: dict): slack_user_service = injector.get(SlackUserService) users_to_update = request['users_to_update'] - response = True + success = True updated_users = [] failed_users = [] for user in users_to_update: try: - result = slack_user_service.update(user['slack_id'], { - 'current_username': user['current_username'], - 'email': user['email'] - }, user['team_id']) + to_update = {} + if 'current_username' in user: + to_update['current_username'] = user['current_username'] + if 'email' in user: + to_update['email'] = user['email'] + if 'active' in user: + to_update['active'] = user['active'] + result = slack_user_service.update(user['slack_id'], to_update, user['team_id']) + + # if user doesnt exist, so add them if result is None: slack_user_service.add({ 'slack_id': user['slack_id'], @@ -61,13 +67,14 @@ def update_slack_user(request: dict): 'email': user['email'] }, user['team_id']) updated_users.append(user['slack_id']) + except Exception as e: logger.warning(e) - response = False + success = False failed_users.append(user['slack_id']) return { - 'success': response, + 'success': success, 'updated_users': updated_users, 'failed_users': failed_users } diff --git a/application/backend/app/services/broker/schemas/get_scheduled_events_for_user.py b/application/backend/app/services/broker/schemas/get_scheduled_events_for_user.py new file mode 100644 index 00000000..020ca975 --- /dev/null +++ b/application/backend/app/services/broker/schemas/get_scheduled_events_for_user.py @@ -0,0 +1,21 @@ +from marshmallow import fields, Schema +from app.models.enums import RSVP +from marshmallow_enum import EnumField + +class GetScheduledEventsForUserRequestSchema(Schema): + user_id = fields.Str(required=True) + team_id = fields.Str(required=True) + + +class ScheduledEventsForUserDataSchema(Schema): + event_id = fields.UUID(required=True) + restaurant_id = fields.UUID(required=True) + time = fields.Str(required=True) + responded = EnumField(RSVP, by_value=True, required=True) + event_finalized = fields.Bool(required=True) + slack_message_channel = fields.Str() + slack_message_ts = fields.Str() + +class GetScheduledEventsForUserResponseSchema(Schema): + events = fields.List(fields.Nested(ScheduledEventsForUserDataSchema), required=True) + diff --git a/application/backend/app/services/broker/schemas/get_slack_installation.py b/application/backend/app/services/broker/schemas/get_slack_installation.py index 8014134e..4d1a7516 100644 --- a/application/backend/app/services/broker/schemas/get_slack_installation.py +++ b/application/backend/app/services/broker/schemas/get_slack_installation.py @@ -13,3 +13,4 @@ class GetSlackInstallationResponseSchema(Schema): app_id = fields.Str(required=True) bot_user_id = fields.Str(required=True) access_token = fields.Str(required=True) + channel_id = fields.Str(required=True, allow_none=True) diff --git a/application/backend/app/services/broker/schemas/new_slack_organization_event.py b/application/backend/app/services/broker/schemas/new_slack_organization_event.py index cc9fcd04..22371281 100644 --- a/application/backend/app/services/broker/schemas/new_slack_organization_event.py +++ b/application/backend/app/services/broker/schemas/new_slack_organization_event.py @@ -3,3 +3,4 @@ class NewSlackOrganizationEventSchema(Schema): team_id = fields.Str(required=True) bot_token = fields.Str(required=True) + user_who_installed = fields.Str(required=True) diff --git a/application/backend/app/services/broker/schemas/set_slack_channel.py b/application/backend/app/services/broker/schemas/set_slack_channel.py index 1357eb9a..b21f7563 100644 --- a/application/backend/app/services/broker/schemas/set_slack_channel.py +++ b/application/backend/app/services/broker/schemas/set_slack_channel.py @@ -9,3 +9,4 @@ class SetSlackChannelRequestSchema(Schema): class SetSlackChannelResponseSchema(Schema): success = fields.Boolean(required=True) old_channel_id = fields.Str() + scheduled_events_count = fields.Integer() diff --git a/application/backend/app/services/broker/schemas/update_slack_user.py b/application/backend/app/services/broker/schemas/update_slack_user.py index 42b01c99..a30d792f 100644 --- a/application/backend/app/services/broker/schemas/update_slack_user.py +++ b/application/backend/app/services/broker/schemas/update_slack_user.py @@ -5,6 +5,7 @@ class SlackUserUpdate(Schema): current_username = fields.Str() email = fields.Str() team_id = fields.Str(required=True) + active = fields.Boolean() class UpdateSlackUserRequestSchema(Schema): users_to_update = fields.Nested(SlackUserUpdate, required=True, many=True) diff --git a/application/backend/app/services/event_service.py b/application/backend/app/services/event_service.py index a4bf6314..fe9e9287 100644 --- a/application/backend/app/services/event_service.py +++ b/application/backend/app/services/event_service.py @@ -6,6 +6,7 @@ from app.repositories.restaurant_repository import RestaurantRepository from app.repositories.group_repository import GroupRepository from app.repositories.invitation_repository import InvitationRepository +from app.repositories.slack_organization_repository import SlackOrganizationRepository from app.models.event_schema import EventSchema from app.services.broker.schemas.deleted_event_event import DeletedEventEventSchema from app.services.broker.schemas.updated_event_event import UpdatedEventEventSchema @@ -14,7 +15,7 @@ class EventService: def get_events_in_need_of_invitations(self): - days_in_advance_to_invite = current_app.config["DAYS_IN_ADVANCE_TO_INVITE"] + days_in_advance_to_invite = int(current_app.config["DAYS_IN_ADVANCE_TO_INVITE"]) return EventRepository.get_events_in_need_of_invitations(days_in_advance_to_invite=days_in_advance_to_invite) def finalize_event_if_complete(self, event_id): @@ -88,6 +89,11 @@ def delete(self, event_id, team_id): def add(self, data, team_id): data.slack_organization_id = team_id + slack_org = SlackOrganizationRepository.get_by_id(team_id) + # If channel_id is none, we cant update users about events + if slack_org is None or slack_org.channel_id is None: + return None + restaurant = RestaurantRepository.get_by_id(data.restaurant_id) if restaurant.slack_organization_id != team_id: @@ -133,3 +139,6 @@ def update(self, event_id, data, team_id): BrokerService.publish("updated_event", queue_event) return updated_event + + def get_scheduled_events_for_user(self, user_id, team_id): + return EventRepository.get_scheduled_events_for_user(user_id, team_id) diff --git a/application/backend/app/services/slack_organization_service.py b/application/backend/app/services/slack_organization_service.py index 4c61f0a7..46600df0 100644 --- a/application/backend/app/services/slack_organization_service.py +++ b/application/backend/app/services/slack_organization_service.py @@ -20,6 +20,9 @@ def _delete_cloudinary_images_for_slack_organization(self, team_id, next_cursor= self._delete_cloudinary_images_for_slack_organization(team_id=team_id, next_cursor=res['next_cursor']) def delete(self, team_id, enterprise_id=None): + if not cloudinary.config().cloud_name: + self.logger.warn("Cloudinary not configured. Skipping deletion of cloudinary images.") + return SlackOrganizationRepository.delete(id=team_id) try: self._delete_cloudinary_images_for_slack_organization(team_id=team_id) return SlackOrganizationRepository.delete(id=team_id) @@ -32,7 +35,15 @@ def upsert(self, schema): return SlackOrganizationRepository.upsert(schema=schema) def set_channel(self, team_id, channel_id): + + scheduled_events_count, scheduled_events = SlackOrganizationRepository.get_scheduled_events(team_id=team_id) + slack_organization = SlackOrganizationRepository.get_by_id(id=team_id) old_channel_id = slack_organization.channel_id + + # if scheduled events exist, dont update channel + if scheduled_events_count > 0: + return old_channel_id, scheduled_events_count, None + slack_organization.channel_id = channel_id - return old_channel_id, SlackOrganizationRepository.upsert(schema=slack_organization) + return old_channel_id, 0, SlackOrganizationRepository.upsert(schema=slack_organization) diff --git a/application/backend/tests/api/test_slack.py b/application/backend/tests/api/test_slack.py index f3b8aae5..5db63210 100644 --- a/application/backend/tests/api/test_slack.py +++ b/application/backend/tests/api/test_slack.py @@ -32,7 +32,10 @@ def test_callback(self, db, mock_broker, mocker): }, 'app_id': 'dontCareAppId', 'bot_user_id': 'dontCareUserId', - 'access_token': 'dontCareAccessToken' + 'access_token': 'dontCareAccessToken', + 'authed_user': { + 'id': 'dontCareAuthedUserId' + }, } response = self.client.post('/api/slack/callback', method='post', json={'code': 'dontCareCode'}) diff --git a/application/backend/tests/services/broker_handlers/test_get_handler.py b/application/backend/tests/services/broker_handlers/test_get_handler.py index 4ea587ca..97579efc 100644 --- a/application/backend/tests/services/broker_handlers/test_get_handler.py +++ b/application/backend/tests/services/broker_handlers/test_get_handler.py @@ -97,6 +97,7 @@ def test_get_slack_installation(self, mock_broker, slack_organizations, rpc_queu 'team_id': slack_organization.team_id, 'app_id': slack_organization.app_id, 'bot_user_id': slack_organization.bot_user_id, + 'channel_id': slack_organization.channel_id, 'access_token': slack_organization.access_token } diff --git a/application/backend/tests/services/test_event_service.py b/application/backend/tests/services/test_event_service.py index 2494ac0f..680423cc 100644 --- a/application/backend/tests/services/test_event_service.py +++ b/application/backend/tests/services/test_event_service.py @@ -121,7 +121,7 @@ def test_add(self, slack_organizations, restaurants, groups, event_service): test_events = Event.query.all() assert len(test_events) == 1 - def test_update(self, db, slack_organizations, events, groups, restaurants, mock_broker, event_service): + def test_update(self, db, slack_organizations, events, groups, restaurants, event_service, mock_broker): team_id = slack_organizations[0].team_id event = events.get(team_id)[0] group = groups.get(team_id)[0] @@ -148,3 +148,35 @@ def test_update(self, db, slack_organizations, events, groups, restaurants, mock mock_broker.send.assert_called() assert len(mock_broker.send.call_args_list) == 1 assert mock_broker.send.call_args_list[0].kwargs['body']['type'] == 'updated_event' + + def test_get_scheduled_events_for_user(self, slack_organizations, events, invitations, slack_users, event_service): + team_id = slack_organizations[0].team_id + slack_user = slack_users.get(team_id)[0].slack_id + team_events = events.get(team_id) + + user_invitations = list(filter( + lambda x: x.slack_id == slack_user, + invitations.get(team_id) + )) + + date_now = datetime.now(pytz.timezone('Europe/Oslo')) + scheduled_events = [] + + for user_invitation in user_invitations: + for event in team_events: + if user_invitation.event_id == event.id and date_now < event.time: + scheduled_events.append({**vars(event), **vars(user_invitation)}) + + + scheduled_event_from_service = event_service.get_scheduled_events_for_user(user_id=slack_user, team_id=team_id) + + assert len(user_invitations) == len(scheduled_event_from_service) + + for test_event in scheduled_events: + event_from_service = next(event_from_service for event_from_service in scheduled_event_from_service + if event_from_service.id == test_event['id']) + assert test_event['time'] == event_from_service.time + assert test_event['rsvp'] == event_from_service.rsvp + assert test_event['finalized'] == event_from_service.finalized + + diff --git a/application/bot/main.py b/application/bot/main.py index aedf9f97..e5d1587e 100644 --- a/application/bot/main.py +++ b/application/bot/main.py @@ -1,11 +1,8 @@ import locale import threading -import pytz import logging import sys -from src.api.bot_api import BotApiConfiguration - from src.scheduler import scheduler from src.slack import slack_handler @@ -14,6 +11,7 @@ from src.broker.amqp_connection import AmqpConnection from src.broker.amqp_connection_pool import AmqpConnectionPool from src.broker.handlers import on_message +from src.i18n import Translator def setup_logger(): @@ -45,13 +43,14 @@ def consume(): def main(): # Set up injector - api_config = BotApiConfiguration(pytz.timezone('Europe/Oslo')) - injector.binder.bind(BotApiConfiguration, to=api_config) - # Set up logging logger = setup_logger() injector.binder.bind(logging.Logger, to=logger, scope=singleton) + # set up translator + translator = Translator(language_folder="./src/lang") + injector.binder.bind(Translator, to=translator, scope=singleton) + # Try setting locale try: locale.setlocale(locale.LC_ALL, "nb_NO.utf8") @@ -61,7 +60,7 @@ def main(): # Set up rabbitmq setup_connection_pool() setup_consumption_queue_listener() - + # start scheduler scheduler.start() diff --git a/application/bot/src/api/bot_api.py b/application/bot/src/api/bot_api.py index a93c231d..d9701564 100644 --- a/application/bot/src/api/bot_api.py +++ b/application/bot/src/api/bot_api.py @@ -10,18 +10,17 @@ from src.broker.broker_client import BrokerClient from src.injector import injector import logging +from src.i18n import Translator -class BotApiConfiguration: - def __init__(self, timezone): - self.timezone = timezone +frontend_uri = os.environ["FRONTEND_URI"] if "FRONTEND_URI" in os.environ else None class BotApi: @inject - def __init__(self, config: BotApiConfiguration, logger: logging.Logger): + def __init__(self, logger: logging.Logger): self.REPLY_DEADLINE_IN_HOURS = int(os.environ["REPLY_DEADLINE_IN_HOURS"]) self.HOURS_BETWEEN_REMINDERS = int(os.environ["HOURS_BETWEEN_REMINDERS"]) - self.timezone = config.timezone - self.logger = logger + self.logger: logging.Logger = logger + self.translator: Translator = injector.get(Translator) def __enter__(self): self.client = injector.get(BrokerClient) @@ -30,48 +29,127 @@ def __enter__(self): def __exit__(self, type, value, traceback): self.client.disconnect() - def welcome(self, slack_client, team_id): - channel_id = self.join_channel(slack_client=slack_client, team_id=team_id) + def welcome(self, slack_client, user_who_installed): self.send_slack_message( - channel_id=channel_id, - text="Hei! Jeg er pizzabot. Hvis dere vil endre hvilke kanal jeg bruker så kan dere gå inn i riktig kanal og bruke kommandoen '/set-pizza-channel'. Hvis kanalen er privat må dere legge meg til først.", + channel_id=user_who_installed, + text=self.translator.translate("botWelcome"), + slack_client=slack_client + ) + self.send_slack_message( + channel_id=user_who_installed, + text=self.translator.translate("adminPanelURLCommand", adminPanelURL=f"{frontend_uri}/admin"), slack_client=slack_client ) + - def join_channel(self, slack_client, team_id, channel_id=None): - # Get default channel if non is given - if channel_id is None: - default_channel = slack_client.get_default_channel() - channel_id = default_channel["id"] + def join_channel(self, slack_client, team_id, channel_id): # Log and exit if we were unable to find a channel if channel_id is None: - self.logger.error("Was unable to find channel") + self.logger.error("Bot cannot join None channel") return None # Join channel join_success = slack_client.join_channel(channel_id) # If we were unable to join the channel if not join_success: - self.logger.error("Was unable to join channel %s", channel_id) - return - - # Send a rpc message to set the channel + self.logger.error("Was unable to join channel %s in team: %s", channel_id, team_id) + return None + + # Send a rpc message to set the channel in db set_channel_response = self.client.set_slack_channel(channel_id=channel_id, team_id=team_id) + if not set_channel_response['success']: + self.send_slack_message( + channel_id=channel_id, + text=self.translator.translate("pizzaChannelError"), + slack_client=slack_client + ) + self.logger.error("Was unable to join channel %s", channel_id) + # Leave the channel we joined after sending the error message to slack + leave_success = slack_client.leave_channel(channel_id) + return None + + # If there are scheduled events + elif 'scheduled_events_count' in set_channel_response and set_channel_response['scheduled_events_count'] > 0: + self.send_slack_message( + channel_id=channel_id, + text=self.translator.translate( + "pizzaChannelErrorScheduledEvents", + count=set_channel_response['scheduled_events_count'] + ), + slack_client=slack_client + ) + leave_success = slack_client.leave_channel(channel_id) return None + + old_channel = set_channel_response['old_channel_id'] if 'old_channel_id' in set_channel_response else None + # Leave channel if old and new channel isnt the same # they can be the same if someone reinstall the app - if 'old_channel_id' in set_channel_response and channel_id != set_channel_response['old_channel_id']: + if old_channel is not None and channel_id != old_channel: leave_success = slack_client.leave_channel(set_channel_response['old_channel_id']) # If we were unable to leave the channel, we dont exit function as it isnt critical to leave if not leave_success: self.logger.error("Was unable to leave channel %s", channel_id) - # return channel return channel_id + + def handle_user_left_channel(self, team_id, user_id, channel_id, slack_client): + slack_installation = self.client.get_slack_installation(team_id=team_id) + if slack_installation is None or 'channel_id' not in slack_installation: + self.logger.error("Failed to get slack installation for team %s", team_id) + return + if slack_installation['channel_id'] != channel_id: + return + + scheduled_events = self.client.get_scheduled_events_for_user(team_id=team_id, user_id=user_id) + if scheduled_events == False: + self.logger.error("Failed to get scheduled events for user %s", user_id) + return + + self.logger.info(f"User {user_id} left channel in organization {team_id}") + + # set non active + user_to_update = { + 'id': user_id, + 'team_id': team_id, + 'active': False + } + + self.client.update_slack_users(slack_users=[user_to_update]) + + # Respond to invited events + for scheduled_event in scheduled_events: + rsvp = scheduled_event['responded'] + success = False + if rsvp == RSVP.not_attending: + continue + elif rsvp == RSVP.attending: + success = self.withdraw_invitation(event_id=scheduled_event['event_id'], slack_id=user_id) + elif rsvp == RSVP.unanswered: + success = self.decline_invitation(event_id=scheduled_event['event_id'], slack_id=user_id) + + if success: + # TODO: fix that ts is overwritten by event handler from bakend on RSVP updated. + self.send_pizza_invited_but_left_channel( + channel_id=scheduled_event['slack_message_channel'], + event_id=scheduled_event['event_id'], + ts=scheduled_event['slack_message_ts'], + slack_client=slack_client, + prev_answer=rsvp + ) + else: + self.logger.error("Failed to decline invitation after leaving channel for user %s", user_id) + + # Send message to user that they no longer will be invited to events + self.send_slack_message( + channel_id=scheduled_event['slack_message_channel'], + text=self.translator.translate("userLeftPizzaChannel"), + slack_client=slack_client + ) def invite_multiple_if_needed(self): events = self.client.invite_multiple_if_needed() @@ -87,15 +165,14 @@ def invite_if_needed(self, event, slack_client): restaurant_name = event['restaurant_name'] # timestamp (timestamp) is converted to UTC timestamp by psycopg2 - # Convert timestamp to Norwegian timestamp - timestamp = pytz.utc.localize(event_time.replace(tzinfo=None), is_dst=None).astimezone(self.timezone) - + # Convert timestamp to appropriate timestamp + timestamp = self.translator.format_timestamp(timestamp=event_time) for user_id in invited_users: slack_message = self.send_pizza_invite( channel_id=user_id, event_id=str(event_id), place=restaurant_name, - datetime=timestamp.strftime("%A %d. %B kl %H:%M"), + datetime=timestamp.strftime("%A %d. %B %H:%M"), deadline=self.REPLY_DEADLINE_IN_HOURS, slack_client = slack_client ) @@ -130,7 +207,7 @@ def send_reminders(self): if invitation['reminded_at'] < remind_timestamp: slack_client.send_slack_message( channel_id=invitation['slack_id'], - text="Hei du! Jeg hørte ikke noe mer? Er du gira?" + text=self.translator.translate("eventReminder") ) was_updated = self.client.update_invitation( slack_id=invitation['slack_id'], @@ -146,8 +223,8 @@ def send_reminders(self): def send_event_finalized(self, timestamp, restaurant_name, slack_ids, channel_id, slack_client): self.logger.info("Finalizing event %s %s", timestamp, restaurant_name) - # Convert timestamp to Norwegian timestamp - timestamp = pytz.utc.localize(timestamp.replace(tzinfo=None), is_dst=None).astimezone(self.timezone) + # Convert timestamp to appropriate timestamp + timestamp = self.translator.format_timestamp(timestamp=timestamp) # Create slack @-id-strings users = ['<@%s>' % user for user in slack_ids] ids_string = ", ".join(users) @@ -158,30 +235,33 @@ def send_event_finalized(self, timestamp, restaurant_name, slack_ids, channel_id # Send the finalization Slack message slack_client.send_slack_message( channel_id=channel_id, - text="Halloi! %s! Dere skal spise 🍕 på %s, %s. %s booker bord, og %s legger ut for maten. Blank betaler!" % (ids_string, restaurant_name, timestamp.strftime("%A %d. %B kl %H:%M"), booker, payer) + text=self.translator.translate("eventFinalized", user_ids=ids_string, restaurant_name=restaurant_name, time_stamp=timestamp.strftime("%A %d. %B %H:%M"), booker=booker, payer=payer) ) def send_event_unfinalized(self, timestamp, restaurant_name, slack_ids, channel_id, slack_client): self.logger.info("Unfinalizing event %s %s", timestamp, restaurant_name) - # Convert timestamp to Norwegian timestamp - timestamp = pytz.utc.localize(timestamp.replace(tzinfo=None), is_dst=None).astimezone(self.timezone) + # Convert timestamp to appropriate timestamp + timestamp = self.translator.format_timestamp(timestamp=timestamp) # Create slack @-id-strings users = ['<@%s>' % user for user in slack_ids] ids_string = ", ".join(users) # Send message that the event unfinalized slack_client.send_slack_message( channel_id=channel_id, - text="Halloi! %s! Hvis den som meldte seg av besøket til %s %s skulle betale eller booke så må nesten en av dere andre sørge for det. I mellomtiden letes det etter en erstatter." % (ids_string, restaurant_name, timestamp.strftime("%A %d. %B kl %H:%M")) + text=self.translator.translate("eventUnfinalized", user_ids=ids_string, restaurant_name=restaurant_name, time_stamp=timestamp.strftime("%A %d. %B %H:%M")) ) # Invite more users for the event self.invite_multiple_if_needed() def send_user_withdrew_after_finalization(self, user_id, timestamp, restaurant_name, channel_id, slack_client): self.logger.info("User %s withdrew from event %s %s", user_id, timestamp, restaurant_name) + + timestamp=self.translator.format_timestamp(timestamp=timestamp) + # Send message that the user withdrew slack_client.send_slack_message( channel_id=channel_id, - text="Halloi! <@%s> meldte seg nettopp av besøket til %s %s." % (user_id, restaurant_name, timestamp.strftime("%A %d. %B kl %H:%M")) + text=self.translator.translate("userWithdrawAfterFinalization", user_id=user_id, restaurant_name=restaurant_name, time_stamp=timestamp.strftime("%A %d. %B %H:%M")) ) # Invite more users for the event self.invite_multiple_if_needed() @@ -219,7 +299,7 @@ def auto_reply(self): # Send the user a message that the invite expired slack_client.send_slack_message( channel_id=invitation['slack_id'], - text="Neivel, da antar jeg du ikke kan/gidder. Håper du blir med neste gang! 🤞" + text=self.translator.translate("autoReplyNoAttending") ) self.logger.info("%s didn't answer. Setting RSVP to not attending." % invitation['slack_id']) else: @@ -260,10 +340,10 @@ def update_invitation_answer(self, slack_id, event_id, answer: RSVP): ) def accept_invitation(self, event_id, slack_id): - self.update_invitation_answer(slack_id=slack_id, event_id=event_id, answer=RSVP.attending) + return self.update_invitation_answer(slack_id=slack_id, event_id=event_id, answer=RSVP.attending) def decline_invitation(self, event_id, slack_id): - self.update_invitation_answer(slack_id=slack_id, event_id=event_id, answer=RSVP.not_attending) + return self.update_invitation_answer(slack_id=slack_id, event_id=event_id, answer=RSVP.not_attending) def withdraw_invitation(self, event_id, slack_id): return self.client.withdraw_invitation(event_id=event_id, slack_id=slack_id) @@ -279,11 +359,23 @@ def sync_users_from_organizations(self): for slack_organization in slack_organizations: self.sync_users_from_organization(team_id=slack_organization['team_id'], bot_token=slack_organization['bot_token']) + # This updates all users on all user for all organizations, regardless if they are in the channel or not. This wont scale + # TODO: optimize this. Might need another message to the backend to get active users and see if the list has changed def sync_users_from_organization(self, team_id, bot_token): + installation_info = self.client.get_slack_installation(team_id=team_id) + if installation_info is None: + self.logger.error("Failed to sync users in workspace %s" % team_id) + return + + channel_id = installation_info['channel_id'] if 'channel_id' in installation_info else None + if channel_id is None: + self.logger.info("Cannot sync team %s, channel id not set" % team_id) + return + slack_client = SlackApi(token=bot_token) - all_slack_users = slack_client.get_slack_users() - slack_users = slack_client.get_real_users(all_slack_users) - response = self.client.update_slack_user(slack_users) + + users_to_update = slack_client.get_users_to_update_by_channel(channel_id=channel_id) + response = self.client.update_slack_users(users_to_update) updated_users = response['updated_users'] for user in updated_users: @@ -310,7 +402,7 @@ def inform_users_unfinalized_event_got_cancelled(self, time, restaurant_name, sl # Send the user a message that the event has been cancelled slack_client.send_slack_message( channel_id=slack_id, - text="Halloi! Besøket til %s, %s har blitt kansellert. Sorry!" % (restaurant_name, time.strftime("%A %d. %B kl %H:%M")) + text=self.translator.translate("unfinalizedEventCancelled", restaurant_name=restaurant_name, time_stamp=time.strftime("%A %d. %B %H:%M")) ) self.logger.info("Informed user: %s" % slack_id) @@ -322,7 +414,7 @@ def inform_users_finalized_event_got_cancelled(self, time, restaurant_name, slac self.logger.info("finalized event got cancelled for users %s" % ", ".join(slack_user_ids)) slack_client.send_slack_message( channel_id=channel_id, - text="Halloi! %s! Besøket til %s, %s har blitt kansellert. Sorry!" % (ids_string, restaurant_name, time.strftime("%A %d. %B kl %H:%M"),) + text=self.translator.translate("finalizedEventCancelled", user_ids=ids_string, restaurant_name=restaurant_name, time_stamp=time.strftime("%A %d. %B %H:%M")) ) # Update invitation message - remove buttons and tell user it has been cancelled for slack_user_data in slack_data: @@ -342,7 +434,7 @@ def inform_users_unfinalized_event_got_updated(self, old_time, time, old_restaur for slack_id in slack_ids: slack_client.send_slack_message( channel_id=slack_id, - text="Halloi! Besøket til %s, %s har blit endret til %s, %s." % (old_restaurant_name, old_time.strftime("%A %d. %B kl %H:%M"), restaurant_name, time.strftime("%A %d. %B kl %H:%M")) + text=self.translator.translate("unfinalizedEventUpdate", old_restaurant_name=old_restaurant_name, old_time_stamp=old_time.strftime("%A %d. %B %H:%M"),restaurant_name=restaurant_name, time_stamp=time.strftime("%A %d. %B %H:%M")) ) self.logger.info("Informed user: %s" % slack_id) @@ -352,7 +444,7 @@ def inform_users_finalized_event_got_updated(self, old_time, time, old_restauran self.logger.info("finalized event got updated for users %s" % ", ".join(slack_ids)) slack_client.send_slack_message( channel_id=channel_id, - text="Halloi! %s! Besøket til %s, %s har blit endret til %s, %s." % (ids_string, old_restaurant_name, old_time.strftime("%A %d. %B kl %H:%M"), restaurant_name, time.strftime("%A %d. %B kl %H:%M")) + text=self.translator.translate("finalizedEventUpdate", user_ids=ids_string, old_restaurant_name=old_restaurant_name, old_time_stamp=old_time.strftime("%A %d. %B %H:%M"),restaurant_name=restaurant_name, time_stamp=time.strftime("%A %d. %B %H:%M")) ) def send_slack_message(self, channel_id, text, slack_client, blocks=None, thread_ts=None): @@ -362,20 +454,20 @@ def update_slack_message(self, channel_id, ts, slack_client, text=None, blocks=N return slack_client.update_slack_message(channel_id, ts, text, blocks) def send_pizza_invite(self, channel_id, event_id, place, datetime, deadline, slack_client): - top_level_title_text = f"Pizzainvitasjon: {place}, {datetime}" + top_level_title_text = self.translator.translate("topLevelPizzaInvitation", restaurant_name=place, time_stamp=datetime) blocks = [ { "type": "header", "text": { "type": "plain_text", - "text": "Pizzainvitasjon" + "text": self.translator.translate("pizzaInvitationHeader") } }, { "type": "section", "text": { "type": "plain_text", - "text": f"Du er invitert til :pizza: på {place}, {datetime}. Pls svar innen {deadline} timer :pray:. Kan du?" + "text": self.translator.translate("pizzaInvitationBody", restaurant_name=place, time_stamp=datetime, deadline=deadline) } }, { @@ -388,7 +480,7 @@ def send_pizza_invite(self, channel_id, event_id, place, datetime, deadline, sla "type": "button", "text": { "type": "plain_text", - "text": "Hells yesss!!! 🍕🍕🍕" + "text": self.translator.translate("pizzaInvitationAttendButton") }, "value": event_id, "action_id": "rsvp_yes", @@ -397,7 +489,7 @@ def send_pizza_invite(self, channel_id, event_id, place, datetime, deadline, sla "type": "button", "text": { "type": "plain_text", - "text": "Nah ☹️" + "text": self.translator.translate("pizzaInvitationNoAttendButton") }, "value": event_id, "action_id": "rsvp_no", @@ -421,7 +513,7 @@ def send_pizza_invite_loading(self, channel_id, ts, old_blocks, event_id, slack_ "type": "section", "text": { "type": "mrkdwn", - "text": ":hourglass_flowing_sand: Behandler forespørselen din...", + "text": self.translator.translate("inviteLoading") } } ] @@ -435,7 +527,7 @@ def send_pizza_invite_not_among_invited_users(self, channel_id, ts, old_blocks, "type": "section", "text": { "type": "plain_text", - "text": "Kunne ikke oppdatere invitasjonen. Du var ikke blant de inviterte.", + "text": self.translator.translate("inviteNotAmongUsers") } } ] @@ -480,7 +572,7 @@ def send_update_pizza_invite_unanswered(self, channel_id, ts, event_id, slack_cl "type": "button", "text": { "type": "plain_text", - "text": "Hells yesss!!! 🍕🍕🍕" + "text": self.translator.translate("pizzaInvitationAttendButton") }, "value": str(event_id), "action_id": "rsvp_yes", @@ -489,7 +581,7 @@ def send_update_pizza_invite_unanswered(self, channel_id, ts, event_id, slack_cl "type": "button", "text": { "type": "plain_text", - "text": "Nah ☹️" + "text": self.translator.translate("pizzaInvitationNoAttendButton") }, "value": str(event_id), "action_id": "rsvp_no", @@ -506,7 +598,7 @@ def send_pizza_invite_answered(self, channel_id, ts, event_id, old_blocks, atten "type": "section", "text": { "type": "plain_text", - "text": f"Du har takket {'ja. Sweet! 🤙' if attending else 'nei. Ok 😕'}", + "text": self.translator.translate("pizzaInviteAnswerAttend") if attending else self.translator.translate("pizzaInviteAnswerNoAttend") , } } ] @@ -518,13 +610,13 @@ def send_pizza_invite_answered(self, channel_id, ts, event_id, old_blocks, atten "type": "section", "text": { "type": "mrkdwn", - "text": "Hvis noe skulle skje så kan du melde deg av ved å klikke på knappen!" + "text": self.translator.translate("unsubscribeBody") }, "accessory": { "type": "button", "text": { "type": "plain_text", - "text": "Meld meg av" + "text": self.translator.translate("unsubscribeButton") }, "value": str(event_id), "action_id": "rsvp_withdraw" @@ -543,7 +635,7 @@ def send_invitation_invalidated_event_cancelled(self, channel_id, ts, old_blocks "type": "section", "text": { "type": "plain_text", - "text": "Arrangementet har blitt avlyst.", + "text": self.translator.translate("invalidatedEventCancelled") } } ] @@ -557,7 +649,7 @@ def send_invitation_expired(self, channel_id, ts, old_blocks, slack_client): "type": "section", "text": { "type": "plain_text", - "text": "Invitasjonen er utløpt.", + "text": self.translator.translate("invitationExpired") } } ] @@ -571,7 +663,7 @@ def send_pizza_invite_withdraw(self, channel_id, ts, old_blocks, slack_client): "type": "section", "text": { "type": "plain_text", - "text": "Du har meldt deg av. Ok 😕", + "text": self.translator.translate("inviteWithdrawn") } } ] @@ -585,9 +677,26 @@ def send_pizza_invite_withdraw_failure(self, channel_id, ts, old_blocks, slack_c "type": "section", "text": { "type": "plain_text", - "text": "Pizza arrangementet er over. Avmelding er ikke mulig.", + "text": self.translator.translate("inviteWithdrawnFailure") } } ] blocks = old_blocks + new_blocks return slack_client.update_slack_message(channel_id=channel_id, ts=ts, blocks=blocks) + + + def send_pizza_invited_but_left_channel(self, channel_id, ts, slack_client, event_id, prev_answer: RSVP): + self.logger.info('user left and sent message of widthdrawal for %s, %s' % (channel_id, event_id)) + invitation_message = slack_client.get_slack_message(channel_id, ts) + blocks = invitation_message["blocks"][0:3] + new_blocks = [ + { + "type": "section", + "text": { + "type": "plain_text", + "text": self.translator.translate("invitedButLeftChannelWithdrawn") if prev_answer == RSVP.attending else self.translator.translate("invitedButLeftChannelUnanswered") + } + } + ] + blocks = blocks + new_blocks + return slack_client.update_slack_message(channel_id=channel_id, ts=ts, blocks=blocks) diff --git a/application/bot/src/api/slack_api.py b/application/bot/src/api/slack_api.py index 0685fd94..45c524a6 100644 --- a/application/bot/src/api/slack_api.py +++ b/application/bot/src/api/slack_api.py @@ -14,6 +14,42 @@ def __init__(self, client=None, token=None): raise ValueError("Either 'client' or 'token' must be provided.") self.logger = injector.get(logging.Logger) + # Returns a list of users in the workspace, + # with their active status updated. + def get_users_to_update_by_channel(self, channel_id): + # list of user ids in channel + members = self.get_channel_users(channel_id=channel_id) + + # list of full user info in workspace + full_users = self.get_slack_users() + users_to_update = self.get_real_users(full_users) + + for user in users_to_update: + user["active"] = user["id"] in members + user["email"] = user["profile"]["email"] + + return users_to_update + + + def get_channel_users(self, channel_id: str) -> list[str]: + first_page = self.client.conversations_members(channel=channel_id) + + if not first_page["ok"]: + self.logger(first_page["error"]) + return [] + + members = first_page["members"] + + # Continue to loop over pages to find the default channel + next_cursor = first_page["response_metadata"]["next_cursor"] + while next_cursor != "": + page = self.client.conversations_list(cursor=next_cursor) + next_cursor = page["response_metadata"]["next_cursor"] + + members = members.extend(page["members"]) + + return members + def get_slack_users(self): first_page = self.client.users_list() @@ -34,7 +70,9 @@ def get_slack_users(self): return members def get_real_users(self, all_users): - return [u for u in all_users if not u['deleted'] and not u['is_bot'] and not u['is_restricted'] and not u['name'] == "slackbot"] # type : list + # 'is_restricted': is multichannel guest. + # 'is_ultra_restricted': is singlechannel guest. + return [u for u in all_users if not u['deleted'] and not u['is_bot'] and not u['name'] == "slackbot"] # type : list def send_slack_message(self, channel_id, text=None, blocks=None, thread_ts=None): return self.client.chat_postMessage( diff --git a/application/bot/src/broker/broker_client.py b/application/bot/src/broker/broker_client.py index dc4256af..e5ebedf7 100644 --- a/application/bot/src/broker/broker_client.py +++ b/application/bot/src/broker/broker_client.py @@ -20,6 +20,7 @@ from src.broker.schemas.get_slack_organizations import GetSlackOrganizationsResponseSchema from src.broker.schemas.deleted_slack_organization_event import DeletedSlackOrganizationEventSchema from src.broker.schemas.set_slack_channel import SetSlackChannelRequestSchema, SetSlackChannelResponseSchema +from src.broker.schemas.get_schedule_events_for_user import GetScheduledEventsForUserRequestSchema, GetScheduledEventsForUserResponseSchema class BrokerClient: messages = {} @@ -165,23 +166,27 @@ def get_invited_unanswered_user_ids(self): response = response_schema.load(response_payload) return response['user_ids'] - def update_slack_user(self, slack_users): + def update_slack_users(self, slack_users): request_payload = { 'users_to_update': [] } - for slack_user in slack_users: - slack_id = slack_user['id'] - current_username = slack_user['name'] - email = slack_user['profile']['email'] - team_id = slack_user['team_id'] - - request_payload['users_to_update'].append({ - 'slack_id': slack_id, - 'current_username': current_username, - 'email': email, - 'team_id': team_id - }) + for slack_user in slack_users: + fields_to_update = { + 'slack_id': slack_user['id'], + 'team_id': slack_user['team_id'] + } + if 'name' in slack_user: + fields_to_update['current_username'] = slack_user['name'] + if 'current_username' in slack_user: + fields_to_update['current_username'] = slack_user['current_username'] + if 'email' in slack_user: + fields_to_update['email'] = slack_user['email'] + if 'active' in slack_user: + fields_to_update['active'] = slack_user['active'] + + request_payload['users_to_update'].append(fields_to_update) + request_payload_schema = UpdateSlackUserRequestSchema() response_payload = self._call(self._create_request("update_slack_user", request_payload_schema.load(request_payload))) response_schema = UpdateSlackUserResponseSchema() @@ -221,3 +226,16 @@ def withdraw_invitation(self, event_id, slack_id): response_schema = WithdrawInvitationResponseSchema() response = response_schema.load(response_payload) return response['success'] + + def get_scheduled_events_for_user(self, user_id, team_id): + request_payload = { + "user_id": user_id, + 'team_id': team_id + } + request_payload_schema = GetScheduledEventsForUserRequestSchema() + response_payload = self._call(self._create_request("get_scheduled_events_for_user", request_payload_schema.load(request_payload))) + if response_payload is None: + return False + response_schema = GetScheduledEventsForUserResponseSchema() + response = response_schema.load(response_payload) + return response['events'] diff --git a/application/bot/src/broker/handlers/new_slack_organization.py b/application/bot/src/broker/handlers/new_slack_organization.py index add0477d..5a576186 100644 --- a/application/bot/src/broker/handlers/new_slack_organization.py +++ b/application/bot/src/broker/handlers/new_slack_organization.py @@ -9,7 +9,6 @@ def new_slack_organization_event(event: dict): with injector.get(BotApi) as ba: slack_client = SlackApi(token=event['bot_token']) - ba.welcome(slack_client=slack_client, team_id=event['team_id']) - ba.sync_users_from_organization(team_id=event['team_id'], bot_token=event['bot_token']) + ba.welcome(slack_client=slack_client, user_who_installed=event['user_who_installed']) diff --git a/application/bot/src/broker/schemas/get_schedule_events_for_user.py b/application/bot/src/broker/schemas/get_schedule_events_for_user.py new file mode 100644 index 00000000..edac24e8 --- /dev/null +++ b/application/bot/src/broker/schemas/get_schedule_events_for_user.py @@ -0,0 +1,21 @@ +from marshmallow import fields, Schema +from src.rsvp import RSVP +from marshmallow_enum import EnumField + +class GetScheduledEventsForUserRequestSchema(Schema): + user_id = fields.Str(required=True) + team_id = fields.Str(required=True) + + +class ScheduledEventsForUserDataSchema(Schema): + event_id = fields.UUID(required=True) + restaurant_id = fields.UUID(required=True) + time = fields.Str(required=True) + responded = EnumField(RSVP, by_value=True, required=True) + event_finalized = fields.Bool(required=True) + slack_message_channel = fields.Str() + slack_message_ts = fields.Str() + +class GetScheduledEventsForUserResponseSchema(Schema): + events = fields.List(fields.Nested(ScheduledEventsForUserDataSchema), required=True) + diff --git a/application/bot/src/broker/schemas/get_slack_installation.py b/application/bot/src/broker/schemas/get_slack_installation.py index 9e65a90b..d286591d 100644 --- a/application/bot/src/broker/schemas/get_slack_installation.py +++ b/application/bot/src/broker/schemas/get_slack_installation.py @@ -13,3 +13,4 @@ class GetSlackInstallationResponseSchema(Schema): app_id = fields.Str(required=True) bot_user_id = fields.Str(required=True) access_token = fields.Str(required=True) + channel_id = fields.Str(required=True, allow_none=True) diff --git a/application/bot/src/broker/schemas/new_slack_organization_event.py b/application/bot/src/broker/schemas/new_slack_organization_event.py index cc9fcd04..22371281 100644 --- a/application/bot/src/broker/schemas/new_slack_organization_event.py +++ b/application/bot/src/broker/schemas/new_slack_organization_event.py @@ -3,3 +3,4 @@ class NewSlackOrganizationEventSchema(Schema): team_id = fields.Str(required=True) bot_token = fields.Str(required=True) + user_who_installed = fields.Str(required=True) diff --git a/application/bot/src/broker/schemas/set_slack_channel.py b/application/bot/src/broker/schemas/set_slack_channel.py index 1357eb9a..b21f7563 100644 --- a/application/bot/src/broker/schemas/set_slack_channel.py +++ b/application/bot/src/broker/schemas/set_slack_channel.py @@ -9,3 +9,4 @@ class SetSlackChannelRequestSchema(Schema): class SetSlackChannelResponseSchema(Schema): success = fields.Boolean(required=True) old_channel_id = fields.Str() + scheduled_events_count = fields.Integer() diff --git a/application/bot/src/broker/schemas/update_slack_user.py b/application/bot/src/broker/schemas/update_slack_user.py index 42b01c99..a30d792f 100644 --- a/application/bot/src/broker/schemas/update_slack_user.py +++ b/application/bot/src/broker/schemas/update_slack_user.py @@ -5,6 +5,7 @@ class SlackUserUpdate(Schema): current_username = fields.Str() email = fields.Str() team_id = fields.Str(required=True) + active = fields.Boolean() class UpdateSlackUserRequestSchema(Schema): users_to_update = fields.Nested(SlackUserUpdate, required=True, many=True) diff --git a/application/bot/src/i18n.py b/application/bot/src/i18n.py new file mode 100644 index 00000000..d02bc189 --- /dev/null +++ b/application/bot/src/i18n.py @@ -0,0 +1,53 @@ +import json +import os +import glob +from src.injector import injector +import logging +from string import Template +import pytz +import datetime + +#valid timezones can be found here +#https://gist.github.com/heyalexej/8bf688fd67d7199be4a1682b3eec7568 +lang_timezone_map = { + "no": "Europe/Oslo", + "en": "Europe/London" +} + +supported_format = ["json"] + +class Translator: + def __init__(self, language_folder="./lang", default_locale="en") -> None: + self.data = {} + self.locale = default_locale + self.logger: logging.Logger = injector.get(logging.Logger) + self.timezone = pytz.timezone(lang_timezone_map[self.locale]) + + for filename in glob.glob(os.path.join(language_folder, '*.json')): + loc = os.path.splitext(os.path.basename(filename))[0] + with open(filename, encoding="utf-8", mode="r") as f: + self.data[loc] = json.load(f) + + def set_locale(self, locale): + if locale in self.data and locale in lang_timezone_map: + self.locale = locale + self.timezone = pytz.timezone(lang_timezone_map[locale]) + else: + self.logger.warn(f"Unvalid locale: {locale}, fallback to default locale: {self.locale}") + + def translate(self, key, **kwargs): + if key in self.data[self.locale]: + text = self.data[self.locale][key] + + try: + return Template(text).substitute(**kwargs) + except (KeyError, ValueError) as e: + self.logger.warn(e) + return Template(text).safe_substitute(**kwargs) + + else: + self.logger.warn(f"The key '{key}' does not match any text. Defaults text to key") + return key + + def format_timestamp(self, timestamp: datetime.datetime): + return pytz.utc.localize(timestamp.replace(tzinfo=None), is_dst=None).astimezone(self.timezone) diff --git a/application/bot/src/lang/en.json b/application/bot/src/lang/en.json new file mode 100644 index 00000000..32fe187b --- /dev/null +++ b/application/bot/src/lang/en.json @@ -0,0 +1,35 @@ +{ + "thanksForFile": "Thanks for the file! 🤙", + "pizzaChannelError": "Something went wrong. Couldn't set Pizza channel.", + "pizzaChannelErrorScheduledEvents": "There are $count scheduled events in the previous pizza channel. Please cancel them in the admin panel before switching channel.", + "pizzaChannelConfirm": "Pizza channel has now been set to <#$channel_id>", + "botWelcome": "Hello! I'm the pizza bot. If you want to change the channel I use, you can go to the appropriate channel and use the command '/set-pizza-channel'. If the channel is private, you need to add me first.", + "eventReminder": "Hey there! I didn't hear any more from you. Are you coming?", + "eventFinalized": "Hello there! $user_ids! You're going to have 🍕 at $restaurant_name, $time_stamp. $booker is booking the table!", + "eventUnfinalized": "Hello there! $user_ids! If the one who withdrew from the visit to $restaurant_name $time_stamp was supposed to book, then one of you others will have to take care of that. Meanwhile, a replacement is being sought.", + "userWithdrawAfterFinalization": "Hello there! <@$user_id> just withdrew from the visit to $restaurant_name $time_stamp.", + "autoReplyNoAttending": "Alright, I guess you can't or don't want to. Hope you'll join next time! 🤞", + "unfinalizedEventCancelled": "Hello there! The visit to $restaurant_name, $time_stamp has been canceled. Sorry!", + "finalizedEventCancelled": "Hello there! $user_ids! The visit to $restaurant_name, $time_stamp has been canceled. Sorry!", + "unfinalizedEventUpdate": "Hello there! The visit to $old_restaurant_name, $old_time_stamp has been updated to $restaurant_name, $time_stamp.", + "finalizedEventUpdate": "Hello there! $user_ids! The visit to $old_restaurant_name, $old_time_stamp has been updated to $restaurant_name, $time_stamp.", + "topLevelPizzaInvitation": "Pizza Invitation: $restaurant_name, $time_stamp", + "pizzaInvitationHeader": "Pizza Invitation", + "pizzaInvitationBody": "You're invited to have :pizza: at $restaurant_name, $time_stamp. Please respond within $deadline hours :pray:. Will you join in?", + "inviteLoading": ":hourglass_flowing_sand: Processing your request...", + "inviteNotAmongUsers": "Couldn't update the invitation. You were not among the invited.", + "pizzaInvitationAttendButton": "Hells yesss!!! 🍕🍕🍕", + "pizzaInvitationNoAttendButton": "Nah ☹️", + "pizzaInviteAnswerAttend": "You've accepted. Sweet! 🤙", + "pizzaInviteAnswerNoAttend": "You've declined. Ok 😕", + "unsubscribeBody": "If something comes up, you can unsubscribe by clicking the button!", + "unsubscribeButton": "Unsubscribe me", + "invalidatedEventCancelled": "The event has been canceled.", + "invitationExpired": "The invitation has expired.", + "inviteWithdrawn": "You've withdrawn. Ok 😕", + "inviteWithdrawnFailure": "The pizza event is over. Withdrawal is not possible.", + "invitedButLeftChannelUnanswered": "Since you left the PizzaBot channel, I assume you do not want to attend 😕", + "invitedButLeftChannelWithdrawn": "Since you left the PizzaBot channel, I assume you do not want to attend 😕 You have been withdrawn from the event", + "userLeftPizzaChannel": "You left the PizzaBot channel 😕 You will no longer receive invitations to future events.", + "adminPanelURLCommand": "The admin panel can be found <$adminPanelURL | here>" +} diff --git a/application/bot/src/lang/no.json b/application/bot/src/lang/no.json new file mode 100644 index 00000000..9a8bc8f6 --- /dev/null +++ b/application/bot/src/lang/no.json @@ -0,0 +1,35 @@ +{ + "thanksForFile": "Takk for fil! 🤙", + "pizzaChannelError": "Noe gikk galt. Klarte ikke å sette Pizzakanal", + "pizzaChannelErrorScheduledEvents": "Det er $count planlagte arrangementer i den forrige pizza kanalen. Vennligst slett i adminpanelet dem før du endrer kanal.", + "pizzaChannelConfirm": "Pizzakanal er nå satt til <#$channel_id>", + "botWelcome": "Hei! Jeg er pizzabot. Hvis dere vil endre hvilke kanal jeg bruker så kan dere gå inn i riktig kanal og bruke kommandoen '/set-pizza-channel'. Hvis kanalen er privat må dere legge meg til først.", + "eventReminder": "Hei du! Jeg hørte ikke noe mer? Er du gira?", + "eventFinalized": "Halloi! $user_ids! Dere skal spise 🍕 på $restaurant_name, $time_stamp. $booker booker bord!", + "eventUnfinalized": "Halloi! $user_ids! Hvis den som meldte seg av besøket til $restaurant_name $time_stamp skulle betale eller booke så må nesten en av dere andre sørge for det. I mellomtiden letes det etter en erstatter.", + "userWithdrawAfterFinalization": "Halloi! <@$user_id> meldte seg nettopp av besøket til $restaurant_name $time_stamp.", + "autoReplyNoAttending": "Neivel, da antar jeg du ikke kan/gidder. Håper du blir med neste gang! 🤞", + "unfinalizedEventCancelled": "Halloi! Besøket til $restaurant_name, $time_stamp har blitt kansellert. Sorry!", + "finalizedEventCnacelled": "Halloi! $user_ids! Besøket til $restaurant_name, $time_stamp har blitt kansellert. Sorry!", + "unfinalizedEventUpdate": "Halloi! Besøket til $old_restaurant_name, $old_time_stamp har blit endret til $restaurant_name, $time_stamp.", + "finalizedEvnetUpdate": "Halloi! $user_ids! Besøket til $old_restaurant_name, $old_time_stamp har blit endret til $restaurant_name, $time_stamp.", + "topLevelPizzaInvitation": "Pizzainvitasjon: $restaurant_name$, $time_stamp", + "pizzaInvitationHeader": "Pizzainvitasjon", + "pizzaInvitationBody": "Du er invitert til :pizza: på $restaurant_name, $time_stamp. Pls svar innen $deadline timer :pray:. Kan du?", + "inviteLoading": ":hourglass_flowing_sand: Behandler forespørselen din...", + "inviteNotAmongUsers": "Kunne ikke oppdatere invitasjonen. Du var ikke blant de inviterte.", + "pizzaInvitationAttendButton": "Hells yesss!!! 🍕🍕🍕", + "pizzaInvitationNoAttendButton": "Nah ☹️", + "pizzaInviteAnswerAttend": "Du har takket ja. Sweet! 🤙", + "pizzaInviteAnswerNoAttend": "Du har takket nei. Ok 😕", + "unsubscribeBody": "Hvis noe skulle skje så kan du melde deg av ved å klikke på knappen!", + "unsubscribeButton": "Meld meg av", + "invalidatedEventCacelled": "Arrangementet har blitt avlyst.", + "invitationExpired": "Invitasjonen er utløpt.", + "inviteWithdrawn": "Du har meldt deg av. Ok 😕", + "inviteWithdrawnFailure": "Arrangementet er over. Avmelding er ikke mulig.", + "invitedButLeftChannelUnanswered": "Siden du forlot PizzaBot kanalen, antar jeg du ikke vil være med 😕", + "invitedButLeftChannelWithdrawn": "Siden du forlot PizzaBot kanalen, antar jeg du ikke vil være med 😕 Du har blitt avmeldt arrangementet.", + "userLeftPizzaChannel": "Du har forlatt PizzaBot kanalen 😕 Du vil ikke lenger motta invitasjoner til fremtidige arrangementer.", + "adminPanelURLCommand": "Adminpanelet kan du finne <$adminPanelURL | her>" +} diff --git a/application/bot/src/slack/__init__.py b/application/bot/src/slack/__init__.py index 29ec4257..4f57cafc 100644 --- a/application/bot/src/slack/__init__.py +++ b/application/bot/src/slack/__init__.py @@ -9,15 +9,19 @@ from slack_bolt.oauth.oauth_settings import OAuthSettings from slack_sdk.oauth.state_store import FileOAuthStateStore -from src.api.bot_api import BotApi, BotApiConfiguration +from src.api.bot_api import BotApi from src.injector import injector from src.slack.installation_store import BrokerInstallationStore from src.api.slack_api import SlackApi +from src.i18n import Translator + slack_signing_secret = os.environ["SLACK_SIGNING_SECRET"] client_id = os.environ["SLACK_CLIENT_ID"], client_secret = os.environ["SLACK_CLIENT_SECRET"], slack_app_token = os.environ["SLACK_APP_TOKEN"] +frontend_uri = os.environ["FRONTEND_URI"] if "FRONTEND_URI" in os.environ else None + slack_app = App( signing_secret=slack_signing_secret, @@ -67,6 +71,21 @@ def handle_event(body, say, context): if "subtype" in event and event["subtype"] == 'file_share': handle_file_share(event=event, say=say, token=token, client=client) + +@slack_app.event("member_left_channel") +@request_time_monitor() +def handle_event(body, say, context): + event = body["event"] + client = SlackApi(client=context["client"]) + # Handle a user leaving a channel + if "channel" in event and "user" in event and "team" in event: + channel_id = event["channel"] + team_id = event["team"] + user_id = event["user"] + with injector.get(BotApi) as ba: + ba.handle_user_left_channel(channel_id=channel_id, team_id=team_id, user_id=user_id, slack_client=client) + + def handle_rsvp(body, ack, attending, client): user = body["user"] user_id = user["id"] @@ -138,11 +157,13 @@ def handle_rsvp_withdraw(ack, body, context): def handle_file_share(event, say, token, client): + return + translator = injector.get(Translator) channel = event["channel"] if 'files' in event and 'thread_ts' not in event: files = event['files'] with injector.get(BotApi) as ba: - ba.send_slack_message(channel_id=channel, text=u'Takk for fil! 🤙', slack_client=client) + ba.send_slack_message(channel_id=channel, text=translator.translate("thanksForFile"), slack_client=client) headers = {u'Authorization': u'Bearer %s' % token} for file in files: r = requests.get( @@ -150,7 +171,7 @@ def handle_file_share(event, say, token, client): b64 = base64.b64encode(r.content).decode('utf-8') payload = { 'file': 'data:image;base64,%s' % b64, - 'upload_preset': 'blank.pizza.v2', + 'upload_preset': 'blank.pizza.v2', # TODO: Change to own preset based on pizzabot v3 and org 'tags': ','.join(['pizza', file['user_team']]) } r2 = requests.post( @@ -163,24 +184,35 @@ def handle_file_share(event, say, token, client): @slack_app.command("/set-pizza-channel") def handle_some_command(ack, body, say, context): + translator = injector.get(Translator) ack() with injector.get(BotApi) as ba: team_id = body["team_id"] message_channel_id = body["channel_id"] client = SlackApi(client=context["client"]) channel_id = ba.join_channel(client, team_id, message_channel_id) - if channel_id is None: - ba.send_slack_message( - channel_id=message_channel_id, - text='Noe gikk galt. Klarte ikke å sette Pizza kanal', - slack_client=client - ) - else: + if channel_id is not None: ba.send_slack_message( channel_id=channel_id, - text='Pizza kanal er nå satt til <#%s>' % channel_id, + text=translator.translate("pizzaChannelConfirm", channel_id=channel_id), slack_client=client ) + ba.sync_users_from_organization(team_id=team_id, bot_token=context["token"]) + +@slack_app.command("/pizzabot-admin-panel") +def handle_some_command(ack, body, say, context): + translator = injector.get(Translator) + ack() + with injector.get(BotApi) as ba: + team_id = body["team_id"] + message_channel_id = body["channel_id"] + client = SlackApi(client=context["client"]) + + ba.send_slack_message( + channel_id=message_channel_id, + text=translator.translate("adminPanelURLCommand", adminPanelURL=f"{frontend_uri}/admin"), + slack_client=client + ) # This only exists to make bolt not throw a warning that we dont handle the file_shared event # We dont use this as we use the message event with subtype file_shared as that one diff --git a/application/bot/src/slack/lmdb.py b/application/bot/src/slack/lmdb.py index 2ca59291..550c1f54 100644 --- a/application/bot/src/slack/lmdb.py +++ b/application/bot/src/slack/lmdb.py @@ -16,8 +16,15 @@ def put(self, key, value): def get(self, key): with self.env.begin() as txn: value = txn.get(key.encode('utf-8')) - if value is not None: - value = value.decode('utf-8') + + if value is None: + return None + + value = value.decode('utf-8') + + if value == "null": + return None + return value def delete(self, key): diff --git a/application/containers/development/.env.example b/application/containers/development/.env.example new file mode 100644 index 00000000..3b50cd34 --- /dev/null +++ b/application/containers/development/.env.example @@ -0,0 +1,16 @@ +# -------------------- For development ------------------- +# Create a file named ".env" in this directory and copy the contents of this file with your credentials into it. +# Slack variables +SLACK_APP_TOKEN= +SLACK_CLIENT_ID= +SLACK_CLIENT_SECRET= +SLACK_SIGNING_SECRET= + +# Cloudinary for image uploads (optional for development) +CLOUDINARY_CLOUD_NAME= +CLOUDINARY_API_KEY= +CLOUDINARY_API_SECRET= + +# URI's - Dont change unless host and ports are changed +FRONTEND_URI=https://localhost:4000 # exposed port routed from nginx +BACKEND_URI=http://backend:3000 # docker-defined host w/ port from flask diff --git a/application/containers/development/Dockerfile.next-frontend b/application/containers/development/Dockerfile.next-frontend new file mode 100644 index 00000000..6c1ff79d --- /dev/null +++ b/application/containers/development/Dockerfile.next-frontend @@ -0,0 +1,5 @@ +FROM node:20 + +WORKDIR /srv/next-frontend + +CMD npm cache verify && npm install && npm run dev diff --git a/application/containers/development/docker-compose.yml b/application/containers/development/docker-compose.yml index 98b47b27..89bf33d4 100644 --- a/application/containers/development/docker-compose.yml +++ b/application/containers/development/docker-compose.yml @@ -1,19 +1,19 @@ -version: '3.8' +version: "3.8" x-common-variables: &common-variables PYTHONUNBUFFERED: 1 PYTHONPATH: /srv/bot x-common-slack-variables: &common-slack-variables - SLACK_APP_TOKEN: "" - SLACK_CLIENT_ID: "" - SLACK_CLIENT_SECRET: "" - SLACK_SIGNING_SECRET: "" + SLACK_APP_TOKEN: "${SLACK_APP_TOKEN}" + SLACK_CLIENT_ID: "${SLACK_CLIENT_ID}" + SLACK_CLIENT_SECRET: "${SLACK_CLIENT_SECRET}" + SLACK_SIGNING_SECRET: "${SLACK_SIGNING_SECRET}" x-common-cloudinary-variables: &common-cloudinary-variables - CLOUDINARY_CLOUD_NAME: "" - CLOUDINARY_API_KEY: "" - CLOUDINARY_API_SECRET: "" + CLOUDINARY_CLOUD_NAME: "${CLOUDINARY_CLOUD_NAME}" + CLOUDINARY_API_KEY: "${CLOUDINARY_API_KEY}" + CLOUDINARY_API_SECRET: "${CLOUDINARY_API_SECRET}" x-common-rabbitmq-variables: &common-rabbitmq-variables MQ_URL: amqp://guest:guest@rabbitmq:5672/%2F @@ -38,6 +38,23 @@ services: - backend networks: - proxy-network + next-frontend: + build: + context: ../../ + dockerfile: containers/development/Dockerfile.next-frontend + expose: + - 4000 + volumes: + - ../../next-frontend:/srv/next-frontend + environment: + - CHOKIDAR_USEPOLLING=true + - WDS_SOCKET_PORT=0 + - WATCHPACK_POLLING=true + - NEXT_PUBLIC_BACKEND_URI=${BACKEND_URI} + depends_on: + - backend + networks: + - proxy-network backend: build: context: ../../ @@ -59,16 +76,19 @@ services: DB_PASSWD: "postgres" DB_PORT: "5432" DAYS_IN_ADVANCE_TO_INVITE: 10 - <<: *common-variables - <<: *common-rabbitmq-variables SECRET_KEY: "V3ryS3cr3tK3y" FLASK_ENV: "production" FLASK_APP: "main.py" FLASK_RUN_HOST: "0.0.0.0" FLASK_RUN_PORT: "3000" - FRONTEND_URI: "https://localhost" - <<: *common-slack-variables - <<: *common-cloudinary-variables + FRONTEND_URI: "${FRONTEND_URI}" + <<: + [ + *common-slack-variables, + *common-variables, + *common-rabbitmq-variables, + *common-cloudinary-variables, + ] networks: - database-network - proxy-network @@ -80,9 +100,9 @@ services: volumes: - ../../bot:/srv/bot environment: - <<: *common-variables - <<: *common-rabbitmq-variables - <<: *common-slack-variables + FRONTEND_URI: "${FRONTEND_URI}" + <<: + [*common-variables, *common-rabbitmq-variables, *common-slack-variables] REPLY_DEADLINE_IN_HOURS: 24 HOURS_BETWEEN_REMINDERS: 4 restart: always @@ -96,7 +116,7 @@ services: - rabbitmq_network rabbitmq: image: rabbitmq:3-management-alpine - container_name: 'rabbitmq' + container_name: "rabbitmq" ports: - 5672:5672 - 15672:15672 @@ -108,7 +128,7 @@ services: timeout: 5s retries: 5 database: - image: postgres + image: postgres:15 restart: always volumes: - database_data:/var/lib/postgressql/data @@ -131,14 +151,16 @@ services: build: context: ./ dockerfile: Dockerfile.nginx - depends_on: + depends_on: - frontend + - next-frontend - backend networks: - proxy-network ports: - - 80:80 - - 443:443 + - 8000:80 + - 4434:443 + - 4000:4000 networks: database-network: diff --git a/application/containers/development/nginx.conf b/application/containers/development/nginx.conf index d3e9a634..0c93323c 100644 --- a/application/containers/development/nginx.conf +++ b/application/containers/development/nginx.conf @@ -4,6 +4,7 @@ server { return 302 https://$host$request_uri; } +# react frontend server { client_body_buffer_size 32k; client_header_buffer_size 8k; @@ -46,6 +47,7 @@ server { proxy_set_header Connection "upgrade"; } + # hot module reloading location ^~ /ws { proxy_pass http://frontend:3000; proxy_set_header Host $host; @@ -79,6 +81,97 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } + location ^~ /doc { + if ($request_uri ~ ^([^.\?]*[^/])$) { + return 301 $1/; + } + rewrite /doc/(.*) /doc/$1 break; + proxy_pass http://backend:3000; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + +# next frontend +server { + client_body_buffer_size 32k; + client_header_buffer_size 8k; + large_client_header_buffers 4 32k; + proxy_buffers 4 256k; + proxy_buffer_size 128k; + proxy_busy_buffers_size 256k; + + listen 4000 default_server ssl; + listen [::]:4000 default_server ssl; + + ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt; + ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key; + + server_name test_server_2; + + root /var/www/; + index index.html index.htm; + + error_page 497 https://$host:4000$request_uri; + + location / { + proxy_pass http://next-frontend:4000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ^~ /sockjs-node { + proxy_pass http://next-frontend:4000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # hot module reloading + location ^~ /_next { + proxy_pass http://next-frontend:4000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location ^~ /api { + rewrite /api/(.*) /api/$1 break; + proxy_pass http://backend:3000; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ^~ /swagger { + rewrite /swagger/(.*) /doc/swagger break; + proxy_pass http://backend:3000; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location ^~ /doc { if ($request_uri ~ ^([^.\?]*[^/])$) { return 301 $1/; diff --git a/application/frontend/.eslintrc.json b/application/frontend/.eslintrc.json index 075289fb..1a6c073e 100644 --- a/application/frontend/.eslintrc.json +++ b/application/frontend/.eslintrc.json @@ -28,4 +28,4 @@ "version": "detect" } } -} \ No newline at end of file +} diff --git a/application/frontend/src/api/httpClient.ts b/application/frontend/src/api/httpClient.ts index 9ca219c5..c717a38c 100644 --- a/application/frontend/src/api/httpClient.ts +++ b/application/frontend/src/api/httpClient.ts @@ -7,10 +7,19 @@ import { RefreshJWT } from './AuthService'; const baseUrl = process.env.BACKEND_URI ? `${process.env.BACKEND_URI.replace(/\/+$/, '')}/api` : '/api'; +function getCookie(name: string) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts && parts.length === 2) return parts.pop()?.split(';')?.shift(); +} + export const httpClient = (token?: string): AxiosInstance => { const headers: AxiosRequestHeaders = {}; + const cookie = getCookie('csrf_access_token'); - if (token) { + if (cookie) { + headers['X-CSRF-TOKEN'] = cookie; + } else if (token) { headers.Authorization = `Bearer ${token}`; } diff --git a/application/frontend/src/assets/locales/nb/translation.json b/application/frontend/src/assets/locales/nb/translation.json index d18322ec..d024cfad 100644 --- a/application/frontend/src/assets/locales/nb/translation.json +++ b/application/frontend/src/assets/locales/nb/translation.json @@ -16,7 +16,7 @@ "login": { "logo": "Pizzabot av", "loginButton": "Logg inn med Slack", - "installButton": "Legg til PizzaBot?", + "installButton": "Legg til PizzaBot? ", "error": { "401": "Noe gikk galt. Er epostadressen din verifisert?", "403": "Noe gikk galt. Er PizzaBot installert i ditt workspace?" diff --git a/application/next-frontend/.eslintrc.json b/application/next-frontend/.eslintrc.json new file mode 100644 index 00000000..abaad8b1 --- /dev/null +++ b/application/next-frontend/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "prettier", "tailwindcss"], + "extends": [ + "next/core-web-vitals", + "plugin:@typescript-eslint/recommended", + "prettier", + "plugin:prettier/recommended", + "plugin:tailwindcss/recommended" + ], + "ignorePatterns": [".idea/*", "dist/*", "public/*", "node_modules/*", ".prettierrc.js", "webpack.*.js", ".next/*"] +} diff --git a/application/next-frontend/.gitignore b/application/next-frontend/.gitignore new file mode 100644 index 00000000..70baf44c --- /dev/null +++ b/application/next-frontend/.gitignore @@ -0,0 +1,32 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/application/next-frontend/.husky/pre-commit b/application/next-frontend/.husky/pre-commit new file mode 100755 index 00000000..89e324ec --- /dev/null +++ b/application/next-frontend/.husky/pre-commit @@ -0,0 +1,11 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +if ! git diff --cached --exit-code -s application/next-frontend +then + echo "Files changed in next-frontend" + cd application/next-frontend + npx lint-staged + npm run typecheck + cd ../.. +fi diff --git a/application/next-frontend/.prettierrc.js b/application/next-frontend/.prettierrc.js new file mode 100644 index 00000000..7592c3c9 --- /dev/null +++ b/application/next-frontend/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: false, + trailingComma: 'all', + singleQuote: true, + printWidth: 120, + tabWidth: 4, +}; diff --git a/application/next-frontend/README.md b/application/next-frontend/README.md new file mode 100644 index 00000000..965a1228 --- /dev/null +++ b/application/next-frontend/README.md @@ -0,0 +1,38 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/application/next-frontend/middleware.ts b/application/next-frontend/middleware.ts new file mode 100644 index 00000000..ccd9a2cd --- /dev/null +++ b/application/next-frontend/middleware.ts @@ -0,0 +1,10 @@ +import { NextFetchEvent, NextRequest, NextResponse } from 'next/server' + +type Environment = 'production' | 'development' | 'test' +export function middleware(req: NextRequest, ev: NextFetchEvent) { + const currentEnv: Environment = process.env.NODE_ENV + if (currentEnv === 'production' && req.nextUrl.protocol !== 'https:') { + return NextResponse.redirect(`https://${req.headers.get('host')}${req.nextUrl.pathname}`, 301) + } + return NextResponse.next() +} diff --git a/application/next-frontend/next.config.js b/application/next-frontend/next.config.js new file mode 100644 index 00000000..38e25b4d --- /dev/null +++ b/application/next-frontend/next.config.js @@ -0,0 +1,5 @@ +/** @type {import('next').NextConfig} */ + +const nextConfig = { reactStrictMode: true } + +module.export = nextConfig diff --git a/application/next-frontend/package-lock.json b/application/next-frontend/package-lock.json new file mode 100644 index 00000000..8f375711 --- /dev/null +++ b/application/next-frontend/package-lock.json @@ -0,0 +1,6233 @@ +{ + "name": "next-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "next-frontend", + "version": "0.1.0", + "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@hookform/resolvers": "^3.2.0", + "@mui/material": "^5.14.5", + "autoprefixer": "10.4.14", + "jwt-decode": "^3.1.2", + "next": "13.4.13", + "ordinal": "^1.0.3", + "postcss": "8.4.27", + "react": "18.2.0", + "react-datepicker": "^4.16.0", + "react-dom": "18.2.0", + "react-hook-form": "^7.45.4", + "react-hot-toast": "^2.4.1", + "swr": "^2.2.1", + "tailwindcss": "3.3.3", + "zod": "^3.22.2" + }, + "devDependencies": { + "@types/node": "20.4.9", + "@types/react": "18.2.20", + "@types/react-datepicker": "^4.15.0", + "@types/react-dom": "18.2.7", + "@typescript-eslint/eslint-plugin": "^5.33.1", + "eslint": "8.46.0", + "eslint-config-next": "13.4.13", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-tailwindcss": "^3.13.0", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "prettier": "3.0.1", + "prettier-plugin-tailwindcss": "^0.4.1", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=20.x" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", + "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.19", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", + "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.19", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "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/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", + "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", + "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hookform/resolvers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.2.0.tgz", + "integrity": "sha512-skXQHhLxq0Sz2xDwCyv5dygBCtXJe1GmWwxDzfdtl0X6agD6qcyTG8HrZWkjJoy8AkiLARqYvSYJ8z7+Nwmi7w==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.11.tgz", + "integrity": "sha512-FdKZGPd8qmC3ZNke7CNhzcEgToc02M6WYZc9hcBsNQ17bgAd3s9F//1bDDYgMVBYxDM71V0sv/hBHlOY4I1ZVA==", + "dependencies": { + "@babel/runtime": "^7.22.6", + "@emotion/is-prop-valid": "^1.2.1", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.14.5", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/base/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.5.tgz", + "integrity": "sha512-+wpGH1USwPcKMFPMvXqYPC6fEvhxM3FzxC8lyDiNK/imLyyJ6y2DPb1Oue7OGIKJWBmYBqrWWtfovrxd1aJHTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + } + }, + "node_modules/@mui/material": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.5.tgz", + "integrity": "sha512-4qa4GMfuZH0Ai3mttk5ccXP8a3sf7aPlAJwyMrUSz6h9hPri6BPou94zeu3rENhhmKLby9S/W1y+pmficy8JKA==", + "dependencies": { + "@babel/runtime": "^7.22.6", + "@mui/base": "5.0.0-beta.11", + "@mui/core-downloads-tracker": "^5.14.5", + "@mui/system": "^5.14.5", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.14.5", + "@types/react-transition-group": "^4.4.6", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/private-theming": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.5.tgz", + "integrity": "sha512-cC4C5RrpXpDaaZyH9QwmPhRLgz+f2SYbOty3cPkk4qPSOSfif2ZEcDD9HTENKDDd9deB+xkPKzzZhi8cxIx8Ig==", + "dependencies": { + "@babel/runtime": "^7.22.6", + "@mui/utils": "^5.14.5", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", + "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.5.tgz", + "integrity": "sha512-mextXZHDeGcR7E1kx43TRARrVXy+gI4wzpUgNv7MqZs1dvTVXQGVeAT6ydj9d6FUqHBPMNLGV/21vJOrpqsL+w==", + "dependencies": { + "@babel/runtime": "^7.22.6", + "@mui/private-theming": "^5.14.5", + "@mui/styled-engine": "^5.13.2", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.14.5", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", + "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.5.tgz", + "integrity": "sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==", + "dependencies": { + "@babel/runtime": "^7.22.6", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^18.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@next/env": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.13.tgz", + "integrity": "sha512-fwz2QgVg08v7ZL7KmbQBLF2PubR/6zQdKBgmHEl3BCyWTEDsAQEijjw2gbFhI1tcKfLdOOJUXntz5vZ4S0Polg==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.13.tgz", + "integrity": "sha512-RpZeXlPxQ9FLeYN84XHDqRN20XxmVNclYCraLYdifRsmibtcWUWdwE/ANp2C8kgesFRsvwfsw6eOkYNl9sLJ3A==", + "dev": true, + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.13.tgz", + "integrity": "sha512-ZptVhHjzUuivnXMNCJ6lER33HN7lC+rZ01z+PM10Ows21NHFYMvGhi5iXkGtBDk6VmtzsbqnAjnx4Oz5um0FjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.13.tgz", + "integrity": "sha512-t9nTiWCLApw8W4G1kqJyYP7y6/7lyal3PftmRturIxAIBlZss9wrtVN8nci50StDHmIlIDxfguYIEGVr9DbFTg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.13.tgz", + "integrity": "sha512-xEHUqC8eqR5DHe8SOmMnDU1K3ggrJ28uIKltrQAwqFSSSmzjnN/XMocZkcVhuncuxYrpbri0iMQstRyRVdQVWg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.13.tgz", + "integrity": "sha512-sNf3MnLAm8rquSSAoeD9nVcdaDeRYOeey4stOWOyWIgbBDtP+C93amSgH/LPTDoUV7gNiU6f+ghepTjTjRgIUQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.13.tgz", + "integrity": "sha512-WhcRaJJSHyx9OWmKjjz+OWHumiPZWRqmM/09Bt7Up4UqUJFFhGExeztR4trtv3rflvULatu9IH/nTV8fUUgaMA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.13.tgz", + "integrity": "sha512-+Y4LLhOWWZQIDKVwr2R17lq2KSN0F1c30QVgGIWfnjjHpH8nrIWHEndhqYU+iFuW8It78CiJjQKTw4f51HD7jA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.13.tgz", + "integrity": "sha512-rWurdOR20uxjfqd1X9vDAgv0Jb26KjyL8akF9CBeFqX8rVaBAnW/Wf6A2gYEwyYY4Bai3T7p1kro6DFrsvBAAw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.13.tgz", + "integrity": "sha512-E8bSPwRuY5ibJ3CzLQmJEt8qaWrPYuUTwnrwygPUEWoLzD5YRx9SD37oXRdU81TgGwDzCxpl7z5Nqlfk50xAog==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.13.tgz", + "integrity": "sha512-4KlyC6jWRubPnppgfYsNTPeWfGCxtWLh5vaOAW/kdzAk9widqho8Qb5S4K2vHmal1tsURi7Onk2MMCV1phvyqA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz", + "integrity": "sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.4.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.9.tgz", + "integrity": "sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.2.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.20.tgz", + "integrity": "sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-datepicker": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.15.0.tgz", + "integrity": "sha512-kr10s8ex4+MmCJmzdhA9kfmoMQBmsW5uDYDlH+8f/PgStrp7rRaz23Y/cvTiMgvESVq8ujDh4SOo6jlSwEw13g==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.2", + "@types/react": "*", + "date-fns": "^2.0.1", + "react-popper": "^2.2.5" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", + "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-is": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", + "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", + "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "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==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001519", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", + "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "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 + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "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==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "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.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.490", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz", + "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", + "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.2", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.13.tgz", + "integrity": "sha512-EXAh5h1yG/YTNa5YdskzaSZncBjKjvFe2zclMCi2KXyTsXha22wB6MPs/U7idB6a2qjpBdbZcruQY1TWjfNMZw==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "13.4.13", + "@rushstack/eslint-patch": "^1.1.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.31.7", + "eslint-plugin-react-hooks": "5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.0.tgz", + "integrity": "sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz", + "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.8.0", + "has": "^1.0.3", + "is-core-module": "^2.12.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", + "object.values": "^1.1.6", + "resolve": "^1.22.3", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", + "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.1.tgz", + "integrity": "sha512-L093k0WAMvr6VhNwReB8VgOq5s2LesZmrpPdKz/kZElQDzqS7G7+DnKoqT+w4JwuiGeAhAvHO0fvy0Eyk4ejDA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.0.0-canary-7118f5dd7-20230705", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-tailwindcss": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.13.0.tgz", + "integrity": "sha512-Fcep4KDRLWaK3KmkQbdyKHG0P4GdXFmXdDaweTIPcgOP60OOuWFbh1++dufRT28Q4zpKTKaHwTsXPJ4O/EjU2Q==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.5", + "postcss": "^8.4.4" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "^3.3.2" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "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==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.0.tgz", + "integrity": "sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/goober": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", + "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", + "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/lint-staged": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", + "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", + "dev": true, + "dependencies": { + "chalk": "5.2.0", + "cli-truncate": "^3.1.0", + "commander": "^10.0.0", + "debug": "^4.3.4", + "execa": "^7.0.0", + "lilconfig": "2.1.0", + "listr2": "^5.0.7", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.3", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.2.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", + "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.19", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/listr2/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/next": { + "version": "13.4.13", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.13.tgz", + "integrity": "sha512-A3YVbVDNeXLhWsZ8Nf6IkxmNlmTNz0yVg186NJ97tGZqPDdPzTrHotJ+A1cuJm2XfuWPrKOUZILl5iBQkIf8Jw==", + "dependencies": { + "@next/env": "13.4.13", + "@swc/helpers": "0.5.1", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.1.1", + "watchpack": "2.4.0", + "zod": "3.21.4" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=16.8.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "13.4.13", + "@next/swc-darwin-x64": "13.4.13", + "@next/swc-linux-arm64-gnu": "13.4.13", + "@next/swc-linux-arm64-musl": "13.4.13", + "@next/swc-linux-x64-gnu": "13.4.13", + "@next/swc-linux-x64-musl": "13.4.13", + "@next/swc-win32-arm64-msvc": "13.4.13", + "@next/swc-win32-ia32-msvc": "13.4.13", + "@next/swc-win32-x64-msvc": "13.4.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/next/node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", + "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ordinal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", + "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "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==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "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==" + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", + "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz", + "integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.4.1.tgz", + "integrity": "sha512-hwn2EiJmv8M+AW4YDkbjJ6HlZCTzLyz1QlySn9sMuKV/Px0fjwldlB7tol8GzdgqtkdPtzT3iJ4UzdnYXP25Ag==", + "dev": true, + "engines": { + "node": ">=12.17.0" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@shufo/prettier-plugin-blade": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "prettier": "^2.2 || ^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*", + "prettier-plugin-twig-melody": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@shufo/prettier-plugin-blade": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "prettier-plugin-twig-melody": { + "optional": true + } + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-datepicker": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.16.0.tgz", + "integrity": "sha512-hNQ0PAg/LQoVbDUO/RWAdm/RYmPhN3cz7LuQ3hqbs24OSp69QCiKOJRrQ4jk1gv1jNR5oYu8SjjgfDh8q6Q1yw==", + "dependencies": { + "@popperjs/core": "^2.11.8", + "classnames": "^2.2.6", + "date-fns": "^2.30.0", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.12.2", + "react-popper": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-hook-form": { + "version": "7.45.4", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.4.tgz", + "integrity": "sha512-HGDV1JOOBPZj10LB3+OZgfDBTn+IeEsNOKiq/cxbQAIbKaiJUe/KV8DBUzsx0Gx/7IG/orWqRRm736JwOfUSWQ==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/sucrase": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swr": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.1.tgz", + "integrity": "sha512-KJVA7dGtOBeZ+2sycEuzUfVIP5lZ/cd0xjevv85n2YG0x1uHJQicjAtahVZL6xG3+TjqhbBqimwYzVo3saeVXQ==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tailwindcss": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", + "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", + "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/application/next-frontend/package.json b/application/next-frontend/package.json new file mode 100644 index 00000000..10f60498 --- /dev/null +++ b/application/next-frontend/package.json @@ -0,0 +1,59 @@ +{ + "name": "next-frontend", + "version": "0.1.0", + "private": true, + "engines": { + "node": ">=20.x" + }, + "scripts": { + "dev": "next dev -p 4000", + "build": "next build", + "start": "next start -p $PORT", + "lint": "next lint --max-warnings 0", + "fix": "next lint --fix", + "typecheck": "tsc --noEmit --incremental false --pretty", + "prepare": "cd ../../ && husky install ./application/next-frontend/.husky" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "prettier --write", + "eslint --fix --max-warnings 0" + ] + }, + "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@hookform/resolvers": "^3.2.0", + "@mui/material": "^5.14.5", + "autoprefixer": "10.4.14", + "jwt-decode": "^3.1.2", + "next": "13.4.13", + "ordinal": "^1.0.3", + "postcss": "8.4.27", + "react": "18.2.0", + "react-datepicker": "^4.16.0", + "react-dom": "18.2.0", + "react-hook-form": "^7.45.4", + "react-hot-toast": "^2.4.1", + "swr": "^2.2.1", + "tailwindcss": "3.3.3", + "zod": "^3.22.2" + }, + "devDependencies": { + "@types/node": "20.4.9", + "@types/react": "18.2.20", + "@types/react-datepicker": "^4.15.0", + "@types/react-dom": "18.2.7", + "@typescript-eslint/eslint-plugin": "^5.33.1", + "eslint": "8.46.0", + "eslint-config-next": "13.4.13", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-tailwindcss": "^3.13.0", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "prettier": "3.0.1", + "prettier-plugin-tailwindcss": "^0.4.1", + "typescript": "5.1.6" + } +} diff --git a/application/next-frontend/pages/_app.tsx b/application/next-frontend/pages/_app.tsx new file mode 100644 index 00000000..9a06f395 --- /dev/null +++ b/application/next-frontend/pages/_app.tsx @@ -0,0 +1,16 @@ +import '@/styles/globals.css' +import '@/styles/reset.css' +import '@/styles/fonts.css' +import '@/styles/tailwindComposables.css' +import type { AppProps } from 'next/app' +import { ModalProvider } from 'Shared/context/ModalContext' +import { Toaster } from 'react-hot-toast' + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + + ) +} diff --git a/application/next-frontend/pages/_document.tsx b/application/next-frontend/pages/_document.tsx new file mode 100644 index 00000000..138cd8d1 --- /dev/null +++ b/application/next-frontend/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/application/next-frontend/pages/admin/index.tsx b/application/next-frontend/pages/admin/index.tsx new file mode 100644 index 00000000..938c1a54 --- /dev/null +++ b/application/next-frontend/pages/admin/index.tsx @@ -0,0 +1,25 @@ +import { GetServerSideProps } from 'next' +import jwtDecode from 'jwt-decode' +import type { JwtToken } from '@/Admin/types/User' +import { AdminMainPage } from '@/Admin/scenarios/AdminMainPage' +import { Navbar } from '@/Admin/scenarios/Navbar' + +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + const jwt = req.cookies['access_token_cookie'] + if (!jwt || !jwtDecode(jwt)) return { redirect: { destination: '/login', permanent: false }, props: {} } + + return { + props: {}, + } +} + +const AdminHome = () => { + return ( +
+ + +
+ ) +} + +export default AdminHome diff --git a/application/next-frontend/pages/admin/onboarding/index.tsx b/application/next-frontend/pages/admin/onboarding/index.tsx new file mode 100644 index 00000000..8a59bada --- /dev/null +++ b/application/next-frontend/pages/admin/onboarding/index.tsx @@ -0,0 +1,21 @@ +import { GetServerSideProps } from 'next' +import { Onboarding as OnboardAdmin } from '@/Admin/scenarios/Onboarding' + +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + const jwt = req.cookies['access_token_cookie'] + if (!jwt) return { redirect: { destination: '/login', permanent: false }, props: {} } + + return { + props: {}, + } +} + +const Onboarding = () => { + return ( +
+ +
+ ) +} + +export default Onboarding diff --git a/application/next-frontend/pages/api/hello.ts b/application/next-frontend/pages/api/hello.ts new file mode 100644 index 00000000..d3979fec --- /dev/null +++ b/application/next-frontend/pages/api/hello.ts @@ -0,0 +1,10 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next' + +type Data = { + name: string +} + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ name: 'John Doe' }) +} diff --git a/application/next-frontend/pages/index.tsx b/application/next-frontend/pages/index.tsx new file mode 100644 index 00000000..4ffa949a --- /dev/null +++ b/application/next-frontend/pages/index.tsx @@ -0,0 +1,11 @@ +import { Landing } from '@/Landing' +import { Header } from '@/Landing/scenarios/Header' + +export default function Home() { + return ( +
+
+ +
+ ) +} diff --git a/application/next-frontend/pages/login/callback.tsx b/application/next-frontend/pages/login/callback.tsx new file mode 100644 index 00000000..13cc6b28 --- /dev/null +++ b/application/next-frontend/pages/login/callback.tsx @@ -0,0 +1,37 @@ +import { serversideApiUri } from '@/api/endpoints' +import { GetServerSideProps } from 'next' +import Link from 'next/link' + +export const getServerSideProps: GetServerSideProps = async ({ query, res }) => { + const code = query.code + if (!query.code || typeof query.code !== 'string') + return { props: { errorMsg: 'Could not get login information from Slack.' } } + + const response = await fetch(`${serversideApiUri}/auth/login/callback?code=${code}`).catch((err) => { + console.error(err) + }) + + if (!response?.ok) { + return { props: { errorMsg: 'Something went wrong when logging inn through Slack.' } } + } + + const cookie = response.headers.get('set-cookie') + if (!cookie) return { props: { errorMsg: 'Login credentials were not set correctly.' } } + + res.setHeader('set-cookie', cookie) + return { redirect: { destination: '/admin', permanent: false }, props: {} } +} + +const LoginCallback = ({ errorMsg }: { errorMsg?: string }) => { + return ( +
+

Failed to log in!

+

{`${errorMsg ?? ''} Try logging in again.`}

+ + Go to login + +
+ ) +} + +export default LoginCallback diff --git a/application/next-frontend/pages/login/index.tsx b/application/next-frontend/pages/login/index.tsx new file mode 100644 index 00000000..98ed1e53 --- /dev/null +++ b/application/next-frontend/pages/login/index.tsx @@ -0,0 +1,13 @@ +import { Header } from '@/Landing/scenarios/Header' +import { LoginPage } from 'Landing/scenarios/LoginPage' + +const Login = () => { + return ( +
+
+ +
+ ) +} + +export default Login diff --git a/application/next-frontend/pages/slack/callback.tsx b/application/next-frontend/pages/slack/callback.tsx new file mode 100644 index 00000000..0eeaea66 --- /dev/null +++ b/application/next-frontend/pages/slack/callback.tsx @@ -0,0 +1,44 @@ +import { serversideApiUri } from '@/api/endpoints' +import { GetServerSideProps } from 'next' +import { SlackCallbackPageSuccess, SlackCallbackPageError } from 'Landing/scenarios/SlackCallbackPage' +import { Header } from '@/Landing/scenarios/Header' + +export const getServerSideProps: GetServerSideProps = async ({ query }) => { + const code = query.code + if (!query.code || typeof query.code !== 'string') return { props: { success: false } } + + const res = await fetch(`${serversideApiUri}/slack/callback`, { + method: 'POST', + body: JSON.stringify({ code: code }), + headers: { + 'Content-Type': 'application/json', + }, + }).catch((err) => { + console.error(err) + }) + + const success = res?.ok + let message: string | undefined + try { + message = (await res?.json()).message + } catch (err) { + console.error(err) + } + + return { props: { success, message: message ?? null } } +} + +const SlackInstallCallback = ({ success, message }: { success?: boolean; message: string | null }) => { + return ( +
+
+ {success ? ( + + ) : ( + + )} +
+ ) +} + +export default SlackInstallCallback diff --git a/application/next-frontend/postcss.config.js b/application/next-frontend/postcss.config.js new file mode 100644 index 00000000..33ad091d --- /dev/null +++ b/application/next-frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/application/next-frontend/public/favicon.ico b/application/next-frontend/public/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/application/next-frontend/public/favicon.ico differ diff --git a/application/next-frontend/public/fonts/QueensCondensedTrial-Medium.ttf b/application/next-frontend/public/fonts/QueensCondensedTrial-Medium.ttf new file mode 100644 index 00000000..3283e033 Binary files /dev/null and b/application/next-frontend/public/fonts/QueensCondensedTrial-Medium.ttf differ diff --git a/application/next-frontend/public/fonts/QueensCondensedTrial-Regular.ttf b/application/next-frontend/public/fonts/QueensCondensedTrial-Regular.ttf new file mode 100644 index 00000000..de344ccc Binary files /dev/null and b/application/next-frontend/public/fonts/QueensCondensedTrial-Regular.ttf differ diff --git a/application/next-frontend/src/Admin/assets/AdminSidebarBackground.svg b/application/next-frontend/src/Admin/assets/AdminSidebarBackground.svg new file mode 100644 index 00000000..59edfeb1 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/AdminSidebarBackground.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Admin/assets/Line.svg b/application/next-frontend/src/Admin/assets/Line.svg new file mode 100644 index 00000000..dd75512b --- /dev/null +++ b/application/next-frontend/src/Admin/assets/Line.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Admin/assets/MascotChilling.svg b/application/next-frontend/src/Admin/assets/MascotChilling.svg new file mode 100644 index 00000000..4fcd7402 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/MascotChilling.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/application/next-frontend/src/Admin/assets/MascotHappy.svg b/application/next-frontend/src/Admin/assets/MascotHappy.svg new file mode 100644 index 00000000..fea647ec --- /dev/null +++ b/application/next-frontend/src/Admin/assets/MascotHappy.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/application/next-frontend/src/Admin/assets/MascotWithFourArms.svg b/application/next-frontend/src/Admin/assets/MascotWithFourArms.svg new file mode 100644 index 00000000..c679969d --- /dev/null +++ b/application/next-frontend/src/Admin/assets/MascotWithFourArms.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/application/next-frontend/src/Admin/assets/Pencil.svg b/application/next-frontend/src/Admin/assets/Pencil.svg new file mode 100644 index 00000000..cfb152a4 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/Pencil.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Admin/assets/PizzaBotLogo.svg b/application/next-frontend/src/Admin/assets/PizzaBotLogo.svg new file mode 100644 index 00000000..d1a76690 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/PizzaBotLogo.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Admin/assets/Triangle.svg b/application/next-frontend/src/Admin/assets/Triangle.svg new file mode 100644 index 00000000..1e9dcaf9 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/Triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Admin/assets/TriangleGreen.svg b/application/next-frontend/src/Admin/assets/TriangleGreen.svg new file mode 100644 index 00000000..6cd38899 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/TriangleGreen.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Admin/assets/TriangleGrey.svg b/application/next-frontend/src/Admin/assets/TriangleGrey.svg new file mode 100644 index 00000000..475fd3c7 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/TriangleGrey.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Admin/assets/pizza/PizzaEaten.svg b/application/next-frontend/src/Admin/assets/pizza/PizzaEaten.svg new file mode 100644 index 00000000..4624e14a --- /dev/null +++ b/application/next-frontend/src/Admin/assets/pizza/PizzaEaten.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/application/next-frontend/src/Admin/assets/pizza/PizzaRound.svg b/application/next-frontend/src/Admin/assets/pizza/PizzaRound.svg new file mode 100644 index 00000000..4371add1 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/pizza/PizzaRound.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Admin/assets/pizza/PizzaSlice.svg b/application/next-frontend/src/Admin/assets/pizza/PizzaSlice.svg new file mode 100644 index 00000000..3e727195 --- /dev/null +++ b/application/next-frontend/src/Admin/assets/pizza/PizzaSlice.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/application/next-frontend/src/Admin/components/AdminSidebar.tsx b/application/next-frontend/src/Admin/components/AdminSidebar.tsx new file mode 100644 index 00000000..aa19160f --- /dev/null +++ b/application/next-frontend/src/Admin/components/AdminSidebar.tsx @@ -0,0 +1,29 @@ +import Image from 'next/image' +import Mascot from 'Admin/assets/MascotChilling.svg' +import TextBackground from 'Admin/assets/AdminSidebarBackground.svg' + +const AdminSidebar = () => { + return ( +
+
+
+ background +

+ Pizza control in progress.. +

+
+ +
+

Welcome to the Admin Page!

+

+ As the administrator, you can edit who is invited, where you are going and when the events take + place. +

+
+
+ mascot +
+ ) +} + +export { AdminSidebar } diff --git a/application/next-frontend/src/Admin/components/Button.tsx b/application/next-frontend/src/Admin/components/Button.tsx new file mode 100644 index 00000000..8e671e16 --- /dev/null +++ b/application/next-frontend/src/Admin/components/Button.tsx @@ -0,0 +1,28 @@ +type ButtonProps = { + onClick?: () => void + className?: string + text: string + buttonStyle: 'primary' | 'secondary' + type?: 'submit' | 'reset' | 'button' + value?: string +} + +const Button = ({ onClick, className, text, buttonStyle, type, value }: ButtonProps) => { + const primaryStyle = 'bg-white font-black hover:bg-green-secondary border-b-8' + const secondaryStyle = 'bg-green-light font-semibold hover:bg-yellow' + + return ( + + ) +} + +export default Button diff --git a/application/next-frontend/src/Admin/components/CardComponentWrapper.tsx b/application/next-frontend/src/Admin/components/CardComponentWrapper.tsx new file mode 100644 index 00000000..e72fd77b --- /dev/null +++ b/application/next-frontend/src/Admin/components/CardComponentWrapper.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import { useHover } from '@/Shared/context/HoverContext' + +const CardComponentWrapper = ({ + title, + children, + addIcon, + onClickCard, + className, +}: { + title: string + children?: React.ReactNode + addIcon?: boolean + onClickCard?: () => void + className?: string +}) => { + const { setHovered, isHovered } = useHover() + return ( +
setHovered(true)} + onMouseLeave={() => setHovered(false)} + onClick={onClickCard} + > +
+
{title}
+
+
+
{children}
+ {addIcon && ( +
+ + Add + + +
+ )} +
+ ) +} + +export { CardComponentWrapper } diff --git a/application/next-frontend/src/Admin/scenarios/AdminMainPage.tsx b/application/next-frontend/src/Admin/scenarios/AdminMainPage.tsx new file mode 100644 index 00000000..585a8cf3 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/AdminMainPage.tsx @@ -0,0 +1,39 @@ +import { Events } from 'Admin/scenarios/Events' +import { Restaurants } from 'Admin/scenarios/Restaurants' +import { SlackChannel } from 'Admin/scenarios/SlackChannel' +import Image from 'next/image' +import MascotHappy from 'Admin/assets/MascotHappy.svg' + +import { HoverProvider } from 'Shared/context/HoverContext' + +const AdminMainPage = () => { + return ( +
+
+
+

Pizza Admin

+ + + + + + +
+
*]:mb-14`}> + pizza bot mascot with four arms + + + +
+
+
+ ) +} + +export { AdminMainPage } diff --git a/application/next-frontend/src/Admin/scenarios/Events/components/CreatePizzaEventCard.tsx b/application/next-frontend/src/Admin/scenarios/Events/components/CreatePizzaEventCard.tsx new file mode 100644 index 00000000..ad243e4c --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Events/components/CreatePizzaEventCard.tsx @@ -0,0 +1,203 @@ +import { useModal } from 'Shared/context/ModalContext' +import { ApiEventPost, useEvents } from '@/api/useEvents' +import { useRestaurants } from '@/api/useRestaurants' + +import { useForm, FormProvider, Controller } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { z } from 'zod' +import Button from 'Admin/components/Button' + +const formatTimeInput = (inp: string) => { + // Remove non digit chars + const formatted = inp.replace(/[^\d]/g, '') + + return formatted.slice(0, 2) +} + +const formatTimeInpOnBlur = (inp: string, min: number, max: number) => { + // Remove non digit chars + let formatted = inp.replace(/[^\d]/g, '') + + if (formatted == '') formatted = '00' + let timeNumber = parseInt(formatted) + + if (timeNumber < min) timeNumber = min + else if (timeNumber > max) timeNumber = max + + if (timeNumber < 10) return `0${timeNumber}` + return `${timeNumber}` +} + +const validationSchema = z.object({ + eventDate: z.number(), + eventMonth: z.number(), + eventHour: z.string(), + eventMinute: z.string(), + participants: z.nullable(z.number().int()), +}) + +type FormData = { + eventDate: number + eventMonth: number + eventHour: string + eventMinute: string + participants?: number +} + +const CreatePizzaEventCard = ({ selectedDate }: { selectedDate: Date }) => { + const months = Array.from({ length: 12 }, (_, i) => { + return new Date(0, i).toLocaleString('en-UK', { month: 'long' }) + }) + + const date = selectedDate.getDate() + const currentMonth = selectedDate.getMonth() + const currentYear = selectedDate.getFullYear() + + const methods = useForm({ + resolver: zodResolver(validationSchema), + defaultValues: { + eventDate: selectedDate.getDate(), + eventMonth: selectedDate.getMonth(), + eventHour: '18', + eventMinute: '00', + participants: 5, + }, + }) + const { handleSubmit, control } = methods + const { closeModal } = useModal() + const { addEvent } = useEvents() + const { data: restaurantData } = useRestaurants() + + const findRestaurant = () => { + if (!restaurantData || !restaurantData.length) { + return + } + + const randomIndex = Math.floor(Math.random() * restaurantData.length) + return restaurantData[randomIndex] + } + + const onSubmit = (event: FormData) => { + const restaurant = findRestaurant() + const newEventDate = new Date( + currentYear, + event.eventMonth, + event.eventDate, + parseInt(event.eventHour), + parseInt(event.eventMinute), + ) + + if (restaurant) { + const event: ApiEventPost = { + time: newEventDate.toISOString(), + restaurant_id: restaurant.id, + people_per_event: 5, + } + addEvent(event) + closeModal() + } + } + + return ( +
+
+ Create Pizza Event? + closeModal()} + > + × + +
+ + +
+
+

Date:

+

Time:

+ +
+ {date}. + {months[currentMonth]} + } + /> + ( + + )} + /> +
+ +
+
+ ( + { + e.target.value = formatTimeInput(e.target.value) + field.onChange(e) + }} + onBlur={(e) => { + e.target.value = formatTimeInpOnBlur(e.target.value, 0, 23) + field.onChange(e) + }} + value={field.value} + /> + )} + /> +
+ : +
+ ( + { + e.target.value = formatTimeInput(e.target.value) + field.onChange(e) + }} + onBlur={(e) => { + e.target.value = formatTimeInpOnBlur(e.target.value, 0, 59) + field.onChange(e) + }} + value={field.value} + /> + )} + /> +
+
+
+
+
+
+
+
+
+
+ ) +} + +export { CreatePizzaEventCard } diff --git a/application/next-frontend/src/Admin/scenarios/Events/components/DeletePizzaEventCard.tsx b/application/next-frontend/src/Admin/scenarios/Events/components/DeletePizzaEventCard.tsx new file mode 100644 index 00000000..ed31160a --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Events/components/DeletePizzaEventCard.tsx @@ -0,0 +1,65 @@ +import Button from '@/Admin/components/Button' +import { useModal } from 'Shared/context/ModalContext' +import { useEvents, ApiEvent } from '@/api/useEvents' +import ordinal from 'ordinal' + +// Should pass ApiEvent instead and use those values + +type Props = { + event: ApiEvent +} + +const DeletePizzaEventCard = ({ event }: Props) => { + const [eventDate, eventId] = [new Date(event.time), event.id] + const months = Array.from({ length: 12 }, (_, i) => { + return new Date(0, i).toLocaleString('en-UK', { month: 'long' }) + }) + const [date, month, time] = [ + eventDate.getDate(), + eventDate.getMonth(), + `${eventDate.getHours()}:${String(eventDate.getMinutes()).padStart(2, '0')}`, + ] + const { delEvent } = useEvents() + const { closeModal } = useModal() + + return ( +
+
+ Delete Pizza Event? + closeModal()} + > + × + +
+ +
+ Date: + Time +
+ +
+ + {ordinal(date)} of {months[month]} + + {time} +
+ +
+
+
+ ) +} + +export { DeletePizzaEventCard } diff --git a/application/next-frontend/src/Admin/scenarios/Events/components/EventCalendar.tsx b/application/next-frontend/src/Admin/scenarios/Events/components/EventCalendar.tsx new file mode 100644 index 00000000..3e55cf1c --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Events/components/EventCalendar.tsx @@ -0,0 +1,221 @@ +import { useMemo, useState } from 'react' +import Image from 'next/image' +import Triangle from 'Admin/assets/Triangle.svg' +import Line from 'Admin/assets/Line.svg' +import PizzaEaten from 'Admin/assets/pizza/PizzaEaten.svg' +import PizzaRound from 'Admin/assets/pizza/PizzaRound.svg' +import PizzaSlice from 'Admin/assets/pizza/PizzaSlice.svg' +import { ApiEvent, useEvents } from '@/api/useEvents' +import { useModal } from 'Shared/context/ModalContext' +import { CreatePizzaEventCard } from './CreatePizzaEventCard' +import { DeletePizzaEventCard } from './DeletePizzaEventCard' +import { useRestaurants } from '@/api/useRestaurants' +import { sendToast } from '@/Shared/toast/defaultToast' +import { useCurrentChannel } from '@/api/useCurrentChannel' + +type ModalData = { + eventId: string | number + selectedDate: Date + event: ApiEvent | undefined +} + +// Goes into the next month - 1 day, which is the last day of current month, and returns this day +// e.g. if we are in October, and our last day is 31st, we return 31 +const daysInMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate() + +// Default is monday 1 ... saturday 6, sunday 0 +// This changes to monday 0 .. saturday 5, sunday 6 +const firstDayOfMonth = (date: Date) => { + const firstDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay() - 1 + return firstDay === -1 ? 6 : firstDay +} + +const EventCalendar = () => { + // This will always be the first of the month, but is used for Year + Month purposes + const [currentDate, setCurrentDate] = useState(new Date()) + const pizzaImages = [PizzaEaten, PizzaRound, PizzaSlice] + const daysInCurrentMonth = Array(daysInMonth(currentDate)) + .fill(null) + .map((_, i) => i + 1) + const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + const months = Array.from({ length: 12 }, (_, i) => { + return new Date(0, i).toLocaleString('en-US', { month: 'long' }) + }) + const fullDay = new Date() + const today = new Date(fullDay.getFullYear(), fullDay.getMonth(), fullDay.getDate()) + const { data } = useEvents() + + const { openModal } = useModal() + const { data: restaurantData } = useRestaurants() + const { data: currentChannelData } = useCurrentChannel() + + const handleOnClick = (modalData: ModalData) => { + if ((!restaurantData || !restaurantData.length) && !modalData.event) { + sendToast('You need to have added restaurants in order to create events.') + } else if (!currentChannelData || !currentChannelData.channel_id) { + sendToast('You need to have set a channel for the bot in order to create events.') + } else { + openModal( + modalData.event ? ( + + ) : ( + + ), + ) + } + } + + const eventsForCurrentMonth = useMemo(() => { + const getEventsForCurrentMonth = () => { + if (!data || data.length == 0) return [] + + return data + .filter((event) => { + const eventDate = new Date(event.time) + return ( + eventDate.getMonth() === currentDate.getMonth() && + eventDate.getFullYear() === currentDate.getFullYear() + ) + }) + .map((event) => [event.id, new Date(event.time).getDate()]) + } + return getEventsForCurrentMonth() + }, [data, currentDate]) + + const getEventId = (selectedDate: Date) => { + if (selectedDate < today) return -1 + + const selectedDay = selectedDate.getDate() + const eventIndex = eventsForCurrentMonth.findIndex(([, m]) => m === selectedDay) + if (eventIndex == -1) return -1 + + const eventId = eventsForCurrentMonth[eventIndex][0] + return eventId + } + + const renderMonth = () => { + // Reverse the list so that we can pop instead of shift when rendering rows + const remainingDays = [...daysInCurrentMonth.reverse()] + const weekRow = [] + const start = firstDayOfMonth(currentDate) + weekRow.push(renderRow(remainingDays, start, 6)) + + while (remainingDays.length > 0) { + weekRow.push(renderRow(remainingDays, 0, Math.min(6, remainingDays.length - 1))) + } + return weekRow + } + + const renderRow = (remainingDays: number[], startIndex: number, endIndex: number) => { + return ( + + {Array(7) + .fill(null) + .map((_, dayOfWeek) => { + if (startIndex <= dayOfWeek && dayOfWeek <= endIndex) { + const day = remainingDays.pop() + if (day) { + // currentToday is at 00:00, so therefore comparisons with today will always yield LT + // "current" as in, the date picked in the calendar + const currentToday = new Date(currentDate.getFullYear(), currentDate.getMonth(), day) + const currentTomorrow = new Date( + currentDate.getFullYear(), + currentDate.getMonth(), + day + 1, + ) + + // Event data. eventId is possibly -1, eventObject is possibly undefined + const eventId = getEventId(currentToday) + const eventObject = data?.find((event) => event.id == eventId) + + // Styling + const image = pizzaImages[Math.floor(Math.random() * pizzaImages.length)] + const styling = `h-[3.75rem] w-[4.15rem] border border-green-primary text-dark + ${ + today >= currentTomorrow + ? 'opacity-50' + : eventsForCurrentMonth.some(([, d]) => d == day) + ? 'cursor-pointer bg-green-primary text-white hover:bg-red-highlight' + : 'cursor-pointer bg-white hover:bg-green-secondary' + }` + + return today >= currentTomorrow ? ( + + {day} + + ) : ( + { + handleOnClick({ + eventId: eventId, + selectedDate: currentToday, + event: eventObject, + }) + }} + > + {day} + {eventId != -1 && ( + pizza + )} + + ) + } + } + return + })} + + ) + } + + const setPreviousMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1)) + } + + const setNextMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1)) + } + + return ( +
+
+ setPreviousMonth()} + width={20} + src={Triangle} + className="rotate-180 cursor-pointer" + alt="show previous month" + /> + {months[currentDate.getMonth()]} + setNextMonth()} + width={20} + src={Triangle} + alt="show next month" + className="cursor-pointer" + /> +
+ line separating calendar and month display + + + + {weekdays.map((day) => ( + + ))} + + + {renderMonth()} +
+ {day} +
+
+ ) +} + +export { EventCalendar } diff --git a/application/next-frontend/src/Admin/scenarios/Events/components/EventCalendarModal.tsx b/application/next-frontend/src/Admin/scenarios/Events/components/EventCalendarModal.tsx new file mode 100644 index 00000000..fd9e6916 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Events/components/EventCalendarModal.tsx @@ -0,0 +1,18 @@ +import { HoverProvider } from 'Shared/context/HoverContext' +import { Events } from '@/Admin/scenarios/Events/' +import { EventCalendar } from '@/Admin/scenarios/Events/components/EventCalendar' + +const EventModal = () => { + return ( +
+
+ + + + +
+
+ ) +} + +export { EventModal } diff --git a/application/next-frontend/src/Admin/scenarios/Events/components/NextEventInfo.tsx b/application/next-frontend/src/Admin/scenarios/Events/components/NextEventInfo.tsx new file mode 100644 index 00000000..ca8f3143 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Events/components/NextEventInfo.tsx @@ -0,0 +1,71 @@ +import { useHover } from '@/Shared/context/HoverContext' +import { ApiInvitation, useInvitations } from '@/api/useInvitations' + +const InvitationsStatus = ({ invitations }: { invitations: ApiInvitation[] }) => { + const responses = invitations.reduce( + (prev, next) => { + if (next.rsvp === 'unanswered') prev.waiting++ + else if (next.rsvp === 'attending') prev.accepted++ + else if (next.rsvp === 'not attending') prev.declined++ + return prev + }, + { waiting: 0, accepted: 0, declined: 0 }, + ) + + return ( +
+ {responses.accepted} Accepted + {responses.waiting} Waiting + {responses.declined} Declined +
+ ) +} + +const NextEventInfo = ({ + event_id, + resturantName, + date, + time, + meridiem, +}: { + event_id: string + resturantName: string + date: string + time: string + meridiem: string +}) => { + const { data, error, isLoading } = useInvitations(event_id) + + const { isHovered } = useHover() + + return ( +
+
Next event:
+

+ {resturantName} +

+

+ {date} +

+ + {time} {meridiem} + + +
+

Invitation status:

+ + {isLoading ? ( + 'Loading...' + ) : error ? ( + `Failed to load invitation status. ${error?.info.msg}` + ) : !data || !data.length ? ( + 'Invitations have not been sent yet.' + ) : ( + + )} +
+
+ ) +} + +export { NextEventInfo } diff --git a/application/next-frontend/src/Admin/scenarios/Events/index.tsx b/application/next-frontend/src/Admin/scenarios/Events/index.tsx new file mode 100644 index 00000000..27913cc9 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Events/index.tsx @@ -0,0 +1,72 @@ +import { CardComponentWrapper } from '@/Admin/components/CardComponentWrapper' +import { useEvents } from '@/api/useEvents' +import { ApiEvent } from '@/api/useEvents' +import { format } from 'date-fns' +import { NextEventInfo } from './components/NextEventInfo' +import { useEffect, useState } from 'react' +import { useModal } from 'Shared/context/ModalContext' +import { EventModal } from 'Admin/scenarios/Events/components/EventCalendarModal' + +const futureDate = (date: ApiEvent) => new Date(date.time) >= new Date() + +const differenceBetweenTwoDates = (date1: ApiEvent, date2: ApiEvent) => + new Date(date1.time).getTime() - new Date(date2.time).getTime() + +const eventDateFormatted = (date: string) => format(new Date(date), 'EEEE, dd. MMMM') + +const eventTimeFormatted = (date: string) => + format(new Date(date), 'h:mm a').toLowerCase().replace('am', 'a.m.').replace('pm', 'p.m.').split(' ') + +const upcomingEventsMessage = (eventsNumber: number) => + eventsNumber === 1 ? 'You have 1 upcoming event.' : `You have ${eventsNumber} upcoming events.` + +const Events = ({ clickable = true }: { clickable?: boolean }) => { + const { data, isLoading, error } = useEvents() + const futureEvents = data?.filter(futureDate).sort(differenceBetweenTwoDates) ?? [] + const [time, meridiem] = futureEvents.length > 0 ? eventTimeFormatted(futureEvents[0].time) : ['0', '0'] + const [eventModalShowing, setEventModalShowing] = useState(false) + const { openModal, modalStack } = useModal() + + const handleOnEventClick = () => { + setEventModalShowing(true) + openModal() + } + + useEffect(() => { + if (modalStack.length == 0) { + setEventModalShowing(false) + } + }, [modalStack]) + + return ( +
+ + {isLoading ? ( + 'Loading...' + ) : error ? ( + `Failed to load events. ${error?.info.msg}` + ) : !data || !data.length || !futureEvents.length ? ( + '' + ) : ( + + )} +
+ {upcomingEventsMessage(futureEvents.length)} +
+
+
+ ) +} + +export { Events } diff --git a/application/next-frontend/src/Admin/scenarios/Navbar.tsx b/application/next-frontend/src/Admin/scenarios/Navbar.tsx new file mode 100644 index 00000000..dbc97c89 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Navbar.tsx @@ -0,0 +1,58 @@ +import { useRouter } from 'next/router' +import { apiRequestHelper } from '@/api/utils' +import { useState } from 'react' +import Image from 'next/image' +import PizzaBotLogo from 'Admin/assets/PizzaBotLogo.svg' + +const Navbar = () => { + const router = useRouter() + const [activeIndex, setActiveIndex] = useState(2) + + const logout = async () => { + try { + const res = await apiRequestHelper.del<{ msg: string }>('/auth/logout') + if (res) router.push('/login') + } catch (err) { + console.error(err) + } + } + + const navbarEntries = [ + { + text: 'Help', + key: 0, + }, + { + text: 'Policy', + key: 1, + }, + { + text: 'Admin', + key: 2, + }, + ] + + return ( +
+
+ pizza bot logo +
+ {navbarEntries.map((entry) => ( + + ))} + +
+ ) +} + +export { Navbar } diff --git a/application/next-frontend/src/Admin/scenarios/Onboarding/components/EventConfig.tsx b/application/next-frontend/src/Admin/scenarios/Onboarding/components/EventConfig.tsx new file mode 100644 index 00000000..d73fcd47 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Onboarding/components/EventConfig.tsx @@ -0,0 +1,109 @@ +import { Controller, FormProvider, useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import z from 'zod' +import { TextField } from '@mui/material' +import { ApiEventPost, useEvents } from '@/api/useEvents' +import DatePicker from 'react-datepicker' +import { useRestaurants } from '@/api/useRestaurants' +import 'react-datepicker/dist/react-datepicker.css' +import { useRouter } from 'next/router' +import Button from '@/Admin/components/Button' + +const EventConfig = () => { + const router = useRouter() + const validationSchema = z.object({ + dateTime: z.date().refine((value) => value !== null, { message: 'Date is required' }), + }) + + interface FormData { + dateTime: Date + } + + const { data: restaurantData } = useRestaurants() + + const findRestauarant = () => { + if (restaurantData === undefined || restaurantData.length === 0) { + return + } + + const randomIndex = Math.floor(Math.random() * restaurantData.length) + return restaurantData[randomIndex] + } + + const today = new Date() + + const methods = useForm({ + resolver: zodResolver(validationSchema), + defaultValues: { + dateTime: today, + }, + }) + const { handleSubmit } = methods + + const { addEvent } = useEvents() + + const onSubmit = (data: FormData) => { + const restaurant = findRestauarant() + + if (restaurant) { + const event: ApiEventPost = { + time: data.dateTime.toISOString(), + people_per_event: 5, + restaurant_id: restaurant.id, + } + addEvent(event) + } + } + + return ( +
+

2/2

+
+ Set up your first event! +
+ +
+
+

Date:

+ ( + field.onChange(date)} + customInput={} + dateFormat="MMMM d, yyyy h:mm aa" + required + minDate={today} + /> + )} + /> +

*Invitations are sent out 10 days in advance*

+
+
+
+
+ + +
+ ) +} + +export { EventConfig } diff --git a/application/next-frontend/src/Admin/scenarios/Onboarding/components/RestaurantsConfig.tsx b/application/next-frontend/src/Admin/scenarios/Onboarding/components/RestaurantsConfig.tsx new file mode 100644 index 00000000..48fd7058 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Onboarding/components/RestaurantsConfig.tsx @@ -0,0 +1,98 @@ +import Button from '@/Admin/components/Button' +import { Restaurant, useRestaurants } from '@/api/useRestaurants' +import { zodResolver } from '@hookform/resolvers/zod' +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import { z } from 'zod' + +interface RestaurantsConfigProps { + onNext: () => void +} + +const RestaurantsConfig = (props: RestaurantsConfigProps) => { + const { addRestaurant } = useRestaurants() + const [restaurants, setRestaurants] = useState([]) + + const validationSchema = z.object({ + name: z.string().min(1, { message: 'Name required' }), + }) + + const { register, handleSubmit, reset, getValues } = useForm({ + resolver: zodResolver(validationSchema), + }) + + const validateForm = (data: Restaurant) => { + setRestaurants((prevRestaurants) => [data, ...prevRestaurants]) + reset() + } + + const addAllRestaurants = () => { + if (restaurants.length !== 0) { + restaurants.forEach(addRestaurant) + setRestaurants([]) + props.onNext() + } + } + + const deleteRestaurant = (index: number) => { + setRestaurants((prevRestaurants) => prevRestaurants.filter((_, i) => i !== index)) + } + + const handleAddClick = () => { + const { name } = getValues() + if (name) { + validateForm({ name }) + } + } + + return ( +
+

1/2

+
+ What Pizza Places would you like to visit? +
+
+
{ + validateForm(data) + })} + className="flex flex-col px-6" + > + +
+
+
+ {restaurants.map((restaurant, index) => ( +
+ +

{restaurant.name}

+
+ ))} +
+
+
+
+
+
+ ) +} + +export { RestaurantsConfig } diff --git a/application/next-frontend/src/Admin/scenarios/Onboarding/index.tsx b/application/next-frontend/src/Admin/scenarios/Onboarding/index.tsx new file mode 100644 index 00000000..83611623 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Onboarding/index.tsx @@ -0,0 +1,23 @@ +import { AdminSidebar } from 'Admin/components/AdminSidebar' +import { RestaurantsConfig } from './components/RestaurantsConfig' +import { EventConfig } from './components/EventConfig' +import { useState } from 'react' + +const Onboarding = () => { + const [step, setStep] = useState(0) + + return ( +
+
+
+ +
+
+
+ {step === 0 ? setStep(1)} /> : } +
+
+ ) +} + +export { Onboarding } diff --git a/application/next-frontend/src/Admin/scenarios/Restaurants/components/NewRestaurantModal.tsx b/application/next-frontend/src/Admin/scenarios/Restaurants/components/NewRestaurantModal.tsx new file mode 100644 index 00000000..28cdea7f --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Restaurants/components/NewRestaurantModal.tsx @@ -0,0 +1,85 @@ +import { useForm } from 'react-hook-form' +import { z } from 'zod' +import { zodResolver } from '@hookform/resolvers/zod' +import React from 'react' +import { useModal } from 'Shared/context/ModalContext' +import { useRestaurants, Restaurant } from '@/api/useRestaurants' + +const validationSchema = z.object({ + name: z.string().min(1, { message: 'Name required' }), + link: z.string().optional(), + tlf: z.string().optional(), + address: z.string().optional(), +}) + +const NewRestaurantModal = () => { + const { + register, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ resolver: zodResolver(validationSchema) }) + + const { closeModal } = useModal() + const { data, addRestaurant, delRestaurant } = useRestaurants() + + const validateForm = (data: Restaurant) => { + const newRestaurant: Restaurant = { name: data.name } + addRestaurant(newRestaurant) + setValue('name', '') + } + + return ( +
+
+
Places
+ closeModal()}> + × + +
+
+
+
+
You have added...
+

+ {data?.length || 0} Pizza Places +

+
+ +
+
{ + validateForm(data as Restaurant) + })} + className="flex flex-col px-6" + > + + {errors.name &&

} +
+ +
+
+ {data && + data.map((restaurant) => ( +
delRestaurant(restaurant.id)} + key={restaurant.id} + > + +

{restaurant.name}

+
+ ))} +
+
+
+
+
+ ) +} + +export { NewRestaurantModal } diff --git a/application/next-frontend/src/Admin/scenarios/Restaurants/index.tsx b/application/next-frontend/src/Admin/scenarios/Restaurants/index.tsx new file mode 100644 index 00000000..c49afe17 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/Restaurants/index.tsx @@ -0,0 +1,42 @@ +import { NewRestaurantModal } from './components/NewRestaurantModal' +import { CardComponentWrapper } from 'Admin/components/CardComponentWrapper' +import { useRestaurants } from '@/api/useRestaurants' +import { useModal } from 'Shared/context/ModalContext' +import { useHover } from 'Shared/context/HoverContext' + +const Restaurants = () => { + const { data, isLoading, error } = useRestaurants() + const { openModal } = useModal() + const { isHovered } = useHover() + + const handleOnClickCard = () => { + openModal() + } + + return ( + +
+ {isLoading ? ( + 'Loading...' + ) : error ? ( + `Failed to load users due to the following error: ${error?.info.msg}` + ) : ( +
+
+ {!data || data.length == 0 ? 'No restaurants found...' : 'You have added...'} +
+

+ + {!data || data.length == 0 + ? 'Try adding one!' + : `${data.length} ${data.length == 1 ? 'Pizza Place' : 'Pizza Places'}`} + +

+
+ )} +
+
+ ) +} + +export { Restaurants } diff --git a/application/next-frontend/src/Admin/scenarios/SlackChannel.tsx b/application/next-frontend/src/Admin/scenarios/SlackChannel.tsx new file mode 100644 index 00000000..4a2203c8 --- /dev/null +++ b/application/next-frontend/src/Admin/scenarios/SlackChannel.tsx @@ -0,0 +1,42 @@ +import { useCurrentChannel } from '@/api/useCurrentChannel' +import { SlackUser } from '@/api/useSlackUsers' +import { CardComponentWrapper } from '../components/CardComponentWrapper' +import { useHover } from '@/Shared/context/HoverContext' + +const numUsersInChannel = (users: SlackUser[]) => { + if (users.length === 0) return 'No users in channel' + if (users.length === 1) return '1 user in channel' + return `${users.length} users in channel` +} + +const SlackChannel = () => { + const { data, isLoading, error } = useCurrentChannel() + + const channelName = data?.channel_name + const channelMembers = data?.users.filter((user) => user.active) ?? [] + const { isHovered } = useHover() + + return ( + + {isLoading ? ( + 'Loading...' + ) : error ? ( + `Failed to load channel info due to the following error: ${error?.info.msg}` + ) : !data || !data.channel_id ? ( +
+ No slack channel has been set for the PizzaBot. Please use the command{' '} + /set‑pizza‑channel in your preferred slack-channel to set. +
+ ) : ( +
+ + #{channelName} + + {numUsersInChannel(channelMembers)} +
+ )} +
+ ) +} + +export { SlackChannel } diff --git a/application/next-frontend/src/Admin/types/User.ts b/application/next-frontend/src/Admin/types/User.ts new file mode 100644 index 00000000..8361e73c --- /dev/null +++ b/application/next-frontend/src/Admin/types/User.ts @@ -0,0 +1,18 @@ +export type User = { + email: string + picture: string + id: string + name: string + roles: Array +} + +export type JwtToken = { + fresh: string + iat: string + jti: string + type: string + sub: string + nbf: string + exp: string + user: User +} diff --git a/application/next-frontend/src/Landing/assets/Amalie.svg b/application/next-frontend/src/Landing/assets/Amalie.svg new file mode 100644 index 00000000..d6265c93 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/Amalie.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/Line.svg b/application/next-frontend/src/Landing/assets/Line.svg new file mode 100644 index 00000000..a8ed263c --- /dev/null +++ b/application/next-frontend/src/Landing/assets/Line.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Landing/assets/Mathilde.svg b/application/next-frontend/src/Landing/assets/Mathilde.svg new file mode 100644 index 00000000..b14bded8 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/Mathilde.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/PizzaAnnouncement.png b/application/next-frontend/src/Landing/assets/PizzaAnnouncement.png new file mode 100644 index 00000000..df533262 Binary files /dev/null and b/application/next-frontend/src/Landing/assets/PizzaAnnouncement.png differ diff --git a/application/next-frontend/src/Landing/assets/PizzaBotWithFourArms.png b/application/next-frontend/src/Landing/assets/PizzaBotWithFourArms.png new file mode 100644 index 00000000..449da951 Binary files /dev/null and b/application/next-frontend/src/Landing/assets/PizzaBotWithFourArms.png differ diff --git a/application/next-frontend/src/Landing/assets/PizzaInvitation.png b/application/next-frontend/src/Landing/assets/PizzaInvitation.png new file mode 100644 index 00000000..25e0b2fd Binary files /dev/null and b/application/next-frontend/src/Landing/assets/PizzaInvitation.png differ diff --git a/application/next-frontend/src/Landing/assets/Vilde.svg b/application/next-frontend/src/Landing/assets/Vilde.svg new file mode 100644 index 00000000..d30f2b40 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/Vilde.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/chooseChannel.svg b/application/next-frontend/src/Landing/assets/chooseChannel.svg new file mode 100644 index 00000000..8edb3cd0 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/chooseChannel.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/chooseDate.svg b/application/next-frontend/src/Landing/assets/chooseDate.svg new file mode 100644 index 00000000..e2d0522e --- /dev/null +++ b/application/next-frontend/src/Landing/assets/chooseDate.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/chooseRestaurants.svg b/application/next-frontend/src/Landing/assets/chooseRestaurants.svg new file mode 100644 index 00000000..fd0de7f6 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/chooseRestaurants.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/EngagedTeams.svg b/application/next-frontend/src/Landing/assets/illustrations/EngagedTeams.svg new file mode 100644 index 00000000..0eafd164 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/EngagedTeams.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/Friendships.svg b/application/next-frontend/src/Landing/assets/illustrations/Friendships.svg new file mode 100644 index 00000000..066e9848 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/Friendships.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/GoodTraditions.svg b/application/next-frontend/src/Landing/assets/illustrations/GoodTraditions.svg new file mode 100644 index 00000000..a2b3334e --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/GoodTraditions.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/HandWithTopping.svg b/application/next-frontend/src/Landing/assets/illustrations/HandWithTopping.svg new file mode 100644 index 00000000..1599db21 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/HandWithTopping.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/HappyMascotLanding.svg b/application/next-frontend/src/Landing/assets/illustrations/HappyMascotLanding.svg new file mode 100644 index 00000000..5b5e57b4 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/HappyMascotLanding.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/next-frontend/src/Landing/assets/illustrations/Leaves1.svg b/application/next-frontend/src/Landing/assets/illustrations/Leaves1.svg new file mode 100644 index 00000000..d4019273 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/Leaves1.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/Leaves2.svg b/application/next-frontend/src/Landing/assets/illustrations/Leaves2.svg new file mode 100644 index 00000000..0030c7c5 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/Leaves2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/application/next-frontend/src/Landing/assets/illustrations/MascotSad.svg b/application/next-frontend/src/Landing/assets/illustrations/MascotSad.svg new file mode 100644 index 00000000..76f9399c --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/MascotSad.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/MascotWithFourArms.svg b/application/next-frontend/src/Landing/assets/illustrations/MascotWithFourArms.svg new file mode 100644 index 00000000..c679969d --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/MascotWithFourArms.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/PizzaBotLogo.svg b/application/next-frontend/src/Landing/assets/illustrations/PizzaBotLogo.svg new file mode 100644 index 00000000..d1a76690 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/PizzaBotLogo.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/PizzaBotLogoFooter.svg b/application/next-frontend/src/Landing/assets/illustrations/PizzaBotLogoFooter.svg new file mode 100644 index 00000000..4a101c7c --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/PizzaBotLogoFooter.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/PizzaBotYellow.svg b/application/next-frontend/src/Landing/assets/illustrations/PizzaBotYellow.svg new file mode 100644 index 00000000..f4ffd54a --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/PizzaBotYellow.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/SafeEnvironments.svg b/application/next-frontend/src/Landing/assets/illustrations/SafeEnvironments.svg new file mode 100644 index 00000000..492527f6 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/SafeEnvironments.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/SlackLogo.svg b/application/next-frontend/src/Landing/assets/illustrations/SlackLogo.svg new file mode 100644 index 00000000..52a69777 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/SlackLogo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/SlackLogoWhite.svg b/application/next-frontend/src/Landing/assets/illustrations/SlackLogoWhite.svg new file mode 100644 index 00000000..7e229dd4 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/SlackLogoWhite.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/application/next-frontend/src/Landing/assets/illustrations/Tape.svg b/application/next-frontend/src/Landing/assets/illustrations/Tape.svg new file mode 100644 index 00000000..d4464565 --- /dev/null +++ b/application/next-frontend/src/Landing/assets/illustrations/Tape.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/next-frontend/src/Landing/components/AddToSlackButton.tsx b/application/next-frontend/src/Landing/components/AddToSlackButton.tsx new file mode 100644 index 00000000..ad49f484 --- /dev/null +++ b/application/next-frontend/src/Landing/components/AddToSlackButton.tsx @@ -0,0 +1,39 @@ +import Image from 'next/image' +import SlackLogo from '@/Landing/assets/illustrations/SlackLogo.svg' +import SlackLogoWhite from '@/Landing/assets/illustrations/SlackLogoWhite.svg' +import { clientsideApiUri } from '@/api/endpoints' + +const addToSlack = async () => { + const res = await fetch(clientsideApiUri + '/slack/install', { method: 'GET' }).then((res) => res.json()) + const redirectURL = res.redirect_url + + if (redirectURL) window.location.assign(redirectURL) +} + +const AddToSlackButton = () => { + return ( + + ) +} + +const AddToSlackButtonWhite = () => { + return ( + + ) +} + +export { AddToSlackButton, AddToSlackButtonWhite } diff --git a/application/next-frontend/src/Landing/components/ImageWithPostit.tsx b/application/next-frontend/src/Landing/components/ImageWithPostit.tsx new file mode 100644 index 00000000..e8ed6fda --- /dev/null +++ b/application/next-frontend/src/Landing/components/ImageWithPostit.tsx @@ -0,0 +1,34 @@ +import Image from 'next/image' +import Tape from 'Landing/assets/illustrations/Tape.svg' +import { useState } from 'react' + +type Props = { + src: string + text: string + styles: { + tape: string + postit: string + postitHover: string + postitHoverAlternative: string + } +} + +const ImageWithPostit = ({ src, text, styles }: Props) => { + const [isHovered, setIsHovered] = useState(false) + + return ( +
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> + tape + fun +
+ {text} +
+
+ ) +} + +export { ImageWithPostit } diff --git a/application/next-frontend/src/Landing/components/secondSection/ImageContainer.tsx b/application/next-frontend/src/Landing/components/secondSection/ImageContainer.tsx new file mode 100644 index 00000000..dff74cc8 --- /dev/null +++ b/application/next-frontend/src/Landing/components/secondSection/ImageContainer.tsx @@ -0,0 +1,15 @@ +import { StaticImport } from 'next/dist/shared/lib/get-img-props' +import Image from 'next/image' + +const ImageContainer = ({ img, text }: { img: string | StaticImport; text: string }) => { + return ( +
+
+ {text} +
+ {text} +
+ ) +} + +export { ImageContainer } diff --git a/application/next-frontend/src/Landing/index.tsx b/application/next-frontend/src/Landing/index.tsx new file mode 100644 index 00000000..9e2524d9 --- /dev/null +++ b/application/next-frontend/src/Landing/index.tsx @@ -0,0 +1,23 @@ +import { FirstSection } from './scenarios/FirstSection' +import { SecondSection } from './scenarios/SecondSection' +import { ThirdSection } from './scenarios/ThirdSection' +import { FourthSection } from './scenarios/FourthSection' +import { FifthSection } from './scenarios/FifthSection' +import { SixthSection } from './scenarios/SixthSection' +import { Footer } from './scenarios/Footer' + +const Landing = () => { + return ( + <> + + + + + + +