From de8ce8c83d7886eb37db9768446697491a773cc9 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Mon, 9 Jun 2025 08:50:02 -0400 Subject: [PATCH 01/25] Setup and structure for testing -Multiple databases that are switched using an environment variable so the db server is not needed during testing -Clean up some dev/prod setup for starting the server so gunicorn/docker are not needed in dev/testing --- api/.env | 2 ++ api/config/config.py | 2 +- api/database/IQueue.py | 12 ++++++++ api/database/IRatings.py | 8 +++++ api/database/db.py | 22 ++++++++++++++ api/database/interface.py | 12 ++++++++ api/database/mock_db/MockDB.py | 7 +++++ api/database/mock_db/MockDBQueue.py | 8 +++++ api/database/mock_db/MockDBRatings.py | 8 +++++ api/database/relational_db/RelationalDB.py | 7 +++++ .../relational_db/RelationalDBQueue.py | 12 ++++++++ .../relational_db/RelationalDBRatings.py | 8 +++++ api/database/testing_db/TestingDB.py | 7 +++++ api/database/testing_db/TestingDBQueue.py | 13 +++++++++ api/database/testing_db/TestingDBRatings.py | 8 +++++ api/models/visits.py | 2 +- api/queue/routes.py | 6 +++- api/requirements.txt | 2 ++ api/run_local.py | 9 ++++++ api/server.py | 8 +++-- api/utils/logging.py | 3 ++ compose.yaml | 3 +- test.py | 5 ++++ tests/api/.env | 2 ++ tests/api/MockDB.py | 6 ++++ tests/api/test_test.py | 29 +++++++++++++++++++ tests/requirements.txt | 3 ++ 27 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 api/.env create mode 100644 api/database/IQueue.py create mode 100644 api/database/IRatings.py create mode 100644 api/database/db.py create mode 100644 api/database/interface.py create mode 100644 api/database/mock_db/MockDB.py create mode 100644 api/database/mock_db/MockDBQueue.py create mode 100644 api/database/mock_db/MockDBRatings.py create mode 100644 api/database/relational_db/RelationalDB.py create mode 100644 api/database/relational_db/RelationalDBQueue.py create mode 100644 api/database/relational_db/RelationalDBRatings.py create mode 100644 api/database/testing_db/TestingDB.py create mode 100644 api/database/testing_db/TestingDBQueue.py create mode 100644 api/database/testing_db/TestingDBRatings.py create mode 100644 api/run_local.py create mode 100644 test.py create mode 100644 tests/api/.env create mode 100644 tests/api/MockDB.py create mode 100644 tests/api/test_test.py create mode 100644 tests/requirements.txt diff --git a/api/.env b/api/.env new file mode 100644 index 0000000..8cb26b5 --- /dev/null +++ b/api/.env @@ -0,0 +1,2 @@ +API_MODE=dev +DB=relational \ No newline at end of file diff --git a/api/config/config.py b/api/config/config.py index d7414d4..2c70389 100644 --- a/api/config/config.py +++ b/api/config/config.py @@ -4,7 +4,7 @@ class Config: - """Configuration class for MOJ api server, stores current configuration state of flask api""" + """Configuration class for MOH api server, stores current configuration state of flask api""" def __init__(self): self.API_MODE = os.getenv("API_MODE", "Can not find mode") diff --git a/api/database/IQueue.py b/api/database/IQueue.py new file mode 100644 index 0000000..cdae80e --- /dev/null +++ b/api/database/IQueue.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod + + +class IQueue(ABC): + + @abstractmethod + def enqueue_student(self, student): + raise NotImplementedError() + + @abstractmethod + def dequeue_student(self): + raise NotImplementedError() diff --git a/api/database/IRatings.py b/api/database/IRatings.py new file mode 100644 index 0000000..ce0f54a --- /dev/null +++ b/api/database/IRatings.py @@ -0,0 +1,8 @@ + +from abc import ABC, abstractmethod + +class IRatings: + + @abstractmethod + def rate_student(self, student, rating, feedback): + raise NotImplementedError() \ No newline at end of file diff --git a/api/database/db.py b/api/database/db.py new file mode 100644 index 0000000..12bafd3 --- /dev/null +++ b/api/database/db.py @@ -0,0 +1,22 @@ +import os + +from api.database.relational_db.RelationalDB import RelationalDB +from api.database.testing_db.TestingDB import TestingDB +from api.database.mock_db.MockDB import MockDB + + +def create_db(): + db_type = os.getenv("DB") + match db_type: + case "relational": + return RelationalDB() + case "testing": + return TestingDB() + case "mock": + return MockDB() + case None: + raise EnvironmentError("environment variable \"DB\" not set") + case _: + raise ModuleNotFoundError("Could not find database named " + db_type) + +db = create_db() \ No newline at end of file diff --git a/api/database/interface.py b/api/database/interface.py new file mode 100644 index 0000000..123c6fb --- /dev/null +++ b/api/database/interface.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod + +from IQueue import IQueue +from IRatings import IRatings + +class DBInterface(IQueue, IRatings, ABC): + + # All database implements must extend this class + + @abstractmethod + def connect(self): + pass \ No newline at end of file diff --git a/api/database/mock_db/MockDB.py b/api/database/mock_db/MockDB.py new file mode 100644 index 0000000..48d032d --- /dev/null +++ b/api/database/mock_db/MockDB.py @@ -0,0 +1,7 @@ +from api.database.mock_db.MockDBQueue import MockDBQueue +from api.database.mock_db.MockDBRatings import MockDBRatings + + +class MockDB(MockDBQueue, MockDBRatings): + + pass \ No newline at end of file diff --git a/api/database/mock_db/MockDBQueue.py b/api/database/mock_db/MockDBQueue.py new file mode 100644 index 0000000..3ac32e1 --- /dev/null +++ b/api/database/mock_db/MockDBQueue.py @@ -0,0 +1,8 @@ +from api.database.IQueue import IQueue + + +class MockDBQueue(IQueue): + + def enqueue_student(self, student): + pass + # do database stuff \ No newline at end of file diff --git a/api/database/mock_db/MockDBRatings.py b/api/database/mock_db/MockDBRatings.py new file mode 100644 index 0000000..1c06eae --- /dev/null +++ b/api/database/mock_db/MockDBRatings.py @@ -0,0 +1,8 @@ +from api.database.IRatings import IRatings + + +class MockDBRatings(IRatings): + + def rate_student(self, student, rating, feedback): + pass + # do database stuff \ No newline at end of file diff --git a/api/database/relational_db/RelationalDB.py b/api/database/relational_db/RelationalDB.py new file mode 100644 index 0000000..e5ae155 --- /dev/null +++ b/api/database/relational_db/RelationalDB.py @@ -0,0 +1,7 @@ +from api.database.relational_db.RelationalDBQueue import RelationalDBQueue +from api.database.relational_db.RelationalDBRatings import RelationalDBRatings + + +class RelationalDB(RelationalDBQueue, RelationalDBRatings): + + pass \ No newline at end of file diff --git a/api/database/relational_db/RelationalDBQueue.py b/api/database/relational_db/RelationalDBQueue.py new file mode 100644 index 0000000..1a420bb --- /dev/null +++ b/api/database/relational_db/RelationalDBQueue.py @@ -0,0 +1,12 @@ +from api.database.IQueue import IQueue + + +class RelationalDBQueue(IQueue): + + def enqueue_student(self, student): + raise NotImplementedError() + # do database stuff + + + def dequeue_student(self): + pass \ No newline at end of file diff --git a/api/database/relational_db/RelationalDBRatings.py b/api/database/relational_db/RelationalDBRatings.py new file mode 100644 index 0000000..b0a4bde --- /dev/null +++ b/api/database/relational_db/RelationalDBRatings.py @@ -0,0 +1,8 @@ +from api.database.IRatings import IRatings + + +class RelationalDBRatings(IRatings): + + def rate_student(self, student, rating, feedback): + pass + # do database stuff \ No newline at end of file diff --git a/api/database/testing_db/TestingDB.py b/api/database/testing_db/TestingDB.py new file mode 100644 index 0000000..41bf41a --- /dev/null +++ b/api/database/testing_db/TestingDB.py @@ -0,0 +1,7 @@ +from api.database.testing_db.TestingDBQueue import TestingDBQueue +from api.database.testing_db.TestingDBRatings import TestingDBRatings + + +class TestingDB(TestingDBQueue, TestingDBRatings): + + pass \ No newline at end of file diff --git a/api/database/testing_db/TestingDBQueue.py b/api/database/testing_db/TestingDBQueue.py new file mode 100644 index 0000000..9dacfd3 --- /dev/null +++ b/api/database/testing_db/TestingDBQueue.py @@ -0,0 +1,13 @@ +from api.database.IQueue import IQueue + + +class TestingDBQueue(IQueue): + + def __init__(self): + self.queue = [] + + def enqueue_student(self, student): + self.queue.append(student) + + def dequeue_student(self): + return self.queue.pop(0) \ No newline at end of file diff --git a/api/database/testing_db/TestingDBRatings.py b/api/database/testing_db/TestingDBRatings.py new file mode 100644 index 0000000..e352650 --- /dev/null +++ b/api/database/testing_db/TestingDBRatings.py @@ -0,0 +1,8 @@ +from api.database.IRatings import IRatings + + +class TestingDBRatings(IRatings): + + def rate_student(self, student, rating, feedback): + pass + # do database stuff \ No newline at end of file diff --git a/api/models/visits.py b/api/models/visits.py index c1c0cdc..c79f5f5 100644 --- a/api/models/visits.py +++ b/api/models/visits.py @@ -1 +1 @@ -# All the infor for an OH visit \ No newline at end of file +# All the info for an OH visit \ No newline at end of file diff --git a/api/queue/routes.py b/api/queue/routes.py index 40da68f..2dddc57 100644 --- a/api/queue/routes.py +++ b/api/queue/routes.py @@ -2,6 +2,8 @@ from flask import Blueprint, request +from api.database.db import db + blueprint = Blueprint("queue", __name__) @@ -27,6 +29,7 @@ def enqueue_card_swipe(): @blueprint.route("/enqueue-ta-override", methods=["POST"]) def enqueue_ta_override(): + db.enqueue_student("Steve") """ Force enqueue a student into the queue. Only usable by TAs and instructors @@ -37,10 +40,11 @@ def enqueue_ta_override(): @blueprint.route("/dequeue", methods=["DELETE"]) def dequeue(): + student = db.dequeue_student() """ Remove the first student from the queue and create a Visit in the DB """ - return "" + return student @blueprint.route("/get-queue", methods=["GET"]) diff --git a/api/requirements.txt b/api/requirements.txt index 2f904d0..6a5d38d 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,3 +1,5 @@ Flask gunicorn requests + +python-dotenv \ No newline at end of file diff --git a/api/run_local.py b/api/run_local.py new file mode 100644 index 0000000..4d6e2e3 --- /dev/null +++ b/api/run_local.py @@ -0,0 +1,9 @@ +# Runs the app locally without Gunicorn. To be used for dev and testing + +from dotenv import load_dotenv +load_dotenv() + +from api.server import create_app + +app = create_app() +app.run() \ No newline at end of file diff --git a/api/server.py b/api/server.py index 81f72de..07f7c07 100644 --- a/api/server.py +++ b/api/server.py @@ -8,6 +8,7 @@ import datetime import io import json +import os import requests from flask import Flask, request @@ -15,10 +16,14 @@ from api.config import config from api.utils.debug import debug_access_only +from api.database.db import create_db import api.auth.routes as auth_routes import api.queue.routes as queue_routes import api.ratings.routes as ratings_routes +from api.database.db import db + + def create_app(): """Create and return Flask API server @@ -37,7 +42,7 @@ def create_app(): @app.route("/", methods=["GET"]) def home(): mode = app.config.get("API_MODE", "Can not find API_MODE") - return f"Welcome to the homepage, you are currently in {mode}" + return f"Welcome to the homepage, you are currently in {mode} mode" @app.route("/favicon.ico", methods=["GET"]) @debug_access_only @@ -58,4 +63,3 @@ def health(): return app -app = create_app() \ No newline at end of file diff --git a/api/utils/logging.py b/api/utils/logging.py index 9df057c..40434ed 100644 --- a/api/utils/logging.py +++ b/api/utils/logging.py @@ -1 +1,4 @@ """Logging utils for MOH API server""" + + +# log levels from an env var \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 89b45f3..9305e4b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,11 +4,12 @@ services: context: ./api/ environment: API_MODE: "dev" + DB: "relational" volumes: - ./api:/app/api ports: - "8000:8000" - command: flask --app api.server:app run --debug --host=0.0.0.0 --port=8000 + command: flask --app 'api.server:create_app()' run --debug --host=0.0.0.0 --port=8000 api-production: extends: diff --git a/test.py b/test.py new file mode 100644 index 0000000..f813911 --- /dev/null +++ b/test.py @@ -0,0 +1,5 @@ + +import api.database.db + +if __name__ == '__main__': + api.database.db.create_db() \ No newline at end of file diff --git a/tests/api/.env b/tests/api/.env new file mode 100644 index 0000000..7279bb1 --- /dev/null +++ b/tests/api/.env @@ -0,0 +1,2 @@ +API_MODE=testing +DB=testing \ No newline at end of file diff --git a/tests/api/MockDB.py b/tests/api/MockDB.py new file mode 100644 index 0000000..0c94bab --- /dev/null +++ b/tests/api/MockDB.py @@ -0,0 +1,6 @@ +from api.database.interface import DBInterface + + +class MockDB(DBInterface): + + pass \ No newline at end of file diff --git a/tests/api/test_test.py b/tests/api/test_test.py new file mode 100644 index 0000000..affde20 --- /dev/null +++ b/tests/api/test_test.py @@ -0,0 +1,29 @@ +import os + +import pytest +from dotenv import load_dotenv + +load_dotenv() + +from api.server import create_app + + +@pytest.fixture +def client(): + app = create_app() + app.testing = True + with app.test_client() as client: + yield client + + +def test_first_test(client): + response = client.get('/') + assert response.status_code == 200 + assert response.get_data() == b"Welcome to the homepage, you are currently in testing mode" + + +def test_that_needs_db(client): + client.post('/enqueue-ta-override', 'not used yet') + response = client.delete('/dequeue') + assert response.get_data() == b"Steve" + diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..bcea013 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +Flask +pytest +python-dotenv From 9e3470a165d640b6800c11d64016e522a9928564 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Wed, 11 Jun 2025 13:24:36 -0400 Subject: [PATCH 02/25] More overall structure for the project. Added roster and lots of TODOs --- api/auth/controller.py | 5 ++ api/auth/routes.py | 5 ++ api/database/IAccounts.py | 9 +++ api/database/IRoster.py | 9 +++ api/database/interface.py | 8 ++- api/database/mock_db/MockDB.py | 23 +++++-- api/database/mock_db/MockDBQueue.py | 8 --- api/database/mock_db/MockDBRatings.py | 8 --- api/database/relational_db/RelationalDB.py | 4 +- api/database/testing_db/TestingDB.py | 14 +++- api/models/live_queue.py | 17 ----- api/models/visits.py | 1 - api/queue/controller.py | 13 ++++ api/queue/routes.py | 76 +++++++++++++++++----- api/ratings/controller.py | 1 + api/ratings/routes.py | 8 ++- api/roster/routes.py | 18 +++++ api/server.py | 11 +--- api/test/.placeholder | 0 test.py | 5 -- tests/api/test_queue_basics.py | 32 +++++++++ 21 files changed, 202 insertions(+), 73 deletions(-) create mode 100644 api/auth/controller.py create mode 100644 api/database/IAccounts.py create mode 100644 api/database/IRoster.py delete mode 100644 api/database/mock_db/MockDBQueue.py delete mode 100644 api/database/mock_db/MockDBRatings.py delete mode 100644 api/models/live_queue.py delete mode 100644 api/models/visits.py create mode 100644 api/queue/controller.py create mode 100644 api/ratings/controller.py create mode 100644 api/roster/routes.py delete mode 100644 api/test/.placeholder delete mode 100644 test.py create mode 100644 tests/api/test_queue_basics.py diff --git a/api/auth/controller.py b/api/auth/controller.py new file mode 100644 index 0000000..a9dfc42 --- /dev/null +++ b/api/auth/controller.py @@ -0,0 +1,5 @@ +from api.database.db import db + +def create_account(username, numeric_identifier, auth_level='student'): + db.create_account(username, numeric_identifier) + pass diff --git a/api/auth/routes.py b/api/auth/routes.py index 3c6bc30..48170dd 100644 --- a/api/auth/routes.py +++ b/api/auth/routes.py @@ -29,3 +29,8 @@ def signup(): The status of the sign-up attempt """ return "Signup arrived" + + +# TODO: update preferred name + +# TODO: accounts has UBIT (For AL lookups) and pn (For card swipes) \ No newline at end of file diff --git a/api/database/IAccounts.py b/api/database/IAccounts.py new file mode 100644 index 0000000..3879951 --- /dev/null +++ b/api/database/IAccounts.py @@ -0,0 +1,9 @@ + +from abc import ABC, abstractmethod + +class IAccounts: + + @abstractmethod + def create_account(self, ubit, pn): + # return id + raise NotImplementedError() \ No newline at end of file diff --git a/api/database/IRoster.py b/api/database/IRoster.py new file mode 100644 index 0000000..d79603b --- /dev/null +++ b/api/database/IRoster.py @@ -0,0 +1,9 @@ + +from abc import ABC, abstractmethod + +class IRoster: + + @abstractmethod + def add_to_roster(self, user_id, role): + raise NotImplementedError() + diff --git a/api/database/interface.py b/api/database/interface.py index 123c6fb..e5feb43 100644 --- a/api/database/interface.py +++ b/api/database/interface.py @@ -1,9 +1,11 @@ from abc import ABC, abstractmethod -from IQueue import IQueue -from IRatings import IRatings +from api.database.IQueue import IQueue +from api.database.IRatings import IRatings +from api.database.IAccounts import IAccounts +from api.database.IRoster import IRoster -class DBInterface(IQueue, IRatings, ABC): +class DBInterface(IQueue, IRatings, IAccounts, IRoster, ABC): # All database implements must extend this class diff --git a/api/database/mock_db/MockDB.py b/api/database/mock_db/MockDB.py index 48d032d..376be27 100644 --- a/api/database/mock_db/MockDB.py +++ b/api/database/mock_db/MockDB.py @@ -1,7 +1,22 @@ -from api.database.mock_db.MockDBQueue import MockDBQueue -from api.database.mock_db.MockDBRatings import MockDBRatings +from api.database.interface import DBInterface -class MockDB(MockDBQueue, MockDBRatings): +class MockDB(DBInterface): - pass \ No newline at end of file + def connect(self): + pass + + def enqueue_student(self, student): + pass + + def dequeue_student(self): + pass + + def rate_student(self, student, rating, feedback): + pass + + def create_account(self, ubit, pn): + pass + + def add_to_roster(self, user_id, role): + pass \ No newline at end of file diff --git a/api/database/mock_db/MockDBQueue.py b/api/database/mock_db/MockDBQueue.py deleted file mode 100644 index 3ac32e1..0000000 --- a/api/database/mock_db/MockDBQueue.py +++ /dev/null @@ -1,8 +0,0 @@ -from api.database.IQueue import IQueue - - -class MockDBQueue(IQueue): - - def enqueue_student(self, student): - pass - # do database stuff \ No newline at end of file diff --git a/api/database/mock_db/MockDBRatings.py b/api/database/mock_db/MockDBRatings.py deleted file mode 100644 index 1c06eae..0000000 --- a/api/database/mock_db/MockDBRatings.py +++ /dev/null @@ -1,8 +0,0 @@ -from api.database.IRatings import IRatings - - -class MockDBRatings(IRatings): - - def rate_student(self, student, rating, feedback): - pass - # do database stuff \ No newline at end of file diff --git a/api/database/relational_db/RelationalDB.py b/api/database/relational_db/RelationalDB.py index e5ae155..acbb073 100644 --- a/api/database/relational_db/RelationalDB.py +++ b/api/database/relational_db/RelationalDB.py @@ -1,7 +1,9 @@ +from api.database.interface import DBInterface + from api.database.relational_db.RelationalDBQueue import RelationalDBQueue from api.database.relational_db.RelationalDBRatings import RelationalDBRatings -class RelationalDB(RelationalDBQueue, RelationalDBRatings): +class RelationalDB(DBInterface, RelationalDBQueue, RelationalDBRatings): pass \ No newline at end of file diff --git a/api/database/testing_db/TestingDB.py b/api/database/testing_db/TestingDB.py index 41bf41a..62bdb62 100644 --- a/api/database/testing_db/TestingDB.py +++ b/api/database/testing_db/TestingDB.py @@ -1,7 +1,17 @@ +from api.database.interface import DBInterface + from api.database.testing_db.TestingDBQueue import TestingDBQueue from api.database.testing_db.TestingDBRatings import TestingDBRatings -class TestingDB(TestingDBQueue, TestingDBRatings): +class TestingDB(DBInterface, TestingDBQueue, TestingDBRatings): + + def connect(self): + pass + + def create_account(self, ubit, pn): + print("Welcome " + ubit) + + def add_to_roster(self, user_id, role): + pass - pass \ No newline at end of file diff --git a/api/models/live_queue.py b/api/models/live_queue.py deleted file mode 100644 index 2b96d41..0000000 --- a/api/models/live_queue.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Office hour student queue""" - - -class LiveQueue: - """A presentation of all the students currently in office hour waiting for TA assistant""" - - def __init__(self): - pass - - def enqueue(self): - """Enqueue given student into LiveQueue""" - - def dequeue(self): - """dequeue student from LiveQueue""" - - def remove(self): - """Remove student from LiveQueue by given identifier""" diff --git a/api/models/visits.py b/api/models/visits.py deleted file mode 100644 index c79f5f5..0000000 --- a/api/models/visits.py +++ /dev/null @@ -1 +0,0 @@ -# All the info for an OH visit \ No newline at end of file diff --git a/api/queue/controller.py b/api/queue/controller.py new file mode 100644 index 0000000..fb037d1 --- /dev/null +++ b/api/queue/controller.py @@ -0,0 +1,13 @@ + +def add_to_queue_by_card_swipe(swipe_data): + pass + +def add_to_queue_by_ta_override(identifier): + # identifier is resolved by checking if it's a valid UBIT, then pn, then account id + pass + + +def add_to_queue(user_account): + # called by both add_to_queue_by_card_swipe and add_to_queue_by_ta_override after user + # has been identified and their account was pulled from the db + pass \ No newline at end of file diff --git a/api/queue/routes.py b/api/queue/routes.py index 2dddc57..976db5d 100644 --- a/api/queue/routes.py +++ b/api/queue/routes.py @@ -10,18 +10,22 @@ @blueprint.route("/enqueue-card-swipe", methods=["POST"]) def enqueue_card_swipe(): """ + role: hardware + Add student to the current live queue for office hours Args: - Request.cookie: A HTTP Cookie with the name `id` for the student being added. - Cookie Example - - "id": "12344567890" # only one field seems weird maybe more? + body.swipe_data: The raw data from the card swipe as a string + + Body: + { + "swipe_data": "..." + } Returns: - A JSON of request status and possible wait time in seconds + A JSON of request status { "message": "You are enqueued", - "wait_time": "5000" } """ return f"{request.path} hit 😎, enqueue method is used" @@ -29,22 +33,38 @@ def enqueue_card_swipe(): @blueprint.route("/enqueue-ta-override", methods=["POST"]) def enqueue_ta_override(): - db.enqueue_student("Steve") """ - Force enqueue a student into the queue. Only usable by TAs and instructors + role: TA + + Force enqueue a student into the queue. + + Resolving the id will be done in the order: UBIT -> pn -> id (Although, these _should_ all be unique so the order + shouldn't matter) + + Args: + body.id: A unique identifier for the student This can either be the id of their account, their pn, or their ubit + + Body: + { + "id": "..." + } Use case: A student didn't bring their card to OH so they can't swipe in. The TA can force add them to the queue """ + pass return "" -@blueprint.route("/dequeue", methods=["DELETE"]) +@blueprint.route("/help-a-student", methods=["POST"]) def dequeue(): - student = db.dequeue_student() """ + role: TA + Remove the first student from the queue and create a Visit in the DB + + Not allowed if TA is already in a visit """ - return student + return "Help them good!" @blueprint.route("/get-queue", methods=["GET"]) @@ -67,13 +87,18 @@ def get_anon_queue(): return "" -@blueprint.route("/remove", methods=["DELETE"]) -def remove(): +@blueprint.route("/remove-self-from-queue", methods=["POST"]) +def remove_self(): """Removing students from the queue based on id Args: - Request.cookie: A HTTP Cookie with the name `id` for the student bring removed. - Cookie Example - - "id": "12344567890" + Request.cookie: The auth token used to identify the requester + body.reason: a text reason for removing the user from the queue. Will appear in the body of the request in a + json object + + Body: + { + "reason": "No show" + } Returns: A JSON of request status @@ -82,3 +107,24 @@ def remove(): } """ return f"{request.path} hit 😎, remove method is used." + + +@blueprint.route("/remove-from-queue/", methods=["POST"]) +def remove(user_id): + """Removing students from the queue by id + Args: + user_id: The id of the student being removed. Note: This is the id of their account, not their UBIT/pn + body.reason: a text reason for removing the user from the queue. Will appear in the body of the request in a + json object + + Body: + { + "reason": "No show" + } + + Returns: + { + "message": "You removed from the queue" + } + """ + return f"{request.path} hit 😎, remove method is used." diff --git a/api/ratings/controller.py b/api/ratings/controller.py new file mode 100644 index 0000000..fc80254 --- /dev/null +++ b/api/ratings/controller.py @@ -0,0 +1 @@ +pass \ No newline at end of file diff --git a/api/ratings/routes.py b/api/ratings/routes.py index be8bc78..635f1a4 100644 --- a/api/ratings/routes.py +++ b/api/ratings/routes.py @@ -5,4 +5,10 @@ blueprint = Blueprint("ratings", __name__) -# TODO: All the end points for students and TAs \ No newline at end of file +# TODO: All the end points for students and TAs. Rating contains rater/ratee/rating/feedback/visitID + +# TODO: create_rating + +# TODO: update rating (must be author) + +# TODO: get ratings (instructor/admin only) \ No newline at end of file diff --git a/api/roster/routes.py b/api/roster/routes.py new file mode 100644 index 0000000..e36d616 --- /dev/null +++ b/api/roster/routes.py @@ -0,0 +1,18 @@ +"""Roster Blueprint for MOH""" + +from flask import Blueprint + +blueprint = Blueprint("roster", __name__) + + +# TODO: upload roster. Take a csv file and add all the students to the roster (ubit,pn,name,role) + +# TODO: get roster + +# TODO: add to roster - to add an individual to the roster + +# TODO: Remove from roster + +# TODO: Whenever someone is added to the roster, check if they have an account and create one for them if not + +# TODO: handle roles here (Will make it easier to move to multiple courses in the future) \ No newline at end of file diff --git a/api/server.py b/api/server.py index 07f7c07..168557c 100644 --- a/api/server.py +++ b/api/server.py @@ -3,25 +3,19 @@ A Flask API server that handles enqueue and dequeuing students from the office hours queue. """ -# TODO: Make the doc string sound better - import datetime import io -import json -import os import requests -from flask import Flask, request +from flask import Flask from flask import send_file from api.config import config from api.utils.debug import debug_access_only -from api.database.db import create_db import api.auth.routes as auth_routes import api.queue.routes as queue_routes import api.ratings.routes as ratings_routes - -from api.database.db import db +import api.roster.routes as roster_routes def create_app(): @@ -38,6 +32,7 @@ def create_app(): app.register_blueprint(auth_routes.blueprint) app.register_blueprint(queue_routes.blueprint) app.register_blueprint(ratings_routes.blueprint) + app.register_blueprint(roster_routes.blueprint) @app.route("/", methods=["GET"]) def home(): diff --git a/api/test/.placeholder b/api/test/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/test.py b/test.py deleted file mode 100644 index f813911..0000000 --- a/test.py +++ /dev/null @@ -1,5 +0,0 @@ - -import api.database.db - -if __name__ == '__main__': - api.database.db.create_db() \ No newline at end of file diff --git a/tests/api/test_queue_basics.py b/tests/api/test_queue_basics.py new file mode 100644 index 0000000..63a08da --- /dev/null +++ b/tests/api/test_queue_basics.py @@ -0,0 +1,32 @@ +import os +import json + +import pytest +from dotenv import load_dotenv + +load_dotenv() + +from api.server import create_app +from api.auth.controller import create_account + + +@pytest.fixture +def client(): + app = create_app() + app.testing = True + create_account("hartloff", "123456789") + with app.test_client() as client: + yield client + + +def test_first_test(client): + response = client.get('/') + assert response.status_code == 200 + assert response.get_data() == b"Welcome to the homepage, you are currently in testing mode" + + +def test_that_needs_db(client): + client.post('/enqueue-ta-override', json.dumps({"id": "hartloff"})) + response = client.post('/help-a-student') + assert response.get_data() == b"hartloff" + From b6a6f5da889a9da29c55a154d95f7843392a63c9 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Sat, 14 Jun 2025 12:25:35 -0400 Subject: [PATCH 03/25] Queue endpoint documentation update; db file renaming to follow Python conventions --- api/auth/controller.py | 4 +- api/database/db.py | 6 +- .../{interface.py => db_interface.py} | 8 +- .../{IAccounts.py => idb_accounts.py} | 2 +- api/database/{IQueue.py => idb_queue.py} | 0 api/database/{IRatings.py => idb_ratings.py} | 0 api/database/{IRoster.py => idb_roster.py} | 0 .../mock_db/{MockDB.py => mock_db.py} | 2 +- api/database/relational_db/RelationalDB.py | 9 -- api/database/relational_db/relational_db.py | 9 ++ ...ionalDBQueue.py => relational_db_queue.py} | 2 +- ...lDBRatings.py => relational_db_ratings.py} | 2 +- api/database/testing_db/TestingDB.py | 17 --- api/database/testing_db/testing_db.py | 18 +++ .../testing_db/testing_db_accounts.py | 14 +++ ...{TestingDBQueue.py => testing_db_queue.py} | 2 +- ...tingDBRatings.py => testing_db_ratings.py} | 2 +- api/queue/routes.py | 114 ++++++++++++++---- tests/api/MockDB.py | 6 - tests/api/test_queue_basics.py | 23 +++- tests/api/test_test.py | 29 ----- 21 files changed, 164 insertions(+), 105 deletions(-) rename api/database/{interface.py => db_interface.py} (53%) rename api/database/{IAccounts.py => idb_accounts.py} (56%) rename api/database/{IQueue.py => idb_queue.py} (100%) rename api/database/{IRatings.py => idb_ratings.py} (100%) rename api/database/{IRoster.py => idb_roster.py} (100%) rename api/database/mock_db/{MockDB.py => mock_db.py} (87%) delete mode 100644 api/database/relational_db/RelationalDB.py create mode 100644 api/database/relational_db/relational_db.py rename api/database/relational_db/{RelationalDBQueue.py => relational_db_queue.py} (81%) rename api/database/relational_db/{RelationalDBRatings.py => relational_db_ratings.py} (74%) delete mode 100644 api/database/testing_db/TestingDB.py create mode 100644 api/database/testing_db/testing_db.py create mode 100644 api/database/testing_db/testing_db_accounts.py rename api/database/testing_db/{TestingDBQueue.py => testing_db_queue.py} (84%) rename api/database/testing_db/{TestingDBRatings.py => testing_db_ratings.py} (74%) delete mode 100644 tests/api/MockDB.py delete mode 100644 tests/api/test_test.py diff --git a/api/auth/controller.py b/api/auth/controller.py index a9dfc42..0314b66 100644 --- a/api/auth/controller.py +++ b/api/auth/controller.py @@ -1,5 +1,5 @@ from api.database.db import db def create_account(username, numeric_identifier, auth_level='student'): - db.create_account(username, numeric_identifier) - pass + account_id = db.create_account(username, numeric_identifier) + return account_id diff --git a/api/database/db.py b/api/database/db.py index 12bafd3..d10c583 100644 --- a/api/database/db.py +++ b/api/database/db.py @@ -1,8 +1,8 @@ import os -from api.database.relational_db.RelationalDB import RelationalDB -from api.database.testing_db.TestingDB import TestingDB -from api.database.mock_db.MockDB import MockDB +from api.database.relational_db.relational_db import RelationalDB +from api.database.testing_db.testing_db import TestingDB +from api.database.mock_db.mock_db import MockDB def create_db(): diff --git a/api/database/interface.py b/api/database/db_interface.py similarity index 53% rename from api/database/interface.py rename to api/database/db_interface.py index e5feb43..6e0dcfe 100644 --- a/api/database/interface.py +++ b/api/database/db_interface.py @@ -1,9 +1,9 @@ from abc import ABC, abstractmethod -from api.database.IQueue import IQueue -from api.database.IRatings import IRatings -from api.database.IAccounts import IAccounts -from api.database.IRoster import IRoster +from api.database.idb_queue import IQueue +from api.database.idb_ratings import IRatings +from api.database.idb_accounts import IAccounts +from api.database.idb_roster import IRoster class DBInterface(IQueue, IRatings, IAccounts, IRoster, ABC): diff --git a/api/database/IAccounts.py b/api/database/idb_accounts.py similarity index 56% rename from api/database/IAccounts.py rename to api/database/idb_accounts.py index 3879951..8a016c3 100644 --- a/api/database/IAccounts.py +++ b/api/database/idb_accounts.py @@ -5,5 +5,5 @@ class IAccounts: @abstractmethod def create_account(self, ubit, pn): - # return id + # Creates an account with the provided ubit and pn. Generates, and returns, a unique id for the new account raise NotImplementedError() \ No newline at end of file diff --git a/api/database/IQueue.py b/api/database/idb_queue.py similarity index 100% rename from api/database/IQueue.py rename to api/database/idb_queue.py diff --git a/api/database/IRatings.py b/api/database/idb_ratings.py similarity index 100% rename from api/database/IRatings.py rename to api/database/idb_ratings.py diff --git a/api/database/IRoster.py b/api/database/idb_roster.py similarity index 100% rename from api/database/IRoster.py rename to api/database/idb_roster.py diff --git a/api/database/mock_db/MockDB.py b/api/database/mock_db/mock_db.py similarity index 87% rename from api/database/mock_db/MockDB.py rename to api/database/mock_db/mock_db.py index 376be27..e3d1aec 100644 --- a/api/database/mock_db/MockDB.py +++ b/api/database/mock_db/mock_db.py @@ -1,4 +1,4 @@ -from api.database.interface import DBInterface +from api.database.db_interface import DBInterface class MockDB(DBInterface): diff --git a/api/database/relational_db/RelationalDB.py b/api/database/relational_db/RelationalDB.py deleted file mode 100644 index acbb073..0000000 --- a/api/database/relational_db/RelationalDB.py +++ /dev/null @@ -1,9 +0,0 @@ -from api.database.interface import DBInterface - -from api.database.relational_db.RelationalDBQueue import RelationalDBQueue -from api.database.relational_db.RelationalDBRatings import RelationalDBRatings - - -class RelationalDB(DBInterface, RelationalDBQueue, RelationalDBRatings): - - pass \ No newline at end of file diff --git a/api/database/relational_db/relational_db.py b/api/database/relational_db/relational_db.py new file mode 100644 index 0000000..8c799a0 --- /dev/null +++ b/api/database/relational_db/relational_db.py @@ -0,0 +1,9 @@ +from api.database.db_interface import DBInterface + +from api.database.relational_db.relational_db_queue import RelationalDBQueue +from api.database.relational_db.relational_db_ratings import RelationalDBRatings + + +class RelationalDB(DBInterface, RelationalDBQueue, RelationalDBRatings): + + pass \ No newline at end of file diff --git a/api/database/relational_db/RelationalDBQueue.py b/api/database/relational_db/relational_db_queue.py similarity index 81% rename from api/database/relational_db/RelationalDBQueue.py rename to api/database/relational_db/relational_db_queue.py index 1a420bb..e8f3980 100644 --- a/api/database/relational_db/RelationalDBQueue.py +++ b/api/database/relational_db/relational_db_queue.py @@ -1,4 +1,4 @@ -from api.database.IQueue import IQueue +from api.database.idb_queue import IQueue class RelationalDBQueue(IQueue): diff --git a/api/database/relational_db/RelationalDBRatings.py b/api/database/relational_db/relational_db_ratings.py similarity index 74% rename from api/database/relational_db/RelationalDBRatings.py rename to api/database/relational_db/relational_db_ratings.py index b0a4bde..67f7588 100644 --- a/api/database/relational_db/RelationalDBRatings.py +++ b/api/database/relational_db/relational_db_ratings.py @@ -1,4 +1,4 @@ -from api.database.IRatings import IRatings +from api.database.idb_ratings import IRatings class RelationalDBRatings(IRatings): diff --git a/api/database/testing_db/TestingDB.py b/api/database/testing_db/TestingDB.py deleted file mode 100644 index 62bdb62..0000000 --- a/api/database/testing_db/TestingDB.py +++ /dev/null @@ -1,17 +0,0 @@ -from api.database.interface import DBInterface - -from api.database.testing_db.TestingDBQueue import TestingDBQueue -from api.database.testing_db.TestingDBRatings import TestingDBRatings - - -class TestingDB(DBInterface, TestingDBQueue, TestingDBRatings): - - def connect(self): - pass - - def create_account(self, ubit, pn): - print("Welcome " + ubit) - - def add_to_roster(self, user_id, role): - pass - diff --git a/api/database/testing_db/testing_db.py b/api/database/testing_db/testing_db.py new file mode 100644 index 0000000..7fbefce --- /dev/null +++ b/api/database/testing_db/testing_db.py @@ -0,0 +1,18 @@ +from api.database.db_interface import DBInterface + +from api.database.testing_db.testing_db_queue import TestingDBQueue +from api.database.testing_db.testing_db_ratings import TestingDBRatings +from api.database.testing_db.testing_db_accounts import TestingDBAccounts + + +class TestingDB(DBInterface, TestingDBQueue, TestingDBRatings, TestingDBAccounts): + + + def connect(self): + pass + + + + def add_to_roster(self, user_id, role): + pass + diff --git a/api/database/testing_db/testing_db_accounts.py b/api/database/testing_db/testing_db_accounts.py new file mode 100644 index 0000000..fcede8c --- /dev/null +++ b/api/database/testing_db/testing_db_accounts.py @@ -0,0 +1,14 @@ +from api.database.idb_accounts import IAccounts + + +class TestingDBAccounts(IAccounts): + + def __init__(self): + super().__init__() + self.queue = [] + self.next_id = 0 + + def create_account(self, ubit, pn): + account_id = self.next_id + self.next_id += 1 + return account_id diff --git a/api/database/testing_db/TestingDBQueue.py b/api/database/testing_db/testing_db_queue.py similarity index 84% rename from api/database/testing_db/TestingDBQueue.py rename to api/database/testing_db/testing_db_queue.py index 9dacfd3..d6168ee 100644 --- a/api/database/testing_db/TestingDBQueue.py +++ b/api/database/testing_db/testing_db_queue.py @@ -1,4 +1,4 @@ -from api.database.IQueue import IQueue +from api.database.idb_queue import IQueue class TestingDBQueue(IQueue): diff --git a/api/database/testing_db/TestingDBRatings.py b/api/database/testing_db/testing_db_ratings.py similarity index 74% rename from api/database/testing_db/TestingDBRatings.py rename to api/database/testing_db/testing_db_ratings.py index e352650..82a3421 100644 --- a/api/database/testing_db/TestingDBRatings.py +++ b/api/database/testing_db/testing_db_ratings.py @@ -1,4 +1,4 @@ -from api.database.IRatings import IRatings +from api.database.idb_ratings import IRatings class TestingDBRatings(IRatings): diff --git a/api/queue/routes.py b/api/queue/routes.py index 976db5d..e8d2ef9 100644 --- a/api/queue/routes.py +++ b/api/queue/routes.py @@ -19,13 +19,14 @@ def enqueue_card_swipe(): Body: { - "swipe_data": "..." + "swipe_data": } Returns: - A JSON of request status + 200 OK - Student was added to the queue + 404 Not Found - No student matching the card swipe was found { - "message": "You are enqueued", + "message": } """ return f"{request.path} hit 😎, enqueue method is used" @@ -36,22 +37,29 @@ def enqueue_ta_override(): """ role: TA - Force enqueue a student into the queue. + Force enqueue a student. Resolving the id will be done in the order: UBIT -> pn -> id (Although, these _should_ all be unique so the order shouldn't matter) Args: - body.id: A unique identifier for the student This can either be the id of their account, their pn, or their ubit + body.identifier: A unique identifier for the student This can either be their UBIT, pn, or the id of their account Body: { - "id": "..." + "identifier": + } + + Returns: + 200 OK - Student was added to the queue + 403 Unauthorized - Requester does not have TA permissions + 404 Not Found - No student matching provided identifier + { + "message": , } Use case: A student didn't bring their card to OH so they can't swipe in. The TA can force add them to the queue """ - pass return "" @@ -63,6 +71,21 @@ def dequeue(): Remove the first student from the queue and create a Visit in the DB Not allowed if TA is already in a visit + + Returns: + 200 OK - Student was dequeued + { + "id": , + "username": , + "pn": , + "preferred_name: + } + + 400 Bad Request - The queue is empty + 403 Unauthorized - Requester does not have TA permissions + { + "message": + } """ return "Help them good!" @@ -70,40 +93,75 @@ def dequeue(): @blueprint.route("/get-queue", methods=["GET"]) def get_queue(): """ - Returns all information about the queue. Only accessible by TAs and instructors + role: TA + + Returns all student accounts in the queue starting with the front of the queue + + Returns: + 200 OK + [ + { + "id": , + "username": , + "pn": , + "preferred_name: + }, + ... + ] + + 403 Unauthorized - Requester does not have TA permissions + { + "message": + } """ return "" -@blueprint.route("/get-anonymous-queue", methods=["GET"]) +@blueprint.route("/get-my-position", methods=["GET"]) def get_anon_queue(): """ - Returns the queue with all private information hidden. Only preferred names are displayed (Or no name for - students who did not enter a preferred name) + role: self - Contains time estimates. This can predict based on tags for the question (eg. a "task 5" tag might have - a higher estimate than a "lecture question" tag) + Returns the position in the queue of the requester. If the requester is not in the queue, return a position of -1 + + Args: + Request.cookie: The auth token used to identify the requester + + Returns: + 200 OK - You're in the queue and here's your position + { + "position": + } + + 400 Bad Request - You are not in the queue + { + "message": + } """ return "" @blueprint.route("/remove-self-from-queue", methods=["POST"]) def remove_self(): - """Removing students from the queue based on id + """ + role: self + + Remove the requester from the queue. Creates a visit in the db to store the reason for the removal + Args: Request.cookie: The auth token used to identify the requester - body.reason: a text reason for removing the user from the queue. Will appear in the body of the request in a - json object + body.reason: a text reason for removing the user from the queue Body: { - "reason": "No show" + "reason": } Returns: - A JSON of request status + 200 OK - You were removed from the queue and a visit was created + 400 Bad Request - You were not in the queue { - "message": "You are removed from the queue" + "message": } """ return f"{request.path} hit 😎, remove method is used." @@ -111,20 +169,26 @@ def remove_self(): @blueprint.route("/remove-from-queue/", methods=["POST"]) def remove(user_id): - """Removing students from the queue by id + """ + role: TA + + Removing students from the queue by id. Creates a visit in the db to store the reason for the removal + Args: - user_id: The id of the student being removed. Note: This is the id of their account, not their UBIT/pn - body.reason: a text reason for removing the user from the queue. Will appear in the body of the request in a - json object + param.user_id: The id of the student being removed. Note: This is the id of their account, not their UBIT/pn + body.reason: a text reason for removing the user from the queue (eg. "No show") Body: { - "reason": "No show" + "reason": } Returns: + 200 OK - Student was removed from the queue and a visit was created + 400 Bad Request - Student with user_id was not in the queue + 403 Unauthorized - Requester does not have TA permissions { - "message": "You removed from the queue" + "message": } """ return f"{request.path} hit 😎, remove method is used." diff --git a/tests/api/MockDB.py b/tests/api/MockDB.py deleted file mode 100644 index 0c94bab..0000000 --- a/tests/api/MockDB.py +++ /dev/null @@ -1,6 +0,0 @@ -from api.database.interface import DBInterface - - -class MockDB(DBInterface): - - pass \ No newline at end of file diff --git a/tests/api/test_queue_basics.py b/tests/api/test_queue_basics.py index 63a08da..f878d3b 100644 --- a/tests/api/test_queue_basics.py +++ b/tests/api/test_queue_basics.py @@ -9,24 +9,39 @@ from api.server import create_app from api.auth.controller import create_account +all_account_data = [ + {"username": "jy123", "pn": "123456789"}, + {"username": "lucy5", "pn": "123456784"}, + {"username": "steve", "pn": "987654321"} +] + +@pytest.fixture +def accounts(): + accounts = {} # id : account + for account_data in all_account_data: + account_id = create_account(account_data["username"], account_data["pn"]) + account_data["id"] = account_id + accounts[account_id] = account_data + yield accounts + @pytest.fixture def client(): app = create_app() app.testing = True - create_account("hartloff", "123456789") with app.test_client() as client: yield client + def test_first_test(client): response = client.get('/') assert response.status_code == 200 assert response.get_data() == b"Welcome to the homepage, you are currently in testing mode" -def test_that_needs_db(client): - client.post('/enqueue-ta-override', json.dumps({"id": "hartloff"})) +def test_that_needs_db(client, accounts): + client.post('/enqueue-ta-override', json.dumps({"id": "lucy5"})) response = client.post('/help-a-student') - assert response.get_data() == b"hartloff" + assert response.get_data() == b"lucy5" diff --git a/tests/api/test_test.py b/tests/api/test_test.py deleted file mode 100644 index affde20..0000000 --- a/tests/api/test_test.py +++ /dev/null @@ -1,29 +0,0 @@ -import os - -import pytest -from dotenv import load_dotenv - -load_dotenv() - -from api.server import create_app - - -@pytest.fixture -def client(): - app = create_app() - app.testing = True - with app.test_client() as client: - yield client - - -def test_first_test(client): - response = client.get('/') - assert response.status_code == 200 - assert response.get_data() == b"Welcome to the homepage, you are currently in testing mode" - - -def test_that_needs_db(client): - client.post('/enqueue-ta-override', 'not used yet') - response = client.delete('/dequeue') - assert response.get_data() == b"Steve" - From 6492bd1f715f9e3d226704ab288aa5611f031a3d Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Sat, 14 Jun 2025 12:31:34 -0400 Subject: [PATCH 04/25] Formatting --- .github/workflows/formatter.yml | 26 ++++++++--------- .github/workflows/pylint.yml | 28 +++++++++---------- api/auth/controller.py | 1 + api/auth/routes.py | 4 +-- api/database/db.py | 3 +- api/database/db_interface.py | 3 +- api/database/idb_accounts.py | 4 +-- api/database/idb_ratings.py | 4 +-- api/database/idb_roster.py | 3 +- api/database/mock_db/mock_db.py | 2 +- api/database/relational_db/relational_db.py | 3 +- .../relational_db/relational_db_queue.py | 3 +- .../relational_db/relational_db_ratings.py | 2 +- api/database/testing_db/testing_db.py | 4 --- api/database/testing_db/testing_db_queue.py | 2 +- api/database/testing_db/testing_db_ratings.py | 2 +- api/queue/controller.py | 4 +-- api/ratings/controller.py | 2 +- api/ratings/routes.py | 3 +- api/roster/routes.py | 3 +- api/run_local.py | 3 +- api/server.py | 2 -- api/utils/logging.py | 3 +- compose.yaml | 2 +- tests/api/test_queue_basics.py | 5 ++-- 25 files changed, 56 insertions(+), 65 deletions(-) diff --git a/.github/workflows/formatter.yml b/.github/workflows/formatter.yml index 9e3d7ee..47c6e83 100644 --- a/.github/workflows/formatter.yml +++ b/.github/workflows/formatter.yml @@ -9,17 +9,17 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: [ "3.10", "3.11", "3.12" ] steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install black - - name: Formatting the code with black - run: | - black $(git ls-files '*.py') \ No newline at end of file + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black + - name: Formatting the code with black + run: | + black $(git ls-files '*.py') \ No newline at end of file diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index c3e47eb..f5ab5c8 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,22 +1,22 @@ name: Pylint run-name: Pylint Checker 🐧 -on: [push] +on: [ push ] jobs: build: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: [ "3.10", "3.11", "3.12" ] steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r ./api/requirements_dev.txt - - name: Analysing the code with pylint - run: | - pylint $(git ls-files '*.py') \ No newline at end of file + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r ./api/requirements.txt + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') \ No newline at end of file diff --git a/api/auth/controller.py b/api/auth/controller.py index 0314b66..f0bead2 100644 --- a/api/auth/controller.py +++ b/api/auth/controller.py @@ -1,5 +1,6 @@ from api.database.db import db + def create_account(username, numeric_identifier, auth_level='student'): account_id = db.create_account(username, numeric_identifier) return account_id diff --git a/api/auth/routes.py b/api/auth/routes.py index 48170dd..fba6d7c 100644 --- a/api/auth/routes.py +++ b/api/auth/routes.py @@ -4,6 +4,7 @@ blueprint = Blueprint("auth", __name__) + @blueprint.route("/login", methods=["POST"]) def login(): """Checks if the current user has the right credentials to log in @@ -30,7 +31,6 @@ def signup(): """ return "Signup arrived" - # TODO: update preferred name -# TODO: accounts has UBIT (For AL lookups) and pn (For card swipes) \ No newline at end of file +# TODO: accounts has UBIT (For AL lookups) and pn (For card swipes) diff --git a/api/database/db.py b/api/database/db.py index d10c583..96ad551 100644 --- a/api/database/db.py +++ b/api/database/db.py @@ -19,4 +19,5 @@ def create_db(): case _: raise ModuleNotFoundError("Could not find database named " + db_type) -db = create_db() \ No newline at end of file + +db = create_db() diff --git a/api/database/db_interface.py b/api/database/db_interface.py index 6e0dcfe..551cefd 100644 --- a/api/database/db_interface.py +++ b/api/database/db_interface.py @@ -5,10 +5,11 @@ from api.database.idb_accounts import IAccounts from api.database.idb_roster import IRoster + class DBInterface(IQueue, IRatings, IAccounts, IRoster, ABC): # All database implements must extend this class @abstractmethod def connect(self): - pass \ No newline at end of file + pass diff --git a/api/database/idb_accounts.py b/api/database/idb_accounts.py index 8a016c3..3d807e8 100644 --- a/api/database/idb_accounts.py +++ b/api/database/idb_accounts.py @@ -1,9 +1,9 @@ - from abc import ABC, abstractmethod + class IAccounts: @abstractmethod def create_account(self, ubit, pn): # Creates an account with the provided ubit and pn. Generates, and returns, a unique id for the new account - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() diff --git a/api/database/idb_ratings.py b/api/database/idb_ratings.py index ce0f54a..1a48c53 100644 --- a/api/database/idb_ratings.py +++ b/api/database/idb_ratings.py @@ -1,8 +1,8 @@ - from abc import ABC, abstractmethod + class IRatings: @abstractmethod def rate_student(self, student, rating, feedback): - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() diff --git a/api/database/idb_roster.py b/api/database/idb_roster.py index d79603b..072fc26 100644 --- a/api/database/idb_roster.py +++ b/api/database/idb_roster.py @@ -1,9 +1,8 @@ - from abc import ABC, abstractmethod + class IRoster: @abstractmethod def add_to_roster(self, user_id, role): raise NotImplementedError() - diff --git a/api/database/mock_db/mock_db.py b/api/database/mock_db/mock_db.py index e3d1aec..2aba52a 100644 --- a/api/database/mock_db/mock_db.py +++ b/api/database/mock_db/mock_db.py @@ -19,4 +19,4 @@ def create_account(self, ubit, pn): pass def add_to_roster(self, user_id, role): - pass \ No newline at end of file + pass diff --git a/api/database/relational_db/relational_db.py b/api/database/relational_db/relational_db.py index 8c799a0..072d382 100644 --- a/api/database/relational_db/relational_db.py +++ b/api/database/relational_db/relational_db.py @@ -5,5 +5,4 @@ class RelationalDB(DBInterface, RelationalDBQueue, RelationalDBRatings): - - pass \ No newline at end of file + pass diff --git a/api/database/relational_db/relational_db_queue.py b/api/database/relational_db/relational_db_queue.py index e8f3980..ed548e4 100644 --- a/api/database/relational_db/relational_db_queue.py +++ b/api/database/relational_db/relational_db_queue.py @@ -7,6 +7,5 @@ def enqueue_student(self, student): raise NotImplementedError() # do database stuff - def dequeue_student(self): - pass \ No newline at end of file + pass diff --git a/api/database/relational_db/relational_db_ratings.py b/api/database/relational_db/relational_db_ratings.py index 67f7588..ef41529 100644 --- a/api/database/relational_db/relational_db_ratings.py +++ b/api/database/relational_db/relational_db_ratings.py @@ -5,4 +5,4 @@ class RelationalDBRatings(IRatings): def rate_student(self, student, rating, feedback): pass - # do database stuff \ No newline at end of file + # do database stuff diff --git a/api/database/testing_db/testing_db.py b/api/database/testing_db/testing_db.py index 7fbefce..0f1c917 100644 --- a/api/database/testing_db/testing_db.py +++ b/api/database/testing_db/testing_db.py @@ -7,12 +7,8 @@ class TestingDB(DBInterface, TestingDBQueue, TestingDBRatings, TestingDBAccounts): - def connect(self): pass - - def add_to_roster(self, user_id, role): pass - diff --git a/api/database/testing_db/testing_db_queue.py b/api/database/testing_db/testing_db_queue.py index d6168ee..a7d1cdc 100644 --- a/api/database/testing_db/testing_db_queue.py +++ b/api/database/testing_db/testing_db_queue.py @@ -10,4 +10,4 @@ def enqueue_student(self, student): self.queue.append(student) def dequeue_student(self): - return self.queue.pop(0) \ No newline at end of file + return self.queue.pop(0) diff --git a/api/database/testing_db/testing_db_ratings.py b/api/database/testing_db/testing_db_ratings.py index 82a3421..6144df6 100644 --- a/api/database/testing_db/testing_db_ratings.py +++ b/api/database/testing_db/testing_db_ratings.py @@ -5,4 +5,4 @@ class TestingDBRatings(IRatings): def rate_student(self, student, rating, feedback): pass - # do database stuff \ No newline at end of file + # do database stuff diff --git a/api/queue/controller.py b/api/queue/controller.py index fb037d1..55053bf 100644 --- a/api/queue/controller.py +++ b/api/queue/controller.py @@ -1,7 +1,7 @@ - def add_to_queue_by_card_swipe(swipe_data): pass + def add_to_queue_by_ta_override(identifier): # identifier is resolved by checking if it's a valid UBIT, then pn, then account id pass @@ -10,4 +10,4 @@ def add_to_queue_by_ta_override(identifier): def add_to_queue(user_account): # called by both add_to_queue_by_card_swipe and add_to_queue_by_ta_override after user # has been identified and their account was pulled from the db - pass \ No newline at end of file + pass diff --git a/api/ratings/controller.py b/api/ratings/controller.py index fc80254..2ae2839 100644 --- a/api/ratings/controller.py +++ b/api/ratings/controller.py @@ -1 +1 @@ -pass \ No newline at end of file +pass diff --git a/api/ratings/routes.py b/api/ratings/routes.py index 635f1a4..bd86aec 100644 --- a/api/ratings/routes.py +++ b/api/ratings/routes.py @@ -4,11 +4,10 @@ blueprint = Blueprint("ratings", __name__) - # TODO: All the end points for students and TAs. Rating contains rater/ratee/rating/feedback/visitID # TODO: create_rating # TODO: update rating (must be author) -# TODO: get ratings (instructor/admin only) \ No newline at end of file +# TODO: get ratings (instructor/admin only) diff --git a/api/roster/routes.py b/api/roster/routes.py index e36d616..189d780 100644 --- a/api/roster/routes.py +++ b/api/roster/routes.py @@ -4,7 +4,6 @@ blueprint = Blueprint("roster", __name__) - # TODO: upload roster. Take a csv file and add all the students to the roster (ubit,pn,name,role) # TODO: get roster @@ -15,4 +14,4 @@ # TODO: Whenever someone is added to the roster, check if they have an account and create one for them if not -# TODO: handle roles here (Will make it easier to move to multiple courses in the future) \ No newline at end of file +# TODO: handle roles here (Will make it easier to move to multiple courses in the future) diff --git a/api/run_local.py b/api/run_local.py index 4d6e2e3..4bfce4d 100644 --- a/api/run_local.py +++ b/api/run_local.py @@ -1,9 +1,10 @@ # Runs the app locally without Gunicorn. To be used for dev and testing from dotenv import load_dotenv + load_dotenv() from api.server import create_app app = create_app() -app.run() \ No newline at end of file +app.run() diff --git a/api/server.py b/api/server.py index 168557c..3b32bee 100644 --- a/api/server.py +++ b/api/server.py @@ -55,6 +55,4 @@ def health(): """Current health of the API server with metadata of the time""" return {"timestamp": str(datetime.datetime.now())} - return app - diff --git a/api/utils/logging.py b/api/utils/logging.py index 40434ed..4338974 100644 --- a/api/utils/logging.py +++ b/api/utils/logging.py @@ -1,4 +1,3 @@ """Logging utils for MOH API server""" - -# log levels from an env var \ No newline at end of file +# log levels from an env var diff --git a/compose.yaml b/compose.yaml index 9305e4b..075ee83 100644 --- a/compose.yaml +++ b/compose.yaml @@ -15,7 +15,7 @@ services: extends: service: api-development environment: - API_MODE: "prod" + API_MODE: "prod" ports: - "8000:8000" command: gunicorn 'api.server:create_app()' diff --git a/tests/api/test_queue_basics.py b/tests/api/test_queue_basics.py index f878d3b..4b39a1e 100644 --- a/tests/api/test_queue_basics.py +++ b/tests/api/test_queue_basics.py @@ -15,9 +15,10 @@ {"username": "steve", "pn": "987654321"} ] + @pytest.fixture def accounts(): - accounts = {} # id : account + accounts = {} # id : account for account_data in all_account_data: account_id = create_account(account_data["username"], account_data["pn"]) account_data["id"] = account_id @@ -33,7 +34,6 @@ def client(): yield client - def test_first_test(client): response = client.get('/') assert response.status_code == 200 @@ -44,4 +44,3 @@ def test_that_needs_db(client, accounts): client.post('/enqueue-ta-override', json.dumps({"id": "lucy5"})) response = client.post('/help-a-student') assert response.get_data() == b"lucy5" - From 6393710315f34db595ef5d3c6db78433dfe8f6b3 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Sat, 14 Jun 2025 12:32:40 -0400 Subject: [PATCH 05/25] install utils for linter --- .github/workflows/pylint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index f5ab5c8..8dd7ede 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -17,6 +17,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r ./api/requirements.txt + pip install -r ./api/utils.txt - name: Analysing the code with pylint run: | pylint $(git ls-files '*.py') \ No newline at end of file From e544bafd5adbc59a4b9f89ce5eb480edaf56ad12 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Sat, 14 Jun 2025 12:41:44 -0400 Subject: [PATCH 06/25] bug fix. need super constructor calls everywhere due to MRO --- api/database/db_interface.py | 3 +++ api/database/idb_accounts.py | 5 ++++- api/database/idb_queue.py | 3 +++ api/database/idb_ratings.py | 5 ++++- api/database/idb_roster.py | 5 ++++- api/database/testing_db/testing_db.py | 3 +++ api/database/testing_db/testing_db_queue.py | 1 + api/database/testing_db/testing_db_ratings.py | 3 +++ 8 files changed, 25 insertions(+), 3 deletions(-) diff --git a/api/database/db_interface.py b/api/database/db_interface.py index 551cefd..e3e2e82 100644 --- a/api/database/db_interface.py +++ b/api/database/db_interface.py @@ -10,6 +10,9 @@ class DBInterface(IQueue, IRatings, IAccounts, IRoster, ABC): # All database implements must extend this class + def __init__(self): + super().__init__() + @abstractmethod def connect(self): pass diff --git a/api/database/idb_accounts.py b/api/database/idb_accounts.py index 3d807e8..805517b 100644 --- a/api/database/idb_accounts.py +++ b/api/database/idb_accounts.py @@ -1,7 +1,10 @@ from abc import ABC, abstractmethod -class IAccounts: +class IAccounts(ABC): + + def __init__(self): + super().__init__() @abstractmethod def create_account(self, ubit, pn): diff --git a/api/database/idb_queue.py b/api/database/idb_queue.py index cdae80e..743d6c1 100644 --- a/api/database/idb_queue.py +++ b/api/database/idb_queue.py @@ -3,6 +3,9 @@ class IQueue(ABC): + def __init__(self): + super().__init__() + @abstractmethod def enqueue_student(self, student): raise NotImplementedError() diff --git a/api/database/idb_ratings.py b/api/database/idb_ratings.py index 1a48c53..49f3a0b 100644 --- a/api/database/idb_ratings.py +++ b/api/database/idb_ratings.py @@ -1,7 +1,10 @@ from abc import ABC, abstractmethod -class IRatings: +class IRatings(ABC): + + def __init__(self): + super().__init__() @abstractmethod def rate_student(self, student, rating, feedback): diff --git a/api/database/idb_roster.py b/api/database/idb_roster.py index 072fc26..93a5395 100644 --- a/api/database/idb_roster.py +++ b/api/database/idb_roster.py @@ -1,7 +1,10 @@ from abc import ABC, abstractmethod -class IRoster: +class IRoster(ABC): + + def __init__(self): + super().__init__() @abstractmethod def add_to_roster(self, user_id, role): diff --git a/api/database/testing_db/testing_db.py b/api/database/testing_db/testing_db.py index 0f1c917..841704d 100644 --- a/api/database/testing_db/testing_db.py +++ b/api/database/testing_db/testing_db.py @@ -7,6 +7,9 @@ class TestingDB(DBInterface, TestingDBQueue, TestingDBRatings, TestingDBAccounts): + def __init__(self): + super().__init__() + def connect(self): pass diff --git a/api/database/testing_db/testing_db_queue.py b/api/database/testing_db/testing_db_queue.py index a7d1cdc..52ebb48 100644 --- a/api/database/testing_db/testing_db_queue.py +++ b/api/database/testing_db/testing_db_queue.py @@ -4,6 +4,7 @@ class TestingDBQueue(IQueue): def __init__(self): + super().__init__() self.queue = [] def enqueue_student(self, student): diff --git a/api/database/testing_db/testing_db_ratings.py b/api/database/testing_db/testing_db_ratings.py index 6144df6..73f1a58 100644 --- a/api/database/testing_db/testing_db_ratings.py +++ b/api/database/testing_db/testing_db_ratings.py @@ -3,6 +3,9 @@ class TestingDBRatings(IRatings): + def __init__(self): + super().__init__() + def rate_student(self, student, rating, feedback): pass # do database stuff From 1d8b6cbc85284f10debe6c567b385dcf9453e333 Mon Sep 17 00:00:00 2001 From: Manav Sharma Date: Thu, 25 Sep 2025 20:01:51 -0400 Subject: [PATCH 07/25] Add web frontend application - Added Next.js frontend application in web/ directory - Includes authentication, queue management, and dashboard components - Added .DS_Store to .gitignore --- .gitignore | 2 +- web/.dockerignore | 94 + web/.gitignore | 48 + web/.vscode/settings.json | 8 + web/DOCKER.md | 159 + web/Dockerfile | 22 + web/bun.lockb | Bin 0 -> 153948 bytes web/components.json | 21 + web/docker-compose.yml | 19 + web/docker-scripts.sh | 165 + web/docs/README.md | 129 + web/middleware.ts | 11 + web/next-auth.d.ts | 30 + web/next.config.ts | 7 + web/package-lock.json | 5497 +++++++++++++++++ web/package.json | 59 + web/postcss.config.mjs | 8 + web/public/logo.png | Bin 0 -> 34784 bytes web/server.js | 26 + web/src/app/(auth)/layout.tsx | 50 + web/src/app/(auth)/page.tsx | 28 + .../app/(screens)/courses/[course]/page.tsx | 134 + web/src/app/(screens)/dashboard/page.tsx | 78 + web/src/app/(screens)/layout.tsx | 59 + .../queue/[office_hours_id]/page.tsx | 254 + web/src/app/api/auth/[...nextauth]/route.ts | 6 + web/src/app/api/auth/callback/route.tsx | 88 + web/src/app/api/jwt/route.ts | 83 + web/src/app/api/queue/get/route.tsx | 13 + web/src/app/api/queue/join/route.tsx | 26 + web/src/app/api/queue/leave/route.tsx | 22 + web/src/app/api/queue/session/update/route.ts | 12 + .../app/api/queue/session/validate/route.ts | 16 + web/src/app/api/token/route.ts | 28 + web/src/app/api/users/route.ts | 18 + web/src/app/favicon.ico | Bin 0 -> 25931 bytes web/src/app/globals.css | 112 + web/src/components/custom/animated-modal.tsx | 64 + .../components/custom/card-input-modal.tsx | 60 + web/src/components/custom/course-card.tsx | 55 + web/src/components/custom/mode-toggle.tsx | 28 + web/src/components/custom/navbar.tsx | 65 + web/src/components/custom/office-hours.tsx | 68 + web/src/components/custom/shiny-text.tsx | 20 + web/src/components/custom/sidebar.tsx | 217 + web/src/components/custom/theme-provider.tsx | 8 + web/src/components/queue/active-session.tsx | 121 + .../components/queue/announcement-card.tsx | 26 + web/src/components/queue/instructor-view.tsx | 241 + .../queue/office-hours-info-card.tsx | 51 + web/src/components/queue/queue-form.tsx | 135 + .../components/queue/queue-status-card.tsx | 183 + .../components/queue/queue-status-table.tsx | 282 + web/src/components/queue/resolution-form.tsx | 101 + web/src/components/queue/student-view.tsx | 242 + web/src/components/ui/avatar.tsx | 50 + web/src/components/ui/badge.tsx | 36 + web/src/components/ui/blur-fade.tsx | 78 + web/src/components/ui/button.tsx | 74 + web/src/components/ui/card.tsx | 76 + web/src/components/ui/checkbox.tsx | 30 + web/src/components/ui/dialog.tsx | 122 + web/src/components/ui/dropdown-menu.tsx | 201 + web/src/components/ui/form.tsx | 178 + web/src/components/ui/input.tsx | 22 + web/src/components/ui/label.tsx | 26 + web/src/components/ui/select.tsx | 160 + web/src/components/ui/separator.tsx | 31 + web/src/components/ui/sheet.tsx | 140 + web/src/components/ui/sidebar.tsx | 772 +++ web/src/components/ui/skeleton.tsx | 16 + web/src/components/ui/table.tsx | 120 + web/src/components/ui/tabs.tsx | 55 + web/src/components/ui/textarea.tsx | 22 + web/src/components/ui/tooltip.tsx | 32 + web/src/hooks/use-mobile.tsx | 19 + web/src/lib/auth/auth-option.ts | 157 + web/src/lib/helper/autolab-helper.tsx | 73 + web/src/lib/helper/database-helper.tsx | 287 + web/src/lib/helper/queue-helper.tsx | 248 + web/src/lib/helper/session-helper.ts | 33 + web/src/lib/helper/session-persistence.ts | 111 + web/src/lib/supabase/supabase-client.ts | 82 + web/src/lib/supabase/supabase.ts | 217 + web/src/lib/utils.ts | 6 + web/src/types/index.ts | 1 + web/src/types/queue-types.ts | 12 + web/src/types/session-types.ts | 28 + web/src/types/supabase-types.ts | 21 + web/tailwind.config.ts | 98 + web/tsconfig.json | 27 + 91 files changed, 12959 insertions(+), 1 deletion(-) create mode 100644 web/.dockerignore create mode 100644 web/.gitignore create mode 100644 web/.vscode/settings.json create mode 100644 web/DOCKER.md create mode 100644 web/Dockerfile create mode 100755 web/bun.lockb create mode 100644 web/components.json create mode 100644 web/docker-compose.yml create mode 100755 web/docker-scripts.sh create mode 100644 web/docs/README.md create mode 100644 web/middleware.ts create mode 100644 web/next-auth.d.ts create mode 100644 web/next.config.ts create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/postcss.config.mjs create mode 100644 web/public/logo.png create mode 100644 web/server.js create mode 100644 web/src/app/(auth)/layout.tsx create mode 100644 web/src/app/(auth)/page.tsx create mode 100644 web/src/app/(screens)/courses/[course]/page.tsx create mode 100644 web/src/app/(screens)/dashboard/page.tsx create mode 100644 web/src/app/(screens)/layout.tsx create mode 100644 web/src/app/(screens)/queue/[office_hours_id]/page.tsx create mode 100644 web/src/app/api/auth/[...nextauth]/route.ts create mode 100644 web/src/app/api/auth/callback/route.tsx create mode 100644 web/src/app/api/jwt/route.ts create mode 100644 web/src/app/api/queue/get/route.tsx create mode 100644 web/src/app/api/queue/join/route.tsx create mode 100644 web/src/app/api/queue/leave/route.tsx create mode 100644 web/src/app/api/queue/session/update/route.ts create mode 100644 web/src/app/api/queue/session/validate/route.ts create mode 100644 web/src/app/api/token/route.ts create mode 100644 web/src/app/api/users/route.ts create mode 100644 web/src/app/favicon.ico create mode 100644 web/src/app/globals.css create mode 100644 web/src/components/custom/animated-modal.tsx create mode 100644 web/src/components/custom/card-input-modal.tsx create mode 100644 web/src/components/custom/course-card.tsx create mode 100644 web/src/components/custom/mode-toggle.tsx create mode 100644 web/src/components/custom/navbar.tsx create mode 100644 web/src/components/custom/office-hours.tsx create mode 100644 web/src/components/custom/shiny-text.tsx create mode 100644 web/src/components/custom/sidebar.tsx create mode 100644 web/src/components/custom/theme-provider.tsx create mode 100644 web/src/components/queue/active-session.tsx create mode 100644 web/src/components/queue/announcement-card.tsx create mode 100644 web/src/components/queue/instructor-view.tsx create mode 100644 web/src/components/queue/office-hours-info-card.tsx create mode 100644 web/src/components/queue/queue-form.tsx create mode 100644 web/src/components/queue/queue-status-card.tsx create mode 100644 web/src/components/queue/queue-status-table.tsx create mode 100644 web/src/components/queue/resolution-form.tsx create mode 100644 web/src/components/queue/student-view.tsx create mode 100644 web/src/components/ui/avatar.tsx create mode 100644 web/src/components/ui/badge.tsx create mode 100644 web/src/components/ui/blur-fade.tsx create mode 100644 web/src/components/ui/button.tsx create mode 100644 web/src/components/ui/card.tsx create mode 100644 web/src/components/ui/checkbox.tsx create mode 100644 web/src/components/ui/dialog.tsx create mode 100644 web/src/components/ui/dropdown-menu.tsx create mode 100644 web/src/components/ui/form.tsx create mode 100644 web/src/components/ui/input.tsx create mode 100644 web/src/components/ui/label.tsx create mode 100644 web/src/components/ui/select.tsx create mode 100644 web/src/components/ui/separator.tsx create mode 100644 web/src/components/ui/sheet.tsx create mode 100644 web/src/components/ui/sidebar.tsx create mode 100644 web/src/components/ui/skeleton.tsx create mode 100644 web/src/components/ui/table.tsx create mode 100644 web/src/components/ui/tabs.tsx create mode 100644 web/src/components/ui/textarea.tsx create mode 100644 web/src/components/ui/tooltip.tsx create mode 100644 web/src/hooks/use-mobile.tsx create mode 100644 web/src/lib/auth/auth-option.ts create mode 100644 web/src/lib/helper/autolab-helper.tsx create mode 100644 web/src/lib/helper/database-helper.tsx create mode 100644 web/src/lib/helper/queue-helper.tsx create mode 100644 web/src/lib/helper/session-helper.ts create mode 100644 web/src/lib/helper/session-persistence.ts create mode 100644 web/src/lib/supabase/supabase-client.ts create mode 100644 web/src/lib/supabase/supabase.ts create mode 100644 web/src/lib/utils.ts create mode 100644 web/src/types/index.ts create mode 100644 web/src/types/queue-types.ts create mode 100644 web/src/types/session-types.ts create mode 100644 web/src/types/supabase-types.ts create mode 100644 web/tailwind.config.ts create mode 100644 web/tsconfig.json diff --git a/.gitignore b/.gitignore index 0a3fcd0..908ec38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .venv/ **/__pycache__/ -.idea/ \ No newline at end of file +.idea/.DS_Store diff --git a/web/.dockerignore b/web/.dockerignore new file mode 100644 index 0000000..b28c474 --- /dev/null +++ b/web/.dockerignore @@ -0,0 +1,94 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Next.js build output +.next +out + +# Production build +build + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo + +# IDE +.vscode +.idea + +# OS +.DS_Store +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + + + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + + +# ssl certificates +localhost-key.pem +localhost.pem \ No newline at end of file diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..eda351c --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,48 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +certificates + + +# ssl certificates +localhost-key.pem +localhost.pem \ No newline at end of file diff --git a/web/.vscode/settings.json b/web/.vscode/settings.json new file mode 100644 index 0000000..17801ff --- /dev/null +++ b/web/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "github.copilot.enable": { + "*": true, + "plaintext": true, + "markdown": true, + "scminput": true + } +} \ No newline at end of file diff --git a/web/DOCKER.md b/web/DOCKER.md new file mode 100644 index 0000000..37a13cb --- /dev/null +++ b/web/DOCKER.md @@ -0,0 +1,159 @@ +# Docker Setup for Queue Application + +This document provides instructions for running the Queue application using Docker. + +## Prerequisites + +- Docker installed on your system +- Docker Compose installed on your system + +## Environment Variables + +Create a `.env.local` file in the root directory with the following variables: + +```bash +# Database +DATABASE_URL=your_database_url_here + +# NextAuth +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=your_nextauth_secret_here + +# Supabase +NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here +NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here +SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key_here + +# Autolab API +AUTOLAB_CLIENT_ID=your_autolab_client_id_here +AUTOLAB_CLIENT_SECRET=your_autolab_client_secret_here +AUTOLAB_REDIRECT_URI=http://localhost:3000/api/auth/callback/autolab + +# JWT +JWT_SECRET=your_jwt_secret_here + +# Environment +NODE_ENV=development +``` + +## Development Mode + +To run the application in development mode with hot reloading: + +```bash +# Build and start the development container +docker-compose --profile dev up --build + +# Or run in detached mode +docker-compose --profile dev up --build -d +``` + +The application will be available at `http://localhost:3000` + +## Production Mode + +To run the application in production mode: + +```bash +# Build and start the production container +docker-compose --profile prod up --build + +# Or run in detached mode +docker-compose --profile prod up --build -d +``` + +## Production Mode with Environment File + +To run with a specific environment file: + +```bash +# Create .env.production file first +docker-compose --profile prod-env up --build +``` + +## Manual Docker Commands + +### Development + +```bash +# Build development image +docker build -f Dockerfile.dev -t queue-app:dev . + +# Run development container +docker run -p 3000:3000 -v $(pwd):/app -v /app/node_modules -v /app/.next queue-app:dev +``` + +### Production + +```bash +# Build production image +docker build -t queue-app:prod . + +# Run production container +docker run -p 3000:3000 queue-app:prod +``` + +## Stopping Containers + +```bash +# Stop all containers +docker-compose down + +# Stop and remove volumes +docker-compose down -v + +# Stop specific profile +docker-compose --profile dev down +``` + +## Useful Commands + +```bash +# View logs +docker-compose logs -f + +# View logs for specific service +docker-compose logs -f app-dev + +# Execute commands in running container +docker-compose exec app-dev sh + +# Rebuild without cache +docker-compose build --no-cache +``` + +## Troubleshooting + +### Port Already in Use +If port 3000 is already in use, change the port mapping in `docker-compose.yml`: + +```yaml +ports: + - "3001:3000" # Maps host port 3001 to container port 3000 +``` + +### Permission Issues +If you encounter permission issues on Linux/macOS: + +```bash +# Fix ownership of node_modules +sudo chown -R $USER:$USER node_modules +``` + +### Build Issues +If you encounter build issues: + +```bash +# Clean up Docker cache +docker system prune -a + +# Rebuild without cache +docker-compose build --no-cache +``` + +## Notes + +- The development setup includes volume mounts for hot reloading +- The production setup uses multi-stage builds for optimized images +- Environment variables should be properly configured before running +- The application uses Next.js standalone output for production builds \ No newline at end of file diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..a19d6d3 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,22 @@ +# Development Dockerfile +FROM node:18-alpine + +# Install dependencies +RUN apk add --no-cache libc6-compat + +WORKDIR /app + +# Copy package files +COPY package.json package-lock.json* ./ + +# Install all dependencies (including dev dependencies) +RUN npm ci + +# Copy source code +COPY . . + +# Expose port +EXPOSE 3000 + +# Start development server +CMD ["node", "server.js"] \ No newline at end of file diff --git a/web/bun.lockb b/web/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..e9c27b17d2b6bf4f9456fa4d564c331bb302ca66 GIT binary patch literal 153948 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%0Av~lw z!LOeDIm2C*316kBdTp~?9JCd#F4!J#GqMY$&S^ub$|i~?7$ zHCe99XV06H44h@V87W(fvkL8!XAya&z|0UmZZfXia&_RU)?458@x0PIVkAOlPGWQ~ zV0G!vZ{TmZ7+n%~vLz=ga#xte0As|o_?#{2o{QeQVkz^>jw+Wq1)e%h8pr%%EMD=e z7k`jP+XBfB5pH$G`GE_P7~KS{ZbndB>iqP1D*Tij z!VSCME@5?bE`O=-S81S*KiXRB+YtOl!ql#3s87Ta3SnSl#?v- zr_GUBcbt2rBmH-Y$E+yq$_O?wx5N!M8*O;c{&qIZ^E!oQ^M$)s7~LeSt~{>ZQFT+( zF`pMt7;I*rT#Fc`XU{h2)-#T(J3u=*Iri|xjVCb)?Y`Tu>|;yTX)d*Uupvi;f1!QA zLc*j?XwO58ZZcN)MCGUtS(1I_OxN-Ct1R4#0$(IHRBMF^�X%)DIoVuVoIo7sh;Y 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 a_|L_LuC8Y8WQ;! zjs+r_UR*9?0f+ru z?`-{?tsl;Z?A%3;zR(oxg`?dv*dr55`)JlQ6bPpLSo=n+f^qnA8Z(eC8tm{jvT{g8 z)^IJnI90O+s%oGh?jigr$v&FmH5*=0E0f^`n(3VlG0l^@SGJeQ^e-7IEiy-Ya9&kLi9&`+c*h7Yuas;S<%_9yRaG(87J76n?>3cnu*M8Q8 zXn(BsCB-;sQ*PSyC4_=w^l=KO5j}--`NaXvZj#U_>z`8zG+Y$G`y33NKb>b#(O*CX zyINSDO%48Z6E;pI-A=U7KLQ#3g~zSzCg4JaaFFd5wlmY&$8MV!>>m?V8wKt}y!4VT z{{=SvK##pUKwUJ3N&*4H8?`eU6T+Ct4YWL_Se7!o}Gh-Hvsc7tET&4hzry)K)UQ+YIEgNz9$sA*Fh0H5~vc?Kv-*BZ{61i6NCRC)ot1 z<~3T?ITFau+LddI5=WG3QWCGwb~+Tu-SM~bV>yyTBWX!TS#xg#)Vv`q-pZyCHYTzJ zbJbw6cV44s*h=peSGSg3y_LT8dZz6sm%GSwEe(l7qmkY6uI)+Bhnjl7e6G|~yV7%! z4i(9`{Z6YlV`5(I(y&#?LXA&BjxxX#Tl4mfaA6-NXR&z_7R1Aw%gDtlL;3ppO<@XdW7p}&B^R?G~F z!Iv>S4I;)@j#hK);=2g(U;Ls_O)@+zHBMen*q~oiSXDW+*C6elLj6WYf!d?{ab2!C zcI^7=SKq(!u%-?D?v#k@H3FBGSN)vsIxN?(bu81abz~yOdo2qJ<)KV;dm`V!n58Ts Ll_K9C)Bpbix-`J2 literal 0 HcmV?d00001 diff --git a/web/components.json b/web/components.json new file mode 100644 index 0000000..a577707 --- /dev/null +++ b/web/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/web/docker-compose.yml b/web/docker-compose.yml new file mode 100644 index 0000000..44c36c4 --- /dev/null +++ b/web/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.8' + +services: + # Development service + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + volumes: + - .:/app + - /app/node_modules + - /app/.next + - ./localhost.pem:/app/localhost.pem:ro + - ./localhost-key.pem:/app/localhost-key.pem:ro + environment: + - NODE_ENV=development + diff --git a/web/docker-scripts.sh b/web/docker-scripts.sh new file mode 100755 index 0000000..a640660 --- /dev/null +++ b/web/docker-scripts.sh @@ -0,0 +1,165 @@ +#!/bin/bash + +# Docker scripts for Queue Application - Development Only + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to check if Docker is running +check_docker() { + if ! docker info > /dev/null 2>&1; then + print_error "Docker is not running. Please start Docker and try again." + exit 1 + fi +} + +# Function to check if required files exist +check_files() { + if [ ! -f "package.json" ]; then + print_error "package.json not found. Please run this script from the project root." + exit 1 + fi + + if [ ! -f "Dockerfile" ]; then + print_error "Dockerfile not found. Please ensure Docker files are created." + exit 1 + fi +} + +# Development commands +dev() { + print_status "Starting development environment..." + check_docker + check_files + docker compose up --build + print_success "Development environment started. Access at https://localhost:3000" +} + +dev_detached() { + print_status "Starting development environment in detached mode..." + check_docker + check_files + docker compose up --build -d + print_success "Development environment started in detached mode. Access at https://localhost:3000" +} + +# Build commands +build_dev() { + print_status "Building development image..." + check_docker + check_files + docker build -f Dockerfile -t queue-app:dev . + print_success "Development image built successfully" +} + +# Utility commands +stop() { + print_status "Stopping all containers..." + check_docker + docker compose down + print_success "All containers stopped" +} + +clean() { + print_status "Cleaning up Docker resources..." + check_docker + docker compose down --volumes --remove-orphans + docker system prune -f + print_success "Docker cleanup completed" +} + +logs() { + print_status "Showing logs for development service..." + check_docker + docker compose logs -f +} + +shell() { + print_status "Opening shell in development container..." + check_docker + check_files + + # Check if container is running + if ! docker compose ps | grep -q "Up"; then + print_warning "No running containers found. Starting development environment first..." + dev_detached + sleep 5 + fi + + docker compose exec app sh +} + +# Help function +show_help() { + echo "Docker Development Scripts for Queue Application" + echo "" + echo "Usage: $0 [command]" + echo "" + echo "Development Commands:" + echo " dev Start development environment" + echo " dev-detached Start development environment in detached mode" + echo " build-dev Build development image" + echo "" + echo "Utility Commands:" + echo " stop Stop all containers" + echo " clean Clean up Docker resources" + echo " logs Show logs for development service" + echo " shell Open shell in development container" + echo "" + echo "Examples:" + echo " $0 dev # Start development environment" + echo " $0 build-dev # Build development image" + echo " $0 logs # Show logs" + echo " $0 shell # Open shell in container" +} + +# Main script logic +case "${1:-help}" in + "dev") + dev + ;; + "dev-detached") + dev_detached + ;; + "build-dev") + build_dev + ;; + "stop") + stop + ;; + "clean") + clean + ;; + "logs") + logs + ;; + "shell") + shell + ;; + "help"|*) + show_help + ;; +esac \ No newline at end of file diff --git a/web/docs/README.md b/web/docs/README.md new file mode 100644 index 0000000..4d05b0b --- /dev/null +++ b/web/docs/README.md @@ -0,0 +1,129 @@ +# MakeOfficeHours Web Application Documentation + +## Overview +The MakeOfficeHours web application is a modern, full-stack office hours queue management system built with Next.js 15, TypeScript, and Tailwind CSS. It provides a seamless experience for students and instructors to manage office hours queues with real-time updates, authentication, and responsive design. + +## 🏗️ Architecture + +### Tech Stack +- **Framework**: Next.js 15.2.4 (App Router) +- **Language**: TypeScript +- **Styling**: Tailwind CSS with custom animations +- **Authentication**: NextAuth.js with OAuth (Autolab integration) +- **Database**: Supabase (PostgreSQL) +- **UI Components**: Radix UI primitives +- **State Management**: React hooks and context +- **Deployment**: Docker with multi-stage builds + +### Project Structure +``` +web/ +├── src/ +│ ├── app/ # Next.js App Router +│ │ ├── (auth)/ # Authentication Screens +│ │ ├── (screens)/ # Main application screens +│ │ └── api/ # API routes +│ ├── components/ # Reusable components +│ │ ├── custom/ # Custom business components +│ │ ├── queue/ # Queue-specific components +│ │ └── ui/ # Base UI components +│ ├── hooks/ # Custom React hooks +│ ├── lib/ # Utility libraries and helpers +│ └── types/ # TypeScript type definitions +├── public/ # Static assets +└── docs/ # Documentation +``` + +## Getting Started + +### Prerequisites +- Node.js 18+ +- npm or yarn +- Docker (optional, for containerized development) +- SSL certificates (for HTTPS development) + +### Installation + +1. **Clone the repository and navigate to web directory** + ```bash + cd /path/to/MakeOfficeHours/web + ``` + +2. **Install dependencies** + ```bash + npm install + ``` + +3. **Set up environment variables** + Create a `.env.local` file in the web directory with the following variables:\ + __Important__: These secrets are not self-generated. Please request access from us to obtain the values. + ```env + # Next.js / NextAuth Configuration + NEXTAUTH_URL="https://localhost:3000" + NODE_ENV="development" + NEXTAUTH_SECRET="your-secret-key" + + # Autolab OAuth Configuration + AUTOLAB_CLIENT_ID="your-autolab-client-id" + AUTOLAB_CLIENT_SECRET="your-autolab-client-secret" + AUTOLAB_AUTHORIZE_ENDPOINT="https://autolab.cse.buffalo.edu/oauth/authorize" + AUTOLAB_TOKEN_ENDPOINT="https://autolab.cse.buffalo.edu/oauth/token" + AUTOLAB_USER_ENDPOINT="https://autolab.cse.buffalo.edu/api/v1/user" + AUTOLAB_COURSE_ENDPOINT="https://autolab.cse.buffalo.edu/api/v1/courses?state=current" + AUTOLAB_REDIRECT_URI="https://localhost:3000/api/auth/callback/autolab" + + # Supabase Configuration + NEXT_PUBLIC_SUPABASE_URL="your-supabase-url" + NEXT_PUBLIC_SUPABASE_ANON_KEY="your-supabase-anon-key" + + # JWT + JWT_SECRET="your-jwt-secret" + ``` + +4. **Set up SSL certificates (for https development as we can't run localhost over http due to autolab Oauth config)** + ```bash + # Install mkcert if not already installed + brew install mkcert # macOS + # or + choco install mkcert # Windows + + # Generate certificates + mkcert -install + mkcert localhost + ``` + +### Development + +#### Option 1: Standard Development Server +```bash +npm run dev +``` +This starts the development server with HTTPS on `https://localhost:3000` + +#### Option 2: Custom HTTPS Server +```bash +npm run dev:https +``` +This uses the custom `server.js` for HTTPS with SSL certificates. + +#### Option 3: Docker Development +```bash +# Use the docker script for development +./docker-scripts.sh dev +``` +## API Routes + +### Authentication Routes +- `GET /api/auth/[...nextauth]` - NextAuth.js handler +- `GET /api/auth/callback` - OAuth callback handler +- `POST /api/jwt` - JWT token generation for Supabase + +### Queue Management Routes +- `GET /api/queue/get` - Fetch queue data +- `POST /api/queue/join` - Join queue +- `POST /api/queue/leave` - Leave queue +- `POST /api/queue/session/update` - Update session status +- `POST /api/queue/session/validate` - Validate session + +### User Management Routes +- `GET /api/users` - Fetch user data diff --git a/web/middleware.ts b/web/middleware.ts new file mode 100644 index 0000000..c0de882 --- /dev/null +++ b/web/middleware.ts @@ -0,0 +1,11 @@ +import { withAuth } from "next-auth/middleware"; + +export default withAuth({ + pages: { + signIn: "/", + }, +}); + +export const config = { + matcher: ["/dashboard/:path*"], +}; diff --git a/web/next-auth.d.ts b/web/next-auth.d.ts new file mode 100644 index 0000000..4bcbc6d --- /dev/null +++ b/web/next-auth.d.ts @@ -0,0 +1,30 @@ +import "next-auth"; + +declare module "next-auth" { + interface Session { + user: { + userID: unknown; + courses: string[]; + id: string; + name: string; + email: string; + accessToken?: string; + refreshToken?: string; + expiresAt?: number; + }; + } + + interface User { + id: string; + name: string; + email: string; + } +} + +declare module "next-auth/jwt" { + interface JWT { + accessToken?: string; + refreshToken?: string; + expiresAt?: number; + } +} diff --git a/web/next.config.ts b/web/next.config.ts new file mode 100644 index 0000000..225e495 --- /dev/null +++ b/web/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: 'standalone', +}; + +export default nextConfig; diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..2d4f91b --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,5497 @@ +{ + "name": "queue", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "queue", + "version": "0.1.0", + "dependencies": { + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-avatar": "^1.1.2", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.1", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-tooltip": "^1.1.6", + "@supabase/supabase-js": "^2.47.10", + "@tanstack/react-table": "^8.20.6", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "crypto": "^1.0.1", + "framer-motion": "^11.18.2", + "jsonwebtoken": "^9.0.2", + "lucide-react": "^0.469.0", + "motion": "^11.15.0", + "next": "15.2.4", + "next-auth": "^4.24.11", + "next-themes": "^0.4.4", + "radix-ui": "^1.0.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.56.1", + "react-icons": "^5.4.0", + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", + "uuid": "^11.0.3", + "zod": "^3.24.3" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.7", + "@types/node": "^20", + "@types/react": "19.0.12", + "@types/react-dom": "19.0.4", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } + }, + "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/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@hookform/resolvers": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz", + "integrity": "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz", + "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz", + "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", + "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", + "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", + "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", + "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", + "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", + "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", + "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "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/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.2.tgz", + "integrity": "sha512-+rnMO0SEfzkcHr93RshkQVpOA26MtGOv4pcS9QUnLg4F8+GDmCJ8c2FEPhPz5e7arf31EzbTqJxFbzg3qen14g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.3.tgz", + "integrity": "sha512-RIQ15mrcvqIkDARJeERSuXSry2N8uYnxkdDetpfmalT/+0ntOXLkFOsh9iwlAsCv+qcmhZjbdJogIm6WBa6c4A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collapsible": "1.1.3", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.6.tgz", + "integrity": "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dialog": "1.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-dialog": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", + "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", + "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.2.tgz", + "integrity": "sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.3.tgz", + "integrity": "sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz", + "integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz", + "integrity": "sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.6.tgz", + "integrity": "sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-menu": "2.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.6.tgz", + "integrity": "sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", + "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.6.tgz", + "integrity": "sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.6.tgz", + "integrity": "sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.5.tgz", + "integrity": "sha512-myMHHQUZ3ZLTi8W381/Vu43Ia0NqakkQZ2vzynMmTUtQQ9kNkjzhOwkZC9TAM5R07OZUVIQyHC06f/9JZJpvvA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz", + "integrity": "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", + "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.2.tgz", + "integrity": "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz", + "integrity": "sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", + "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz", + "integrity": "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", + "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz", + "integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.3.tgz", + "integrity": "sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.3.tgz", + "integrity": "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", + "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz", + "integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.2.tgz", + "integrity": "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.2.tgz", + "integrity": "sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-toggle": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.2.tgz", + "integrity": "sha512-wT20eQ7ScFk+kBMDmHp+lMk18cgxhu35b2Bn5deUcPxiVwfn5vuZgi7NGcHu8ocdkinahmp4FaSZysKDyRVPWQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-separator": "1.1.2", + "@radix-ui/react-toggle-group": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz", + "integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@supabase/auth-js": { + "version": "2.67.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.67.3.tgz", + "integrity": "sha512-NJDaW8yXs49xMvWVOkSIr8j46jf+tYHV0wHhrwOaLLMZSFO4g6kKAf+MfzQ2RaD06OCUkUHIzctLAxjTgEVpzw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.17.7.tgz", + "integrity": "sha512-aOzOYaTADm/dVTNksyqv9KsbhVa1gHz1Hoxb2ZEF2Ed9H7qlWOfptECQWmkEmrrFjtNaiPrgiSaPECvzI/seDA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", + "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.18.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.47.10", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.47.10.tgz", + "integrity": "sha512-vJfPF820Ho5WILYHfKiBykDQ1SB9odTHrRZ0JxHfuLMC8GRvv21YLkUZQK7/rSVCkLvD6/ZwMWaOAfdUd//guw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.67.3", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.17.7", + "@supabase/realtime-js": "2.11.2", + "@supabase/storage-js": "2.7.1" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.20.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz", + "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz", + "integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "engines": { + "node": ">=12" + }, + "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/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/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/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.30001689", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", + "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "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" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "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/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "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/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", + "license": "ISC" + }, + "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.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "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/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "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/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "optional": true + }, + "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-core-module": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "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-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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "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/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/lucide-react": { + "version": "0.469.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz", + "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/motion": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-11.15.0.tgz", + "integrity": "sha512-iZ7dwADQJWGsqsSkBhNHdI2LyYWU+hA1Nhy357wCLZq1yHxGImgt3l7Yv0HT/WOskcYDq9nxdedyl4zUv7UFFw==", + "license": "MIT", + "dependencies": { + "framer-motion": "^11.15.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "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.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "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/next": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz", + "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==", + "license": "MIT", + "dependencies": { + "@next/env": "15.2.4", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.2.4", + "@next/swc-darwin-x64": "15.2.4", + "@next/swc-linux-arm64-gnu": "15.2.4", + "@next/swc-linux-arm64-musl": "15.2.4", + "@next/swc-linux-x64-gnu": "15.2.4", + "@next/swc-linux-x64-musl": "15.2.4", + "@next/swc-win32-arm64-msvc": "15.2.4", + "@next/swc-win32-x64-msvc": "15.2.4", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-auth": { + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/next-themes": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.4.tgz", + "integrity": "sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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/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/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, + "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/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/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==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "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==", + "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-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "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/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.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "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.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "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.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "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/preact": { + "version": "10.25.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", + "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, + "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/radix-ui": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.0.1.tgz", + "integrity": "sha512-qfGibbqtbOlxP3b+1JjbLUc8Q7+e9DL8gFycLtkBkoAQyUkKuHAEBfFUcyG5MaQHjqRuML+YLtt/R1/dUYQafQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-accessible-icon": "latest", + "@radix-ui/react-accordion": "latest", + "@radix-ui/react-alert-dialog": "latest", + "@radix-ui/react-aspect-ratio": "latest", + "@radix-ui/react-avatar": "latest", + "@radix-ui/react-checkbox": "latest", + "@radix-ui/react-collapsible": "latest", + "@radix-ui/react-context-menu": "latest", + "@radix-ui/react-dialog": "latest", + "@radix-ui/react-direction": "latest", + "@radix-ui/react-dropdown-menu": "latest", + "@radix-ui/react-hover-card": "latest", + "@radix-ui/react-label": "latest", + "@radix-ui/react-navigation-menu": "latest", + "@radix-ui/react-popover": "latest", + "@radix-ui/react-portal": "latest", + "@radix-ui/react-progress": "latest", + "@radix-ui/react-radio-group": "latest", + "@radix-ui/react-scroll-area": "latest", + "@radix-ui/react-select": "latest", + "@radix-ui/react-separator": "latest", + "@radix-ui/react-slider": "latest", + "@radix-ui/react-slot": "latest", + "@radix-ui/react-switch": "latest", + "@radix-ui/react-tabs": "latest", + "@radix-ui/react-toast": "latest", + "@radix-ui/react-toggle": "latest", + "@radix-ui/react-toggle-group": "latest", + "@radix-ui/react-toolbar": "latest", + "@radix-ui/react-tooltip": "latest", + "@radix-ui/react-visually-hidden": "latest" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-hook-form": { + "version": "7.58.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.58.1.tgz", + "integrity": "sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-icons": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", + "integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", + "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", + "dependencies": { + "is-core-module": "^2.16.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/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/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/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "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==", + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "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-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "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-cjs": { + "name": "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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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==" + }, + "node_modules/string-width-cjs/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "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": ">=16 || 14 >=14.17" + } + }, + "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/tailwind-merge": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", + "integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", + "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "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/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/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "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/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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==", + "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-cjs/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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==" + }, + "node_modules/wrap-ansi-cjs/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..7b11098 --- /dev/null +++ b/web/package.json @@ -0,0 +1,59 @@ +{ + "name": "queue", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "npx next dev --experimental-https", + "dev:https": "NODE_EXTRA_CA_CERTS=\"$(mkcert -CAROOT)/rootCA.pem\" node server.js", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-avatar": "^1.1.2", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.1", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-tooltip": "^1.1.6", + "@supabase/supabase-js": "^2.47.10", + "@tanstack/react-table": "^8.20.6", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "crypto": "^1.0.1", + "framer-motion": "^11.18.2", + "jsonwebtoken": "^9.0.2", + "lucide-react": "^0.469.0", + "motion": "^11.15.0", + "next": "15.2.4", + "next-auth": "^4.24.11", + "next-themes": "^0.4.4", + "radix-ui": "^1.0.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.56.1", + "react-icons": "^5.4.0", + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", + "uuid": "^11.0.3", + "zod": "^3.24.3" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.7", + "@types/node": "^20", + "@types/react": "19.0.12", + "@types/react-dom": "19.0.4", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + }, + "overrides": { + "@types/react": "19.0.12", + "@types/react-dom": "19.0.4" + } +} diff --git a/web/postcss.config.mjs b/web/postcss.config.mjs new file mode 100644 index 0000000..1a69fd2 --- /dev/null +++ b/web/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/web/public/logo.png b/web/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..31c64a8f50d081efe58a11f1b59608c16c11859e GIT binary patch literal 34784 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 t;D1~Ge*w;a;T#a#{ueXrSatS?w@81AEO7iM* { + createServer(httpsOptions, (req, res) => { + const parsedUrl = parse(req.url, true); + handle(req, res, parsedUrl); + }).listen(port, err => { + if (err) throw err; + console.log(`> Ready on https://localhost:${port}`); + }); +}); diff --git a/web/src/app/(auth)/layout.tsx b/web/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..b662ed5 --- /dev/null +++ b/web/src/app/(auth)/layout.tsx @@ -0,0 +1,50 @@ +'use client'; + +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "../globals.css"; +import Navbar from "@/components/custom/navbar"; +import { ThemeProvider } from "@/components/custom/theme-provider"; +import { SessionProvider } from "next-auth/react"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + + return ( + + + + + +
{children}
+
+
+ + + ); +} diff --git a/web/src/app/(auth)/page.tsx b/web/src/app/(auth)/page.tsx new file mode 100644 index 0000000..8edcf3f --- /dev/null +++ b/web/src/app/(auth)/page.tsx @@ -0,0 +1,28 @@ +'use client' + +import { ShinyText } from "@/components/custom/shiny-text"; +import { Button } from "../../components/ui/button"; +import { useRouter } from "next/navigation"; +import { signIn } from "next-auth/react"; + +export default function Home() { + const router = useRouter(); + + const handleClick = async () => { + signIn("autolab") + }; + return ( +
+
+ JOIN THE QUEUE +

+ Welcome to the queue! Here, you can get help during office hours without needing to be physically present. Join the queue to reserve your spot in office hours. +

+
+
+ + +
+
+ ) +} \ No newline at end of file diff --git a/web/src/app/(screens)/courses/[course]/page.tsx b/web/src/app/(screens)/courses/[course]/page.tsx new file mode 100644 index 0000000..ea76081 --- /dev/null +++ b/web/src/app/(screens)/courses/[course]/page.tsx @@ -0,0 +1,134 @@ +'use client'; +import { useParams, useSearchParams, useRouter } from 'next/navigation'; +import React, { useEffect } from 'react'; +import { OfficeHoursCard } from '@/components/custom/office-hours'; +import { getCourseId, getOfficeHoursSchedule } from '@/lib/helper/database-helper'; +import { Skeleton } from '@/components/ui/skeleton'; +import { signOut, useSession } from 'next-auth/react'; +import { getUserCoursesFromSession } from '@/lib/helper/autolab-helper'; + +interface UserCourse { + auth_level: string; + display_name: string; + name: string; +} + +interface OfficeHours { + id: string; + location: string; + start: number; + end: number; + day: number; + instructor: string[]; +} + +export default function CoursePage() { + const [officeHours, setOfficeHours] = React.useState([]); + const [loading, setLoading] = React.useState(true); + + const params = useParams(); + const searchParams = useSearchParams(); + const router = useRouter(); + + const courseCode: string = params?.course as string; + const courseName = searchParams.get('course'); + + const { data: session, status } = useSession(); + + // User authentication and authorization + useEffect(() => { + if (status === "loading") return; // Skip while loading + + const validateUser = async () => { + try { + const userCourses = await getUserCoursesFromSession(); + const isAuthorized = userCourses.some( + (course: UserCourse) => + course.name === courseCode && course.display_name === courseName + ); + + if (!isAuthorized) { + // router.push('/error'); + } + } catch (error) { + console.error('Error occurred while fetching user courses:', error); + // router.push('/error'); + } + }; + + if (status === 'unauthenticated') { + signOut({ + redirect: true, + callbackUrl: '/', + }); + } else if (status === 'authenticated') { + validateUser(); + } + }, [session, status, courseCode, courseName, router]); + + // Fetch office hours data + useEffect(() => { + const fetchOfficeHours = async () => { + try { + setLoading(true); + + if (courseName && courseCode) { + const courseId = await getCourseId(courseName, courseCode); + if (!courseId) { + console.error("Course ID is null"); + // router.push('/error'); + return; + } + + const schedule = await getOfficeHoursSchedule(courseId); + setOfficeHours(schedule); + } + } catch (error) { + console.error('An unexpected error occurred:', error); + } finally { + setLoading(false); + } + }; + + fetchOfficeHours(); + }, [courseCode, courseName, router]); + + // Render skeletons while loading + const renderSkeletons = () => { + return Array.from({ length: 6 }).map((_, index) => ( +
+ +
+ )); + }; + + // Display a message if no office hours are available + const displayMessage = () => { + return ( +
+

