From 8e61d3eff58d51af1fe4b2e9c0f4a26e5b18e07e Mon Sep 17 00:00:00 2001 From: seangaaab Date: Sun, 22 Feb 2026 16:13:43 +0800 Subject: [PATCH 01/43] fix: update serverless config to have sparcs prefixes --- .github/workflows/deploy_backend.yml | 2 +- .../pycon_registration_controller.py | 2 +- backend/main.py | 4 +- backend/resources/api.yml | 40 +++++++-------- backend/resources/functions.yml | 6 +-- backend/serverless.yaml | 49 ++++++++----------- backend/usecase/certificate_usecase.py | 4 +- backend/utils/logger.py | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- 10 files changed, 54 insertions(+), 61 deletions(-) diff --git a/.github/workflows/deploy_backend.yml b/.github/workflows/deploy_backend.yml index 7a32546c..da36a4c8 100644 --- a/.github/workflows/deploy_backend.yml +++ b/.github/workflows/deploy_backend.yml @@ -1,4 +1,4 @@ -name: Deploy DurianPy Events Service +name: Deploy SPARCS Events Service on: push: paths: diff --git a/backend/controller/pycon_registration_controller.py b/backend/controller/pycon_registration_controller.py index 5d774a58..3d0daf3a 100644 --- a/backend/controller/pycon_registration_controller.py +++ b/backend/controller/pycon_registration_controller.py @@ -79,7 +79,7 @@ def register_pycon( 'lastName': 'Dela Cruz', 'nickname': 'Juan', 'pronouns': 'he/him', - 'email': 'durianpy.davao+juan.delacruz@gmail.com', + 'email': 'sparcsup+juan.delacruz@gmail.com', 'eventId': 'pycon-davao-2024', 'contactNumber': '+639171234567', 'organization': 'Tech Company Inc', diff --git a/backend/main.py b/backend/main.py index 6d010ecb..7207c568 100644 --- a/backend/main.py +++ b/backend/main.py @@ -12,10 +12,10 @@ app = FastAPI( root_path=root_path, - title='DURIANPY Event Service', + title='SPARCS Event Service', contact={ 'name': 'Society of Programmers and Refined Computer Scientists', - 'email': 'contact@durianpy.org', + 'email': 'sparcsup@gmail.com', }, ) diff --git a/backend/resources/api.yml b/backend/resources/api.yml index 339addf9..8efb244e 100644 --- a/backend/resources/api.yml +++ b/backend/resources/api.yml @@ -17,7 +17,7 @@ app: path: /docs method: get authorizer: - arn: arn:aws:lambda:${self:provider.region}:${aws:accountId}:function:durianpy-auth-service-${self:custom.stage}-basicAuthAuthorizer + arn: arn:aws:lambda:${self:provider.region}:${aws:accountId}:function:sparcs-auth-service-${self:custom.stage}-basicAuthAuthorizer resultTtlInSeconds: 0 iamRoleStatements: - Effect: Allow @@ -31,51 +31,51 @@ app: Resource: - { "Fn::GetAtt": [Evaluations, Arn] } - "Fn::Join": - - "/" - - - "Fn::GetAtt": [ Evaluations, Arn ] - - "index" - - "*" + - "/" + - - "Fn::GetAtt": [Evaluations, Arn] + - "index" + - "*" - Effect: Allow Action: - "dynamodb:*" Resource: - { "Fn::GetAtt": [Events, Arn] } - "Fn::Join": - - "/" - - - "Fn::GetAtt": [ Events, Arn ] - - "index" - - "*" + - "/" + - - "Fn::GetAtt": [Events, Arn] + - "index" + - "*" - Effect: Allow Action: - "dynamodb:*" Resource: - { "Fn::GetAtt": [Registrations, Arn] } - "Fn::Join": - - "/" - - - "Fn::GetAtt": [ Registrations, Arn ] - - "index" - - "*" + - "/" + - - "Fn::GetAtt": [Registrations, Arn] + - "index" + - "*" - Effect: Allow Action: - "dynamodb:*" Resource: - { "Fn::GetAtt": [PreRegistrations, Arn] } - "Fn::Join": - - "/" - - - "Fn::GetAtt": [ PreRegistrations, Arn ] - - "index" - - "*" + - "/" + - - "Fn::GetAtt": [PreRegistrations, Arn] + - "index" + - "*" - Effect: Allow Action: - ssm:GetParameter Resource: - - arn:aws:ssm:*:*:parameter/durianpy-auth-secret-key-${self:custom.stage} + - arn:aws:ssm:*:*:parameter/sparcs-auth-secret-key-${self:custom.stage} - Effect: Allow Action: - "sqs:*" Resource: - - !Sub arn:aws:sqs:ap-southeast-1:${AWS::AccountId}:${self:custom.stage}-durianpy-events-email-queue.fifo - - !Sub arn:aws:sqs:ap-southeast-1:${AWS::AccountId}:${self:custom.stage}-durianpy-events-certificate-queue.fifo + - !Sub arn:aws:sqs:ap-southeast-1:${AWS::AccountId}:${self:custom.stage}-sparcs-events-email-queue.fifo + - !Sub arn:aws:sqs:ap-southeast-1:${AWS::AccountId}:${self:custom.stage}-sparcs-events-certificate-queue.fifo - Effect: Allow Action: - s3:PutObject diff --git a/backend/resources/functions.yml b/backend/resources/functions.yml index 826c4974..da2f9428 100644 --- a/backend/resources/functions.yml +++ b/backend/resources/functions.yml @@ -23,13 +23,13 @@ paymentHandler: events: - sqs: arn: - "Fn::GetAtt": [ PaymentQueue, Arn ] + "Fn::GetAtt": [PaymentQueue, Arn] iamRoleStatements: - Effect: Allow Action: - "sqs:*" Resource: - - "Fn::GetAtt": [ PaymentQueue, Arn ] + - "Fn::GetAtt": [PaymentQueue, Arn] - Effect: Allow Action: - "dynamodb:PutItem" @@ -48,4 +48,4 @@ paymentHandler: - "sqs:SendMessage" - "sqs:ReceiveMessage" - "sqs:DeleteMessage" - Resource: "arn:aws:sqs:${self:provider.region}:${aws:accountId}:${self:custom.stage}-durianpy-events-email-queue.fifo" + Resource: "arn:aws:sqs:${self:provider.region}:${aws:accountId}:${self:custom.stage}-sparcs-events-email-queue.fifo" diff --git a/backend/serverless.yaml b/backend/serverless.yaml index dbabe11c..7ec526fb 100644 --- a/backend/serverless.yaml +++ b/backend/serverless.yaml @@ -1,8 +1,8 @@ -service: durianpy-events-api -frameworkVersion: '^3.40.0' +service: sparcs-events-api +frameworkVersion: "^3.40.0" custom: - projectName: durianpy-events + projectName: sparcs-events serviceName: events stage: ${opt:stage, self:provider.stage} paymentQueue: ${self:custom.stage}-${self:custom.projectName}-payment-queue.fifo @@ -16,12 +16,9 @@ custom: # konfHubApiKey: ${ssm:/konfhub/api-key-${self:custom.stage}} betterCredentials: enabled: true - emailQueue: - !ImportValue EmailQueue-${self:custom.stage} - certificateQueue: - !ImportValue CertificateQueue-${self:custom.stage} - basicAuthorizerARN: - !ImportValue BasicAuthAuthorizerARN-${self:custom.stage} + emailQueue: !ImportValue EmailQueue-${self:custom.stage} + certificateQueue: !ImportValue CertificateQueue-${self:custom.stage} + basicAuthorizerARN: !ImportValue BasicAuthAuthorizerARN-${self:custom.stage} pythonRequirements: dockerizePip: true usePoetry: false @@ -30,7 +27,7 @@ custom: - boto3 - botocore layer: - name: durianpy-events-${self:custom.stage}-python-requirements + name: sparcs-events-${self:custom.stage}-python-requirements compatibleRuntimes: - python3.11 slim: true @@ -65,16 +62,13 @@ provider: CERTIFICATE_QUEUE: ${self:custom.certificateQueue} S3_BUCKET: ${self:custom.bucket} # KONFHUB_API_KEY: ${self:custom.konfHubApiKey} - USER_POOL_ID: - !ImportValue UserPoolId-${self:custom.stage} - USER_POOL_CLIENT_ID: - !ImportValue AppClientId-${self:custom.stage} + USER_POOL_ID: !ImportValue UserPoolId-${self:custom.stage} + USER_POOL_CLIENT_ID: !ImportValue AppClientId-${self:custom.stage} SPARCS_GMAIL: sparcsup@gmail.com logs: restApi: role: !GetAtt ApiGatewayCloudWatchRole.Arn - resources: - ${file(resources/dynamodb.yml)} - ${file(resources/api_gateway.yml)} @@ -88,30 +82,29 @@ resources: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - - Effect: Allow - Principal: - Service: apigateway.amazonaws.com - Action: sts:AssumeRole + - Effect: Allow + Principal: + Service: apigateway.amazonaws.com + Action: sts:AssumeRole Policies: - PolicyName: AllowCloudWatchLogs PolicyDocument: Version: "2012-10-17" Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:DescribeLogGroups - - logs:DescribeLogStreams - - logs:PutLogEvents - Resource: "*" + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:DescribeLogGroups + - logs:DescribeLogStreams + - logs:PutLogEvents + Resource: "*" Outputs: ApiGatewayCloudWatchRoleArn: Value: !GetAtt ApiGatewayCloudWatchRole.Arn Export: Name: ApiGatewayCloudWatchRole-${self:custom.serviceName}-${self:custom.stage} - functions: - ${file(resources/api.yml)} - ${file(resources/functions.yml)} diff --git a/backend/usecase/certificate_usecase.py b/backend/usecase/certificate_usecase.py index 34d81439..7b3a181a 100644 --- a/backend/usecase/certificate_usecase.py +++ b/backend/usecase/certificate_usecase.py @@ -40,8 +40,8 @@ def generate_certificates(self, event_id: str, registration_id: str = None) -> T try: timestamp = datetime.utcnow().isoformat(timespec='seconds') payload = {'eventId': event_id} - message_group_id = f'durianpy-certificates-{event_id}' - message_dedup_id = f'durianpy-certificates-{event_id}-{timestamp}' + message_group_id = f'sparcs-certificates-{event_id}' + message_dedup_id = f'sparcs-certificates-{event_id}-{timestamp}' if registration_id: payload['registrationId'] = registration_id diff --git a/backend/utils/logger.py b/backend/utils/logger.py index e62d9c7b..97bd142a 100644 --- a/backend/utils/logger.py +++ b/backend/utils/logger.py @@ -2,7 +2,7 @@ import os from sys import stdout -logger = logging.getLogger('durianpy-certificates-service') +logger = logging.getLogger('sparcs-certificates-service') handler = logging.StreamHandler(stdout) if os.getenv('AWS_EXECUTION_ENV'): # pragma: no cover logger.propagate = False diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c04ef854..57ee03b8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "durianpy-techtix", + "name": "sparcs-techtix", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "durianpy-techtix", + "name": "sparcs-techtix", "version": "0.0.1", "dependencies": { "@hookform/resolvers": "^5.2.1", diff --git a/frontend/package.json b/frontend/package.json index 20823a51..92ba69cc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "durianpy-techtix", + "name": "sparcs-techtix", "private": true, "version": "0.0.1", "type": "module", From a42d12c7baae8b4b685fa2bc0d56f7b210c9544e Mon Sep 17 00:00:00 2001 From: bren Date: Sun, 22 Feb 2026 16:29:52 +0800 Subject: [PATCH 02/43] feat: dev containers for participants --- .devcontainer/devcontainer.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..4dd90b5a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "TechTix Monorepo", + "image": "mcr.microsoft.com/devcontainers/universal:latest", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "3.8" + } + }, + "postCreateCommand": "npm install --prefix frontend && pipenv install --skip-lock --python 3.8 --manifest backend/Pipfile", + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-python.python", + "ms-python.vscode-pylance" + ] + } + }, + "remoteUser": "root" +} \ No newline at end of file From 077e17bb0e51bb6c391482ed05a84cd294d03456 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Sun, 22 Feb 2026 20:28:54 +0800 Subject: [PATCH 03/43] feat: added ssm parameters resources to be used by other services --- backend/resources/dynamodb.yml | 43 ++++++++++++++++++++++++++++++---- backend/resources/s3.yml | 21 +++++++++++------ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/backend/resources/dynamodb.yml b/backend/resources/dynamodb.yml index 05e7b534..08fa560e 100644 --- a/backend/resources/dynamodb.yml +++ b/backend/resources/dynamodb.yml @@ -62,10 +62,10 @@ Resources: LocalSecondaryIndexes: - IndexName: QuestionLSI KeySchema: - - AttributeName: hashKey - KeyType: HASH - - AttributeName: question - KeyType: RANGE + - AttributeName: hashKey + KeyType: HASH + - AttributeName: question + KeyType: RANGE Projection: ProjectionType: ALL @@ -140,3 +140,38 @@ Resources: KeyType: RANGE Projection: ProjectionType: ALL + + EntitiesParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-entities + Type: String + Value: ${self:custom.entities} + + RegistrationsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-registrations + Type: String + Value: ${self:custom.registrations} + + PreRegistrationsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-preregistrations + Type: String + Value: ${self:custom.preregistrations} + + EvaluationsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-evaluations + Type: String + Value: ${self:custom.evaluations} + + EventsParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-events + Type: String + Value: ${self:custom.events} diff --git a/backend/resources/s3.yml b/backend/resources/s3.yml index c9e08c59..62f1ca65 100644 --- a/backend/resources/s3.yml +++ b/backend/resources/s3.yml @@ -7,13 +7,20 @@ Resources: Status: Enabled CorsConfiguration: CorsRules: - - AllowedHeaders: ['*'] + - AllowedHeaders: ["*"] AllowedMethods: [GET, PUT, HEAD] - AllowedOrigins: ['*'] + AllowedOrigins: ["*"] Id: ${self:custom.bucket}-name - MaxAge: '3600' + MaxAge: "3600" PublicAccessBlockConfiguration: - BlockPublicAcls: false - IgnorePublicAcls: false - BlockPublicPolicy: false - RestrictPublicBuckets: false + BlockPublicAcls: false + IgnorePublicAcls: false + BlockPublicPolicy: false + RestrictPublicBuckets: false + + BucketNameParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /${self:custom.stage}-sparcs-events-file-bucket + Type: String + Value: ${self:custom.bucket} From 5fbfe7a607086c4f0cba9da41ca59bf86be7f44f Mon Sep 17 00:00:00 2001 From: seangaaab Date: Sun, 22 Feb 2026 20:29:43 +0800 Subject: [PATCH 04/43] feat: added config builder --- backend/scripts/generate-env.py | 150 ++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 backend/scripts/generate-env.py diff --git a/backend/scripts/generate-env.py b/backend/scripts/generate-env.py new file mode 100644 index 00000000..bd19e500 --- /dev/null +++ b/backend/scripts/generate-env.py @@ -0,0 +1,150 @@ +import argparse +import json +import os +from enum import Enum + +import boto3 +from botocore.exceptions import ClientError + + +class Environments(str, Enum): + DEV = 'dev' + PROD = 'prod' + LOCAL = 'local' + TEST = 'test' + + +class ConfigAssembler: + """ + Assembles the TechTix (Backend) config file + """ + + def __init__(self, aws_region='ap-southeast-1', environment=Environments.DEV.value): + self.__input_environment = environment + self.__project_name = 'sparcs-events' + + # Determine the deployment stage, defaulting to 'dev' for None or 'local' environments + if not self.__input_environment or self.__input_environment == Environments.LOCAL.value: + self.__stage = Environments.DEV.value + else: + self.__stage = self.__input_environment + + self.__region = 'ap-southeast-1' if aws_region is None else aws_region + self.__ssm_client = boto3.client('ssm', region_name=self.__region) + self.__secrets_client = boto3.client('secretsmanager', region_name=self.__region) + self.__base_dir = os.getcwd() + + def __get_parameter(self, key, decrypt=False) -> str: + """ + Retrieves parameter values from SSM + + :param key: key of parameter value to be retrieved + :param decrypt: flag if value is decrypted + :return: parameter value string + """ + kwargs = {'Name': key, 'WithDecryption': decrypt} + value = '' + try: + resp = self.__ssm_client.get_parameter(**kwargs) + except ClientError as e: + print(f'Error: {e.response["Error"]["Code"]} - {key}') + else: + value = resp['Parameter']['Value'] + return value + + def __get_secret(self, secret_arn) -> str: + """ + Retrieves secret value from AWS Secrets Manager + + :param secret_arn: ARN of the secret to retrieve + :return: secret value string + """ + try: + resp = self.__secrets_client.get_secret_value(SecretId=secret_arn) + return resp['SecretString'] + except ClientError as e: + print(f'Error retrieving secret: {e.response["Error"]["Code"]} - {secret_arn}') + return '' + + @staticmethod + def escape_env_value(value: str) -> str: + return value.replace('$', '$$') + + @staticmethod + def write_config(file_handle, key, value) -> None: + """ + Writes specified config key-value in the config file + + :param file_handle: File pointer + :param key: key of config + :param value: value of config + :return: None + """ + entry = f'{key}={ConfigAssembler.escape_env_value(str(value))}\n' + file_handle.write(entry) + + def construct_config_file(self) -> None: + """ + Constructs the config file for Helix + + :return: None + """ + + region = 'ap-southeast-1' + stage = self.__stage + entities_table = self.__get_parameter(f"/{stage}-sparcs-events-entities") + registrations_table = self.__get_parameter(f"/{stage}-sparcs-events-registrations") + preregistrations_table = self.__get_parameter(f"/{stage}-sparcs-events-preregistrations") + evaluations_table = self.__get_parameter(f"/{stage}-sparcs-events-evaluations") + events_table = self.__get_parameter(f"/{stage}-sparcs-events") + email_queue = self.__get_parameter(f"/sparcs-events-email-queue-url-{stage}") + certificate_queue = self.__get_parameter(f"/sparcs-events-certificate-queue-url-{stage}") + s3_bucket = self.__get_parameter(f"/{stage}-sparcs-events-file-bucket") + + userpool_id = f"techtix/cognito-user-pool-id-{stage}" + userpool_client_id = f"techtix/cognito-user-pool-client-id-{stage}" + + if self.__input_environment == Environments.LOCAL.value or stage == Environments.LOCAL.value: + frontend_url = 'http://localhost:3000' + else: + frontend_url = self.__get_parameter(f"techtix/frontend-url-{stage}") + + # Determine if this is a local environment + is_local = ( + self.__input_environment == Environments.LOCAL.value + or self.__input_environment == Environments.TEST.value + ) + + config_file = f'{self.__base_dir}/.env' + + with open(config_file, 'w', encoding='utf-8') as file_handle: + self.write_config(file_handle, 'REGION', region) + self.write_config(file_handle, 'FRONTEND_URL', frontend_url) + self.write_config(file_handle, 'ENTITIES_TABLE', entities_table) + self.write_config(file_handle, 'REGISTRATIONS_TABLE', registrations_table) + self.write_config(file_handle, 'PREREGISTRATIONS_TABLE', preregistrations_table) + self.write_config(file_handle, 'EVALUATIONS_TABLE', evaluations_table) + self.write_config(file_handle, 'EVENTS_TABLE', events_table) + self.write_config(file_handle, 'EMAIL_QUEUE', email_queue) + self.write_config(file_handle, 'CERTIFICATE_QUEUE', certificate_queue) + self.write_config(file_handle, 'S3_BUCKET', s3_bucket) + self.write_config(file_handle, 'USERPOOL_ID', userpool_id) + self.write_config(file_handle, 'USERPOOL_CLIENT_ID', userpool_client_id) + self.write_config(file_handle, 'STAGE', stage) + + print(f'Configuration file created successfully at: {config_file}') + + +if __name__ == '__main__': + print(Environments) + parser = argparse.ArgumentParser(description='TechTix (Backend) Configuration Assembler') + parser.add_argument('-r', '--region', help='AWS Region (default: ap-southeast-1)') + parser.add_argument('-s', '--stage', help='Environment Name (default: dev)') + args = parser.parse_args() + + print('Arguments:', args) + region = args.region + input_stage = args.stage + + config_assembler = ConfigAssembler(region, input_stage) + config_assembler.construct_config_file() \ No newline at end of file From 33dc036b0f974a3b6f4d02a2338cc59405a19c02 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Sun, 22 Feb 2026 20:33:43 +0800 Subject: [PATCH 05/43] feat: added role for frontend to fetch ssm parameters --- backend/resources/frontend_role.yml | 43 +++++++++++++++++++++++++++++ backend/serverless.yaml | 1 + 2 files changed, 44 insertions(+) create mode 100644 backend/resources/frontend_role.yml diff --git a/backend/resources/frontend_role.yml b/backend/resources/frontend_role.yml new file mode 100644 index 00000000..66ed4e7a --- /dev/null +++ b/backend/resources/frontend_role.yml @@ -0,0 +1,43 @@ +Resources: + FrontendSSMReadOnlyRole: + Type: AWS::IAM::Role + Properties: + RoleName: frontend-ssm-role-${sls:stage} + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - PolicyName: SSMReadOnlyAccess + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + - ssm:GetParametersByPath + Resource: + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/auth-api-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/events-api-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/payment-api-url-${sls:stage}" + + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-user-pool-id-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-user-pool-client-id-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-domain-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-signin-redirect-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/techtix/cognito-signin-redirect-local" + + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${sls:stage}-sparcs-events*" + + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/sparcs-events-email-queue-url-${sls:stage}" + - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/sparcs-events-certificate-queue-url-${sls:stage}" + +Outputs: + FrontendRoleArn: + Value: !GetAtt FrontendSSMReadOnlyRole.Arn diff --git a/backend/serverless.yaml b/backend/serverless.yaml index 7ec526fb..88ce06d7 100644 --- a/backend/serverless.yaml +++ b/backend/serverless.yaml @@ -74,6 +74,7 @@ resources: - ${file(resources/api_gateway.yml)} - ${file(resources/s3.yml)} - ${file(resources/sqs.yml)} + - ${file(resources/frontend_role.yml)} - Resources: ApiGatewayCloudWatchRole: Type: AWS::IAM::Role From b2f442d5575a24d558ae60e3a462e3951a3b46c4 Mon Sep 17 00:00:00 2001 From: ASPactores Date: Sun, 22 Feb 2026 19:10:40 +0800 Subject: [PATCH 06/43] fix: update S3 bucket configuration for consistent formatting and naming --- backend/resources/s3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/resources/s3.yml b/backend/resources/s3.yml index 62f1ca65..bd334502 100644 --- a/backend/resources/s3.yml +++ b/backend/resources/s3.yml @@ -2,7 +2,7 @@ Resources: Bucket: Type: AWS::S3::Bucket Properties: - BucketName: ${self:custom.bucket} + BucketName: ${self:custom.bucket}-2 VersioningConfiguration: Status: Enabled CorsConfiguration: From 083153b2406a65c572c22ca47a79e5ab8e5e0cd8 Mon Sep 17 00:00:00 2001 From: ASPactores Date: Sun, 22 Feb 2026 19:17:25 +0800 Subject: [PATCH 07/43] fix: update deployment stage from staging to dev in workflow --- .github/workflows/deploy_backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_backend.yml b/.github/workflows/deploy_backend.yml index da36a4c8..a2cdd117 100644 --- a/.github/workflows/deploy_backend.yml +++ b/.github/workflows/deploy_backend.yml @@ -64,6 +64,6 @@ jobs: if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then sls deploy --stage prod --verbose elif [[ "${{ github.ref }}" == "refs/heads/stage" ]]; then - sls deploy --stage staging --verbose + sls deploy --stage dev --verbose fi working-directory: backend From 7e72119cbead0decf22328ee95fc8cf680f86b57 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Mon, 23 Feb 2026 14:35:53 +0800 Subject: [PATCH 08/43] fix: update service --- backend/resources/frontend_role.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/resources/frontend_role.yml b/backend/resources/frontend_role.yml index 66ed4e7a..20cda21c 100644 --- a/backend/resources/frontend_role.yml +++ b/backend/resources/frontend_role.yml @@ -9,7 +9,7 @@ Resources: - Effect: Allow Principal: Service: - - lambda.amazonaws.com + - amplify.amazonaws.com Action: - sts:AssumeRole Policies: From 28836dbd514499eddc1463b3e81d1214b21bf20c Mon Sep 17 00:00:00 2001 From: seangaaab Date: Mon, 23 Feb 2026 14:37:38 +0800 Subject: [PATCH 09/43] docs: added comments to which ssm parameters are added manually --- frontend/scripts/generate-amplify-outputs.mjs | 4 ++-- frontend/scripts/generate-env.mjs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/scripts/generate-amplify-outputs.mjs b/frontend/scripts/generate-amplify-outputs.mjs index 89a7657e..573fa6ff 100644 --- a/frontend/scripts/generate-amplify-outputs.mjs +++ b/frontend/scripts/generate-amplify-outputs.mjs @@ -33,10 +33,10 @@ const ssm = new SSMClient({ region: REGION }); const parameterNames = { // REGION is optional (falls back to env/REGION) COGNITO_DOMAIN_URL: `/techtix/cognito-domain-url-${STAGE}`, - COGNITO_REDIRECT_SIGNIN_URI: `/techtix/cognito-signin-redirect-${STAGE}`, + COGNITO_REDIRECT_SIGNIN_URI: `/techtix/cognito-signin-redirect-${STAGE}`, // Added manually in AWS console for non-prod stages, can be missing in prod since we can use the same redirect URI for all prod-like stages COGNITO_USER_POOL_ID: `/techtix/cognito-user-pool-id-${STAGE}`, COGNITO_CLIENT_ID: `/techtix/cognito-user-pool-client-id-${STAGE}`, - ...(STAGE !== 'prod' && { COGNITO_REDIRECT_SIGNIN_URI_LOCAL: '/techtix/cognito-signin-redirect-local' }) + ...(STAGE !== 'prod' && { COGNITO_REDIRECT_SIGNIN_URI_LOCAL: '/techtix/cognito-signin-redirect-local' }) // Only used for local development, not needed in prod since we can use the same redirect URI for all prod-like stages }; async function getByNames(map) { diff --git a/frontend/scripts/generate-env.mjs b/frontend/scripts/generate-env.mjs index 5ff232bd..3794743c 100644 --- a/frontend/scripts/generate-env.mjs +++ b/frontend/scripts/generate-env.mjs @@ -28,6 +28,7 @@ const ssm = new SSMClient({ region: REGION }); /** * Map of logical keys -> array of candidate SSM parameter names (first found wins). + * Note: These SSM parameters are added manually in the AWS console, and must match the names here. */ const parameterNames = { VITE_API_AUTH_BASE_URL: `/techtix/auth-api-url-${STAGE}`, From 8a3a0dec727667426ffd7b19ebeb9620ff3fe8c7 Mon Sep 17 00:00:00 2001 From: ASPactores Date: Mon, 23 Feb 2026 18:33:47 +0800 Subject: [PATCH 10/43] feat: update deployment workflow to use workflow_run and improve AWS role configuration --- .github/workflows/deploy_backend.yml | 82 ++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/.github/workflows/deploy_backend.yml b/.github/workflows/deploy_backend.yml index a2cdd117..03a1f5c9 100644 --- a/.github/workflows/deploy_backend.yml +++ b/.github/workflows/deploy_backend.yml @@ -1,42 +1,81 @@ name: Deploy SPARCS Events Service on: - push: - paths: - - backend/** - branches: - - main - - stage - workflow_dispatch: + workflow_run: + workflows: ["Backend Formatting Check on Push"] + types: + - completed permissions: id-token: write contents: read jobs: - deploy: + deploy-dev: + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' }} runs-on: ubuntu-latest + environment: dev steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Configure AWS Credentials for Dev + uses: aws-actions/configure-aws-credentials@v4.1.0 + with: + audience: sts.amazonaws.com + aws-region: ap-southeast-1 + role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN_STAGE }} + role-session-name: GithubActionSession - - name: Set ARN for Role to Assume + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version-file: "backend/.python-version" + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Install dependencies run: | - if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - echo "AWS_OIDC_ROLE_ARN=${{ secrets.AWS_OIDC_ROLE_ARN_PROD }}" >> $GITHUB_ENV - elif [[ "${{ github.ref }}" == "refs/heads/stage" ]]; then - echo "AWS_OIDC_ROLE_ARN=${{ secrets.AWS_OIDC_ROLE_ARN_STAGE }}" >> $GITHUB_ENV - fi + uv sync --locked --all-extras --dev + working-directory: backend - - name: Configure AWS Credentials + - name: Install Serverless Framework + run: | + npm install + npm install -g serverless@3.40.0 + working-directory: backend + + - name: Create requirements.txt + run: uv export --format requirements-txt --no-hashes --output-file requirements.txt --no-dev + working-directory: backend + + - name: Deploy with Serverless v3 + run: sls deploy --stage dev --verbose + working-directory: backend + + deploy-prod: + if: ${{ github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'release/') }} + runs-on: ubuntu-latest + environment: prod + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Configure AWS Credentials for Prod uses: aws-actions/configure-aws-credentials@v4.1.0 with: audience: sts.amazonaws.com aws-region: ap-southeast-1 - role-to-assume: ${{ env.AWS_OIDC_ROLE_ARN }} + role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN_PROD }} role-session-name: GithubActionSession - - name: Set up Python + - name: Setup Python uses: actions/setup-python@v5 with: python-version-file: "backend/.python-version" @@ -60,10 +99,5 @@ jobs: working-directory: backend - name: Deploy with Serverless v3 - run: | - if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - sls deploy --stage prod --verbose - elif [[ "${{ github.ref }}" == "refs/heads/stage" ]]; then - sls deploy --stage dev --verbose - fi + run: sls deploy --stage prod --verbose working-directory: backend From 466c70c5cf1c498b5cef258b8c160e9cf0493499 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Wed, 25 Feb 2026 19:01:38 +0800 Subject: [PATCH 11/43] fix: update generate env script --- backend/scripts/generate-env.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/scripts/generate-env.py b/backend/scripts/generate-env.py index bd19e500..7b153ab1 100644 --- a/backend/scripts/generate-env.py +++ b/backend/scripts/generate-env.py @@ -1,5 +1,4 @@ import argparse -import json import os from enum import Enum @@ -92,22 +91,23 @@ def construct_config_file(self) -> None: region = 'ap-southeast-1' stage = self.__stage + events_table = f"{stage}-sparcs-events" + entities_table = self.__get_parameter(f"/{stage}-sparcs-events-entities") registrations_table = self.__get_parameter(f"/{stage}-sparcs-events-registrations") preregistrations_table = self.__get_parameter(f"/{stage}-sparcs-events-preregistrations") evaluations_table = self.__get_parameter(f"/{stage}-sparcs-events-evaluations") - events_table = self.__get_parameter(f"/{stage}-sparcs-events") email_queue = self.__get_parameter(f"/sparcs-events-email-queue-url-{stage}") certificate_queue = self.__get_parameter(f"/sparcs-events-certificate-queue-url-{stage}") s3_bucket = self.__get_parameter(f"/{stage}-sparcs-events-file-bucket") - userpool_id = f"techtix/cognito-user-pool-id-{stage}" - userpool_client_id = f"techtix/cognito-user-pool-client-id-{stage}" + userpool_id = self.__get_parameter(f"/techtix/cognito-user-pool-id-{stage}") + userpool_client_id = self.__get_parameter(f"/techtix/cognito-user-pool-client-id-{stage}") if self.__input_environment == Environments.LOCAL.value or stage == Environments.LOCAL.value: - frontend_url = 'http://localhost:3000' + frontend_url = 'http://localhost:5173' else: - frontend_url = self.__get_parameter(f"techtix/frontend-url-{stage}") + frontend_url = self.__get_parameter(f"/techtix/frontend-url-{stage}") # Determine if this is a local environment is_local = ( From 05f6f8dc340815baf724471b1a14c768cbf87710 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Wed, 25 Feb 2026 19:02:06 +0800 Subject: [PATCH 12/43] feat: added additional ssm parameters --- backend/resources/frontend_urls.yml | 13 +++++++++++++ backend/serverless.yaml | 15 +++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 backend/resources/frontend_urls.yml diff --git a/backend/resources/frontend_urls.yml b/backend/resources/frontend_urls.yml new file mode 100644 index 00000000..3cbd7360 --- /dev/null +++ b/backend/resources/frontend_urls.yml @@ -0,0 +1,13 @@ +Resources: + FrontEndURLDevSSMParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /techtix/frontend-url-dev + Type: String + Value: https://dev.nonprod.techtix.com + FrontEndURLProdSSMParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /techtix/frontend-url-prod + Type: String + Value: https://www.techtix.com diff --git a/backend/serverless.yaml b/backend/serverless.yaml index 88ce06d7..99fc9b9c 100644 --- a/backend/serverless.yaml +++ b/backend/serverless.yaml @@ -100,6 +100,21 @@ resources: - logs:DescribeLogStreams - logs:PutLogEvents Resource: "*" + EventServiceApiEndpointParameter: + Type: AWS::SSM::Parameter + Properties: + Name: /techtix/events-api-url-${self:custom.stage} + Type: String + Value: + Fn::Join: + - "" + - - "https://" + - Ref: ApiGatewayRestApi + - ".execute-api." + - ${self:provider.region} + - ".amazonaws.com/" + - ${self:custom.stage} + Outputs: ApiGatewayCloudWatchRoleArn: Value: !GetAtt ApiGatewayCloudWatchRole.Arn From 59b3d7ded827b73f270d991f57791d58857c610a Mon Sep 17 00:00:00 2001 From: seangaaab Date: Thu, 26 Feb 2026 14:54:55 +0800 Subject: [PATCH 13/43] docs: added contributing.md file --- CONTRIBUTING.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1938d902 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,91 @@ +# Contributing to the Project + +Thank you for your interest in contributing! To maintain a clean history and ensure a smooth development process, we follow a specific workflow. Please follow these guidelines to get started. + +--- + +## 1. Getting Started + +### Fork the Repository + +First, create your own copy of the repository by clicking the **Fork** button at the top of the page. Clone your fork to your local machine: + +```bash +git clone https://github.com/YOUR-USERNAME/repository-name.git +cd repository-name + +``` + +### Configure Upstream + +Add the original repository as a remote to keep your local fork in sync: + +```bash +git remote add upstream https://github.com/ORIGINAL-OWNER/repository-name.git + +``` + +--- + +## 2. Branching and Development + +### Create a Feature Branch + +Before starting any work, create a new branch. We use **Conventional Naming** based on the issue or ticket you are addressing: + +- `feat/issue-ID` (e.g., `feat/123-add- login-logic`) +- `fix/issue-ID` (e.g., `fix/456-patch-security-hole`) +- `docs/issue-ID` +- `refactor/issue-ID` + +```bash +git checkout -b feat/123-short-description +``` + +See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification). + +### Stay in Sync (Rebase Flow) + +Our repository uses a **rebase flow** to keep the history linear. When pulling updates from the main repository, always use the `--rebase` (or `-r`) flag to avoid merge commits: + +```bash +git pull -r upstream main + +``` + +--- + +## 3. Commit Guidelines + +We follow the **Conventional Commits** specification. Commits should be structured as follows: + +`(): ` + +- **feat**: A new feature +- **fix**: A bug fix +- **docs**: Documentation only changes +- **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc) +- **refactor**: A code change that neither fixes a bug nor adds a feature + +**Example:** +`feat(auth): add JWT validation to login endpoint` + +See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification). + +--- + +## 4. Submitting a Pull Request + +Once you have completed your work and verified it locally: + +1. **Push to your fork:** + +```bash +git push origin your-branch-name +``` + +2. **Raise a PR:** Navigate to the original repository on GitHub. You will see a prompt to "Compare & pull request" from your fork. +3. **Target Branch:** Ensure your PR is targeting the `main` repository's `dev` or `main` branch as specified in the issue. +4. **Review:** Wait for the maintainers to review your code. We may request changes before merging. + +Thank you for helping us improve the project! From 196d002afe0c470738718790da32ed5cf47cf076 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Thu, 26 Feb 2026 14:55:28 +0800 Subject: [PATCH 14/43] chore(fe-ci): renamed and updated fe ci --- ...-to-prod-pipeline.yml => fe_deploy_to_main.yml} | 14 +++++++------- ...-to-stage-pipeline.yml => fe_pr_and_deploy.yml} | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) rename .github/workflows/{FE-deploy-stage-to-prod-pipeline.yml => fe_deploy_to_main.yml} (79%) rename .github/workflows/{FE-pr-and-deploy-to-stage-pipeline.yml => fe_pr_and_deploy.yml} (76%) diff --git a/.github/workflows/FE-deploy-stage-to-prod-pipeline.yml b/.github/workflows/fe_deploy_to_main.yml similarity index 79% rename from .github/workflows/FE-deploy-stage-to-prod-pipeline.yml rename to .github/workflows/fe_deploy_to_main.yml index d5194719..d089d854 100644 --- a/.github/workflows/FE-deploy-stage-to-prod-pipeline.yml +++ b/.github/workflows/fe_deploy_to_main.yml @@ -1,11 +1,11 @@ -name: Deploy stage to main +name: Deploy dev to main on: workflow_dispatch: jobs: - deploy-stage-to-main: - name: Deploy stage to main + deploy-dev-to-main: + name: Deploy dev to main runs-on: ubuntu-latest defaults: run: @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - ref: stage + ref: dev - uses: actions/setup-node@v4 with: node-version: "22.15" @@ -37,12 +37,12 @@ jobs: uses: trstringer/manual-approval@v1 with: secret: ${{ secrets.GITHUB_TOKEN }} - approvers: ArJSarmiento,ASPactores,seangaaab + approvers: kindadailybren, prcsmae, seangaaab minimum-approvals: 1 - issue-title: "Deploying stage to main" + issue-title: "Deploying dev to main" issue-body: "Please approve or deny the deployment." - - name: Sync stage to main + - name: Sync dev to main uses: connor-baer/action-sync-branch@main with: branch: main diff --git a/.github/workflows/FE-pr-and-deploy-to-stage-pipeline.yml b/.github/workflows/fe_pr_and_deploy.yml similarity index 76% rename from .github/workflows/FE-pr-and-deploy-to-stage-pipeline.yml rename to .github/workflows/fe_pr_and_deploy.yml index a056d770..307fd250 100644 --- a/.github/workflows/FE-pr-and-deploy-to-stage-pipeline.yml +++ b/.github/workflows/fe_pr_and_deploy.yml @@ -1,15 +1,15 @@ -name: Build and Deploy PR to stage +name: Build and Deploy PR to dev on: push: - branches: [stage] + branches: [dev] pull_request: - branches: [stage] + branches: [dev] workflow_dispatch: jobs: build-and-deploy: - name: Build and Deploy PR to stage + name: Build and Deploy PR to dev runs-on: ubuntu-latest defaults: run: @@ -32,6 +32,6 @@ jobs: run: npm run build:dummy - name: Deploy to Amplify - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' run: | curl -X POST -d '{}' "${{ secrets.AMPLIFY_WEBHOOK_URL }}" -H "Content-Type:application/json" From ce83cbce4936ce067cdaa589498473716b1efb17 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Thu, 26 Feb 2026 14:56:00 +0800 Subject: [PATCH 15/43] feat(unit-tests-fe): added unit tests and sample test to fe --- frontend/package-lock.json | 1109 +++++++++++++++++- frontend/package.json | 8 +- frontend/src/utils/__tests__/classes.test.ts | 31 + frontend/vitest.config.mts | 10 + 4 files changed, 1125 insertions(+), 33 deletions(-) create mode 100644 frontend/src/utils/__tests__/classes.test.ts create mode 100644 frontend/vitest.config.mts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 57ee03b8..8774a208 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -73,14 +73,24 @@ "eslint": "^9.33.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", + "jsdom": "^28.1.0", "postcss": "^8.5.6", "prettier": "^3.6.2", "tailwindcss": "^4.1.12", "typescript": "^5.9.2", "vite": "^7.1.3", - "vite-plugin-pwa": "^1.0.3" + "vite-plugin-pwa": "^1.0.3", + "vite-tsconfig-paths": "^6.1.1", + "vitest": "^4.0.18" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -108,6 +118,64 @@ "node": ">=6.0.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@aws-amplify/analytics": { "version": "7.0.86", "resolved": "https://registry.npmjs.org/@aws-amplify/analytics/-/analytics-7.0.86.tgz", @@ -187,6 +255,7 @@ "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-6.13.1.tgz", "integrity": "sha512-PtSGhC7pv+xdr4Ozy6jzeZraB/kjBZiK9tOLOaBH5Trt0pBuLbh2mneE7a72xHGf64XJbl7v3MWPaVscb8KXkw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/types": "3.398.0", @@ -1826,6 +1895,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.621.0.tgz", "integrity": "sha512-mMjk3mFUwV2Y68POf1BQMTF+F6qxt5tPu6daEUCNGC9Cenk3h2YXQQoS4/eSyYzuBiYk3vx49VgleRvdvkg8rg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -1931,6 +2001,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.621.0.tgz", "integrity": "sha512-707uiuReSt+nAx6d0c21xLjLm2lxeKc7padxjv92CIrIocnQSlJPxSCM7r5zBhwiahJA6MNQwmTl2xznU67KgA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -3514,6 +3585,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -5089,6 +5161,153 @@ "node": ">=6.9.0" } }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz", + "integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@date-fns/tz": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", @@ -5723,6 +5942,24 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", + "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@floating-ui/core": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", @@ -5737,6 +5974,7 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -8345,6 +8583,13 @@ "node": ">=16.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", @@ -8731,6 +8976,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.2.2.tgz", "integrity": "sha512-+9cSesDsImbHzhESjP/0imD2V/s6kcFUeay6nd5P2pDv6yMWipor9aAgzu+eJ5wxrEHx8WxreFws2WdSGEspkQ==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -8953,6 +9199,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.2.2.tgz", "integrity": "sha512-o7HFScNCyxuET0nw7OLhfVYvd2Jqk6kqWYMC6ynEmqXVRhSkYUdEVTridI1aFDsbbiL55b6PUdCbQtpKnINWPw==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9084,6 +9331,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.2.2.tgz", "integrity": "sha512-AMA9Jsr1X1S5dq0PZubi0avw8HTlG/tqFMWePeHN3WNiBo3jreIZt3NoxtYNNrbJ6nwhFXNhQpEh915JBx/rPw==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9098,6 +9346,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.2.2.tgz", "integrity": "sha512-Eju6vLpNMXubjZ3gjiTKIQIyhnUk4BLjU76t67EBFJkbQr/VvRNzn3BD/4XEmj6/LT0TgB12m50gaIb/BTAGng==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -9270,6 +9519,24 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -9311,6 +9578,7 @@ "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -9321,6 +9589,7 @@ "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -9331,6 +9600,7 @@ "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -9397,6 +9667,7 @@ "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.40.0", "@typescript-eslint/types": "8.40.0", @@ -9617,12 +9888,134 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -9640,6 +10033,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -9730,6 +10133,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -9885,6 +10298,16 @@ ], "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bowser": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.0.tgz", @@ -9934,6 +10357,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -10045,6 +10469,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -10222,34 +10656,126 @@ "node": ">=8" } }, - "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, - "license": "MIT" - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "node_modules/cssstyle": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz", + "integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "license": "MIT", @@ -10317,6 +10843,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -10545,6 +11078,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -10660,6 +11200,7 @@ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -10899,6 +11440,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -11381,6 +11932,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -11507,6 +12065,47 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/idb": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/idb/-/idb-5.0.6.tgz", @@ -11877,6 +12476,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -12122,6 +12728,86 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -12550,9 +13236,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.18", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", - "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12585,6 +13271,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -12784,6 +13477,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -12881,6 +13585,32 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12918,6 +13648,13 @@ "dev": true, "license": "MIT" }, + "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/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -12993,6 +13730,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13128,6 +13866,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.3.tgz", "integrity": "sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==", "license": "MIT", + "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -13157,6 +13896,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -13205,6 +13945,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.1.tgz", "integrity": "sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -13272,6 +14013,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13302,6 +14044,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -13314,6 +14057,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -13624,6 +14368,7 @@ "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -13787,6 +14532,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -13970,6 +14728,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/smob": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", @@ -14030,6 +14795,20 @@ "dev": true, "license": "MIT" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -14207,6 +14986,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", @@ -14221,7 +15007,8 @@ "version": "4.1.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -14322,15 +15109,32 @@ "node": ">=10" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "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.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -14363,6 +15167,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14370,6 +15175,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", + "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.23" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", + "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14383,6 +15218,19 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -14406,6 +15254,27 @@ "typescript": ">=4.8.4" } }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -14522,6 +15391,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14564,6 +15434,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", @@ -14761,6 +15641,7 @@ "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -14861,6 +15742,21 @@ } } }, + "node_modules/vite-tsconfig-paths": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz", + "integrity": "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -14880,6 +15776,98 @@ } }, "node_modules/vite/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", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", @@ -14898,6 +15886,19 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -14905,6 +15906,16 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", @@ -15029,6 +16040,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -15203,6 +16231,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -15257,6 +16286,7 @@ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -15418,6 +16448,23 @@ "dev": true, "license": "ISC" }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 92ba69cc..2d907693 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,8 @@ "format:check:ci": "./node_modules/.bin/prettier --check .", "generate:outputs": "node scripts/generate-amplify-outputs.mjs", "generate:env": "node scripts/generate-env.mjs", - "generate": "node scripts/generate-all.mjs" + "generate": "node scripts/generate-all.mjs", + "test:unit": "vitest run" }, "dependencies": { "@hookform/resolvers": "^5.2.1", @@ -82,11 +83,14 @@ "eslint": "^9.33.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", + "jsdom": "^28.1.0", "postcss": "^8.5.6", "prettier": "^3.6.2", "tailwindcss": "^4.1.12", "typescript": "^5.9.2", "vite": "^7.1.3", - "vite-plugin-pwa": "^1.0.3" + "vite-plugin-pwa": "^1.0.3", + "vite-tsconfig-paths": "^6.1.1", + "vitest": "^4.0.18" } } diff --git a/frontend/src/utils/__tests__/classes.test.ts b/frontend/src/utils/__tests__/classes.test.ts new file mode 100644 index 00000000..5e935d88 --- /dev/null +++ b/frontend/src/utils/__tests__/classes.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { cn } from '../classes'; + +// Basic merging + +describe('cn', () => { + it('merges multiple class names', () => { + expect(cn('foo', 'bar')).toBe('foo bar'); + }); + + it('deduplicates class names', () => { + expect(cn('text-sm', 'text-sm', 'bar')).toBe('text-sm bar'); + }); + + it('handles falsy values', () => { + expect(cn('foo', false, null, undefined, '', 'bar')).toBe('foo bar'); + }); + + it('merges tailwind classes and resolves conflicts', () => { + expect(cn('p-2', 'p-4')).toBe('p-4'); // tailwind-merge keeps the last + expect(cn('text-sm', 'text-lg')).toBe('text-lg'); + }); + + it('handles conditional classes (clsx object syntax)', () => { + expect(cn('foo', { bar: true, baz: false })).toBe('foo bar'); + }); + + it('returns an empty string if no classes are provided', () => { + expect(cn()).toBe(''); + }); +}); diff --git a/frontend/vitest.config.mts b/frontend/vitest.config.mts new file mode 100644 index 00000000..a4781e68 --- /dev/null +++ b/frontend/vitest.config.mts @@ -0,0 +1,10 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [tsconfigPaths(), react()], + test: { + environment: 'jsdom' + } +}); From 6796879ed8b6b07f2afc598424ad98b3a5af14ca Mon Sep 17 00:00:00 2001 From: seangaaab Date: Thu, 26 Feb 2026 14:56:36 +0800 Subject: [PATCH 16/43] fix: update serverless.yml to include frontend-urls ssm parameters --- backend/serverless.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/serverless.yaml b/backend/serverless.yaml index 99fc9b9c..5d655084 100644 --- a/backend/serverless.yaml +++ b/backend/serverless.yaml @@ -75,6 +75,7 @@ resources: - ${file(resources/s3.yml)} - ${file(resources/sqs.yml)} - ${file(resources/frontend_role.yml)} + - ${file(resources/frontend_urls.yml)} - Resources: ApiGatewayCloudWatchRole: Type: AWS::IAM::Role From 76c157c31d9c9482d6ddd124035abdea7c29aaac Mon Sep 17 00:00:00 2001 From: bren Date: Fri, 27 Feb 2026 23:18:06 +0800 Subject: [PATCH 17/43] fix: post command on devcontainer.json --- .devcontainer/devcontainer.json | 6 +++--- frontend/package-lock.json | 36 +-------------------------------- 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4dd90b5a..2fca0e1d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "TechTix Monorepo", - "image": "mcr.microsoft.com/devcontainers/universal:latest", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "ghcr.io/devcontainers/features/node:1": { "version": "20" @@ -9,7 +9,7 @@ "version": "3.8" } }, - "postCreateCommand": "npm install --prefix frontend && pipenv install --skip-lock --python 3.8 --manifest backend/Pipfile", + "postCreateCommand": "npm install --prefix frontend && cd backend && uv sync", "customizations": { "vscode": { "extensions": [ @@ -21,4 +21,4 @@ } }, "remoteUser": "root" -} \ No newline at end of file +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8774a208..816071d3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -255,7 +255,6 @@ "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-6.13.1.tgz", "integrity": "sha512-PtSGhC7pv+xdr4Ozy6jzeZraB/kjBZiK9tOLOaBH5Trt0pBuLbh2mneE7a72xHGf64XJbl7v3MWPaVscb8KXkw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/types": "3.398.0", @@ -1895,7 +1894,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.621.0.tgz", "integrity": "sha512-mMjk3mFUwV2Y68POf1BQMTF+F6qxt5tPu6daEUCNGC9Cenk3h2YXQQoS4/eSyYzuBiYk3vx49VgleRvdvkg8rg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -2001,7 +1999,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.621.0.tgz", "integrity": "sha512-707uiuReSt+nAx6d0c21xLjLm2lxeKc7padxjv92CIrIocnQSlJPxSCM7r5zBhwiahJA6MNQwmTl2xznU67KgA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -3585,7 +3582,6 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -5262,7 +5258,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -5303,7 +5298,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -5974,7 +5968,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", - "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -8976,7 +8969,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.2.2.tgz", "integrity": "sha512-+9cSesDsImbHzhESjP/0imD2V/s6kcFUeay6nd5P2pDv6yMWipor9aAgzu+eJ5wxrEHx8WxreFws2WdSGEspkQ==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9199,7 +9191,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.2.2.tgz", "integrity": "sha512-o7HFScNCyxuET0nw7OLhfVYvd2Jqk6kqWYMC6ynEmqXVRhSkYUdEVTridI1aFDsbbiL55b6PUdCbQtpKnINWPw==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9331,7 +9322,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.2.2.tgz", "integrity": "sha512-AMA9Jsr1X1S5dq0PZubi0avw8HTlG/tqFMWePeHN3WNiBo3jreIZt3NoxtYNNrbJ6nwhFXNhQpEh915JBx/rPw==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -9346,7 +9336,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.2.2.tgz", "integrity": "sha512-Eju6vLpNMXubjZ3gjiTKIQIyhnUk4BLjU76t67EBFJkbQr/VvRNzn3BD/4XEmj6/LT0TgB12m50gaIb/BTAGng==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -9578,7 +9567,6 @@ "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -9589,7 +9577,6 @@ "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -9600,7 +9587,6 @@ "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -9667,7 +9653,6 @@ "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.40.0", "@typescript-eslint/types": "8.40.0", @@ -10015,7 +10000,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10357,7 +10341,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -11200,7 +11183,6 @@ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -12734,7 +12716,6 @@ "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.8.1", @@ -13730,7 +13711,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13866,7 +13846,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.3.tgz", "integrity": "sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==", "license": "MIT", - "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -13896,7 +13875,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -13945,7 +13923,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.1.tgz", "integrity": "sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -14013,7 +13990,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14044,7 +14020,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -14057,7 +14032,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -14368,7 +14342,6 @@ "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -15007,8 +14980,7 @@ "version": "4.1.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -15167,7 +15139,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15391,7 +15362,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15641,7 +15611,6 @@ "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -15781,7 +15750,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16231,7 +16199,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -16286,7 +16253,6 @@ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, From 63add1e0341ea1969aa5ecfc9d34869b44d98539 Mon Sep 17 00:00:00 2001 From: bren Date: Fri, 27 Feb 2026 23:31:40 +0800 Subject: [PATCH 18/43] fix: install uv beforehand --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2fca0e1d..dc81603f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,7 +9,7 @@ "version": "3.8" } }, - "postCreateCommand": "npm install --prefix frontend && cd backend && uv sync", + "postCreateCommand": "npm install --prefix frontend && cd backend && pip install uv && uv sync", "customizations": { "vscode": { "extensions": [ From de88970490fc56bad367882b5ad59bc7500215ce Mon Sep 17 00:00:00 2001 From: bren Date: Fri, 27 Feb 2026 23:43:37 +0800 Subject: [PATCH 19/43] chore: change devcontainer python version to 3.11 --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index dc81603f..a37c7e74 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "version": "20" }, "ghcr.io/devcontainers/features/python:1": { - "version": "3.8" + "version": "3.11" } }, "postCreateCommand": "npm install --prefix frontend && cd backend && pip install uv && uv sync", From 94d34ab17cca9ee50512d21ab99563ba24c5819b Mon Sep 17 00:00:00 2001 From: "Jay Emerson P. Navares" Date: Fri, 27 Feb 2026 21:18:01 +0800 Subject: [PATCH 20/43] Revise README for TechTix Frontend setup Updated README to reflect project name change and improved setup instructions. --- frontend/README.md | 237 ++++++++++++++++++++++++++++++--------------- 1 file changed, 160 insertions(+), 77 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index 178fd116..e757d54a 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,129 +1,212 @@ -# Front-End Guide +# TechTix Frontend (React) -## Setting up your local environment +This folder contains the TechTix web frontend. -- Ensure that you have `node 22.15+` installed. -- Clone the repository in your machine. -- Open the repository in your IDE -- Open up your IDE's terminal. Make sure you're at the root folder of the project. Enter `cd frontend` in the terminal - ![image](https://github.com/SPARCS-UP-Mindanao/SPARCS-Event-Platform/assets/85269524/adcdd520-d032-4380-8a85-81bbf39805c2) -- After that, you can install the dependencies by running the `npm install` command. -- After installing, you can now run the project by entering `npm run dev` and click on the url provided. +## Development Setup + +### Recommended: DevContainer (Sprint Default) + +For this sprint, development uses **VS Code Dev Containers**. Refer to the root [`README.md`](../README.md) for the complete DevContainer setup, requirements, and how AWS credentials are mounted into the container. + +Once the DevContainer is running, use the DevContainer terminal: + +```bash +npm install +npm run dev +``` + +Then open the URL printed in the terminal. + +### Alternative: Local (without DevContainer) + +If you are not using DevContainers, ensure you have: + +- `node 22.15+` + +Then: + +1. Clone the repository to your machine. +2. Open the repository in your IDE. +3. Open a terminal at the repository root and switch to the frontend folder: + +```bash +cd frontend +``` + +![image](https://github.com/SPARCS-UP-Mindanao/SPARCS-Event-Platform/assets/85269524/adcdd520-d032-4380-8a85-81bbf39805c2) + +4. Install dependencies and start the dev server: + +```bash +npm install +npm run dev +``` ![image](https://github.com/SPARCS-UP-Mindanao/SPARCS-Event-Platform/assets/85269524/c44ea864-f1da-4d78-bb0c-980345125b3f) -## Directory guide +## AWS SSO (Frontend) -We will mostly work in the `frontend/src` folder. +Some frontend workflows require AWS access (e.g., generating local configuration files). -![image](https://github.com/SPARCS-UP-Mindanao/SPARCS-Event-Platform/assets/85269524/f6cb7718-3817-4c12-9574-ff5c7a09f033) +Before running scripts that call AWS, authenticate using AWS SSO (details and the recommended workflow are documented in the root [`README.md`](../README.md)). -We have these folders, and here's what they're for: +If you use a named AWS profile, ensure it is selected in your terminal session: -- `api` - contains all of the API calls that we will throughout the project -- `assets` - contains all other files like pictures, fonts, gifs, etc. -- `components` - contains all of the **reusable** components throughout the project. -- `context` - contains all of the contexts that the project uses. -- `hooks` - contains all the custom hooks. -- `model` - contains the interface/types of the APIs that we use. -- `pages` - contains all of the pages and their respective components. -- `routes` - has the route of the project. -- `styles` - has the css files that the project uses. -- `utils` - has the helper functions that is used throughout project. +```bash +export AWS_PROFILE= +``` -With the help of `tsconfig.json`, we are able to use aliases for our directories. -We can simply use `@/{folderInSRC}` for accessing folders or files under the `frontend/src` directory. -This makes it easy for us as we don't need to do `../../../../folder` just to navigate through the directory. +### Generating `amplify_outputs.json` and `.env` -If you're new to tailwind, you can also read about tailwind here and the cheat sheet. +Use these scripts to generate files required to run the frontend locally: -- [Tailwind Docs](https://tailwindcss.com/) -- [Tailwind Cheat Sheet](https://nerdcave.com/tailwind-cheat-sheet) +- `amplify_outputs.json` +- `.env` -Also, please install the [tailwind extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) to have autocomplete feature. +Run: -# Generating amplify_outputs.json and .env file +```bash +# $stage = dev | staging +npm run generate:env $stage +npm run generate:outputs $stage +``` -Utilize these scripts to generate the files required to run the project locally. +## Directory guide -Note: You must first setup AWS CLI in your machine and login with your provided AWS account. You might also need to do `export AWS_PROFILE=`. +We will mostly work in the `frontend/src` folder. -$stage = dev | staging +![image](https://github.com/SPARCS-UP-Mindanao/SPARCS-Event-Platform/assets/85269524/f6cb7718-3817-4c12-9574-ff5c7a09f033) -- `npm run generate:env $stage` -- `npm run generate:outputs $stage` +We have these folders, and here's what they're for: + +- `api` — API calls and API utilities (e.g., `createApi`) +- `assets` — images, fonts, gifs, and other static assets +- `components` — reusable UI components +- `context` — React contexts used by the app +- `hooks` — custom hooks +- `model` — TypeScript interfaces/types used by the app and API layer +- `pages` — page-level components +- `routes` — app routing and route layouts +- `styles` — CSS and styling files used by the project +- `utils` — helper functions used throughout the project + +### Path aliases + +With the help of `tsconfig.json`, we can use aliases for our directories: + +- `@/{folderInSRC}` + +This avoids long relative imports like `../../../../someFolder`. ## Components -In this project, we are using shadcn/ui for our components. +This project uses **shadcn/ui** for components. + +You can read the docs here: https://ui.shadcn.com/ -You can read the docs [here](https://ui.shadcn.com/). +### Naming conventions -Some of the components have been modified to match the needs of this document. -Please view the component file or just message [Sean](https://www.facebook.com/seangaaab) for questions. +- Components: **PascalCase** (example: `PascalCase.tsx`) +- Hooks: camel case starting with `use` (example: `useCamelCase`) -For naming components, we will be using Pascal case. Example: `PascalCase.tsx` +Reminder: We use a different directory than what is written in the shadcn/ui docs. Use: -Reminder: We are using a different directory than what is written in the shadcn/ui docs. Please use `@/components/{componentName.tsx}` when importing components. +- `@/components/{componentName.tsx}` ### Using the Form component -The form component is customized so that FE devs can easily setup a form. +The form component is customized so that FE devs can easily set up a form. -The form can be used this way: +Example usage: -``` +```tsx const form = useForm(); + return ( - - {({ field, fieldState, formState }) => ( -
- {Label} - - {description} - -
- )} -
- -) + + {({ field, fieldState, formState }) => ( +
+ {Label} + + {description} + +
+ )} +
+
+); ``` Where: - `FormProvider` provides the form context to its children -- `FormLabel` - Label if any -- `FormDescription` - Description if any -- `FormError` - Errors if any - -With this implementation, we can easily add custom components. We just need to pass the `value`, and the `onChange` from `field`. +- `FormLabel` — label (if any) +- `FormDescription` — description (if any) +- `FormError` — errors (if any) -for anything about `field`, `fieldState`, and `formState`, please read up on [`react-hook-form`](https://react-hook-form.com/) and about [Controllers](https://react-hook-form.com/docs/usecontroller/controller). +For anything about `field`, `fieldState`, and `formState`, read up on: +- [`react-hook-form`](https://react-hook-form.com/) +- [Controllers](https://react-hook-form.com/docs/usecontroller/controller) ## Hooks -These contain the logic for most pages like handling forms. We can use this to separate the design from its logic. -Usually, these hooks return functions or variables needed to apply the logic to our design. -You can read more about it [here](https://react.dev/learn/reusing-logic-with-custom-hooks). +Hooks contain the logic for pages (e.g., handling forms). This helps separate UI/design from logic. -For naming hooks, we will use Camel case with the starting word `use`. Example: `useCamelCase` +- Location: `src/hooks/` +- Naming: `useSomething` +- Prefer returning a clear API (data + loading/error state, and action functions) -## Code formatting +## Contexts + +Contexts live in `src/context/` and are used for app-wide state and providers. + +- Prefer exposing a `useSomething()` hook as the public API for consuming context values. -Please use `prettier` for formatting code to make it look cleaner. -You can find it as an extension in `VSCode`. +## Git Hooks (Pre-commit) -You may also utilized the command `npm run pretty` to format the code. +This project uses git hooks to prevent committing secrets and to enforce commit message conventions. -## +### Hooks used -You can always refer to the existing code to follow our best practices. +- **gitleaks** (pre-commit): scans staged changes for secrets (API keys, tokens, passwords). +- **commitizen** (commit-msg): validates commit message format. -When in doubt, please feel free to ask. +### Install hooks locally -## +Run inside the DevContainer terminal: -**Your brother in Christ,** +```bash +npm i --save-dev prek +npx prek install --hook-type commit-msg --hook-type pre-commit +``` + +If a commit is rejected: +- **gitleaks**: remove the secret from the change (and rotate the credential if it was exposed). +- **commitizen**: rewrite the commit message to match the required format. + +## Code formatting + +Use **Prettier** for formatting code. + +Format the code with: + +```bash +npm run pretty +``` + +## Useful Commands + +Run inside the DevContainer terminal: + +```bash +npm run lint +npm run test:unit +npm run build +npm run start +npm run pretty +npm run generate +``` -**Sean** +- `npm run start` runs the Vite preview server (built app). +- `npm run generate` runs both env + outputs generation (see `package.json`). From 3234d8c2d18b68283a3732c2ebdce90572657160 Mon Sep 17 00:00:00 2001 From: "Jay Emerson P. Navares" Date: Fri, 27 Feb 2026 23:28:55 +0800 Subject: [PATCH 21/43] Enhance README with DevContainer and AWS CLI details Updated the README to include detailed DevContainer setup instructions and AWS CLI requirements. --- frontend/README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index e757d54a..803f943f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -2,11 +2,20 @@ This folder contains the TechTix web frontend. -## Development Setup +## Development Setup (DevContainer) -### Recommended: DevContainer (Sprint Default) +For this sprint, development uses **VS Code Dev Containers** (containerized VS Code). This means you do **not** need to manually install project runtimes/dependencies (like Node) on your host machine—everything runs inside the container. -For this sprint, development uses **VS Code Dev Containers**. Refer to the root [`README.md`](../README.md) for the complete DevContainer setup, requirements, and how AWS credentials are mounted into the container. +### DevContainer dependencies (host machine) + +To use DevContainers, install: + +- VS Code +- VS Code Extension: **Dev Containers** +- Docker (Docker Desktop on Windows/macOS, Docker Engine on Linux) + + +### Run the frontend (inside the DevContainer) Once the DevContainer is running, use the DevContainer terminal: @@ -44,11 +53,12 @@ npm run dev ![image](https://github.com/SPARCS-UP-Mindanao/SPARCS-Event-Platform/assets/85269524/c44ea864-f1da-4d78-bb0c-980345125b3f) -## AWS SSO (Frontend) +## AWS CLI / AWS SSO (Frontend) Some frontend workflows require AWS access (e.g., generating local configuration files). -Before running scripts that call AWS, authenticate using AWS SSO (details and the recommended workflow are documented in the root [`README.md`](../README.md)). +- Ensure you have authenticated via **AWS SSO**. +- Ensure the **AWS CLI** is available in your environment (the DevContainer should provide it; see root [`README.md`](../README.md) for the canonical setup). If you use a named AWS profile, ensure it is selected in your terminal session: From 800aecd93765ad5a7a1488684a546cfabaec5232 Mon Sep 17 00:00:00 2001 From: seangaaab Date: Sat, 28 Feb 2026 01:09:38 +0800 Subject: [PATCH 22/43] fix: update env variable name --- backend/scripts/generate-env.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/scripts/generate-env.py b/backend/scripts/generate-env.py index 7b153ab1..1d72436d 100644 --- a/backend/scripts/generate-env.py +++ b/backend/scripts/generate-env.py @@ -128,8 +128,8 @@ def construct_config_file(self) -> None: self.write_config(file_handle, 'EMAIL_QUEUE', email_queue) self.write_config(file_handle, 'CERTIFICATE_QUEUE', certificate_queue) self.write_config(file_handle, 'S3_BUCKET', s3_bucket) - self.write_config(file_handle, 'USERPOOL_ID', userpool_id) - self.write_config(file_handle, 'USERPOOL_CLIENT_ID', userpool_client_id) + self.write_config(file_handle, 'USER_POOL_ID', userpool_id) + self.write_config(file_handle, 'USER_POOL_CLIENT_ID', userpool_client_id) self.write_config(file_handle, 'STAGE', stage) print(f'Configuration file created successfully at: {config_file}') From 167f59dbf160b74f2658800a8c55e82bd0755def Mon Sep 17 00:00:00 2001 From: jl Date: Thu, 26 Feb 2026 13:08:35 +0800 Subject: [PATCH 23/43] docs(contributing): Add CONTRIBUTING markdown for backend and update Readme --- backend/CONTRIBUITING.md | 404 +++++++++++++++++++++++++++++++++++++++ backend/README.md | 89 ++++++++- 2 files changed, 491 insertions(+), 2 deletions(-) create mode 100644 backend/CONTRIBUITING.md diff --git a/backend/CONTRIBUITING.md b/backend/CONTRIBUITING.md new file mode 100644 index 00000000..cb313be0 --- /dev/null +++ b/backend/CONTRIBUITING.md @@ -0,0 +1,404 @@ +# Contributing to SPARCS TECHTIX API + +Thank you for your interest in contributing to the SPARCS Events API! This document provides guidelines and best practices for contributing to this project. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) +- [Coding Conventions](#coding-conventions) +- [Commit Guidelines](#commit-guidelines) +- [Pull Request Process](#pull-request-process) +- [Code Review](#code-review) + +## Code of Conduct + +Please be respectful and constructive in all interactions. We aim to maintain a welcoming and inclusive environment for all contributors. + +## Getting Started + +1. Follow the setup instructions in the [README.md](README.md) +2. Create a new branch for your feature or bugfix: + ```shell + git checkout -b feature/your-feature-name + ``` +3. Make your changes following the conventions below +4. Test your changes thoroughly +5. Submit a pull request + +## Coding Conventions + +### Python Style Guide + +We follow PEP 8 with some project-specific conventions: + +#### Naming Conventions + +- **snake_case** for variables, functions, methods, and module names: + ```python + def calculate_total_price(item_count, unit_price): + total_price = item_count * unit_price + return total_price + ``` + +- **PascalCase** for class names: + ```python + class UserRepository: + pass + + class EventRegistration: + pass + ``` + +- **UPPER_SNAKE_CASE** for constants: + ```python + MAX_RETRY_ATTEMPTS = 3 + DEFAULT_TIMEOUT_SECONDS = 30 + API_BASE_URL = "https://api.example.com" + ``` + +#### Tuple Deconstruction on Returns + +When returning multiple values, use tuple deconstruction for clarity: + +**✅ Good:** +```python +def get_user_info(user_id: str) -> tuple[str, str, int]: + """ + Retrieves user information. + + :param user_id: The unique identifier for the user + :type user_id: str + + :return: A tuple containing (username, email, age) + :rtype: tuple[str, str, int] + """ + # ... implementation + return username, email, age + +# Usage with tuple deconstruction +username, email, age = get_user_info("123") +``` + +**❌ Bad:** +```python +def get_user_info(user_id: str) -> dict: + # ... implementation + return {"username": username, "email": email, "age": age} + +# Less clear usage +user_info = get_user_info("123") +username = user_info["username"] +``` + +For complex returns with many values, prefer using dataclasses or Pydantic models: + +```python +from dataclasses import dataclass + +@dataclass +class UserInfo: + username: str + email: str + age: int + created_at: datetime + is_active: bool + +def get_user_info(user_id: str) -> UserInfo: + """ + Retrieves detailed user information. + + :param user_id: The unique identifier for the user + :type user_id: str + + :return: User information object + :rtype: UserInfo + """ + # ... implementation + return UserInfo( + username=username, + email=email, + age=age, + created_at=created_at, + is_active=is_active + ) +``` + +#### Docstrings + +Use reStructuredText (reST) format as specified in PEP 287: + +```python +def process_payment(amount: float, currency: str, payment_method: str) -> tuple[bool, str]: + """ + Processes a payment transaction. + + :param amount: The payment amount + :type amount: float + + :param currency: The currency code (e.g., 'USD', 'EUR') + :type currency: str + + :param payment_method: The payment method identifier + :type payment_method: str + + :return: A tuple containing (success_status, transaction_id) + :rtype: tuple[bool, str] + + :raises ValueError: If amount is negative or currency is invalid + :raises PaymentProcessingError: If payment processing fails + """ + # ... implementation + return success, transaction_id +``` + +#### Type Hints + +Always use type hints for function parameters and return values: + +```python +from typing import Optional, List, Dict + +def find_events( + category: str, + start_date: Optional[datetime] = None, + limit: int = 10 +) -> List[Dict[str, any]]: + """Find events matching the criteria.""" + # ... implementation + return events +``` + +#### Clean Architecture Principles + +Follow the dependency rule: **dependencies point inward** + +```python +# ❌ Bad: Domain layer depending on infrastructure +# model/event.py +from repository.event_repository import EventRepository # Wrong! + +class Event: + def save(self): + repo = EventRepository() + repo.save(self) + +# ✅ Good: Infrastructure depends on domain +# repository/event_repository.py +from model.event import Event # Correct! + +class EventRepository: + def save(self, event: Event) -> bool: + # ... implementation + return success +``` + +Layer structure: +1. **Domain (`model/`)** - No dependencies on other layers +2. **Application (`usecase/`)** - Depends on domain only +3. **Infrastructure (`repository/`, `aws/`)** - Depends on domain and application +4. **Presentation (`controller/`)** - Depends on application and domain +5. **External (`external_gateway/`, `functions/`)** - Outermost layer + +## Commit Guidelines + +We follow [Conventional Commits](https://www.conventionalcommits.org/) specification. + +### Commit Message Format + +``` +(): + + + +