No office hours available

+
+ ); + }; + + return ( +
+

Office Hours

+ {loading + ? renderSkeletons() + : officeHours.length === 0 + ? displayMessage() + : officeHours.map((OH) => ( + + ))} +
+ ); +} diff --git a/web/src/app/(screens)/dashboard/page.tsx b/web/src/app/(screens)/dashboard/page.tsx new file mode 100644 index 0000000..f766c14 --- /dev/null +++ b/web/src/app/(screens)/dashboard/page.tsx @@ -0,0 +1,78 @@ +'use client' + +import { CourseCard } from '@/components/custom/course-card'; +import React, {useEffect} from 'react'; +import { Skeleton } from '@/components/ui/skeleton'; +import {getUserCoursesFromSession } from '@/lib/helper/autolab-helper'; +import { signOut, useSession } from 'next-auth/react'; +import { UserCourse } from '@/types/session-types'; + +export default function Dashboard() { + const [userCourses, setUserCourses] = React.useState([]); + const [loading, setLoading] = React.useState(true); + + const { data: session, status } = useSession(); + + useEffect(() => { + const fetchUserCourses = async () => { + try { + setLoading(true); + const user_courses = await getUserCoursesFromSession(); + if(Array.isArray(user_courses)){ + setUserCourses(user_courses); + return; + }else{ + setUserCourses([]); + } + } catch (error) { + console.error("An error occurred while fetching user courses:", error); + } finally { + setLoading(false); + } + }; + + if (status === 'unauthenticated') { + signOut({ + redirect: true, + callbackUrl: '/', + }); + } else if (status === 'authenticated') { + fetchUserCourses(); + } + }, [session, status]); + + const renderSkeletons = () => { + return Array.from({ length: 6 }).map((i, index) => ( +
+ +
+ )); + }; + + return ( +
+

Dashboard

+
+ {loading? + renderSkeletons() + : userCourses.length > 0 ? + userCourses.map((course) => { + return( + + ) + }) + : +

You don't have any current courses!

+ } +
+
+ ); +} + diff --git a/web/src/app/(screens)/layout.tsx b/web/src/app/(screens)/layout.tsx new file mode 100644 index 0000000..5dcfbc7 --- /dev/null +++ b/web/src/app/(screens)/layout.tsx @@ -0,0 +1,59 @@ +'use client'; + +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "../globals.css"; +import { ThemeProvider } from "@/components/custom/theme-provider"; +import { AppSidebar } from "@/components/custom/sidebar"; +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { SessionProvider } from "next-auth/react"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + + + return ( + + + + + + +
+ + {children} +
+
+ +
+
+ + + ); +} diff --git a/web/src/app/(screens)/queue/[office_hours_id]/page.tsx b/web/src/app/(screens)/queue/[office_hours_id]/page.tsx new file mode 100644 index 0000000..ace7b92 --- /dev/null +++ b/web/src/app/(screens)/queue/[office_hours_id]/page.tsx @@ -0,0 +1,254 @@ +'use client' + +import { Skeleton } from "@/components/ui/skeleton"; +import { getCourseName, getOfficeHoursEntry } from "@/lib/helper/database-helper"; +import { getUserCoursesFromSession } from "@/lib/helper/autolab-helper"; +import { getQueue, removeFromQueue } from "@/lib/helper/queue-helper"; +import { signOut, useSession } from "next-auth/react"; +import { useParams } from "next/navigation"; +import React, { useEffect, useState } from "react"; +import type { Queue } from "@/types"; +import InstructorView from "@/components/queue/instructor-view"; +import StudentView from "@/components/queue/student-view"; +import { UserCourse } from "@/types/session-types"; +import { Button } from "@/components/ui/button"; +import { getClientSupabaseClient } from "@/lib/supabase/supabase-client"; +import { SupabaseClient } from "@supabase/supabase-js"; + + + +export default function QueuePage() { + const [authLevel, setAuthLevel] = React.useState<"student" | "instructor" | null>(null); + const [loading, setLoading] = React.useState(true); + const [course, setCourse] = React.useState(); + const [queue, setQueue] = React.useState([]); + const [error, setError] = React.useState(null); + const { data: session, status } = useSession(); + const params = useParams(); + const office_hours_id: string = params?.office_hours_id as string; + // Validate queue and fetch course and user details + React.useEffect(() => { + const validateQueue = async () => { + try { + setLoading(true); + setError(null); + + const officeHourEntry = await getOfficeHoursEntry(office_hours_id); + if (!officeHourEntry) { + setError("Invalid office hours queue."); + return; + } + + const current_course = await getCourseName(officeHourEntry.class); + const user_courses = await getUserCoursesFromSession(); + const user_course = Array.isArray(user_courses) ? user_courses.find( + (course: any) => + course.display_name === current_course.name && + course.name === current_course.code + ) : null; + + if (!user_course) { + setError("User is not authorized to view this queue."); + return; + } + + setCourse(user_course); + setAuthLevel(user_course.auth_level); + } catch (error) { + console.error("Error while validating queue:", error); + setError("An unexpected error occurred. Please try again."); + } finally { + setLoading(false); // Ensure loading state is set to false once validation is complete + } + }; + + if (status === "unauthenticated") { + signOut({ + redirect: true, + callbackUrl: "/", + }); + } else { + validateQueue(); + } + }, [status, session, office_hours_id]); + + // Fetch queue info + React.useEffect(() => { + const getQueueInfo = async () => { + try { + setLoading(true); + const fetchedQueue = await getQueue(office_hours_id); + + if (!fetchedQueue) { + console.error("Error while fetching queue info"); + return; + } + if (Array.isArray(fetchedQueue)) { + setQueue(fetchedQueue); + } else { + console.error("Queue is not an array"); + } + } catch (error) { + console.error("Error while fetching queue info:", error); + } finally { + setLoading(false); // Ensure loading state is set to false once fetching is complete + } + }; + + if (status !== "unauthenticated") { + getQueueInfo(); + } + }, [status, session, office_hours_id]); + + const enrichQueueEntry = async (entry: any): Promise => { + try { + const response = await fetch(`/api/users?id=${entry.student}`) + if(!response.ok){ + console.error("Error while fetching user", response.statusText) + return entry + } + const data = await response.json() + return { + ...entry, + name: data.name, + email: data.email, + }; + } catch (err) { + console.error("Failed to enrich queue entry:", err); + return entry; + } + }; + + + useEffect(() => { + let channel: ReturnType | null = null; + const setupRealtime = async () => { + const supabase = await getClientSupabaseClient() + channel = supabase.channel("realtime-queue").on( + "postgres_changes", + { + event: "*", + schema: "public", + table: "queue", + }, + async (payload) => { + if (payload.eventType === "INSERT" && payload.new) { + const newQueueEntry = payload.new as Queue; + if (newQueueEntry.office_hours !== office_hours_id) return; + + setQueue((prev) => { + const alreadyExists = prev.some(q => q.id === newQueueEntry.id); + if (alreadyExists) return prev; + + if (!newQueueEntry.name || !newQueueEntry.email) { + enrichQueueEntry(newQueueEntry).then(enriched => { + setQueue(current => + current.some(q => q.id === enriched.id) ? current : [...current, enriched] + ); + }); + return prev; + } else { + return [...prev, newQueueEntry]; + } + }); + } + + + if (payload.eventType === "UPDATE" && payload.new) { + const updatedEntry = payload.new as Queue; + if (updatedEntry.office_hours !== office_hours_id) return; + + const enriched = await enrichQueueEntry(updatedEntry); + setQueue((prev) => + prev.map((q) => (q.id === enriched.id ? enriched : q)) + ); + } + + if (payload.eventType === "DELETE" && payload.old) { + const deletedEntry = payload.old as Queue; + setQueue((prev) => prev.filter(q => q.id !== deletedEntry.id)); + } + } + ).subscribe() + + } + + setupRealtime() + + return () => { + if (channel) { + channel.unsubscribe() + } + } + }, [office_hours_id]) + + // Render skeletons while loading + const renderSkeletons = () => ( +
+ {Array.from({ length: 6 }).map((_, index) => ( +
+ +
+ ))} +
+ ); + + const handleRemoveFromQueue = async (queueEntry: Queue[]) => { + const updatedQueue = await removeFromQueue(queueEntry); + console.log(queueEntry) + console.log(updatedQueue) + if (queueEntry) { + setQueue((prevQueue) => { + const updatedQueue = prevQueue.filter((entry) => !queueEntry.includes(entry)); + return updatedQueue.map((entry, index) => ({ + ...entry, + position: index + 1, + })); + }); + } else { + console.error("Error while removing from queue"); + } + }; + + const renderStudentView = () => ( +
+ +
+ ); + + const renderInstructorView = () => ( +
+ +
+ ); + + // // If still loading or auth level is not set + // if (authLevel === null) { + // return
{renderSkeletons()}
; + // } + + // If there is an error, display it + if (error) { + return
{error}
; + } + + // Handle rendering based on auth level + return ( +
+ + {authLevel === "student" ? renderStudentView() : renderInstructorView()} +
+ ); +} diff --git a/web/src/app/api/auth/[...nextauth]/route.ts b/web/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..4a174dc --- /dev/null +++ b/web/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from "next-auth"; +import { authOptions } from "@/lib/auth/auth-option"; // Keep auth logic in a separate file for reusability + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/web/src/app/api/auth/callback/route.tsx b/web/src/app/api/auth/callback/route.tsx new file mode 100644 index 0000000..d2835d7 --- /dev/null +++ b/web/src/app/api/auth/callback/route.tsx @@ -0,0 +1,88 @@ +// 'use server' + +// import { NextRequest, NextResponse } from "next/server"; +// import { pushUserToDataBase } from "@/lib/helper/getFromDatabase"; +// import {getUserCoursesFromAutolab, getUserInfo } from "@/lib/helper/getUserInfo"; +// import { cookies } from "next/headers"; + +// export async function GET(request: NextRequest) { +// try{ +// const { searchParams } = new URL(request.url); +// const code = searchParams.get("code"); + +// if(!code){ +// return NextResponse.json({ message: "No code received" }, { status: 400 }); +// } + +// const tokenResponse = await fetch("https://autolab.cse.buffalo.edu/oauth/token",{ +// method: "POST", +// headers:{ +// "Content-Type": "application/x-www-form-urlencoded" +// }, +// body: new URLSearchParams({ +// grant_type: "authorization_code", +// code, +// redirect_uri:process.env.AUTOLAB_REDIRECT_URI as string, +// client_id: process.env.AUTOLAB_CLIENT_ID as string, +// client_secret: process.env.AUTOLAB_CLIENT_SECRET as string, +// }) +// }) + +// if(!tokenResponse.ok){ +// const error = await tokenResponse.json(); +// return NextResponse.json({ error: "Error while getting token", details:error}, { status: 500 }); +// } + +// const tokenData = await tokenResponse.json(); +// const access_token = tokenData.access_token; +// const refresh_token = tokenData.refresh_token; +// const expires_in = tokenData.expires_in; + +// const userData = await getUserInfo({access_token}); + +// const userId = await pushUserToDataBase({ +// name: `${userData.first_name} ${userData.last_name}`, +// email: userData.email, +// access_token +// }); + +// const user_courses = await getUserCoursesFromAutolab({access_token}); + +// const cookieStore = await cookies(); + +// cookieStore.set("access_token", access_token, { +// maxAge: 60*60*5, +// httpOnly: true, +// secure: process.env.NODE_ENV === "production", +// path: "/" +// }); + +// cookieStore.set("refresh_token", refresh_token, { +// maxAge: 60*60*5, +// httpOnly: true, +// secure: process.env.NODE_ENV === "production", +// path: "/" +// }); + +// cookieStore.set("expires_at", Date.now() + expires_in, { +// maxAge: 60*60*5, +// httpOnly: true, +// secure: process.env.NODE_ENV === "production", +// path: "/" +// }) + +// cookieStore.set("user_courses", JSON.stringify(user_courses), { +// maxAge: 60*60*5, +// httpOnly: true, +// secure: process.env.NODE_ENV === "production", +// path: "/" +// }) + +// const response = NextResponse.redirect('https://localhost:3000/dashboard'); +// return response; +// } +// catch(error){ +// return NextResponse.json({ error: "Error occurred while getting authentication code", details : error }, { status: 500 }); +// } +// } + diff --git a/web/src/app/api/jwt/route.ts b/web/src/app/api/jwt/route.ts new file mode 100644 index 0000000..a17a919 --- /dev/null +++ b/web/src/app/api/jwt/route.ts @@ -0,0 +1,83 @@ +import jwt from "jsonwebtoken"; +import crypto from "crypto"; + +import { NextResponse, NextRequest } from "next/server"; +import { JWTApiError, JWTApiResponse, SupabaseJWTPayload } from "@/types/supabase-types"; + +export async function POST(req: NextRequest):Promise> { + try{ + const body = await req.json(); + const { access_token } = body; + + if (!access_token) { + return NextResponse.json( + { error: "Access token not provided" }, + { status: 400 } + ); + } + if (!process.env.AUTOLAB_USER_ENDPOINT) { + return NextResponse.json( + { error: "Missing required environment variables" }, + { status: 500 } + ); + } + + // Getting user data from Autolab + const autolabResponse = await fetch(process.env.AUTOLAB_USER_ENDPOINT, { + method: "GET", + headers: { + Authorization: `Bearer ${access_token}`, + }, + }); + + // Validating Autolab response + + if (!autolabResponse.ok) { + const error = await autolabResponse.json(); + return NextResponse.json( + { error: "Error while getting user data", details: error }, + { status: 500 } + ); + } + + const userData = await autolabResponse.json(); + + // Generating JWT payload + const now = Math.floor(Date.now() / 1000) + const jwtPayload: SupabaseJWTPayload = { + sub: userData.email, + email: userData.email, + phone: "", + aud: "authenticated", + role: "authenticated", + iat: now, + exp: now + 2 * 60 * 60, // 2 hours + aal: "aal1", + session_id: crypto.randomUUID(), + } + + if (!process.env.JWT_SECRET) { + return NextResponse.json( + { error: "Missing required environment variables" }, + { status: 500 } + ); + } + + const supabase_jwt = jwt.sign(jwtPayload,process.env.JWT_SECRET as string); + + return NextResponse.json({supabase_jwt}); + + + }catch(error){ + console.error("Error generating JWT:", error) + return NextResponse.json( + { + error: "Error while generating jwt", + details: error instanceof Error ? error.message : "Unknown error", + }, + { status: 500 }, + ) + } + +} + diff --git a/web/src/app/api/queue/get/route.tsx b/web/src/app/api/queue/get/route.tsx new file mode 100644 index 0000000..1f0b59f --- /dev/null +++ b/web/src/app/api/queue/get/route.tsx @@ -0,0 +1,13 @@ +import { getQueue, getQueueEntry } from "@/lib/helper/queue-helper"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request : NextRequest){ + try{ + const body = await request.json() + const { office_hours_id } = body; + const studentQueueData = await getQueue(office_hours_id); + return NextResponse.json(studentQueueData); + }catch(error){ + return NextResponse.json({error: "Error while fetching queue", details: error}, {status: 500}); + } +} \ No newline at end of file diff --git a/web/src/app/api/queue/join/route.tsx b/web/src/app/api/queue/join/route.tsx new file mode 100644 index 0000000..34491ab --- /dev/null +++ b/web/src/app/api/queue/join/route.tsx @@ -0,0 +1,26 @@ +'use server' + +import { JoinQueue } from "@/lib/helper/queue-helper"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { office_hours_id } = body; + + if (!office_hours_id) { + return NextResponse.json( + { error: "Office hours id is missing" }, + { status: 400 } + ); + } + + const data = await JoinQueue(office_hours_id); + return NextResponse.json(data); + } catch (error : any) { + return NextResponse.json( + { error: "Error while joining queue", details: error.message }, + { status: 500 } + ); + } +} diff --git a/web/src/app/api/queue/leave/route.tsx b/web/src/app/api/queue/leave/route.tsx new file mode 100644 index 0000000..01014f1 --- /dev/null +++ b/web/src/app/api/queue/leave/route.tsx @@ -0,0 +1,22 @@ +import { leaveQueue } from "@/lib/helper/queue-helper"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request : NextRequest){ + try { + const body = await request.json() + const {office_hours_id} = body; + if(!office_hours_id){ + return NextResponse.json({error: "Office hours id is missing"}, {status: 400}); + } + const leftQueue = await leaveQueue(office_hours_id); + return NextResponse.json({leftQueue}, {status: 200}); + // if(leftQueue){ + // return NextResponse.json({message: "User has left the queue", leftQueue : true}, {status: 200}); + // } + // else{ + // return NextResponse.json({message: "User is not in the queue", leftQueue : false}, {status: 200}) + // }; + } catch (error) { + return NextResponse.json({error: "Error while removing the user from the queue", details: error}, {status: 500}); + } +} \ No newline at end of file diff --git a/web/src/app/api/queue/session/update/route.ts b/web/src/app/api/queue/session/update/route.ts new file mode 100644 index 0000000..3dcbbba --- /dev/null +++ b/web/src/app/api/queue/session/update/route.ts @@ -0,0 +1,12 @@ +import { updateSession } from "@/lib/helper/session-helper"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + try { + const { id, status } = await request.json(); + const data = await updateSession(id, status) + return NextResponse.json({ message: "Session Updated", data: data }); + } catch (error) { + return NextResponse.json({ error: "Failed to update session", details: error }, { status: 500 }); + } +} \ No newline at end of file diff --git a/web/src/app/api/queue/session/validate/route.ts b/web/src/app/api/queue/session/validate/route.ts new file mode 100644 index 0000000..9df49b7 --- /dev/null +++ b/web/src/app/api/queue/session/validate/route.ts @@ -0,0 +1,16 @@ +import { validateSession } from "@/lib/helper/database-helper"; +import { NextResponse, NextRequest } from "next/server"; + +export async function GET(request: NextRequest){ + try { + const office_hours_id = request.nextUrl.searchParams.get("office_hours_id"); + const student_id = request.nextUrl.searchParams.get("student_id"); + if(!office_hours_id || !student_id){ + return NextResponse.json({ error: "Office hours id and student id are required" }, { status: 400 }); + } + const {expired, is_valid} = await validateSession(office_hours_id, student_id); + return NextResponse.json({expired, is_valid}); + } catch (error) { + return NextResponse.json({ error: "Failed to validate session" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/web/src/app/api/token/route.ts b/web/src/app/api/token/route.ts new file mode 100644 index 0000000..b10cdd0 --- /dev/null +++ b/web/src/app/api/token/route.ts @@ -0,0 +1,28 @@ +'use server' + + +import { authOptions } from "@/lib/auth/auth-option"; +import { m } from "framer-motion"; +import { getServerSession } from "next-auth"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(request: NextRequest) { + try{ + const session = await getServerSession(authOptions); + + + if(!session){ + return NextResponse.json({ message: "No session found" }, { status: 400 }); + } + const access_token = session?.user?.accessToken; + + + if(!access_token){ + return NextResponse.json({ message: "No access token found" }, { status: 400 }); + } + return NextResponse.json({access_token}); + + } catch (error) { + return NextResponse.json({ error: 'Error occurred while retrieving access token', details: error }, { status: 500 }); + } +} \ No newline at end of file diff --git a/web/src/app/api/users/route.ts b/web/src/app/api/users/route.ts new file mode 100644 index 0000000..85b447c --- /dev/null +++ b/web/src/app/api/users/route.ts @@ -0,0 +1,18 @@ +import { getUserFromDatabase } from "@/lib/helper/database-helper" +import { NextRequest } from "next/server" +import { NextResponse } from "next/server" + + +export async function GET(request: NextRequest){ + try{ + const {searchParams} = new URL(request.url) + const id = searchParams.get("id") + if(!id){ + return NextResponse.json({error: "Id is required"}, {status: 400}) + } + const user = await getUserFromDatabase(id) + return NextResponse.json(user) + }catch(error){ + return NextResponse.json({error: "Error while fetching user", details: error}, {status: 500}) + } +} \ No newline at end of file diff --git a/web/src/app/favicon.ico b/web/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/web/src/app/globals.css b/web/src/app/globals.css new file mode 100644 index 0000000..9c31a95 --- /dev/null +++ b/web/src/app/globals.css @@ -0,0 +1,112 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +.shiny-text { + background: linear-gradient(90deg, #ff6ec4, #7873f5, #ffc6b4, #f093fb, #ff6ec4); + /* background: linear-gradient(90deg, #64748b, #e2e8f0, #64748b, #e2e8f0, #64748b); */ + background-size: 200% 100%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + animation: shiny-gradient 3s linear infinite; +} + +@keyframes shiny-gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 70% 50%; + } + 100% { + background-position: 0% 50%; + } +} + + + + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/web/src/components/custom/animated-modal.tsx b/web/src/components/custom/animated-modal.tsx new file mode 100644 index 0000000..c2f1a26 --- /dev/null +++ b/web/src/components/custom/animated-modal.tsx @@ -0,0 +1,64 @@ +'use client' + +import { AnimatePresence, motion } from "framer-motion"; +import { X } from "lucide-react"; +import React from "react"; +import { Button } from "../ui/button"; +import { on } from "events"; + +interface AnimatedModalProps{ + header:string, + description:string, + subDescription?:string, + isOpen:boolean, + onClose:()=>void + onLeave:()=>void +} + +export const AnimatedModal : React.FC = ({header,description,subDescription,isOpen, onClose, onLeave}) => { + return( + + {isOpen &&( + + e.stopPropagation()} + className="dark:bg-zinc-900 bg-zinc-100 rounded-lg sm:w-[25rem] max-w-lg shadow-xl cursor-default relative overflow-hidden" + > +
+
+

{header}

+ +
+
+
+

{description}

+

{subDescription}

+
+
+
+ + +
+
+ + + + )} + + ) +} \ No newline at end of file diff --git a/web/src/components/custom/card-input-modal.tsx b/web/src/components/custom/card-input-modal.tsx new file mode 100644 index 0000000..d4001e0 --- /dev/null +++ b/web/src/components/custom/card-input-modal.tsx @@ -0,0 +1,60 @@ +'use client' + +import { AnimatePresence, motion } from "framer-motion"; +import { X } from "lucide-react"; +import React, { useState } from "react"; +import { Button } from "../ui/button"; +import { QueueForm } from "../queue/queue-form"; + +interface CardInputModalProps { + header: string; + isOpen: boolean; + onClose: () => void; + onSubmit: (formData: any) => void; +} + +export const CardInputModal: React.FC = ({ + header, + isOpen, + onClose, + onSubmit +}) => { + + return ( + + {isOpen && ( + + e.stopPropagation()} + className="dark:bg-zinc-900 bg-zinc-100 rounded-lg w-[90%] max-w-3xl shadow-xl cursor-default relative overflow-hidden" + > +
+
+

{header}

+ +
+
+ +
+
+
+
+ )} +
+ ); +}; diff --git a/web/src/components/custom/course-card.tsx b/web/src/components/custom/course-card.tsx new file mode 100644 index 0000000..da6ecc1 --- /dev/null +++ b/web/src/components/custom/course-card.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card'; +import { Button } from '../ui/button'; +import Link from 'next/link'; + +interface CourseCardProps { + courseName: string; + description: string; + instructor: boolean; + officeHours: number; + courseCodeName: string; +} + +export const CourseCard: React.FC = ({ courseName, description, instructor, officeHours, courseCodeName }) => { + return ( +
+ + + +

{courseName}

+ {instructor ? ( +
+ Instructor +
+ ) : null} +
+ + {description} + +
+ +
+ {officeHours === 0 ? ( +
+ Inactive +
+ ) : ( + <> +
+

{`${officeHours} active office hour`}

+ + )} +
+
+ + + + + +
+
+ ); +}; diff --git a/web/src/components/custom/mode-toggle.tsx b/web/src/components/custom/mode-toggle.tsx new file mode 100644 index 0000000..240ebd1 --- /dev/null +++ b/web/src/components/custom/mode-toggle.tsx @@ -0,0 +1,28 @@ +'use client' + +import { useTheme } from "next-themes" +import React from "react" +import { Button } from "../ui/button"; +import { MoonIcon, SunIcon } from "lucide-react"; +import { Tooltip, TooltipProvider, TooltipContent, TooltipTrigger } from "../ui/tooltip"; + +export default function ModeToggle() { + const { theme, setTheme } = useTheme(); + return ( + + + + + + Change Theme + + + ) +} \ No newline at end of file diff --git a/web/src/components/custom/navbar.tsx b/web/src/components/custom/navbar.tsx new file mode 100644 index 0000000..a226306 --- /dev/null +++ b/web/src/components/custom/navbar.tsx @@ -0,0 +1,65 @@ +'use client' + +import Image from "next/image" +import React, { useEffect } from "react" +import LOGO from '../../../public/logo.png' +import ModeToggle from "./mode-toggle" +import { Button } from "../ui/button" +import Link from "next/link" +import { Avatar, AvatarFallback } from "../ui/avatar" +import { signIn, useSession } from "next-auth/react" + +export default function Navbar() { + const [auth, setAuth] = React.useState(false) + const [loading, setLoading] = React.useState(true) + const {data : session, status} = useSession(); + + useEffect(() => { + const fetchAuthStatus = async () => { + try { + setAuth(status === "authenticated"); + } finally { + setLoading(false); + } + } + fetchAuthStatus(); + }, []) + + const handleClick = async () => { + signIn("autolab") + }; + + return ( + + ) +} diff --git a/web/src/components/custom/office-hours.tsx b/web/src/components/custom/office-hours.tsx new file mode 100644 index 0000000..a407735 --- /dev/null +++ b/web/src/components/custom/office-hours.tsx @@ -0,0 +1,68 @@ +'use client' + +import { CalendarPlus, Clock, Users } from "lucide-react" +import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card" +import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar" +import { Button } from "../ui/button" +import { Badge } from "../ui/badge" +import Link from "next/link" + +interface OfficeHoursCardProps { + id: string, + location: string, + start: number, + end: number, + day: number, + instructors: string[], +} + +export const OfficeHoursCard : React.FC = ({id, location, start, end,day, instructors}) => { + const convertTime = (time: number): string => { + const hours = Math.floor(time / 100); // Extract hours + const minutes = time % 100; // Extract minutes + const period = hours >= 12 ? 'PM' : 'AM'; + const adjustedHours = hours % 12 || 12; // Convert 0 to 12 for midnight + + return `${adjustedHours}:${minutes.toString().padStart(2, '0')} ${period}`; +}; + return ( +
+ +
+
+

{location}

+
+ + {start} - {end} +
+
+ +
+ {instructors.map((instructor, index) => ( + + {instructor} + + // {instructor} + //
+ // {instructor} + //
+ ))} +
+
+ +
+
+ + + + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/web/src/components/custom/shiny-text.tsx b/web/src/components/custom/shiny-text.tsx new file mode 100644 index 0000000..1215682 --- /dev/null +++ b/web/src/components/custom/shiny-text.tsx @@ -0,0 +1,20 @@ +'use client' + +import { cn } from '@/lib/utils' +import { div } from 'motion/react-client' +import React, { FC, ReactNode } from 'react' + +interface ShinyTextProps { + children: ReactNode + className?: string +} + +export const ShinyText: FC = ({ children, className }) => { + return ( +
+

+ {children} +

+
+ ) +} diff --git a/web/src/components/custom/sidebar.tsx b/web/src/components/custom/sidebar.tsx new file mode 100644 index 0000000..f6217b8 --- /dev/null +++ b/web/src/components/custom/sidebar.tsx @@ -0,0 +1,217 @@ +'use client' + +import { + ListEnd, + Home, + MessageSquareQuote, + UserRoundPen, + BookOpen, + ChevronsUpDown, + LogOut, + CircleAlert, + MoonIcon, + SunIcon } from "lucide-react" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" +import React, { useEffect } from "react" +import { Skeleton } from "../ui/skeleton" +import { getUserCoursesFromAutolab, getUserCoursesFromSession } from "@/lib/helper/autolab-helper" +import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../ui/dropdown-menu" +import { useTheme } from "next-themes" +import { signOut, useSession } from "next-auth/react" +import { useRouter } from "next/navigation" + +interface UserCourse{ + auth_level:string, + display_name:string, + name:string, +} + +const menu_items = [ + { + title: "Dashboard", + url: "/dashboard", + icon: Home, + }, + { + title: "Queue", + url: "/dashboard", + icon: ListEnd, + }, + { + title: "Feedback", + url: "/dashboard", + icon: MessageSquareQuote, + }, + { + title: "Profile", + url: "/dashboard", + icon: UserRoundPen, + }, +] + +export function AppSidebar() { + const [courses, setCourses] = React.useState([]); + const [loading, setLoading] = React.useState(true); + const { theme, setTheme } = useTheme(); + + const { data: session, status } = useSession(); + + useEffect(() => { + const fetchUserCourses = async () => { + try { + setLoading(true); + const user_courses = await getUserCoursesFromSession(); + if(user_courses){ + setCourses(user_courses); + return; + }else{ + setCourses([]); + } + } catch (error) { + console.error("An error occurred while fetching user courses:", error); + } finally { + setLoading(false); + } + }; + + if (status === "authenticated") { + fetchUserCourses(); + } + }, [session, status]); + + const handleLogOut = async () => { + try { + await signOut({ + redirect: true, + callbackUrl: '/', + }); + } catch (error) { + console.error("Error during logout:", error); + } + }; + + const renderCourseSkeleton = () => { + return Array.from({ length: 6 }).map((i, index) => ( + + )); + }; + + return ( + + +

Next Up

+ {/* NextUp */} +
+ + + Menu + + + {menu_items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + {courses.length > 0 ? Courses : null} + + + {loading + ?
+ {renderCourseSkeleton()} +
+ : Array.isArray(courses) && courses.length > 0 + ? courses.map((item) => ( + + + + + {item.display_name} + + + + )) + : null} +
+
+
+
+ + + + +
+ MS +
+

Manav Sharma

+ +
+
+ + + {/* +
+
+ MS +
+
+

Manav Sharma

+

manavsha@buffalo.edu

+
+
+
+ */} + + + Feedback + + + + Report Issue + +
+ + setTheme(theme === "dark" ? "light" : "dark")}> + {theme === "dark" ? : } + Change Theme + + + + + + + Log Out + + +
+
+
+
+ ) +} diff --git a/web/src/components/custom/theme-provider.tsx b/web/src/components/custom/theme-provider.tsx new file mode 100644 index 0000000..210d0cd --- /dev/null +++ b/web/src/components/custom/theme-provider.tsx @@ -0,0 +1,8 @@ +'use client' + +import React from "react" +import { ThemeProvider as NextThemeProvider } from "next-themes" + +export function ThemeProvider({children, ...props} : React.ComponentProps){ + return {children} +} diff --git a/web/src/components/queue/active-session.tsx b/web/src/components/queue/active-session.tsx new file mode 100644 index 0000000..b08642c --- /dev/null +++ b/web/src/components/queue/active-session.tsx @@ -0,0 +1,121 @@ +"use client" +import { useState, useEffect } from "react" +import type { SessionData } from "@/types/session-types" +import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" +import { Clock, Pause, Play, StopCircle, User, CircleAlert } from "lucide-react" + +interface ActiveSessionProps { + session: SessionData + onEndSession: () => void + onTogglePause: () => void + setTime: (time: number) => void + handleDeleteSession: () => void +} + +export function ActiveSession({ session, onEndSession, onTogglePause, setTime, handleDeleteSession }: ActiveSessionProps) { + const [elapsedTime, setElapsedTime] = useState(0) + const [formattedTime, setFormattedTime] = useState("00:00") + const [pausedTime, setPausedTime] = useState(0) // Track total paused time + const [pauseStartTime, setPauseStartTime] = useState(null) + + // Calculate and update elapsed time + useEffect(() => { + if (!session) return + + // Handle pause state changes + if (session.isPaused) { + if (pauseStartTime === null) { + setPauseStartTime(Date.now()) + } + return + } else { + // If resuming from pause, add the paused duration to total paused time + if (pauseStartTime !== null) { + setPausedTime(prev => prev + (Date.now() - pauseStartTime)) + setPauseStartTime(null) + } + } + + const startTime = session.startTime.getTime() + const interval = setInterval(() => { + const now = Date.now() + const totalElapsed = now - startTime + const activeElapsed = Math.floor((totalElapsed - pausedTime) / 1000) + + setElapsedTime(activeElapsed) + + // Format time as MM:SS + const minutes = Math.floor(activeElapsed / 60) + const seconds = activeElapsed % 60 + setFormattedTime(`${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`) + }, 1000) + + return () => clearInterval(interval) + }, [session, pausedTime, pauseStartTime]) + + // Reset paused time when session changes + useEffect(() => { + setPausedTime(0) + setPauseStartTime(null) + setElapsedTime(0) + }, [session?.startTime]) + + const handleEndSession = () => { + setTime(elapsedTime) + onEndSession() + } + + return ( +
+
+

Active Session

+
+ + {formattedTime} +
+
+ + + +
+
+
+ +

Student

+
+
+

{session.student.name}

+
+
+
+
+

Other Information

+
+
+
+
+
+ +
+ + + {handleDeleteSession &&} +
+
+ ) +} \ No newline at end of file diff --git a/web/src/components/queue/announcement-card.tsx b/web/src/components/queue/announcement-card.tsx new file mode 100644 index 0000000..b9d92f8 --- /dev/null +++ b/web/src/components/queue/announcement-card.tsx @@ -0,0 +1,26 @@ +import { AlertCircle } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; + +export default function AnnouncementCard({announcements} : {announcements : string[]}) { + return ( +
+ + + +

Announcements

+
+
+ +
+ {announcements.map((announcement, index) => ( +
+ +

{announcement}

+
+ ))} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/web/src/components/queue/instructor-view.tsx b/web/src/components/queue/instructor-view.tsx new file mode 100644 index 0000000..ee37c31 --- /dev/null +++ b/web/src/components/queue/instructor-view.tsx @@ -0,0 +1,241 @@ +"use client" +import { CardInputModal } from "../custom/card-input-modal"; +import type { Queue } from "@/types/queue-types"; +import { Button } from "../ui/button"; +import { useState, useEffect } from "react" +import { QueueStatusTable } from "./queue-status-table" +import { ActiveSession } from "@/components/queue/active-session" +import { ResolutionForm } from "@/components/queue/resolution-form" +import type { Student, SessionData } from "@/types/session-types" +import { Card, CardContent } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { sessionPersistence } from "@/lib/helper/session-persistence"; + +interface InstructorViewProps { + queueState: Queue[]; + handleRemoveFromQueue: (queueEntry: Queue[]) => void + office_hours_id: string +} + +export default function InstructorView({queueState, handleRemoveFromQueue, office_hours_id}: InstructorViewProps) { + const [activeSession, setActiveSession] = useState(null) + const [showResolutionForm, setShowResolutionForm] = useState(false) + const [activeTab, setActiveTab] = useState("queue") + const [time, setTimeTaken] = useState(0) + const [modalOpen, setModalOpen] = useState(false); + const [loading, setLoading] = useState(false) + + const handleModalOpen = () => { + setModalOpen(true); + }; + + const handleModalClose = () => { + setModalOpen(false); + }; + + const handleFormSubmit = (formData: FormData) => { + console.log(formData) + } + + useEffect(() => { + const storedSession = sessionPersistence.loadSession(); + if (storedSession) { + const now = new Date() + const sessionAge = now.getTime() - new Date(storedSession.startTime).getTime() + const maxSessionAge = 1000 * 60 * 60 * 4 + if (sessionAge < maxSessionAge) { + setActiveSession(storedSession) + setActiveTab("active-session") + } else { + sessionPersistence.clearSession() + } + } + }, []); + + useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (activeSession && !showResolutionForm) { + e.preventDefault() + e.returnValue = "You have an active session. Are you sure you want to leave?" + return "You have an active session. Are you sure you want to leave?" + } + } + + if (activeSession) { + window.addEventListener("beforeunload", handleBeforeUnload) + } + + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload) + } + }, [activeSession, showResolutionForm]) + + + // Start a session with a student + const startSession = async (queue: Queue) => { + // check if the student is already being helped by another instructor + try { + setLoading(true) + const isActive = queueState.some(q => q.id === queue.id && q.status === "active") + if(isActive){ + alert("Student is already being helped by another instructor") + return + } + const student = { + id: queue.id, + name: queue.name, + email: queue.email, + status: "active" as const, + created_at: new Date().toISOString(), + position: queue.position, + } + const newSession = { + id: `session-${Date.now()}`, + student, + startTime: new Date(), + isPaused: false, + } + const response = await fetch(`/api/queue/session/update`, { + method: "POST", + body: JSON.stringify({id: queue.id, status: "active"}), + }) + if (!response.ok) { + throw new Error("Failed to start session") + } + setActiveSession(newSession) + if (sessionPersistence.hasStoredSession()){ + sessionPersistence.clearSession() + } + sessionPersistence.saveSession(newSession) + setActiveTab("active-session") + + } catch (error) { + console.error("Error while starting session:", error) + }finally{ + setLoading(false) + } + + } + + // End the current session + const endSession = async () => { + try { + setShowResolutionForm(true) + } catch (error) { + console.error("Error while ending session:", error) + } + } + + // Pause or resume the current session + const togglePauseSession = () => { + if (activeSession) { + const updatedSession = { + ...activeSession, + isPaused: !activeSession.isPaused, + } + + setActiveSession(updatedSession) + + // Save updated session to localStorage + sessionPersistence.saveSession(updatedSession) + } + } + + const submitResolution = (resolution: { issue: string; resolution: string; feedback: string; taEmail: string; studentEmail: string; time?: number }) => { + resolution.time = time + console.log(resolution) + sessionPersistence.clearSession() + setActiveSession(null) + setShowResolutionForm(false) + setActiveTab("queue") + handleRemoveFromQueue(queueState.filter(q => q.email === resolution.studentEmail)) + } + + const handleDeleteSession = async () => { + try { + const response = await fetch(`/api/queue/session/update`, { + method: "POST", + body: JSON.stringify({id: activeSession?.student.id, status: "waiting"}), + }) + if (!response.ok) { + throw new Error("Failed to end session") + } + if (activeSession) { + sessionPersistence.clearSession() + setActiveSession(null) + setShowResolutionForm(false) + setActiveTab("queue") + } + } catch (error) { + console.error("Error while deleting session:", error) + } + } + + return ( +
+

Office Hours Queue

+ + + + Student Queue + + Active Session + + + + + + + + + + + + + {activeSession && ( + + + + + + )} + + + + {showResolutionForm && activeSession && ( + setShowResolutionForm(false)} + /> + )} + +
+ +
+ + +
+ ) +} + + + + diff --git a/web/src/components/queue/office-hours-info-card.tsx b/web/src/components/queue/office-hours-info-card.tsx new file mode 100644 index 0000000..9ab2807 --- /dev/null +++ b/web/src/components/queue/office-hours-info-card.tsx @@ -0,0 +1,51 @@ +import { Book, BookMarked, Clock, MapPin, UserRound } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; +import { Badge } from "../ui/badge"; + +export default function OfficeHoursInfoCard(){ + return( +
+ + + +

Office Hours Information

+
+
+ +
+
+
+ +

Location

+
+

Davis Carl's Corner

+
+
+
+ +

Course

+
+

CSE 220: Systems Programming

+
+
+
+ +

Instructors

+
+
+ John Doe +
+
+
+
+ +

Time

+
+

8:00 Am - 10:00 Am

+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/web/src/components/queue/queue-form.tsx b/web/src/components/queue/queue-form.tsx new file mode 100644 index 0000000..bc2fe69 --- /dev/null +++ b/web/src/components/queue/queue-form.tsx @@ -0,0 +1,135 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage +} from "../ui/form"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; + +interface QueueFormProps { + role: "student" | "ta"; + onSubmit: (data: any) => void; // Callback to handle form submission + onClose: () => void; +} + +const studentFormSchema = z.object({ + ubit: z.string().min(1, "Enter a valid UBIT Name").max(10), + name: z.string().min(1, "Enter a valid name").max(20), + issue: z.string().min(1, "Enter a valid reason").max(100), +}); + +const taFormSchema = z.object({ + ubit: z.string().min(1, "Enter a valid UBIT Name").max(10), + name: z.string().min(1, "Enter a valid name").max(20), + issue: z.string().min(1, "Enter a valid reason").max(100), +}); + +const schema = z.union([studentFormSchema, taFormSchema]); + +export const QueueForm: React.FC = ({ role, onSubmit, onClose }) => { + const form = useForm>({ + resolver: zodResolver(schema), + defaultValues: { + ubit: "", + name: "", + issue: "", + }, + }); + + const handleFormSubmit = (values: z.infer) => { + onSubmit(values); // Send data to parent component + onClose(); + }; + + return ( +
+ + ( + + UBIT + + + + + + )} + /> + ( + + Name + + + + + + )} + /> + + {/* Assignment Type */} + {/* ( + + Assignment + + + + )} + /> */} + + ( + + Issue + + + + + + )} + /> + +
+ + +
+ + + + ); +}; diff --git a/web/src/components/queue/queue-status-card.tsx b/web/src/components/queue/queue-status-card.tsx new file mode 100644 index 0000000..d6b061f --- /dev/null +++ b/web/src/components/queue/queue-status-card.tsx @@ -0,0 +1,183 @@ +'use client' + +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "../ui/card" +import { Button } from "../ui/button" +import React from "react" +import { Skeleton } from "../ui/skeleton" +import { CheckCircle, Clock, Users } from "lucide-react" +import { AnimatedModal } from "../custom/animated-modal" +import { Separator } from "../ui/separator" +import { Badge } from "../ui/badge" +import { Queue } from "@/types" + + +interface QueueStatusProps { + queue: Queue[] + courseName: string + office_hours_id: string + loading: boolean + isJoining: boolean + hasJoinedQueue: boolean + sessionStatus: "waiting" | "active" | "ready" | "completed" + queuePosition: number + taName: string + sessionTimer: string + modalOpen: boolean + estimatedWaitTime: number + onJoinQueue: () => void + onLeaveQueue: () => void + onCloseModal: () => void + onOpenModal: () => void +} + +export default function QueueStatus({ + queue, + courseName, + office_hours_id, + loading, + isJoining, + hasJoinedQueue, + sessionStatus, + queuePosition, + taName, + sessionTimer, + modalOpen, + estimatedWaitTime, + onJoinQueue, + onLeaveQueue, + onCloseModal, + onOpenModal +}: QueueStatusProps) { + + const renderSkeleton = () => ( +
+ +
+ ) + + return ( +
+ {loading ? ( + renderSkeleton() + ) : !hasJoinedQueue ? ( + + + + Join Queue + + + + +

Ready to join office hours?

+

Click below to join the queue and get help from a TA.

+ +
+
+

{queue.length}

+

In Queue

+
+
+

{estimatedWaitTime} min

+

Est. Wait

+
+
+ + +
+
+ ) : ( + + + + Session Status + + {sessionStatus === "active" ? "In Progress" : + sessionStatus === "completed" ? "Completed" : "Waiting"} + + + + + {sessionStatus === "waiting" && ( + <> +
+ +

You're in the queue!

+

Please wait for a TA to assist you

+
+ + + +
+
+

{queuePosition}

+

Position in Queue

+
+
+

{estimatedWaitTime} min

+

Estimated Wait

+
+
+ +
+ {queue.length} student{queue.length !== 1 && "s"} in queue +
+ + )} + + {sessionStatus === "active" && ( + <> +
+ +

Session Active!

+

{taName} is helping you now

+
+ +
+

Session Duration

+

{sessionTimer}

+
+ + + )} + + {sessionStatus === "ready" && ( + <> +
+

You're up next!

+

Please wait for a TA to assist you

+
+ + + )} + + +
+
+ )} + + +
+ ) +} + diff --git a/web/src/components/queue/queue-status-table.tsx b/web/src/components/queue/queue-status-table.tsx new file mode 100644 index 0000000..c84218b --- /dev/null +++ b/web/src/components/queue/queue-status-table.tsx @@ -0,0 +1,282 @@ +"use client" + +import * as React from "react" +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table" +import { ArrowUpDown, ChevronDown, CirclePause, Loader2, MoreHorizontal, Pause, Play, Trash, Trash2 } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { Queue } from "@/types" + +export interface QueueStatusTableProps { + queue : Queue[] + handleRemoveFromQueue : (queueEntry : Queue[]) => void + handleStartSession : (queueEntry : Queue) => void + loading : boolean +} + + +export const QueueStatusTable:React.FC = ({queue, handleRemoveFromQueue, handleStartSession, loading}) => { + const [sorting, setSorting] = React.useState([ + { id: "position", desc: false }, + ]) + const [columnFilters, setColumnFilters] = React.useState( + [] + ) + const [columnVisibility, setColumnVisibility] = + React.useState({}) + const [rowSelection, setRowSelection] = React.useState({}) + + const columns: ColumnDef[] = [ + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + id: "name", + accessorKey: "name", + header: "Name", + cell:({row}) => ( +
{row.getValue("name")}
+ ) + }, + { + accessorKey: "email", + header: "Email", + cell: ({ row }) =>
{row.getValue("email")}
, + }, + { + accessorKey: "position", + header: "Position", + cell: ({ row }) => ( +
{row.getValue("position")}
+ ), + sortingFn: "basic", + }, + // { + // id: "session", + // header: "Session", + // cell: ({ row }) => { + // const queue = row.original + // return ( + // + // ) + // }, + // }, + { + id: "actions", + header: "Actions", + enableHiding: false, + cell: ({ row }) => { + const queue = row.original + + return ( + + + + + + Actions + handleStartSession(queue)}> + Start Session + + handleRemoveFromQueue([queue])}> + + Remove + + + + ) + }, + }, + ] + + const handleRemoveAll = () => { + const entries: Queue[] = [] + console.log(table.getFilteredSelectedRowModel().rows.map((row) => { + entries.push(row.original) + })) + handleRemoveFromQueue(entries) + } + + const table = useReactTable({ + columns, + onColumnFiltersChange: setColumnFilters, + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + data: queue, + }) + + return ( +
+
+ + table.getColumn("name")?.setFilterValue(event.target.value) + } + className="max-w-sm" + /> + + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ) + })} + + +
+
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+ +
+
+
+ ) +} diff --git a/web/src/components/queue/resolution-form.tsx b/web/src/components/queue/resolution-form.tsx new file mode 100644 index 0000000..c6669e8 --- /dev/null +++ b/web/src/components/queue/resolution-form.tsx @@ -0,0 +1,101 @@ +"use client" + +import type React from "react" + +import { useState } from "react" +import type { Student } from "@/types/session-types" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Textarea } from "@/components/ui/textarea" +import { Label } from "@/components/ui/label" +import { useSession } from "next-auth/react" + +interface ResolutionFormProps { + student: Student + onSubmit: (resolution: { issue: string; resolution: string; feedback: string; taEmail: string; studentEmail: string }) => void + onCancel: () => void +} + +export function ResolutionForm({ student, onSubmit, onCancel }: ResolutionFormProps) { + const [issue, setIssue] = useState("") + const [resolution, setResolution] = useState("") + const [feedback, setFeedback] = useState("") + + const { data: session, status } = useSession(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!session?.user?.email) return + onSubmit({ issue, resolution, feedback, taEmail: session.user.email, studentEmail: student.email }) + } + + return ( + onCancel()}> + + + Session Resolution + + Please provide details about the session with {student.name}. + + + +
+
+ + +
+
+ + +
+
+
-
+{#
#}

Fih (he/him)

@@ -39,38 +52,7 @@

Fih (he/him)

diff --git a/client/templates/visit.html b/client/templates/visit.html new file mode 100644 index 0000000..1656dc3 --- /dev/null +++ b/client/templates/visit.html @@ -0,0 +1,22 @@ + + +
+

Fih (he/him)

+

fih@buffalo.edu

+ +
+ + +
+ +
+ + + + + +
+ + + +
\ No newline at end of file From f0aec2a5c7103cf585a0a2ddf661a8fffc914775 Mon Sep 17 00:00:00 2001 From: jbcarras Date: Wed, 14 Jan 2026 21:32:06 -0500 Subject: [PATCH 18/25] Big frontend rework. Switch to independent frontend server and using Vue for framework --- api/auth/controller.py | 12 + api/auth/routes.py | 19 +- api/database/idb_queue.py | 4 + api/database/idb_visits.py | 3 +- api/database/relational_db/relational_db.py | 4 +- .../relational_db/relational_db_queue.py | 13 +- .../relational_db/relational_db_visits.py | 8 +- api/queue/controller.py | 2 +- api/queue/routes.py | 30 +- api/server.py | 26 - client/.gitignore | 36 + client/README.md | 19 + client/env.d.ts | 1 + client/index.html | 13 + client/note.txt | 1 - client/package-lock.json | 2967 +++++++++++++++++ client/package.json | 31 + client/src/App.vue | 25 + client/{static => src/assets}/css/base.css | 8 +- .../{static => src/assets}/css/dev-login.css | 0 .../assets}/css/instructor-queue.css | 25 +- .../assets}/css/student-queue.css | 17 +- client/{static => src/assets}/css/visit.css | 1 + client/src/components/ConfirmationDialog.vue | 47 + client/src/components/QueueEntry.vue | 44 + client/src/components/Visit.vue | 74 + client/src/main.ts | 23 + client/src/pages/DevLogin.vue | 101 + client/src/pages/Home.vue | 19 + client/src/pages/InstructorQueue.vue | 147 + client/src/pages/Queue.vue | 36 + client/src/pages/StudentQueue.vue | 146 + client/static/js/base.js | 62 - client/static/js/instructor_queue.js | 142 - client/static/js/student_queue.js | 54 - client/static/js/visit.js | 45 - client/templates/base.html | 31 - client/templates/dev_login.html | 81 - client/templates/edit_info.html | 0 client/templates/home.html | 15 - client/templates/instructor_queue.html | 62 - client/templates/student_queue.html | 60 - client/templates/visit.html | 22 - client/tsconfig.app.json | 12 + client/tsconfig.json | 11 + client/tsconfig.node.json | 19 + client/vite.config.ts | 27 + 47 files changed, 3901 insertions(+), 644 deletions(-) create mode 100644 client/.gitignore create mode 100644 client/README.md create mode 100644 client/env.d.ts create mode 100644 client/index.html delete mode 100644 client/note.txt create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/src/App.vue rename client/{static => src/assets}/css/base.css (95%) rename client/{static => src/assets}/css/dev-login.css (100%) rename client/{static => src/assets}/css/instructor-queue.css (57%) rename client/{static => src/assets}/css/student-queue.css (70%) rename client/{static => src/assets}/css/visit.css (97%) create mode 100644 client/src/components/ConfirmationDialog.vue create mode 100644 client/src/components/QueueEntry.vue create mode 100644 client/src/components/Visit.vue create mode 100644 client/src/main.ts create mode 100644 client/src/pages/DevLogin.vue create mode 100644 client/src/pages/Home.vue create mode 100644 client/src/pages/InstructorQueue.vue create mode 100644 client/src/pages/Queue.vue create mode 100644 client/src/pages/StudentQueue.vue delete mode 100644 client/static/js/base.js delete mode 100644 client/static/js/instructor_queue.js delete mode 100644 client/static/js/student_queue.js delete mode 100644 client/static/js/visit.js delete mode 100644 client/templates/base.html delete mode 100644 client/templates/dev_login.html delete mode 100644 client/templates/edit_info.html delete mode 100644 client/templates/home.html delete mode 100644 client/templates/instructor_queue.html delete mode 100644 client/templates/student_queue.html delete mode 100644 client/templates/visit.html create mode 100644 client/tsconfig.app.json create mode 100644 client/tsconfig.json create mode 100644 client/tsconfig.node.json create mode 100644 client/vite.config.ts diff --git a/api/auth/controller.py b/api/auth/controller.py index d49adbb..78de89d 100644 --- a/api/auth/controller.py +++ b/api/auth/controller.py @@ -1,7 +1,19 @@ from api.database.db import db +import os +AUTOLAB_SECRET = os.getenv("AUTOLAB_CLIENT_SECRET") +AUTOLAB_ID = os.getenv("AUTOLAB_CLIENT_ID") +REDIRECT_URI = os.getenv("AUTOLAB_REDIRECT_URI") def create_account(username, numeric_identifier, auth_level="student"): account_id = db.create_account(username, numeric_identifier) db.add_to_roster(account_id, auth_level) return account_id + +def get_user(cookies): + if "auth_token" not in cookies: + return None + + return db.get_authenticated_user(cookies["auth_token"]) + + diff --git a/api/auth/routes.py b/api/auth/routes.py index 41e4b9d..00e3785 100644 --- a/api/auth/routes.py +++ b/api/auth/routes.py @@ -1,9 +1,11 @@ """Authentication Blueprint for MOH""" import json +import urllib.parse -from flask import Blueprint, request, make_response +from flask import Blueprint, request, make_response, redirect from api.database.db import db +from api.auth.controller import AUTOLAB_ID, AUTOLAB_SECRET, REDIRECT_URI blueprint = Blueprint("auth", __name__) @@ -92,6 +94,21 @@ def signout(): return res +@blueprint.route("/authorize") +def authorize_al(): + + params = { + "response_type": "code", + "client_id": AUTOLAB_ID, + "redirect_uri": REDIRECT_URI, + "scope": "user_info" + } + + return redirect( + f"https://autolab.cse.buffalo.edu/oauth/authorize?{urllib.parse.urlencode(params)}" + ) + + # TODO: update preferred name diff --git a/api/database/idb_queue.py b/api/database/idb_queue.py index 1c0e414..9e1bcc5 100644 --- a/api/database/idb_queue.py +++ b/api/database/idb_queue.py @@ -29,3 +29,7 @@ def remove_student(self, student): @abstractmethod def clear_queue(self): raise NotImplementedError() + + @abstractmethod + def set_reason(self, student, reason): + raise NotImplementedError() \ No newline at end of file diff --git a/api/database/idb_visits.py b/api/database/idb_visits.py index efe0d79..2239ac2 100644 --- a/api/database/idb_visits.py +++ b/api/database/idb_visits.py @@ -6,7 +6,7 @@ def __init__(self): super().__init__() @abstractmethod - def create_visit(self, student, ta, enqueue_time) -> int: + def create_visit(self, student, ta, enqueue_time, visit_reason) -> int: """Create a database entry for the ongoing visit between the specified student and TA. @@ -17,6 +17,7 @@ def create_visit(self, student, ta, enqueue_time) -> int: :param ta: The user ID of the TA :param enqueue_time: Timestamp when the student joined the queue, in the format "YYYY-MM-DD HH:MM:SS" + :param visit_reason: Student-supplied reason for joining the queue :return: A numeric ID representing the specific visit """ raise NotImplementedError() diff --git a/api/database/relational_db/relational_db.py b/api/database/relational_db/relational_db.py index 5b20f7c..526d7a0 100644 --- a/api/database/relational_db/relational_db.py +++ b/api/database/relational_db/relational_db.py @@ -38,7 +38,8 @@ def initialize(self): ( user_id INTEGER UNIQUE, joined TEXT DEFAULT (datetime('now', 'localtime')), - priority INTEGER + priority INTEGER, + enqueue_reason TEXT ); """ ) @@ -62,6 +63,7 @@ def initialize(self): visit_id INTEGER PRIMARY KEY, student_id INTEGER, ta_id INTEGER, + student_visit_reason TEXT, session_start TEXT DEFAULT (datetime('now','localtime')), session_end TEXT, session_end_reason TEXT, diff --git a/api/database/relational_db/relational_db_queue.py b/api/database/relational_db/relational_db_queue.py index 7505e12..eda3e58 100644 --- a/api/database/relational_db/relational_db_queue.py +++ b/api/database/relational_db/relational_db_queue.py @@ -56,7 +56,7 @@ def dequeue_specified_student(self, student_id): with self.cursor() as cursor: user = cursor.execute( """ - SELECT users.user_id, preferred_name, ubit, person_num, joined + SELECT users.user_id, preferred_name, ubit, person_num, joined, enqueue_reason FROM queue INNER JOIN users ON queue.user_id = users.user_id WHERE users.user_id = ? @@ -73,7 +73,8 @@ def dequeue_specified_student(self, student_id): "preferred_name": user[1], "ubit": user[2], "person_num": str(user[3]), - "enqueue_time": user[4] + "enqueue_time": user[4], + "enqueue_reason": user[5] } def get_queue(self): @@ -108,4 +109,10 @@ def remove_student(self, student): cursor.execute("DELETE FROM queue WHERE user_id = ?", (student, )) - return {"user_id": queue_info[0], "joined": queue_info[1]} \ No newline at end of file + return {"user_id": queue_info[0], "joined": queue_info[1]} + + def set_reason(self, student, reason): + with self.cursor() as cursor: + cursor.execute( + "UPDATE queue SET enqueue_reason = ? WHERE user_id = ?", (reason, student) + ) \ No newline at end of file diff --git a/api/database/relational_db/relational_db_visits.py b/api/database/relational_db/relational_db_visits.py index 7e100cd..8b81033 100644 --- a/api/database/relational_db/relational_db_visits.py +++ b/api/database/relational_db/relational_db_visits.py @@ -8,13 +8,13 @@ class RelationalDBVisits(IVisits): def __init__(self): super().__init__() - def create_visit(self, student, ta, enqueue_time) -> int: + def create_visit(self, student, ta, enqueue_time, visit_reason) -> int: with self.cursor() as cursor: visit_id = cursor.execute(""" - INSERT INTO visits (student_id, ta_id, enqueue_time) VALUES ( - ?, ?, ?) + INSERT INTO visits (student_id, ta_id, enqueue_time, student_visit_reason) VALUES ( + ?, ?, ?, ?) RETURNING visit_id - """, (student, ta, enqueue_time)).fetchone()[0] + """, (student, ta, enqueue_time, visit_reason)).fetchone()[0] return visit_id diff --git a/api/queue/controller.py b/api/queue/controller.py index d561500..ecd1bbf 100644 --- a/api/queue/controller.py +++ b/api/queue/controller.py @@ -38,6 +38,6 @@ def add_to_front_of_queue(user_account): def remove_from_queue_without_visit(student, reason): queue_info = db.remove_student(student) - visit = db.create_visit(student, None, queue_info["joined"]) + visit = db.create_visit(student, None, queue_info["joined"], "") db.end_visit(visit, reason) diff --git a/api/queue/routes.py b/api/queue/routes.py index f45f2bb..d84604b 100644 --- a/api/queue/routes.py +++ b/api/queue/routes.py @@ -3,6 +3,7 @@ from flask import Blueprint, request import api.queue.controller as controller +from api.auth.controller import get_user from api.queue.controller import remove_from_queue_without_visit from api.roster.controller import min_level from api.database.db import db @@ -105,7 +106,9 @@ def dequeue(): "id": , "username": , "pn": , - "preferred_name: + "preferred_name: , + "visitID": , + "visit_reason": } 400 Bad Request - The queue is empty or user is not in the queue @@ -128,14 +131,15 @@ def dequeue(): if student is None: return {"message": "The queue is empty"}, 400 - visit = db.create_visit(body["id"], user_id, student["enqueue_time"]) + visit = db.create_visit(body["id"], user_id, student["enqueue_time"], student["enqueue_reason"]) return { "id": int(student["user_id"]), "username": student["ubit"], "pn": str(student["person_num"]), "preferred_name": student["preferred_name"], - "visitID": visit + "visitID": visit, + "visit_reason": student["enqueue_reason"] } @@ -165,8 +169,6 @@ def get_queue(): } """ - # todo: permission checking. needs auth. - return db.get_queue() @@ -255,6 +257,7 @@ def remove_self(): @blueprint.route("/remove-from-queue", methods=["POST"]) +@min_level('ta') def remove(user_id): """ role: TA @@ -332,7 +335,24 @@ def end_visit(): return {"message": "Ended the visit"} +@blueprint.route("/update-reason", methods=["PATCH"]) +@min_level('student') +def update_reason(): + body = request.get_json() + + user = get_user(request.cookies) + + if user is None: + return {"message": "You are not authenticated"}, 401 + + reason = body.get("reason") + + if not reason: + return {"message": "Malformed request"}, 400 + + db.set_reason(user["user_id"], reason) + return {"message": "Reason updated"} # TODO: move to end of queue (Called by TAs to add the students they just saw back to the end of the queue) diff --git a/api/server.py b/api/server.py index 4a7823d..51a4ffb 100644 --- a/api/server.py +++ b/api/server.py @@ -38,26 +38,6 @@ def create_app(): app.register_blueprint(roster_routes.blueprint) app.register_blueprint(debug_routes.blueprint) - @app.route("/", methods=["GET"]) - def home(): - return render_template("home.html") - - @app.route("/queue", methods=["GET"]) - def queue(): - if not (auth_token := request.cookies.get("auth_token")): - return redirect("/") - - if not (user := db.get_authenticated_user(auth_token)): - return redirect("/") - - if user["course_role"] == "student": - return render_template("student_queue.html") - - if get_power_level(user["course_role"]) >= 5: - return render_template("instructor_queue.html", manager=True) - - return render_template("instructor_queue.html", manager=False) - @app.route("/user/", methods=["GET"]) @min_level('ta') def get_user_info(user_id): @@ -74,12 +54,6 @@ def get_my_info(): return user - - @app.route("/dev-login", methods=["GET"]) - @debug_access_only - def dev_login(): - return render_template("dev_login.html") - @app.route("/favicon.ico", methods=["GET"]) @debug_access_only def favicon(): diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..a3f7a51 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,36 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..1a14263 --- /dev/null +++ b/client/README.md @@ -0,0 +1,19 @@ +# MakeOfficeHours + +## Getting Started + +Currently, the frontend is built using the Vue framework. + +To run the frontend in development, cd into the client directory and run the following commands: +```bash +npm install +``` +```bash +npm run dev +``` + +The frontend server expects the backend to be running on port 5000, and requests for paths starting with `/api/` to be proxied to the backend. Vite is configured to do this; however, in deployment this will have to be done independently. + +### Some Notes + +At the moment, all Vue files are written in Typescript. For consistency, future files should be as well unless this gets changed globally. \ No newline at end of file diff --git a/client/env.d.ts b/client/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/client/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..ed8cf81 --- /dev/null +++ b/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Make Office Hours + + +
+ + + diff --git a/client/note.txt b/client/note.txt deleted file mode 100644 index 3dbd160..0000000 --- a/client/note.txt +++ /dev/null @@ -1 +0,0 @@ -At the moment, the API server is hosting the frontend \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..083f27f --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,2967 @@ +{ + "name": "web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "0.0.0", + "dependencies": { + "vue": "^3.5.26", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.3", + "@types/node": "^24.10.4", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.1", + "npm-run-all2": "^8.0.4", + "typescript": "~5.9.3", + "vite": "^7.3.0", + "vite-plugin-vue-devtools": "^8.0.5", + "vue-tsc": "^3.2.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.6.tgz", + "integrity": "sha512-RVdFPPyY9fCRAX68haPmOk2iyKW8PKJFthmm8NeSI3paNxKWGZIn99+VbIf0FrtCpFnPgnpF/L48tadi617ULg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tsconfig/node24": { + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node24/-/node24-24.0.3.tgz", + "integrity": "sha512-vcERKtKQKHgzt/vfS3Gjasd8SUI2a0WZXpgJURdJsMySpS5+ctgbPfuLj2z/W+w4lAfTWxoN4upKfu2WzIRYnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.8.tgz", + "integrity": "sha512-r0bBaXu5Swb05doFYO2kTWHMovJnNVbCsII0fhesM8bNRlLhXIuckley4a2DaD+vOdmm5G+zGkQZAPZsF80+YQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", + "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.53" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.27" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/devtools-core": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.0.5.tgz", + "integrity": "sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.0.5", + "@vue/devtools-shared": "^8.0.5", + "mitt": "^3.0.1", + "nanoid": "^5.1.5", + "pathe": "^2.0.3", + "vite-hot-client": "^2.1.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", + "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^2.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", + "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.2.tgz", + "integrity": "sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz", + "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz", + "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", + "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/runtime-core": "3.5.26", + "@vue/shared": "3.5.26", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz", + "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "vue": "3.5.26" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/alien-signals": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "dev": true, + "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" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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-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, + "license": "MIT", + "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-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-dev-rpc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", + "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^2.4.0", + "vite-hot-client": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" + } + }, + "node_modules/vite-hot-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.1.0.tgz", + "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", + "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.1.0", + "debug": "^4.4.1", + "error-stack-parser-es": "^1.0.5", + "ohash": "^2.0.11", + "open": "^10.2.0", + "perfect-debounce": "^2.0.0", + "sirv": "^3.0.1", + "unplugin-utils": "^0.3.0", + "vite-dev-rpc": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.0.5.tgz", + "integrity": "sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.0.5", + "@vue/devtools-kit": "^8.0.5", + "@vue/devtools-shared": "^8.0.5", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^5.3.2" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.2.tgz", + "integrity": "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", + "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-sfc": "3.5.26", + "@vue/runtime-dom": "3.5.26", + "@vue/server-renderer": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-tsc": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.2.tgz", + "integrity": "sha512-r9YSia/VgGwmbbfC06hDdAatH634XJ9nVl6Zrnz1iK4ucp8Wu78kawplXnIDa3MSu1XdQQePTHLXYwPDWn+nyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.2" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..5ccb084 --- /dev/null +++ b/client/package.json @@ -0,0 +1,31 @@ +{ + "name": "web", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "scripts": { + "dev": "vite --port 3000", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build" + }, + "dependencies": { + "vue": "^3.5.26", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.3", + "@types/node": "^24.10.4", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.1", + "npm-run-all2": "^8.0.4", + "typescript": "~5.9.3", + "vite": "^7.3.0", + "vite-plugin-vue-devtools": "^8.0.5", + "vue-tsc": "^3.2.2" + } +} diff --git a/client/src/App.vue b/client/src/App.vue new file mode 100644 index 0000000..1dfa846 --- /dev/null +++ b/client/src/App.vue @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/client/static/css/base.css b/client/src/assets/css/base.css similarity index 95% rename from client/static/css/base.css rename to client/src/assets/css/base.css index a40c3d8..fb0db43 100644 --- a/client/static/css/base.css +++ b/client/src/assets/css/base.css @@ -15,6 +15,8 @@ background-color: #CFF7D3; height: 5vh; color: black; + margin-top: -32px; + margin-bottom: 16px; } button { @@ -82,8 +84,13 @@ header { align-items: flex-start; } +.right-al-item { + align-self: flex-end; +} + .columns { flex-direction: column; + display: flex; } .columns > * { @@ -155,7 +162,6 @@ input { } .modal, .small-modal { - display: none; margin: auto; padding: 64px; border: 2px solid #B9B9B9; diff --git a/client/static/css/dev-login.css b/client/src/assets/css/dev-login.css similarity index 100% rename from client/static/css/dev-login.css rename to client/src/assets/css/dev-login.css diff --git a/client/static/css/instructor-queue.css b/client/src/assets/css/instructor-queue.css similarity index 57% rename from client/static/css/instructor-queue.css rename to client/src/assets/css/instructor-queue.css index 8676b65..44d8cf3 100644 --- a/client/static/css/instructor-queue.css +++ b/client/src/assets/css/instructor-queue.css @@ -21,29 +21,8 @@ #queue-container { margin: 32px 16px; -} - -.queue-entry { - display: flex; - border: 2px solid #B9B9B9; - border-top: 0; - align-items: center; - padding: 16px; - background-color: #F5F5F5; -} - -.queue-entry.top { - border-top: 2px solid #B9B9B9; -} - -.queue-entry-info { - font-weight: bold; -} - -.queue-entry-buttons { - margin-left: auto; - display: flex; - gap: 4px; + border: 2px solid #D9D9D9; + border-bottom: 0; } #buttons-l, #buttons-r { diff --git a/client/static/css/student-queue.css b/client/src/assets/css/student-queue.css similarity index 70% rename from client/static/css/student-queue.css rename to client/src/assets/css/student-queue.css index 0ada0c8..db191b4 100644 --- a/client/static/css/student-queue.css +++ b/client/src/assets/css/student-queue.css @@ -34,8 +34,19 @@ width: 30%; } -textarea { +#student-visit-reason { resize: none; - width: 30%; - height: 50%; + height: 8rem; + width: 20rem; + padding: 16px; + border: 2px solid #D9D9D9; + border-radius: 10px; +} + +br { + margin-top: 16px; +} + +#student-name { + text-align: center; } \ No newline at end of file diff --git a/client/static/css/visit.css b/client/src/assets/css/visit.css similarity index 97% rename from client/static/css/visit.css rename to client/src/assets/css/visit.css index a6bc017..9129421 100644 --- a/client/static/css/visit.css +++ b/client/src/assets/css/visit.css @@ -4,6 +4,7 @@ height: 70%; margin: auto; flex-direction: row; + display: flex; } #student-info { diff --git a/client/src/components/ConfirmationDialog.vue b/client/src/components/ConfirmationDialog.vue new file mode 100644 index 0000000..c23791a --- /dev/null +++ b/client/src/components/ConfirmationDialog.vue @@ -0,0 +1,47 @@ + + + + + \ No newline at end of file diff --git a/client/src/components/QueueEntry.vue b/client/src/components/QueueEntry.vue new file mode 100644 index 0000000..e7c9dc5 --- /dev/null +++ b/client/src/components/QueueEntry.vue @@ -0,0 +1,44 @@ + + + + + + \ No newline at end of file diff --git a/client/src/components/Visit.vue b/client/src/components/Visit.vue new file mode 100644 index 0000000..97efe5d --- /dev/null +++ b/client/src/components/Visit.vue @@ -0,0 +1,74 @@ + + + + + \ No newline at end of file diff --git a/client/src/main.ts b/client/src/main.ts new file mode 100644 index 0000000..cf686dd --- /dev/null +++ b/client/src/main.ts @@ -0,0 +1,23 @@ + +import { createApp } from 'vue' +import App from './App.vue' +import Home from './pages/Home.vue' +import {createRouter, createWebHistory} from "vue-router"; +import DevLogin from "@/pages/DevLogin.vue"; +import Queue from "@/pages/Queue.vue"; + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', component: Home}, + { path: '/dev-login', component: DevLogin}, + { path: '/queue', component: Queue} + ] +}) + +const app = createApp(App) + +app.use(router) + +app.mount('#app') + diff --git a/client/src/pages/DevLogin.vue b/client/src/pages/DevLogin.vue new file mode 100644 index 0000000..fbb05f5 --- /dev/null +++ b/client/src/pages/DevLogin.vue @@ -0,0 +1,101 @@ + + + + + \ No newline at end of file diff --git a/client/src/pages/Home.vue b/client/src/pages/Home.vue new file mode 100644 index 0000000..9116f92 --- /dev/null +++ b/client/src/pages/Home.vue @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/client/src/pages/InstructorQueue.vue b/client/src/pages/InstructorQueue.vue new file mode 100644 index 0000000..ca3b1d7 --- /dev/null +++ b/client/src/pages/InstructorQueue.vue @@ -0,0 +1,147 @@ + + + + + \ No newline at end of file diff --git a/client/src/pages/Queue.vue b/client/src/pages/Queue.vue new file mode 100644 index 0000000..46e03d8 --- /dev/null +++ b/client/src/pages/Queue.vue @@ -0,0 +1,36 @@ + + + + + \ No newline at end of file diff --git a/client/src/pages/StudentQueue.vue b/client/src/pages/StudentQueue.vue new file mode 100644 index 0000000..6c36a34 --- /dev/null +++ b/client/src/pages/StudentQueue.vue @@ -0,0 +1,146 @@ + + + + + \ No newline at end of file diff --git a/client/static/js/base.js b/client/static/js/base.js deleted file mode 100644 index ae0b3cd..0000000 --- a/client/static/js/base.js +++ /dev/null @@ -1,62 +0,0 @@ -// Banner handling functions; i.e. "You are not in the queue!" - - -const banner = document.getElementById("queue-banner") -let bannerBad = false; -let bannerVisible = false; - - -function setBannerVisibility(to) { - bannerVisible = to; - if (to) { - banner.className = `${bannerBad ? "bad" : ""}` - } else { - banner.className = "disabled" - } -} - -function setBannerText(to) { - banner.textContent = to; -} - -function setBannerBad(to) { - bannerBad = to; - if (bannerVisible && bannerBad) { - banner.className = "bad" - } else if (bannerVisible) { - banner.className = "" - } -} - -// Return info of the currently logged-in user. -// null if the user isn't logged in at all -async function getMyInfo() { - const res= await fetch("/me") - if (!res.ok) { - return null; - } - return res.json() -} - -// Return info of the requested user -// Only works if the user is logged in as -// TA or higher. -async function getUserInfo(user) { - const res = await fetch (`/user/${user}`) - if (!res.ok) { - return null; - } - return res.json() -} - -// Modal stuff - -function showTargetModal(target) { - target.style.display = "flex" - target.showModal() -} - -function hideTargetModal(target) { - target.style.display = "none" - target.close() -} \ No newline at end of file diff --git a/client/static/js/instructor_queue.js b/client/static/js/instructor_queue.js deleted file mode 100644 index aa103cc..0000000 --- a/client/static/js/instructor_queue.js +++ /dev/null @@ -1,142 +0,0 @@ -setBannerVisibility(true); -setBannerText("You are not clocked in! Clock in to broadcast your availability to students.") -setBannerBad(true); - - -getMyInfo().then(info => { - if (info == null) { - window.location = "/" - } - document.getElementById("welcome-text").textContent = `Hello, ${info["preferred_name"]}!` -}) - -// Force enqueue modal - -const enqueueDialog = document.getElementById("force-enqueue-dialog") - -document.getElementById("enqueue-dialog-button").addEventListener("click", () => { - enqueueDialog.style.display = "flex" - document.getElementById("force-enqueue").value = "" - enqueueDialog.showModal() - document.getElementById("enqueue-error-message").textContent = "" -}) - -document.getElementById("close-enqueue-dialog").addEventListener("click", () => { - enqueueDialog.close() -}) - -enqueueDialog.addEventListener("close", () => { - enqueueDialog.style.display = "none" -}) - -// Clear queue modal - -const clearQueueDialog = document.getElementById("clear-queue-dialog") - -document.getElementById("clear-queue-dialog-button").addEventListener("click", () => { - showTargetModal(clearQueueDialog) - document.getElementById("clear-queue-error-message").textContent = "" -}) - -document.getElementById("clear-queue-confirm").addEventListener("click", () => { - fetch("/clear-queue", {method: "DELETE"}).then(res => { - if (res.ok) { - clearQueueDialog.close() - setTimeout(fetchQueue, 50) - } - return res.json() - }).then(json => { - document.getElementById("clear-queue-error-message").textContent = json["message"] - })}) - -document.getElementById("clear-queue-cancel").addEventListener("click", () => hideTargetModal(clearQueueDialog)) - -clearQueueDialog.addEventListener("close", () => {clearQueueDialog.style.display = "none"}) - -document.getElementById("submit-force-enqueue").onclick = () => { - fetch("/enqueue-ta-override", {method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({"identifier": document.getElementById("force-enqueue").value})}).then(res => { - if (res.ok) { - enqueueDialog.close() - setTimeout(fetchQueue, 50) - } - return res.json() - }).then(json => { - document.getElementById("enqueue-error-message").textContent = json["message"] - }) -} - -const queueContainer = document.getElementById("queue-container") - -function addQueueEntry(student) { - let entry = document.createElement("div") - entry.className = "queue-entry" - - - let entryInfo = document.createElement("div") - entryInfo.className = "queue-entry-info" - entryInfo.textContent = `${student["preferred_name"]} (${student["ubit"]})` - - let entryButtons = document.createElement("div") - entryButtons.className = "queue-entry-buttons" - let startVisitButton = document.createElement("button") - startVisitButton.textContent = "Start Visit" - - startVisitButton.onclick = () => { - fetch("/help-a-student", { - method: "POST", - body: JSON.stringify({"id": student["id"]}), - headers: {"Content-Type": "application/json"} - }).then((res) => { - return res.json() - }).then(data => { - setVisit(data) - showTargetModal(visitModal) - }) - } - - let removeButton = document.createElement("button") - removeButton.textContent = "Remove" - let moveToEndButton = document.createElement("button") - moveToEndButton.textContent = "Move to End" - - entryButtons.append(startVisitButton, removeButton, moveToEndButton) - - entry.append(entryInfo, entryButtons) - - queueContainer.append(entry) -} - -function fetchQueue() { - fetch("/get-queue").then(data => { - return data.json() - }).then(data => { - queueContainer.innerHTML = '' - - for (let entry of data) { - addQueueEntry(entry) - } - - - if (queueContainer.children.length > 0) { - document.getElementById("student-count-text").textContent = `${queueContainer.children.length} students in the queue.` - queueContainer.children[0].className = queueContainer.children[0].className + " top" - } else { - document.getElementById("student-count-text").textContent = "The queue is empty!" - } - }) -} - -function poll() { - fetchQueue() - setTimeout(poll, 5000) -} - -poll() - - - - diff --git a/client/static/js/student_queue.js b/client/static/js/student_queue.js deleted file mode 100644 index 754d327..0000000 --- a/client/static/js/student_queue.js +++ /dev/null @@ -1,54 +0,0 @@ -setBannerVisibility(true) - -const exitQueueButton = document.getElementById("exit-queue") - -const exitQueueModal = document.getElementById("self-dequeue-dialog") - -exitQueueButton.addEventListener("click", () => showTargetModal(exitQueueModal)) - -document.getElementById("close-dequeue-dialog").addEventListener("click", () => hideTargetModal(exitQueueModal)) - -exitQueueModal.addEventListener("close", () => exitQueueModal.style.display = "none") - -document.getElementById("submit-self-dequeue").addEventListener("click", () => { - const reason = document.getElementById("self-dequeue-reason") - - reason.reportValidity() - - if (reason.checkValidity()) { - fetch("/remove-self-from-queue", {method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({"reason": reason.value})}).then(() => { - hideTargetModal(exitQueueModal) - fetchPosition() - } - ) - - } -}) - -function fetchPosition() { - fetch("/get-my-position").then(data => { - return data.json() - }).then(data => { - if (data["message"] === undefined) { - document.getElementById("my-position").textContent = `${data["position"]}` - setBannerText("You are in the queue!") - setBannerBad(false); - exitQueueButton.disabled = false - } else { - document.getElementById("my-position").textContent = `N/A` - setBannerText("You are not in the queue!") - setBannerBad(true) - exitQueueButton.disabled = true; - } - document.getElementById("queue-length").textContent = `${data["length"]}` - }) -} - -function poll() { - fetchPosition() - setTimeout(poll, 5000) -} - -poll() \ No newline at end of file diff --git a/client/static/js/visit.js b/client/static/js/visit.js deleted file mode 100644 index 1eae6ce..0000000 --- a/client/static/js/visit.js +++ /dev/null @@ -1,45 +0,0 @@ -const visitModal = document.getElementById("visit") - -// visitModal.style.display = "flex" -// visitModal.showModal() - -visitModal.addEventListener("keydown", (e) => { - if (e.key === "Escape") { - e.preventDefault() - } -}) - -visitModal.addEventListener("close", () => { - visitModal.style.display = "none" -}) - -function setVisit(info) { - const name = info["preferred_name"] - const ubit = info["username"] - const studentReason = info["reason"] - const visitID = info["visitID"] - - console.log(info) - - const taReason = document.getElementById("ta-visit-notes") - - document.getElementById("visit-student-name").textContent = name - document.getElementById("visit-student-email").textContent = `${ubit}@buffalo.edu` - document.getElementById("student-visit-reason").textContent = studentReason - - document.getElementById("end-visit").onclick = () => { - - taReason.reportValidity() - - if (taReason.checkValidity()) { - fetch("/end-visit", { - method: "POST", - body: JSON.stringify({"id": visitID, "reason": taReason.value}), - headers: {"Content-Type": "application/json"} - }).then( - () => hideTargetModal(visitModal) - ) - } - } - -} \ No newline at end of file diff --git a/client/templates/base.html b/client/templates/base.html deleted file mode 100644 index 561e534..0000000 --- a/client/templates/base.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - makeofficehours | {% block title %}{% endblock %} - - - {% block head %} {% endblock %} - - - - - - - -
- {% block body %} - - {% endblock %} -
- - - - - \ No newline at end of file diff --git a/client/templates/dev_login.html b/client/templates/dev_login.html deleted file mode 100644 index bbca897..0000000 --- a/client/templates/dev_login.html +++ /dev/null @@ -1,81 +0,0 @@ -{% extends "base.html" %} - -{% block title %} Dev Login {% endblock %} -{% block head %} - -{% endblock %} - - -{% block body %} - -
-
-

Force Enroll

- - - -
- - -
- - -
- - -
-
-

Register

- -
- - -
- - -
- -
-
-
-

Login

-
- - -
- - -
- -
-
-
- -
- - - -{% endblock %} diff --git a/client/templates/edit_info.html b/client/templates/edit_info.html deleted file mode 100644 index e69de29..0000000 diff --git a/client/templates/home.html b/client/templates/home.html deleted file mode 100644 index afeba64..0000000 --- a/client/templates/home.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base.html" %} - -{% block title %} Home {% endblock %} - -{% block body %} - -
- - - {% if config["DEBUG"] %} - - {% endif %} -
- -{% endblock %} \ No newline at end of file diff --git a/client/templates/instructor_queue.html b/client/templates/instructor_queue.html deleted file mode 100644 index 6692852..0000000 --- a/client/templates/instructor_queue.html +++ /dev/null @@ -1,62 +0,0 @@ -{% extends "base.html" %} - -{% block title %} Queue {% endblock %} - -{% block head %} - - - - -{% endblock %} - -{% block body %} - - -
- - -
- - -
- -

- -
- - -

Are you sure?

-

If you clear the queue, all students will permanently lose their position.

-
- - -
- -

-
- - {% include "visit.html" %} - - -
-
-

Hello, Jimmy!

-

0 students in the queue.

-
-
-
- - -
-
- {% if manager %} - - {% endif %} - - -
-
-
-
- -{% endblock %} \ No newline at end of file diff --git a/client/templates/student_queue.html b/client/templates/student_queue.html deleted file mode 100644 index 8198505..0000000 --- a/client/templates/student_queue.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends "base.html" %} - -{% block title %} Queue {% endblock %} -{% block head %} - - - -{% endblock %} - - -{% block banner %} - - -{% endblock %} - -{% block body %} - - -

Sorry to see you go! If you leave the queue now, you'll lose your position.

-

To rejoin, you will have to swipe back in.

- - -
-
- - -
-
- -
-
-{#
#} -
-

Fih (he/him)

- -
-
-
-
-
67
- Your Position -
-
-
123
- Queue Size -
-
-
- -
-
- - - - -{% endblock %} \ No newline at end of file diff --git a/client/templates/visit.html b/client/templates/visit.html deleted file mode 100644 index 1656dc3..0000000 --- a/client/templates/visit.html +++ /dev/null @@ -1,22 +0,0 @@ - - -
-

Fih (he/him)

-

fih@buffalo.edu

- -
- - -
- -
- - - - - -
- - - -
\ No newline at end of file diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json new file mode 100644 index 0000000..913b8f2 --- /dev/null +++ b/client/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json new file mode 100644 index 0000000..822562d --- /dev/null +++ b/client/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/client/vite.config.ts b/client/vite.config.ts new file mode 100644 index 0000000..17846e1 --- /dev/null +++ b/client/vite.config.ts @@ -0,0 +1,27 @@ +import {fileURLToPath, URL} from 'node:url' + +import {defineConfig} from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + server: { + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + }, + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, +}) From 00f10a6c7f45997b15c18352f97623e712a6a38c Mon Sep 17 00:00:00 2001 From: jbcarras Date: Fri, 16 Jan 2026 18:45:59 -0500 Subject: [PATCH 19/25] preferred name updating and removals --- api/database/idb_queue.py | 4 ++ .../relational_db/relational_db_accounts.py | 14 +++- .../relational_db/relational_db_queue.py | 13 ++++ api/queue/controller.py | 5 ++ api/queue/routes.py | 40 ++++++++--- api/roster/routes.py | 21 ++++++ client/README.md | 2 + client/src/components/EditInfo.vue | 55 +++++++++++++++ client/src/components/QueueEntry.vue | 4 +- client/src/components/Visit.vue | 2 +- client/src/pages/InstructorQueue.vue | 67 ++++++++++++++++--- client/src/pages/StudentQueue.vue | 9 ++- client/vite.config.ts | 2 +- 13 files changed, 211 insertions(+), 27 deletions(-) create mode 100644 client/src/components/EditInfo.vue diff --git a/api/database/idb_queue.py b/api/database/idb_queue.py index 9e1bcc5..03c7e7b 100644 --- a/api/database/idb_queue.py +++ b/api/database/idb_queue.py @@ -32,4 +32,8 @@ def clear_queue(self): @abstractmethod def set_reason(self, student, reason): + raise NotImplementedError() + + @abstractmethod + def move_to_end(self, student): raise NotImplementedError() \ No newline at end of file diff --git a/api/database/relational_db/relational_db_accounts.py b/api/database/relational_db/relational_db_accounts.py index 7ca5bb0..2fbc0db 100644 --- a/api/database/relational_db/relational_db_accounts.py +++ b/api/database/relational_db/relational_db_accounts.py @@ -196,4 +196,16 @@ def get_roster(self): def set_preferred_name(self, identifier, name): - pass \ No newline at end of file + with self.cursor() as cursor: + + user = cursor.execute( + """ + UPDATE users SET preferred_name = ? + WHERE ubit = ? OR person_num = ? OR user_id = ? + RETURNING user_id + """, (name, identifier, identifier, identifier)).fetchone() + + if user is None: + return None + + return user[0] \ No newline at end of file diff --git a/api/database/relational_db/relational_db_queue.py b/api/database/relational_db/relational_db_queue.py index eda3e58..5596d31 100644 --- a/api/database/relational_db/relational_db_queue.py +++ b/api/database/relational_db/relational_db_queue.py @@ -1,3 +1,5 @@ +import datetime + from api.database.idb_queue import IQueue @@ -107,6 +109,9 @@ def remove_student(self, student): with self.cursor() as cursor: queue_info = cursor.execute("SELECT * FROM queue WHERE user_id = ?", (student, )).fetchone() + if queue_info is None: + return None + cursor.execute("DELETE FROM queue WHERE user_id = ?", (student, )) return {"user_id": queue_info[0], "joined": queue_info[1]} @@ -115,4 +120,12 @@ def set_reason(self, student, reason): with self.cursor() as cursor: cursor.execute( "UPDATE queue SET enqueue_reason = ? WHERE user_id = ?", (reason, student) + ) + + def move_to_end(self, student): + now = str(datetime.datetime.now().isoformat(' ', timespec="seconds")) + + with self.cursor() as cursor: + cursor.execute( + "UPDATE queue SET joined = ?, priority = 0 WHERE user_id = ?", (now, student) ) \ No newline at end of file diff --git a/api/queue/controller.py b/api/queue/controller.py index ecd1bbf..705c6a3 100644 --- a/api/queue/controller.py +++ b/api/queue/controller.py @@ -38,6 +38,11 @@ def add_to_front_of_queue(user_account): def remove_from_queue_without_visit(student, reason): queue_info = db.remove_student(student) + + if queue_info is None: + return False + visit = db.create_visit(student, None, queue_info["joined"], "") db.end_visit(visit, reason) + return True diff --git a/api/queue/routes.py b/api/queue/routes.py index d84604b..7a692d3 100644 --- a/api/queue/routes.py +++ b/api/queue/routes.py @@ -249,29 +249,28 @@ def remove_self(): user_id = user["user_id"] body = request.get_json() - remove_from_queue_without_visit(user_id, f"[SELF-REMOVE]: {body["reason"]}") - - - - return {"message":"Removed self from queue."} + if remove_from_queue_without_visit(user_id, f"[SELF-REMOVE]: {body["reason"]}"): + return {"message":"Removed self from queue."} + else: + return {"message": "You are not in the queue!"}, 400 @blueprint.route("/remove-from-queue", methods=["POST"]) @min_level('ta') -def remove(user_id): +def remove(): """ role: TA Removing students from the queue by id. Creates a visit in the db to store the reason for the removal Args: - param.user_id: The id of the student being removed. Note: This is the id of their account, not their UBIT/pn body.reason: a text reason for removing the user from the queue (eg. "No show") body.user_id: user ID of the student being removed Body: { - "reason": + "reason": , + "user_id": } Returns: @@ -282,7 +281,20 @@ def remove(user_id): "message": } """ - return f"{request.path} hit 😎, remove method is used." + + body = request.get_json() + + if body.get("user_id") is None or body.get("reason") is None: + return {"message": "Malformed request"}, 400 + + + user_id = body.get("user_id") + reason = body.get("reason") + + if remove_from_queue_without_visit(user_id, f"[REMOVED BY TA]: {reason}"): + return {"message": "Removed student from queue"} + return {"message": "Student is not in queue"}, 400 + @blueprint.route("/clear-queue", methods=["DELETE"]) @min_level('ta') @@ -354,6 +366,14 @@ def update_reason(): return {"message": "Reason updated"} -# TODO: move to end of queue (Called by TAs to add the students they just saw back to the end of the queue) +@blueprint.route("/move-to-end", methods=["PATCH"]) +@min_level('ta') +def move_to_end(): + body = request.get_json() + + if (user_id := body.get("user_id")) is None: + return {"message": "Malformed request"}, 400 + db.move_to_end(user_id) + return {"message": "Moved student to end of the queue"} diff --git a/api/roster/routes.py b/api/roster/routes.py index ecf4d07..9ee0981 100644 --- a/api/roster/routes.py +++ b/api/roster/routes.py @@ -2,6 +2,7 @@ from flask import Blueprint, request +from api.auth.controller import get_user from api.roster.controller import min_level, add_to_roster from api.database.db import db @@ -88,6 +89,26 @@ def get_roster(): """ pass +@min_level('student') +@blueprint.route("/update-name", methods=["PATCH"]) +def update_preferred_name(): + user = get_user(request.cookies) + + if user is None: + return {"message": "You are not authenticated!"}, 401 + + body = request.get_json() + + if (name := body.get("name")) is None: + return {"message": "Malformed request."}, 400 + + db.set_preferred_name(user["ubit"], name) + + return {"message": "Updated preferred name."} + + + + diff --git a/client/README.md b/client/README.md index 1a14263..1679f1b 100644 --- a/client/README.md +++ b/client/README.md @@ -12,6 +12,8 @@ npm install npm run dev ``` +This will start the development server on port 3000. + The frontend server expects the backend to be running on port 5000, and requests for paths starting with `/api/` to be proxied to the backend. Vite is configured to do this; however, in deployment this will have to be done independently. ### Some Notes diff --git a/client/src/components/EditInfo.vue b/client/src/components/EditInfo.vue new file mode 100644 index 0000000..e508847 --- /dev/null +++ b/client/src/components/EditInfo.vue @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/client/src/components/QueueEntry.vue b/client/src/components/QueueEntry.vue index e7c9dc5..e228cc5 100644 --- a/client/src/components/QueueEntry.vue +++ b/client/src/components/QueueEntry.vue @@ -3,7 +3,7 @@ import {ref} from "vue"; defineProps(["name", "ubit", "id"]) -defineEmits(["call-student"]) +defineEmits(["call-student", "remove-student"]) @@ -13,7 +13,7 @@ defineEmits(["call-student"])
- +
diff --git a/client/src/components/Visit.vue b/client/src/components/Visit.vue index 97efe5d..1493f78 100644 --- a/client/src/components/Visit.vue +++ b/client/src/components/Visit.vue @@ -53,7 +53,7 @@ function submitVisit() {
- +
diff --git a/client/src/pages/InstructorQueue.vue b/client/src/pages/InstructorQueue.vue index ca3b1d7..32edd47 100644 --- a/client/src/pages/InstructorQueue.vue +++ b/client/src/pages/InstructorQueue.vue @@ -4,9 +4,24 @@ import QueueEntry from "@/components/QueueEntry.vue"; import {ref} from "vue"; import ConfirmationDialog from "@/components/ConfirmationDialog.vue"; import Visit from "@/components/Visit.vue"; +import EditInfo from "@/components/EditInfo.vue"; +import {useRouter} from "vue-router"; const students = ref([]) +const router = useRouter() + +const taName = ref(""); + +fetch("/api/me").then(res => { + if (!res.ok) { + router.push("/") + } + return res.json() +}).then(data => { + taName.value = data["preferred_name"] +}) + function getQueue() { fetch("/api/get-queue").then(res => { return res.json() @@ -81,6 +96,33 @@ function clearQueue() { }) } + +const removeStudentDialog = ref(); +let removeStudentId: number | undefined = undefined; +const removeStudentReason = ref(""); + +function showRemoveStudentDialog(id: number) { + removeStudentId = id; + removeStudentDialog.value?.show(); +} + +function removeStudent() { + fetch("/api/remove-from-queue", { + method: "POST", + body: JSON.stringify({"reason": removeStudentReason.value, "user_id": removeStudentId}), + headers: {"Content-Type": "application/json"} + }).then(res => { + if (res.ok) { + removeStudentDialog.value?.hide(); + removeStudentReason.value = ""; + removeStudentId = undefined; + getQueue(); + } + }) +} + +const editInfo = ref(); + diff --git a/client/src/pages/StudentQueue.vue b/client/src/pages/StudentQueue.vue index 6c36a34..ad0a3d2 100644 --- a/client/src/pages/StudentQueue.vue +++ b/client/src/pages/StudentQueue.vue @@ -3,6 +3,7 @@ import {ref} from "vue"; import {useRouter} from "vue-router"; import ConfirmationDialog from "@/components/ConfirmationDialog.vue"; +import EditInfo from "@/components/EditInfo.vue"; const router = useRouter() @@ -18,7 +19,7 @@ fetch("/api/me").then(res => { } return res.json() }).then(data => { - studentName.value = `${data["preferred_name"]} ${data["last_name"]}` + studentName.value = `${data["preferred_name"]}` }) let bannerClass = ref("bad") @@ -85,6 +86,8 @@ function updateReason() { } +const preferredNameUpdate = ref(); +