diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..343fb06 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,43 @@ +{ + "name": "Event Management System", + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "8.0" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + }, + "ghcr.io/devcontainers/features/azure-cli:1": {} + }, + "postCreateCommand": "npm install -g azure-functions-core-tools@4 --unsafe-perm true && cd frontend/events-app && npm install && cd ../../backend/EventsApi && dotnet restore", + "forwardPorts": [5173, 7071], + "portsAttributes": { + "5173": { + "label": "Frontend", + "protocol": "https" + }, + "7071": { + "label": "Backend API", + "protocol": "https" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.vscode-typescript-next", + "ms-dotnettools.csharp", + "ms-azuretools.vscode-azurefunctions", + "Vue.volar", + "esbenp.prettier-vscode", + "ms-vscode.vscode-eslint" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + } + } + }, + "remoteEnv": { + "VITE_API_BASE_URL": "https://${CODESPACE_NAME}-7071.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}/api" + } +} \ No newline at end of file diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..a13865b --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,90 @@ +# Security Policy + +## Supported Versions + +We are committed to maintaining the security of our event management system. Security updates will be provided for the following versions: + +| Version | Supported | +| ------- | ------------------ | +| Latest | :white_check_mark: | +| < Latest| :x: | + +## Reporting a Vulnerability + +We take security seriously. If you discover a security vulnerability, please report it responsibly. + +### How to Report + +1. **Do not** open a public GitHub issue for security vulnerabilities +2. Email security concerns to: [security@example.com] (replace with actual email) +3. Include the following information: + - Description of the vulnerability + - Steps to reproduce the issue + - Potential impact + - Any suggested fixes + +### What to Expect + +- **Response Time**: We will acknowledge receipt within 48 hours +- **Investigation**: We will investigate and assess the severity within 5 business days +- **Updates**: We will provide updates on the progress every 5 business days +- **Resolution**: Critical vulnerabilities will be patched within 7 days, others within 30 days + +### Security Best Practices + +#### For Backend (C# Azure Functions) +- All user inputs are validated and sanitized +- CORS is properly configured +- Authentication/authorization should be implemented for production +- Secrets should be stored in Azure Key Vault or environment variables +- Use HTTPS in production environments +- Implement rate limiting to prevent abuse +- Regular dependency updates via Dependabot + +#### For Frontend (VueJS) +- Input validation on all forms +- XSS protection through proper data binding +- Content Security Policy (CSP) headers +- Secure cookie settings +- Regular dependency updates via Dependabot +- Environment variables for configuration +- Build-time security scanning + +#### Infrastructure Security +- Container images are scanned for vulnerabilities +- Runtime security monitoring +- Network security groups for access control +- Regular security audits +- Backup and disaster recovery procedures + +### Security Features + +#### Currently Implemented +- CORS configuration in Azure Functions +- Input validation on API endpoints +- Error handling without information disclosure +- Basic form validation in frontend +- Security headers in nginx configuration +- Automated dependency updates + +#### Planned Security Enhancements +- Authentication and authorization +- API rate limiting +- Audit logging +- Security scanning in CI/CD pipeline +- Penetration testing +- Security training for developers + +### Responsible Disclosure + +We are committed to working with security researchers to verify, reproduce, and respond to legitimate reported vulnerabilities. We will publicly acknowledge your responsible disclosure if you wish. + +### Contact + +For security-related questions or concerns: +- Email: [security@example.com] +- GPG Key: [Link to public key if applicable] + +--- + +This policy is effective as of [DATE] and will be reviewed quarterly. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..12d8925 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,50 @@ +version: 2 +updates: + # Backend (.NET) dependencies + - package-ecosystem: "nuget" + directory: "/backend/EventsApi" + schedule: + interval: "daily" + time: "04:00" + open-pull-requests-limit: 10 + reviewers: + - "kvaes" + labels: + - "dependencies" + - "backend" + commit-message: + prefix: "chore(backend)" + include: "scope" + + # Frontend (npm) dependencies + - package-ecosystem: "npm" + directory: "/frontend/events-app" + schedule: + interval: "daily" + time: "04:00" + open-pull-requests-limit: 10 + reviewers: + - "kvaes" + labels: + - "dependencies" + - "frontend" + commit-message: + prefix: "chore(frontend)" + include: "scope" + + # GitHub Actions dependencies + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "04:00" + open-pull-requests-limit: 5 + reviewers: + - "kvaes" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "chore(ci)" + include: "scope" \ No newline at end of file diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml new file mode 100644 index 0000000..fc4481c --- /dev/null +++ b/.github/workflows/backend.yml @@ -0,0 +1,54 @@ +name: Backend CI/CD + +on: + push: + branches: [ main, develop ] + paths: + - 'backend/**' + - '.github/workflows/backend.yml' + pull_request: + branches: [ main ] + paths: + - 'backend/**' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore dependencies + run: dotnet restore backend/EventsApi/EventsApi.csproj + + - name: Build + run: dotnet build backend/EventsApi/EventsApi.csproj --no-restore + + - name: Test + run: dotnet test backend/EventsApi/EventsApi.csproj --no-build --verbosity normal + + build-container: + needs: test + runs-on: ubuntu-latest + if: github.event_name == 'push' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: ./backend/EventsApi + file: ./backend/EventsApi/Dockerfile + push: false + tags: events-api:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml new file mode 100644 index 0000000..826b293 --- /dev/null +++ b/.github/workflows/frontend.yml @@ -0,0 +1,67 @@ +name: Frontend CI/CD + +on: + push: + branches: [ main, develop ] + paths: + - 'frontend/**' + - '.github/workflows/frontend.yml' + pull_request: + branches: [ main ] + paths: + - 'frontend/**' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/events-app/package-lock.json + + - name: Install dependencies + run: | + cd frontend/events-app + npm ci + + - name: Type check + run: | + cd frontend/events-app + npm run type-check + + - name: Lint + run: | + cd frontend/events-app + npm run lint --if-present + + - name: Build + run: | + cd frontend/events-app + npm run build + + build-container: + needs: test + runs-on: ubuntu-latest + if: github.event_name == 'push' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: ./frontend/events-app + file: ./frontend/events-app/Dockerfile + push: false + tags: events-frontend:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6a6d77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +backend/EventsApi/bin/ +backend/EventsApi/obj/ +frontend/events-app/dist/ +frontend/events-app/node_modules/ + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE +.vscode/ +.vs/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +*.temp +/tmp/ + +# Logs +*.log + +# Coverage reports +coverage/ +*.lcov + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Azure Functions +local.settings.json + +# Package files +*.tgz +*.tar.gz \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4137d0c --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Technopolis GitHub Copilot Agent Mode Scratch + +A full-stack application for event management built with VueJS frontend and C# Azure Functions backend. + +## Repository Structure + +- `.github/workflows`: GitHub Actions workflows for CI/CD +- `backend/`: C# Azure Functions backend API +- `frontend/`: VueJS frontend application +- `datacontract/`: Data models and contracts shared between frontend and backend +- `docs/`: Documentation + +## Quick Start + +### Prerequisites + +- Node.js 18+ and npm +- .NET 8.0 SDK +- Azure Functions Core Tools +- Docker (for containerization) + +### Local Development + +See [docs/local-development.md](docs/local-development.md) for detailed setup instructions. + +### GitHub Codespaces + +See [docs/codespaces-setup.md](docs/codespaces-setup.md) for end-to-end development in Codespaces. + +## Features + +### Backend APIs +- Event CRUD operations (Create, Read, Update, Delete) +- Event filtering by date and location +- Built-in CORS handling +- Error handling and validation + +### Frontend +- Event listing with filtering capabilities +- Event details view +- Event registration functionality +- Responsive design + +## Data Model + +Events include: +- Name +- Location +- Date +- Start time + +Registration includes: +- Name +- Email +- Pronouns +- Communication opt-in + +## License + +MIT License - see [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/backend/EventsApi/Dockerfile b/backend/EventsApi/Dockerfile new file mode 100644 index 0000000..4bc77b6 --- /dev/null +++ b/backend/EventsApi/Dockerfile @@ -0,0 +1,31 @@ +# Use the official .NET runtime as base image +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +# Use the .NET SDK for building +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src +COPY ["EventsApi.csproj", "."] +RUN dotnet restore "EventsApi.csproj" +COPY . . +WORKDIR "/src" +RUN dotnet build "EventsApi.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "EventsApi.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +# Install Azure Functions Core Tools +RUN apt-get update && \ + apt-get install -y curl && \ + curl -sL https://deb.nodesource.com/setup_18.x | bash - && \ + apt-get install -y nodejs && \ + npm install -g azure-functions-core-tools@4 --unsafe-perm true && \ + apt-get clean + +ENTRYPOINT ["func", "start", "--port", "80"] \ No newline at end of file diff --git a/backend/EventsApi/EventsApi.csproj b/backend/EventsApi/EventsApi.csproj new file mode 100644 index 0000000..d59e8b3 --- /dev/null +++ b/backend/EventsApi/EventsApi.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + v4 + Exe + enable + enable + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + diff --git a/backend/EventsApi/Functions/EventFunctions.cs b/backend/EventsApi/Functions/EventFunctions.cs new file mode 100644 index 0000000..d4d45a0 --- /dev/null +++ b/backend/EventsApi/Functions/EventFunctions.cs @@ -0,0 +1,225 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; +using EventsApi.Models; +using EventsApi.Services; +using System.Net; +using System.Text.Json; +using System.ComponentModel.DataAnnotations; + +namespace EventsApi.Functions; + +public class EventFunctions +{ + private readonly ILogger _logger; + private readonly IEventService _eventService; + + public EventFunctions(ILoggerFactory loggerFactory, IEventService eventService) + { + _logger = loggerFactory.CreateLogger(); + _eventService = eventService; + } + + [Function("CreateEvent")] + public async Task CreateEvent([HttpTrigger(AuthorizationLevel.Anonymous, "post", "options", Route = "events")] HttpRequestData req) + { + // Handle CORS preflight + if (req.Method == "OPTIONS") + { + return CreateCorsResponse(req, HttpStatusCode.OK); + } + + try + { + var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + var createRequest = JsonSerializer.Deserialize(requestBody, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (createRequest == null) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.BadRequest, + new ApiResponse { Success = false, Error = "Invalid request body" }); + } + + // Validate the request + var validationResults = new List(); + var validationContext = new ValidationContext(createRequest); + if (!Validator.TryValidateObject(createRequest, validationContext, validationResults, true)) + { + var errors = string.Join(", ", validationResults.Select(v => v.ErrorMessage)); + return await CreateJsonResponseAsync(req, HttpStatusCode.BadRequest, + new ApiResponse { Success = false, Error = $"Validation failed: {errors}" }); + } + + var eventItem = await _eventService.CreateEventAsync(createRequest); + + return await CreateJsonResponseAsync(req, HttpStatusCode.Created, + new ApiResponse { Success = true, Data = eventItem, Message = "Event created successfully" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating event"); + return await CreateJsonResponseAsync(req, HttpStatusCode.InternalServerError, + new ApiResponse { Success = false, Error = "Internal server error" }); + } + } + + [Function("GetEvent")] + public async Task GetEvent([HttpTrigger(AuthorizationLevel.Anonymous, "get", "options", Route = "events/{id}")] HttpRequestData req, string id) + { + // Handle CORS preflight + if (req.Method == "OPTIONS") + { + return CreateCorsResponse(req, HttpStatusCode.OK); + } + + try + { + var eventItem = await _eventService.GetEventAsync(id); + + if (eventItem == null) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.NotFound, + new ApiResponse { Success = false, Error = "Event not found" }); + } + + return await CreateJsonResponseAsync(req, HttpStatusCode.OK, + new ApiResponse { Success = true, Data = eventItem }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting event {EventId}", id); + return await CreateJsonResponseAsync(req, HttpStatusCode.InternalServerError, + new ApiResponse { Success = false, Error = "Internal server error" }); + } + } + + [Function("GetEvents")] + public async Task GetEvents([HttpTrigger(AuthorizationLevel.Anonymous, "get", "options", Route = "events")] HttpRequestData req) + { + // Handle CORS preflight + if (req.Method == "OPTIONS") + { + return CreateCorsResponse(req, HttpStatusCode.OK); + } + + try + { + var query = System.Web.HttpUtility.ParseQueryString(req.Url.Query); + var date = query["date"]; + var location = query["location"]; + var limit = int.TryParse(query["limit"], out var l) ? l : 10; + var offset = int.TryParse(query["offset"], out var o) ? o : 0; + + var events = await _eventService.GetEventsAsync(date, location, limit, offset); + + return await CreateJsonResponseAsync(req, HttpStatusCode.OK, + new ApiResponse { Success = true, Data = events }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting events"); + return await CreateJsonResponseAsync(req, HttpStatusCode.InternalServerError, + new ApiResponse { Success = false, Error = "Internal server error" }); + } + } + + [Function("UpdateEvent")] + public async Task UpdateEvent([HttpTrigger(AuthorizationLevel.Anonymous, "put", "options", Route = "events/{id}")] HttpRequestData req, string id) + { + // Handle CORS preflight + if (req.Method == "OPTIONS") + { + return CreateCorsResponse(req, HttpStatusCode.OK); + } + + try + { + var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + var updateRequest = JsonSerializer.Deserialize(requestBody, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (updateRequest == null) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.BadRequest, + new ApiResponse { Success = false, Error = "Invalid request body" }); + } + + var eventItem = await _eventService.UpdateEventAsync(id, updateRequest); + + if (eventItem == null) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.NotFound, + new ApiResponse { Success = false, Error = "Event not found" }); + } + + return await CreateJsonResponseAsync(req, HttpStatusCode.OK, + new ApiResponse { Success = true, Data = eventItem, Message = "Event updated successfully" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating event {EventId}", id); + return await CreateJsonResponseAsync(req, HttpStatusCode.InternalServerError, + new ApiResponse { Success = false, Error = "Internal server error" }); + } + } + + [Function("DeleteEvent")] + public async Task DeleteEvent([HttpTrigger(AuthorizationLevel.Anonymous, "delete", "options", Route = "events/{id}")] HttpRequestData req, string id) + { + // Handle CORS preflight + if (req.Method == "OPTIONS") + { + return CreateCorsResponse(req, HttpStatusCode.OK); + } + + try + { + var deleted = await _eventService.DeleteEventAsync(id); + + if (!deleted) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.NotFound, + new ApiResponse { Success = false, Error = "Event not found" }); + } + + return await CreateJsonResponseAsync(req, HttpStatusCode.OK, + new ApiResponse { Success = true, Message = "Event deleted successfully" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting event {EventId}", id); + return await CreateJsonResponseAsync(req, HttpStatusCode.InternalServerError, + new ApiResponse { Success = false, Error = "Internal server error" }); + } + } + + private HttpResponseData CreateCorsResponse(HttpRequestData req, HttpStatusCode statusCode) + { + var response = req.CreateResponse(statusCode); + response.Headers.Add("Access-Control-Allow-Origin", "*"); + response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization"); + return response; + } + + private async Task CreateJsonResponseAsync(HttpRequestData req, HttpStatusCode statusCode, T data) + { + var response = req.CreateResponse(statusCode); + response.Headers.Add("Content-Type", "application/json"); + response.Headers.Add("Access-Control-Allow-Origin", "*"); + response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization"); + + await response.WriteStringAsync(JsonSerializer.Serialize(data, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + + return response; + } +} \ No newline at end of file diff --git a/backend/EventsApi/Functions/RegistrationFunctions.cs b/backend/EventsApi/Functions/RegistrationFunctions.cs new file mode 100644 index 0000000..d41e92c --- /dev/null +++ b/backend/EventsApi/Functions/RegistrationFunctions.cs @@ -0,0 +1,135 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; +using EventsApi.Models; +using EventsApi.Services; +using System.Net; +using System.Text.Json; +using System.ComponentModel.DataAnnotations; + +namespace EventsApi.Functions; + +public class RegistrationFunctions +{ + private readonly ILogger _logger; + private readonly IRegistrationService _registrationService; + private readonly IEventService _eventService; + + public RegistrationFunctions(ILoggerFactory loggerFactory, IRegistrationService registrationService, IEventService eventService) + { + _logger = loggerFactory.CreateLogger(); + _registrationService = registrationService; + _eventService = eventService; + } + + [Function("CreateRegistration")] + public async Task CreateRegistration([HttpTrigger(AuthorizationLevel.Anonymous, "post", "options", Route = "registrations")] HttpRequestData req) + { + // Handle CORS preflight + if (req.Method == "OPTIONS") + { + return CreateCorsResponse(req, HttpStatusCode.OK); + } + + try + { + var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + var createRequest = JsonSerializer.Deserialize(requestBody, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (createRequest == null) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.BadRequest, + new ApiResponse { Success = false, Error = "Invalid request body" }); + } + + // Validate the request + var validationResults = new List(); + var validationContext = new ValidationContext(createRequest); + if (!Validator.TryValidateObject(createRequest, validationContext, validationResults, true)) + { + var errors = string.Join(", ", validationResults.Select(v => v.ErrorMessage)); + return await CreateJsonResponseAsync(req, HttpStatusCode.BadRequest, + new ApiResponse { Success = false, Error = $"Validation failed: {errors}" }); + } + + // Check if event exists + var eventItem = await _eventService.GetEventAsync(createRequest.EventId); + if (eventItem == null) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.BadRequest, + new ApiResponse { Success = false, Error = "Event not found" }); + } + + var registration = await _registrationService.CreateRegistrationAsync(createRequest); + + return await CreateJsonResponseAsync(req, HttpStatusCode.Created, + new ApiResponse { Success = true, Data = registration, Message = "Registration created successfully" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating registration"); + return await CreateJsonResponseAsync(req, HttpStatusCode.InternalServerError, + new ApiResponse { Success = false, Error = "Internal server error" }); + } + } + + [Function("GetRegistrationsByEvent")] + public async Task GetRegistrationsByEvent([HttpTrigger(AuthorizationLevel.Anonymous, "get", "options", Route = "events/{eventId}/registrations")] HttpRequestData req, string eventId) + { + // Handle CORS preflight + if (req.Method == "OPTIONS") + { + return CreateCorsResponse(req, HttpStatusCode.OK); + } + + try + { + // Check if event exists + var eventItem = await _eventService.GetEventAsync(eventId); + if (eventItem == null) + { + return await CreateJsonResponseAsync(req, HttpStatusCode.NotFound, + new ApiResponse { Success = false, Error = "Event not found" }); + } + + var registrations = await _registrationService.GetRegistrationsByEventAsync(eventId); + + return await CreateJsonResponseAsync(req, HttpStatusCode.OK, + new ApiResponse> { Success = true, Data = registrations }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting registrations for event {EventId}", eventId); + return await CreateJsonResponseAsync(req, HttpStatusCode.InternalServerError, + new ApiResponse { Success = false, Error = "Internal server error" }); + } + } + + private HttpResponseData CreateCorsResponse(HttpRequestData req, HttpStatusCode statusCode) + { + var response = req.CreateResponse(statusCode); + response.Headers.Add("Access-Control-Allow-Origin", "*"); + response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization"); + return response; + } + + private async Task CreateJsonResponseAsync(HttpRequestData req, HttpStatusCode statusCode, T data) + { + var response = req.CreateResponse(statusCode); + response.Headers.Add("Content-Type", "application/json"); + response.Headers.Add("Access-Control-Allow-Origin", "*"); + response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization"); + + await response.WriteStringAsync(JsonSerializer.Serialize(data, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + })); + + return response; + } +} \ No newline at end of file diff --git a/backend/EventsApi/Models/DataModels.cs b/backend/EventsApi/Models/DataModels.cs new file mode 100644 index 0000000..fb398c8 --- /dev/null +++ b/backend/EventsApi/Models/DataModels.cs @@ -0,0 +1,101 @@ +using System.ComponentModel.DataAnnotations; + +namespace EventsApi.Models; + +public class Event +{ + public string Id { get; set; } = string.Empty; + + [Required] + public string Name { get; set; } = string.Empty; + + [Required] + public string Location { get; set; } = string.Empty; + + [Required] + public string Date { get; set; } = string.Empty; // ISO 8601 date string + + [Required] + public string StartTime { get; set; } = string.Empty; // HH:mm format + + public string CreatedAt { get; set; } = string.Empty; + + public string UpdatedAt { get; set; } = string.Empty; +} + +public class Registration +{ + public string Id { get; set; } = string.Empty; + + [Required] + public string EventId { get; set; } = string.Empty; + + [Required] + public string Name { get; set; } = string.Empty; + + [Required] + [EmailAddress] + public string Email { get; set; } = string.Empty; + + public string? Pronouns { get; set; } + + public bool OptInCommunication { get; set; } + + public string RegisteredAt { get; set; } = string.Empty; +} + +public class CreateEventRequest +{ + [Required] + public string Name { get; set; } = string.Empty; + + [Required] + public string Location { get; set; } = string.Empty; + + [Required] + public string Date { get; set; } = string.Empty; + + [Required] + public string StartTime { get; set; } = string.Empty; +} + +public class UpdateEventRequest +{ + public string? Name { get; set; } + public string? Location { get; set; } + public string? Date { get; set; } + public string? StartTime { get; set; } +} + +public class CreateRegistrationRequest +{ + [Required] + public string EventId { get; set; } = string.Empty; + + [Required] + public string Name { get; set; } = string.Empty; + + [Required] + [EmailAddress] + public string Email { get; set; } = string.Empty; + + public string? Pronouns { get; set; } + + public bool OptInCommunication { get; set; } +} + +public class ApiResponse +{ + public bool Success { get; set; } + public T? Data { get; set; } + public string? Error { get; set; } + public string? Message { get; set; } +} + +public class EventListResponse +{ + public List Events { get; set; } = new(); + public int Total { get; set; } + public int Offset { get; set; } + public int Limit { get; set; } +} \ No newline at end of file diff --git a/backend/EventsApi/Program.cs b/backend/EventsApi/Program.cs new file mode 100644 index 0000000..b42fbbc --- /dev/null +++ b/backend/EventsApi/Program.cs @@ -0,0 +1,19 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using EventsApi.Services; + +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureServices(services => + { + services.AddApplicationInsightsTelemetryWorkerService(); + services.ConfigureFunctionsApplicationInsights(); + + // Register services + services.AddSingleton(); + services.AddSingleton(); + }) + .Build(); + +host.Run(); \ No newline at end of file diff --git a/backend/EventsApi/Services/EventService.cs b/backend/EventsApi/Services/EventService.cs new file mode 100644 index 0000000..0f707f6 --- /dev/null +++ b/backend/EventsApi/Services/EventService.cs @@ -0,0 +1,139 @@ +using EventsApi.Models; +using System.Collections.Concurrent; + +namespace EventsApi.Services; + +public interface IEventService +{ + Task CreateEventAsync(CreateEventRequest request); + Task GetEventAsync(string id); + Task GetEventsAsync(string? date = null, string? location = null, int limit = 10, int offset = 0); + Task UpdateEventAsync(string id, UpdateEventRequest request); + Task DeleteEventAsync(string id); +} + +public interface IRegistrationService +{ + Task CreateRegistrationAsync(CreateRegistrationRequest request); + Task> GetRegistrationsByEventAsync(string eventId); +} + +public class InMemoryEventService : IEventService +{ + private readonly ConcurrentDictionary _events = new(); + + public Task CreateEventAsync(CreateEventRequest request) + { + var eventItem = new Event + { + Id = Guid.NewGuid().ToString(), + Name = request.Name, + Location = request.Location, + Date = request.Date, + StartTime = request.StartTime, + CreatedAt = DateTime.UtcNow.ToString("O"), + UpdatedAt = DateTime.UtcNow.ToString("O") + }; + + _events[eventItem.Id] = eventItem; + return Task.FromResult(eventItem); + } + + public Task GetEventAsync(string id) + { + _events.TryGetValue(id, out var eventItem); + return Task.FromResult(eventItem); + } + + public Task GetEventsAsync(string? date = null, string? location = null, int limit = 10, int offset = 0) + { + var events = _events.Values.AsEnumerable(); + + if (!string.IsNullOrEmpty(date)) + { + events = events.Where(e => e.Date == date); + } + + if (!string.IsNullOrEmpty(location)) + { + events = events.Where(e => e.Location.Contains(location, StringComparison.OrdinalIgnoreCase)); + } + + var total = events.Count(); + var filteredEvents = events + .OrderBy(e => e.Date) + .ThenBy(e => e.StartTime) + .Skip(offset) + .Take(limit) + .ToList(); + + return Task.FromResult(new EventListResponse + { + Events = filteredEvents, + Total = total, + Offset = offset, + Limit = limit + }); + } + + public Task UpdateEventAsync(string id, UpdateEventRequest request) + { + if (!_events.TryGetValue(id, out var eventItem)) + { + return Task.FromResult(null); + } + + if (!string.IsNullOrEmpty(request.Name)) + eventItem.Name = request.Name; + + if (!string.IsNullOrEmpty(request.Location)) + eventItem.Location = request.Location; + + if (!string.IsNullOrEmpty(request.Date)) + eventItem.Date = request.Date; + + if (!string.IsNullOrEmpty(request.StartTime)) + eventItem.StartTime = request.StartTime; + + eventItem.UpdatedAt = DateTime.UtcNow.ToString("O"); + + return Task.FromResult(eventItem); + } + + public Task DeleteEventAsync(string id) + { + return Task.FromResult(_events.TryRemove(id, out _)); + } +} + +public class InMemoryRegistrationService : IRegistrationService +{ + private readonly ConcurrentDictionary _registrations = new(); + + public Task CreateRegistrationAsync(CreateRegistrationRequest request) + { + var registration = new Registration + { + Id = Guid.NewGuid().ToString(), + EventId = request.EventId, + Name = request.Name, + Email = request.Email, + Pronouns = request.Pronouns, + OptInCommunication = request.OptInCommunication, + RegisteredAt = DateTime.UtcNow.ToString("O") + }; + + _registrations[registration.Id] = registration; + return Task.FromResult(registration); + } + + public Task> GetRegistrationsByEventAsync(string eventId) + { + var registrations = _registrations.Values + .Where(r => r.EventId == eventId) + .OrderBy(r => r.RegisteredAt) + .ToList(); + + return Task.FromResult(registrations); + } +} \ No newline at end of file diff --git a/backend/EventsApi/host.json b/backend/EventsApi/host.json new file mode 100644 index 0000000..78be514 --- /dev/null +++ b/backend/EventsApi/host.json @@ -0,0 +1,16 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + }, + "functionTimeout": "00:05:00" +} \ No newline at end of file diff --git a/backend/EventsApi/local.settings.json b/backend/EventsApi/local.settings.json new file mode 100644 index 0000000..4aadea0 --- /dev/null +++ b/backend/EventsApi/local.settings.json @@ -0,0 +1,10 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" + }, + "Host": { + "CORS": "*" + } +} \ No newline at end of file diff --git a/datacontract/README.md b/datacontract/README.md new file mode 100644 index 0000000..5fb2a39 --- /dev/null +++ b/datacontract/README.md @@ -0,0 +1,61 @@ +# Data Contract + +This directory contains the data models and contracts that ensure consistency between the frontend and backend applications. + +## Models + +### Event +Represents an event with the following properties: +- `id`: Unique identifier (string) +- `name`: Event name (string) +- `location`: Event location (string) +- `date`: Event date (ISO 8601 date string) +- `startTime`: Event start time (string in HH:mm format) +- `createdAt`: Creation timestamp (ISO 8601 datetime string) +- `updatedAt`: Last update timestamp (ISO 8601 datetime string) + +### Registration +Represents an event registration with the following properties: +- `id`: Unique identifier (string) +- `eventId`: Associated event ID (string) +- `name`: Registrant name (string) +- `email`: Registrant email (string) +- `pronouns`: Registrant pronouns (string, optional) +- `optInCommunication`: Whether user opted in for further communication (boolean) +- `registeredAt`: Registration timestamp (ISO 8601 datetime string) + +## Usage + +These models should be implemented consistently across: +- Backend C# classes/DTOs +- Frontend TypeScript interfaces +- API request/response schemas +- Database schemas + +## JSON Examples + +### Event +```json +{ + "id": "event-123", + "name": "Tech Conference 2024", + "location": "Brussels, Belgium", + "date": "2024-07-15", + "startTime": "09:00", + "createdAt": "2024-01-15T10:30:00Z", + "updatedAt": "2024-01-20T14:45:00Z" +} +``` + +### Registration +```json +{ + "id": "reg-456", + "eventId": "event-123", + "name": "John Doe", + "email": "john.doe@example.com", + "pronouns": "he/him", + "optInCommunication": true, + "registeredAt": "2024-02-01T09:15:00Z" +} +``` \ No newline at end of file diff --git a/datacontract/types.ts b/datacontract/types.ts new file mode 100644 index 0000000..731e96d --- /dev/null +++ b/datacontract/types.ts @@ -0,0 +1,64 @@ +// TypeScript interfaces for data contracts + +export interface Event { + id: string; + name: string; + location: string; + date: string; // ISO 8601 date string (YYYY-MM-DD) + startTime: string; // HH:mm format + createdAt: string; // ISO 8601 datetime string + updatedAt: string; // ISO 8601 datetime string +} + +export interface Registration { + id: string; + eventId: string; + name: string; + email: string; + pronouns?: string; // Optional + optInCommunication: boolean; + registeredAt: string; // ISO 8601 datetime string +} + +export interface CreateEventRequest { + name: string; + location: string; + date: string; // ISO 8601 date string + startTime: string; // HH:mm format +} + +export interface UpdateEventRequest { + name?: string; + location?: string; + date?: string; // ISO 8601 date string + startTime?: string; // HH:mm format +} + +export interface CreateRegistrationRequest { + eventId: string; + name: string; + email: string; + pronouns?: string; + optInCommunication: boolean; +} + +export interface EventFilter { + date?: string; // ISO 8601 date string + location?: string; + limit?: number; + offset?: number; +} + +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + message?: string; +} + +export interface EventListResponse { + events: Event[]; + total: number; + offset: number; + limit: number; +} \ No newline at end of file diff --git a/docs/codespaces-setup.md b/docs/codespaces-setup.md new file mode 100644 index 0000000..1e36073 --- /dev/null +++ b/docs/codespaces-setup.md @@ -0,0 +1,353 @@ +# GitHub Codespaces Setup + +This guide explains how to set up and use GitHub Codespaces for end-to-end development of the event management system. + +## What is GitHub Codespaces? + +GitHub Codespaces provides a complete, configurable dev environment in the cloud. It includes all the tools and dependencies needed to develop, test, and debug the application without local setup. + +## Quick Start + +1. **Open in Codespaces:** + - Go to the repository on GitHub + - Click the green "Code" button + - Select "Codespaces" tab + - Click "Create codespace on main" + +2. **Wait for setup:** + - Codespaces will automatically install all dependencies + - This may take 3-5 minutes on first launch + +3. **Start development:** + - Both backend and frontend will be pre-configured + - Use the integrated terminal to run commands + +## Codespaces Configuration + +The repository includes a `.devcontainer` configuration that automatically: + +- Installs .NET 8.0 SDK +- Installs Node.js 20 and npm +- Installs Azure Functions Core Tools +- Sets up VS Code extensions +- Configures port forwarding +- Sets up environment variables + +## Development Workflow in Codespaces + +### Running the Full Stack + +1. **Terminal 1 - Start Backend:** + ```bash + cd backend/EventsApi + func start --port 7071 + ``` + +2. **Terminal 2 - Start Frontend:** + ```bash + cd frontend/events-app + npm run dev -- --host 0.0.0.0 --port 5173 + ``` + +### Accessing the Applications + +Codespaces automatically forwards ports and provides URLs: + +- **Frontend**: Click on the "Ports" tab and open the port 5173 URL +- **Backend API**: Click on the "Ports" tab and open the port 7071 URL +- **Both URLs are publicly accessible** (with HTTPS) and will be shown in VS Code + +### Environment Variables in Codespaces + +The frontend will automatically use the correct backend URL thanks to Codespaces port forwarding: + +```bash +# Frontend .env will be automatically configured +VITE_API_BASE_URL=https://[codespace-name]-7071.app.github.dev/api +``` + +## End-to-End Testing in Codespaces + +### 1. Full Application Testing + +1. **Start both services** (backend and frontend) +2. **Open the frontend URL** from the Ports tab +3. **Test the complete workflow:** + - Browse events on the home page + - Create a new event + - View event details + - Register for an event + - Filter events by date/location + +### 2. API Testing + +1. **Backend testing with curl:** + ```bash + # Get the backend URL from Ports tab + BACKEND_URL="https://[codespace-name]-7071.app.github.dev" + + # Test API endpoints + curl "$BACKEND_URL/api/events" + + # Create an event + curl -X POST "$BACKEND_URL/api/events" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Test Event", + "location": "Virtual", + "date": "2024-12-25", + "startTime": "14:00" + }' + ``` + +2. **Frontend API integration:** + - The frontend automatically connects to the backend + - All CORS is pre-configured + - No additional setup needed + +### 3. Database Simulation + +The current setup uses in-memory storage, so: +- Data persists while the backend is running +- Data is reset when the backend restarts +- Perfect for development and testing + +## Advanced Codespaces Features + +### Using Multiple Terminals + +VS Code in Codespaces supports multiple terminals: + +1. **Terminal 1**: Backend development + ```bash + cd backend/EventsApi + func start + ``` + +2. **Terminal 2**: Frontend development + ```bash + cd frontend/events-app + npm run dev -- --host 0.0.0.0 + ``` + +3. **Terminal 3**: General commands + ```bash + # Build, test, or run other commands + ``` + +### Port Forwarding + +Codespaces automatically forwards these ports: +- `5173`: Frontend development server +- `7071`: Azure Functions backend +- `3000`: Alternative frontend port + +You can add more ports through: +- VS Code Ports panel +- `.devcontainer/devcontainer.json` configuration + +### Pre-configured VS Code Extensions + +The following extensions are automatically installed: +- Azure Functions +- C# for Visual Studio Code +- Vetur (Vue.js support) +- TypeScript and JavaScript Language Features +- ESLint +- Prettier + +### Environment Persistence + +- **Code changes**: Automatically synced to GitHub +- **Dependencies**: Cached between sessions +- **Settings**: Preserved across Codespace rebuilds +- **Data**: Reset on backend restart (in-memory storage) + +## Codespaces Devcontainer Configuration + +Create `.devcontainer/devcontainer.json`: + +```json +{ + "name": "Event Management System", + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "8.0" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + }, + "ghcr.io/devcontainers/features/azure-cli:1": {} + }, + "postCreateCommand": "npm install -g azure-functions-core-tools@4 --unsafe-perm true && cd frontend/events-app && npm install && cd ../../backend/EventsApi && dotnet restore", + "forwardPorts": [5173, 7071], + "portsAttributes": { + "5173": { + "label": "Frontend", + "protocol": "https" + }, + "7071": { + "label": "Backend API", + "protocol": "https" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.vscode-typescript-next", + "ms-dotnettools.csharp", + "ms-azuretools.vscode-azurefunctions", + "vue.volar", + "esbenp.prettier-vscode", + "ms-vscode.vscode-eslint" + ] + } + } +} +``` + +## Collaborative Development + +### Multiple Developers + +1. **Each developer gets their own Codespace** +2. **Shared codebase** through Git +3. **Individual development environments** +4. **Easy code sharing** through pull requests + +### Sharing Your Environment + +1. **Make your Codespace public** (in Codespace settings) +2. **Share the URLs** for frontend and backend +3. **Others can test your changes** without setup + +## Testing Workflow + +### Complete End-to-End Test + +1. **Start the stack:** + ```bash + # Terminal 1 + cd backend/EventsApi && func start + + # Terminal 2 + cd frontend/events-app && npm run dev -- --host 0.0.0.0 + ``` + +2. **Access frontend** through Ports panel + +3. **Test user journey:** + - Create account → Create event → Register → View events + - Test filters and search functionality + - Verify API responses + +4. **Check backend logs** in Terminal 1 + +5. **Verify data persistence** during session + +### API Testing with Codespaces + +```bash +# Use the auto-generated backend URL +curl "https://[your-codespace]-7071.app.github.dev/api/events" | jq + +# Test CORS +curl -H "Origin: https://[your-codespace]-5173.app.github.dev" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: Content-Type" \ + -X OPTIONS \ + "https://[your-codespace]-7071.app.github.dev/api/events" +``` + +## Performance and Limitations + +### Codespaces Specs +- **2-core machines**: Good for development +- **4-core machines**: Better for intensive builds +- **8-core machines**: Optimal for large projects + +### Storage +- **Up to 32GB** persistent storage +- **Automatic cleanup** after inactivity +- **Regular backups** to GitHub + +### Network +- **Fast internet connectivity** +- **Global edge locations** +- **HTTPS everywhere** + +## Troubleshooting in Codespaces + +### Common Issues + +1. **Port not accessible:** + - Check Ports panel + - Ensure service is running + - Verify port forwarding settings + +2. **Backend not starting:** + ```bash + # Check .NET installation + dotnet --version + + # Reinstall Functions tools + npm install -g azure-functions-core-tools@4 --unsafe-perm true + ``` + +3. **Frontend build errors:** + ```bash + # Clear and reinstall dependencies + cd frontend/events-app + rm -rf node_modules package-lock.json + npm install + ``` + +4. **CORS issues:** + - Verify backend CORS configuration + - Check frontend API base URL + - Ensure both services are running + +### Debugging + +1. **Backend debugging:** + - VS Code debugger with Azure Functions + - Console logs in terminal + - API testing with curl + +2. **Frontend debugging:** + - Browser developer tools + - Vue DevTools extension + - Console logs and network tab + +## Best Practices + +### Development +1. **Use separate terminals** for backend and frontend +2. **Keep Codespace running** during active development +3. **Commit changes frequently** to avoid data loss +4. **Use port forwarding** for testing external integrations + +### Collaboration +1. **Create feature branches** for new development +2. **Share Codespace URLs** for testing +3. **Document changes** in pull requests +4. **Use consistent environments** through devcontainer + +### Resource Management +1. **Stop Codespace** when not in use +2. **Use appropriate machine size** for your work +3. **Clean up unused files** to save storage +4. **Monitor usage** through GitHub settings + +## Next Steps + +After setting up Codespaces: + +1. **Test the complete workflow** end-to-end +2. **Explore the codebase** with full IDE support +3. **Make your first changes** and test them +4. **Share your environment** with team members +5. **Set up automated testing** in the cloud + +This setup provides a complete, production-like environment for developing and testing the event management system without any local dependencies. \ No newline at end of file diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 0000000..5fdb6e6 --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,264 @@ +# Local Development Setup + +This guide will help you set up the event management system for local development. + +## Prerequisites + +Before you begin, ensure you have the following installed: + +### Required Software +- **Node.js 18+** and **npm** - for the frontend +- **.NET 8.0 SDK** - for the backend +- **Azure Functions Core Tools v4** - for running Azure Functions locally +- **Git** - for version control +- **Docker** (optional) - for containerized development + +### Installation Commands + +#### Windows (using Chocolatey) +```powershell +choco install nodejs dotnet-8.0-sdk azure-functions-core-tools git docker-desktop +``` + +#### macOS (using Homebrew) +```bash +brew install node dotnet azure-functions-core-tools git docker +``` + +#### Ubuntu/Debian +```bash +# Node.js +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt-get install -y nodejs + +# .NET 8.0 +wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb +sudo dpkg -i packages-microsoft-prod.deb +sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0 + +# Azure Functions Core Tools +npm install -g azure-functions-core-tools@4 --unsafe-perm true + +# Git and Docker +sudo apt-get install -y git docker.io +``` + +## Project Structure + +``` +├── backend/ +│ └── EventsApi/ # C# Azure Functions API +├── frontend/ +│ └── events-app/ # VueJS frontend application +├── datacontract/ # Shared data models +├── docs/ # Documentation +└── .github/ # GitHub workflows and configurations +``` + +## Backend Setup (C# Azure Functions) + +1. **Navigate to the backend directory:** + ```bash + cd backend/EventsApi + ``` + +2. **Restore dependencies:** + ```bash + dotnet restore + ``` + +3. **Build the project:** + ```bash + dotnet build + ``` + +4. **Configure local settings:** + + The `local.settings.json` file should already be configured for local development: + ```json + { + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" + }, + "Host": { + "CORS": "*" + } + } + ``` + +5. **Start the Azure Functions locally:** + ```bash + func start + ``` + + The API will be available at `http://localhost:7071` + +### Backend API Endpoints + +Once running, the following endpoints will be available: + +- `GET /api/events` - Get all events (with optional filtering) +- `GET /api/events/{id}` - Get a specific event +- `POST /api/events` - Create a new event +- `PUT /api/events/{id}` - Update an event +- `DELETE /api/events/{id}` - Delete an event +- `POST /api/registrations` - Register for an event +- `GET /api/events/{eventId}/registrations` - Get registrations for an event + +## Frontend Setup (VueJS) + +1. **Navigate to the frontend directory:** + ```bash + cd frontend/events-app + ``` + +2. **Install dependencies:** + ```bash + npm install + ``` + +3. **Configure environment variables:** + + Create or verify the `.env` file: + ```bash + VITE_API_BASE_URL=http://localhost:7071/api + ``` + +4. **Start the development server:** + ```bash + npm run dev + ``` + + The frontend will be available at `http://localhost:5173` + +### Frontend Development Commands + +- `npm run dev` - Start development server +- `npm run build` - Build for production +- `npm run preview` - Preview production build +- `npm run type-check` - Type checking +- `npm run lint` - Lint code (if configured) + +## Running Both Applications + +To run the full stack locally: + +1. **Terminal 1 - Backend:** + ```bash + cd backend/EventsApi + func start + ``` + +2. **Terminal 2 - Frontend:** + ```bash + cd frontend/events-app + npm run dev + ``` + +3. **Access the application:** + - Frontend: http://localhost:5173 + - Backend API: http://localhost:7071 + - API Documentation: http://localhost:7071/api (functions list) + +## Development Workflow + +### Making Changes + +1. **Backend Changes:** + - Edit C# files in `backend/EventsApi/` + - The Functions runtime will automatically reload on changes + - Check terminal for any compilation errors + +2. **Frontend Changes:** + - Edit Vue files in `frontend/events-app/src/` + - Hot Module Replacement (HMR) will update the browser automatically + - TypeScript errors will show in the terminal and browser + +### Testing Changes + +1. **Backend Testing:** + ```bash + cd backend/EventsApi + dotnet test # Run unit tests (when available) + ``` + +2. **Frontend Testing:** + ```bash + cd frontend/events-app + npm run type-check # TypeScript checking + npm test # Run unit tests (when available) + ``` + +### Building for Production + +1. **Backend:** + ```bash + cd backend/EventsApi + dotnet publish -c Release + ``` + +2. **Frontend:** + ```bash + cd frontend/events-app + npm run build + ``` + +## Troubleshooting + +### Common Issues + +1. **Azure Functions not starting:** + - Ensure Azure Functions Core Tools v4 is installed + - Check that port 7071 is not in use + - Verify .NET 8.0 SDK is installed + +2. **Frontend not loading:** + - Ensure Node.js 18+ is installed + - Check that port 5173 is not in use + - Verify npm dependencies are installed + +3. **CORS errors:** + - Ensure backend is running on http://localhost:7071 + - Check the CORS configuration in `local.settings.json` + - Verify the frontend's API base URL is correct + +4. **TypeScript errors:** + - Run `npm run type-check` to see detailed errors + - Ensure all dependencies are installed + - Check that data contract types are consistent + +### Ports + +| Service | Default Port | URL | +|---------|-------------|------| +| Backend API | 7071 | http://localhost:7071 | +| Frontend | 5173 | http://localhost:5173 | + +### Environment Variables + +#### Backend +- `AzureWebJobsStorage`: Storage connection (uses development storage locally) +- `FUNCTIONS_WORKER_RUNTIME`: Set to "dotnet-isolated" + +#### Frontend +- `VITE_API_BASE_URL`: Backend API URL (http://localhost:7071/api) + +## Next Steps + +Once you have the local environment running: + +1. Create some test events through the frontend +2. Test the registration functionality +3. Explore the API endpoints with a tool like Postman +4. Review the code structure and make your first changes +5. Set up GitHub Codespaces for cloud development (see [codespaces-setup.md](codespaces-setup.md)) + +## Additional Resources + +- [VueJS Documentation](https://vuejs.org/) +- [Azure Functions Documentation](https://docs.microsoft.com/en-us/azure/azure-functions/) +- [.NET 8.0 Documentation](https://docs.microsoft.com/en-us/dotnet/) +- [Pinia State Management](https://pinia.vuejs.org/) +- [TypeScript Handbook](https://www.typescriptlang.org/docs/) \ No newline at end of file diff --git a/docs/project-overview.md b/docs/project-overview.md new file mode 100644 index 0000000..f6b66db --- /dev/null +++ b/docs/project-overview.md @@ -0,0 +1,382 @@ +# Project Documentation + +This documentation provides an overview of the Event Management System architecture, components, and development guidelines. + +## Architecture Overview + +The Event Management System is a full-stack application built with modern web technologies: + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Frontend │ │ Backend │ │ Data Layer │ +│ (VueJS) │────│ (Azure │────│ (In-Memory) │ +│ │ │ Functions) │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### Technology Stack + +#### Frontend +- **Framework**: Vue.js 3 with Composition API +- **Language**: TypeScript +- **State Management**: Pinia +- **Routing**: Vue Router +- **Build Tool**: Vite +- **Styling**: CSS3 with CSS Variables + +#### Backend +- **Platform**: Azure Functions (.NET 8.0) +- **Language**: C# +- **Architecture**: Serverless Functions +- **Runtime**: .NET Isolated Worker +- **Storage**: In-Memory (for development) + +#### DevOps +- **CI/CD**: GitHub Actions +- **Containerization**: Docker +- **Dependency Management**: Dependabot +- **Development Environment**: GitHub Codespaces + +## Project Structure + +``` +/ +├── backend/ +│ └── EventsApi/ +│ ├── Functions/ # Azure Function endpoints +│ ├── Models/ # Data models and DTOs +│ ├── Services/ # Business logic services +│ ├── Program.cs # Application entry point +│ ├── host.json # Functions host configuration +│ └── local.settings.json # Local development settings +├── frontend/ +│ └── events-app/ +│ ├── src/ +│ │ ├── components/ # Reusable Vue components +│ │ ├── views/ # Page components +│ │ ├── stores/ # Pinia stores +│ │ ├── services/ # API service layer +│ │ ├── types/ # TypeScript interfaces +│ │ ├── router/ # Vue Router configuration +│ │ └── main.ts # Application entry point +│ ├── public/ # Static assets +│ └── package.json # Dependencies and scripts +├── datacontract/ # Shared data models +├── docs/ # Documentation +├── .github/ +│ ├── workflows/ # CI/CD workflows +│ ├── dependabot.yml # Dependency updates +│ └── SECURITY.md # Security policy +└── .devcontainer/ # Codespaces configuration +``` + +## Data Models + +### Event +The core entity representing an event: + +```typescript +interface Event { + id: string; + name: string; + location: string; + date: string; // ISO 8601 date + startTime: string; // HH:mm format + createdAt: string; + updatedAt: string; +} +``` + +### Registration +Represents a user registration for an event: + +```typescript +interface Registration { + id: string; + eventId: string; + name: string; + email: string; + pronouns?: string; + optInCommunication: boolean; + registeredAt: string; +} +``` + +## API Design + +### RESTful Endpoints + +#### Events +- `GET /api/events` - List events with optional filtering +- `GET /api/events/{id}` - Get specific event +- `POST /api/events` - Create new event +- `PUT /api/events/{id}` - Update event +- `DELETE /api/events/{id}` - Delete event + +#### Registrations +- `POST /api/registrations` - Register for event +- `GET /api/events/{eventId}/registrations` - Get event registrations + +### Request/Response Format + +All API responses follow this structure: + +```json +{ + "success": true, + "data": { /* response data */ }, + "message": "Optional success message", + "error": null +} +``` + +Error responses: + +```json +{ + "success": false, + "data": null, + "message": null, + "error": "Error description" +} +``` + +## Frontend Architecture + +### Component Structure + +``` +src/ +├── App.vue # Root component +├── main.ts # Application bootstrap +├── components/ # Reusable components +├── views/ # Page-level components +│ ├── HomeView.vue # Landing page +│ ├── EventsView.vue # Events listing +│ ├── EventDetailView.vue # Event details & registration +│ └── CreateEventView.vue # Event creation form +├── stores/ +│ └── events.ts # Event state management +├── services/ +│ └── api.ts # API communication layer +└── types/ + └── index.ts # TypeScript interfaces +``` + +### State Management + +Uses Pinia for centralized state management: + +- **Events Store**: Manages event data, loading states, and API calls +- **Reactive State**: Automatic UI updates when data changes +- **Error Handling**: Centralized error state management + +### Routing + +Vue Router handles navigation: + +- `/` - Home page +- `/events` - Events listing +- `/events/create` - Create event form +- `/events/:id` - Event details and registration + +## Backend Architecture + +### Azure Functions Structure + +``` +EventsApi/ +├── Functions/ +│ ├── EventFunctions.cs # Event CRUD operations +│ └── RegistrationFunctions.cs # Registration operations +├── Services/ +│ ├── IEventService.cs # Event service interface +│ ├── IRegistrationService.cs # Registration service interface +│ └── EventService.cs # In-memory implementations +└── Models/ + └── DataModels.cs # C# data models +``` + +### Dependency Injection + +Services are registered in `Program.cs`: + +```csharp +services.AddSingleton(); +services.AddSingleton(); +``` + +### CORS Configuration + +CORS is handled directly in the Functions code: + +```csharp +response.Headers.Add("Access-Control-Allow-Origin", "*"); +response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); +response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization"); +``` + +## Security Considerations + +### Current Implementation +- Input validation on all endpoints +- CORS configuration for cross-origin requests +- Error handling without information disclosure +- Basic form validation in frontend + +### Production Recommendations +- Implement authentication and authorization +- Add rate limiting to prevent abuse +- Use HTTPS everywhere +- Store secrets in Azure Key Vault +- Implement audit logging +- Add input sanitization +- Set up monitoring and alerting + +## Development Workflow + +### Local Development +1. Start backend: `cd backend/EventsApi && func start` +2. Start frontend: `cd frontend/events-app && npm run dev` +3. Access at http://localhost:5173 + +### GitHub Codespaces +1. Open repository in Codespaces +2. Both services start automatically +3. Access through forwarded ports + +### Continuous Integration +- **Backend**: Build, test, and containerize on changes +- **Frontend**: Type check, build, and containerize on changes +- **Dependencies**: Automated updates via Dependabot + +## Testing Strategy + +### Current State +- Build verification for both projects +- Type checking for TypeScript +- Basic validation testing + +### Recommended Additions +- Unit tests for business logic +- Integration tests for API endpoints +- End-to-end tests for user workflows +- Performance testing +- Security testing + +## Deployment + +### Containerization +Both projects include Dockerfiles for containerized deployment: + +- **Backend**: Multi-stage build with Azure Functions runtime +- **Frontend**: Nginx-based serving with optimized build + +### Cloud Deployment Options +- **Azure**: Functions + Static Web Apps +- **AWS**: Lambda + S3/CloudFront +- **Google Cloud**: Cloud Functions + Cloud Storage +- **Kubernetes**: Container orchestration + +## Monitoring and Logging + +### Development +- Console logging in both frontend and backend +- Browser developer tools for frontend debugging +- Azure Functions runtime logs for backend + +### Production Recommendations +- Application Insights for Azure deployments +- Structured logging with correlation IDs +- Error tracking and alerting +- Performance monitoring +- User analytics + +## Contributing Guidelines + +### Code Style +- **C#**: Follow .NET conventions +- **TypeScript/Vue**: Use ESLint and Prettier +- **Naming**: PascalCase for C#, camelCase for TypeScript +- **Comments**: Document public APIs and complex logic + +### Pull Request Process +1. Create feature branch +2. Make changes with tests +3. Update documentation +4. Submit pull request +5. Code review and approval +6. Merge to main + +### Git Workflow +- **main**: Production-ready code +- **feature/***: New features +- **bugfix/***: Bug fixes +- **hotfix/***: Critical fixes + +## Performance Considerations + +### Frontend +- Code splitting with Vue Router +- Lazy loading of components +- Efficient state management +- Optimized bundle size + +### Backend +- Stateless functions for scalability +- Efficient data serialization +- Connection pooling (when using databases) +- Caching strategies + +## Scalability + +### Current Limitations +- In-memory storage (single instance) +- No persistent data layer +- Basic error handling + +### Scaling Recommendations +- Add persistent database (Azure SQL, CosmosDB) +- Implement caching layer (Redis) +- Add load balancing +- Implement horizontal scaling +- Add message queues for async processing + +## Future Enhancements + +### Planned Features +- User authentication system +- Event categories and tags +- Email notifications +- Calendar integration +- Advanced search and filtering +- Event analytics and reporting + +### Technical Improvements +- Database integration +- Real-time updates with SignalR +- Mobile application +- Offline support +- Advanced caching +- Microservices architecture + +## Resources + +### Documentation Links +- [Local Development Setup](local-development.md) +- [GitHub Codespaces Setup](codespaces-setup.md) +- [Security Policy](../.github/SECURITY.md) + +### External Resources +- [Vue.js Documentation](https://vuejs.org/) +- [Azure Functions Documentation](https://docs.microsoft.com/en-us/azure/azure-functions/) +- [.NET Documentation](https://docs.microsoft.com/en-us/dotnet/) +- [TypeScript Handbook](https://www.typescriptlang.org/docs/) + +## Support + +For questions, issues, or contributions: +- Create GitHub issues for bugs and feature requests +- Submit pull requests for improvements +- Review documentation for setup and usage +- Follow security policy for vulnerability reports \ No newline at end of file diff --git a/frontend/events-app/.gitignore b/frontend/events-app/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/frontend/events-app/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/frontend/events-app/.vscode/extensions.json b/frontend/events-app/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/frontend/events-app/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/frontend/events-app/Dockerfile b/frontend/events-app/Dockerfile new file mode 100644 index 0000000..463a70d --- /dev/null +++ b/frontend/events-app/Dockerfile @@ -0,0 +1,31 @@ +# Build stage +FROM node:20-alpine AS build + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Production stage +FROM nginx:alpine AS production + +# Copy built files to nginx +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port 80 +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/events-app/README.md b/frontend/events-app/README.md new file mode 100644 index 0000000..be3ef1c --- /dev/null +++ b/frontend/events-app/README.md @@ -0,0 +1,33 @@ +# events-app + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` diff --git a/frontend/events-app/env.d.ts b/frontend/events-app/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/events-app/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/events-app/index.html b/frontend/events-app/index.html new file mode 100644 index 0000000..9e5fc8f --- /dev/null +++ b/frontend/events-app/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/frontend/events-app/nginx.conf b/frontend/events-app/nginx.conf new file mode 100644 index 0000000..4279c87 --- /dev/null +++ b/frontend/events-app/nginx.conf @@ -0,0 +1,24 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Handle SPA routing + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; +} \ No newline at end of file diff --git a/frontend/events-app/package-lock.json b/frontend/events-app/package-lock.json new file mode 100644 index 0000000..985dcc0 --- /dev/null +++ b/frontend/events-app/package-lock.json @@ -0,0 +1,3200 @@ +{ + "name": "events-app", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "events-app", + "version": "0.0.0", + "dependencies": { + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.1", + "@types/node": "^22.14.0", + "@vitejs/plugin-vue": "^5.2.3", + "@vue/tsconfig": "^0.7.0", + "npm-run-all2": "^7.0.2", + "typescript": "~5.8.0", + "vite": "^6.2.4", + "vite-plugin-vue-devtools": "^7.7.2", + "vue-tsc": "^2.2.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", + "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", + "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.3", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", + "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", + "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz", + "integrity": "sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.2.tgz", + "integrity": "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", + "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.14.tgz", + "integrity": "sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.14" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.14.tgz", + "integrity": "sha512-5TeKKMh7Sfxo8021cJfmBzcjfY1SsXsPMMjMvjY7ivesdnybqqS+GxGAoXHAOUawQTwtdUxgP65Im+dEmvWtYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.14.tgz", + "integrity": "sha512-p8Z6f/bZM3/HyCdRNFZOEEzts51uV8WHeN8Tnfnm2EBv6FDB2TQLzfVx7aJvnl8ofKAOnS64B2O8bImBFaauRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.14", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz", + "integrity": "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.4.0.tgz", + "integrity": "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "@vue/babel-helper-vue-transform-on": "1.4.0", + "@vue/babel-plugin-resolve-type": "1.4.0", + "@vue/shared": "^3.5.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.4.0.tgz", + "integrity": "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/parser": "^7.26.9", + "@vue/compiler-sfc": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", + "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.2", + "@vue/shared": "3.5.16", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", + "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.16", + "@vue/shared": "3.5.16" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", + "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.2", + "@vue/compiler-core": "3.5.16", + "@vue/compiler-dom": "3.5.16", + "@vue/compiler-ssr": "3.5.16", + "@vue/shared": "3.5.16", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.17", + "postcss": "^8.5.3", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", + "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.16", + "@vue/shared": "3.5.16" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.6.tgz", + "integrity": "sha512-b2Xx0KvXZObePpXPYHvBRRJLDQn5nhKjXh7vUhMEtWxz1AYNFOVIsh5+HLP8xDGL7sy+Q7hXeUxPHB/KgbtsPw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.6" + } + }, + "node_modules/@vue/devtools-core": { + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.6.tgz", + "integrity": "sha512-ghVX3zjKPtSHu94Xs03giRIeIWlb9M+gvDRVpIZ/cRIxKHdW6HE/sm1PT3rUYS3aV92CazirT93ne+7IOvGUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.6", + "@vue/devtools-shared": "^7.7.6", + "mitt": "^3.0.1", + "nanoid": "^5.1.0", + "pathe": "^2.0.3", + "vite-hot-client": "^2.0.4" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.6.tgz", + "integrity": "sha512-geu7ds7tem2Y7Wz+WgbnbZ6T5eadOvozHZ23Atk/8tksHMFOFylKi1xgGlQlVn0wlkEf4hu+vd5ctj1G4kFtwA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.6", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.6.tgz", + "integrity": "sha512-yFEgJZ/WblEsojQQceuyK6FzpFDx4kqrz2ohInxNj5/DnhoX023upTv4OD6lNPLAA5LLkbwPVb10o/7b+Y4FVA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.10.tgz", + "integrity": "sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz", + "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.16" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz", + "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.16", + "@vue/shared": "3.5.16" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", + "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.16", + "@vue/runtime-core": "3.5.16", + "@vue/shared": "3.5.16", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz", + "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.16", + "@vue/shared": "3.5.16" + }, + "peerDependencies": { + "vue": "3.5.16" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", + "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.3.0.tgz", + "integrity": "sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.162", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.162.tgz", + "integrity": "sha512-hQA+Zb5QQwoSaXJWEAGEw1zhk//O7qDzib05Z4qTqZfNju/FAkrm5ZInp0JbTp4Z18A6bilopdZWEYrFSsfllA==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", + "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/execa": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-7.0.2.tgz", + "integrity": "sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "minimatch": "^9.0.0", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0", + "npm": ">= 9" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pinia": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.2.tgz", + "integrity": "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-hot-client": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.0.4.tgz", + "integrity": "sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", + "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "debug": "^4.3.7", + "error-stack-parser-es": "^0.1.5", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.1.1", + "sirv": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.6.tgz", + "integrity": "sha512-L7nPVM5a7lgit/Z+36iwoqHOaP3wxqVi1UvaDJwGCfblS9Y6vNqf32ILlzJVH9c47aHu90BhDXeZc+rgzHRHcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^7.7.6", + "@vue/devtools-kit": "^7.7.6", + "@vue/devtools-shared": "^7.7.6", + "execa": "^9.5.2", + "sirv": "^3.0.1", + "vite-plugin-inspect": "0.8.9", + "vite-plugin-vue-inspector": "^5.3.1" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz", + "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz", + "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.16", + "@vue/compiler-sfc": "3.5.16", + "@vue/runtime-dom": "3.5.16", + "@vue/server-renderer": "3.5.16", + "@vue/shared": "3.5.16" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.10.tgz", + "integrity": "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.2.10" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/events-app/package.json b/frontend/events-app/package.json new file mode 100644 index 0000000..ea9e13c --- /dev/null +++ b/frontend/events-app/package.json @@ -0,0 +1,29 @@ +{ + "name": "events-app", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build" + }, + "dependencies": { + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.1", + "@types/node": "^22.14.0", + "@vitejs/plugin-vue": "^5.2.3", + "@vue/tsconfig": "^0.7.0", + "npm-run-all2": "^7.0.2", + "typescript": "~5.8.0", + "vite": "^6.2.4", + "vite-plugin-vue-devtools": "^7.7.2", + "vue-tsc": "^2.2.8" + } +} diff --git a/frontend/events-app/public/favicon.ico b/frontend/events-app/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/frontend/events-app/public/favicon.ico differ diff --git a/frontend/events-app/src/App.vue b/frontend/events-app/src/App.vue new file mode 100644 index 0000000..ef8760e --- /dev/null +++ b/frontend/events-app/src/App.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/frontend/events-app/src/assets/base.css b/frontend/events-app/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/frontend/events-app/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/frontend/events-app/src/assets/logo.svg b/frontend/events-app/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/frontend/events-app/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/frontend/events-app/src/assets/main.css b/frontend/events-app/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/frontend/events-app/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/frontend/events-app/src/components/HelloWorld.vue b/frontend/events-app/src/components/HelloWorld.vue new file mode 100644 index 0000000..d174cf8 --- /dev/null +++ b/frontend/events-app/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/frontend/events-app/src/components/TheWelcome.vue b/frontend/events-app/src/components/TheWelcome.vue new file mode 100644 index 0000000..6092dff --- /dev/null +++ b/frontend/events-app/src/components/TheWelcome.vue @@ -0,0 +1,94 @@ + + + diff --git a/frontend/events-app/src/components/WelcomeItem.vue b/frontend/events-app/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/frontend/events-app/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/frontend/events-app/src/components/icons/IconCommunity.vue b/frontend/events-app/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/frontend/events-app/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/events-app/src/components/icons/IconDocumentation.vue b/frontend/events-app/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/frontend/events-app/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/events-app/src/components/icons/IconEcosystem.vue b/frontend/events-app/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/frontend/events-app/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/events-app/src/components/icons/IconSupport.vue b/frontend/events-app/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/frontend/events-app/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/events-app/src/components/icons/IconTooling.vue b/frontend/events-app/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/frontend/events-app/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/frontend/events-app/src/main.ts b/frontend/events-app/src/main.ts new file mode 100644 index 0000000..5dcad83 --- /dev/null +++ b/frontend/events-app/src/main.ts @@ -0,0 +1,14 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/frontend/events-app/src/router/index.ts b/frontend/events-app/src/router/index.ts new file mode 100644 index 0000000..3e6ac13 --- /dev/null +++ b/frontend/events-app/src/router/index.ts @@ -0,0 +1,42 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' +import EventsView from '../views/EventsView.vue' +import EventDetailView from '../views/EventDetailView.vue' +import CreateEventView from '../views/CreateEventView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView, + }, + { + path: '/events', + name: 'events', + component: EventsView, + }, + { + path: '/events/create', + name: 'create-event', + component: CreateEventView, + }, + { + path: '/events/:id', + name: 'event-detail', + component: EventDetailView, + props: true, + }, + { + path: '/about', + name: 'about', + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: () => import('../views/AboutView.vue'), + }, + ], +}) + +export default router diff --git a/frontend/events-app/src/services/api.ts b/frontend/events-app/src/services/api.ts new file mode 100644 index 0000000..b88f633 --- /dev/null +++ b/frontend/events-app/src/services/api.ts @@ -0,0 +1,155 @@ +import type { + Event, + Registration, + CreateEventRequest, + UpdateEventRequest, + CreateRegistrationRequest, + EventFilter, + ApiResponse, + EventListResponse +} from '@/types'; + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:7071/api'; + +class ApiError extends Error { + constructor(message: string, public status?: number) { + super(message); + this.name = 'ApiError'; + } +} + +async function handleResponse(response: Response): Promise { + if (!response.ok) { + const text = await response.text(); + let errorMessage = `HTTP ${response.status}: ${response.statusText}`; + + try { + const errorData = JSON.parse(text); + if (errorData.error) { + errorMessage = errorData.error; + } + } catch { + // If not JSON, use the text as error message + if (text) { + errorMessage = text; + } + } + + throw new ApiError(errorMessage, response.status); + } + + const data = await response.json(); + return data; +} + +export const eventApi = { + async getEvents(filter?: EventFilter): Promise { + const params = new URLSearchParams(); + if (filter?.date) params.append('date', filter.date); + if (filter?.location) params.append('location', filter.location); + if (filter?.limit) params.append('limit', filter.limit.toString()); + if (filter?.offset) params.append('offset', filter.offset.toString()); + + const url = `${API_BASE_URL}/events${params.toString() ? '?' + params.toString() : ''}`; + const response = await fetch(url); + const apiResponse = await handleResponse>(response); + + if (!apiResponse.success || !apiResponse.data) { + throw new ApiError(apiResponse.error || 'Failed to fetch events'); + } + + return apiResponse.data; + }, + + async getEvent(id: string): Promise { + const response = await fetch(`${API_BASE_URL}/events/${id}`); + const apiResponse = await handleResponse>(response); + + if (!apiResponse.success || !apiResponse.data) { + throw new ApiError(apiResponse.error || 'Failed to fetch event'); + } + + return apiResponse.data; + }, + + async createEvent(event: CreateEventRequest): Promise { + const response = await fetch(`${API_BASE_URL}/events`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(event), + }); + + const apiResponse = await handleResponse>(response); + + if (!apiResponse.success || !apiResponse.data) { + throw new ApiError(apiResponse.error || 'Failed to create event'); + } + + return apiResponse.data; + }, + + async updateEvent(id: string, event: UpdateEventRequest): Promise { + const response = await fetch(`${API_BASE_URL}/events/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(event), + }); + + const apiResponse = await handleResponse>(response); + + if (!apiResponse.success || !apiResponse.data) { + throw new ApiError(apiResponse.error || 'Failed to update event'); + } + + return apiResponse.data; + }, + + async deleteEvent(id: string): Promise { + const response = await fetch(`${API_BASE_URL}/events/${id}`, { + method: 'DELETE', + }); + + const apiResponse = await handleResponse>(response); + + if (!apiResponse.success) { + throw new ApiError(apiResponse.error || 'Failed to delete event'); + } + }, +}; + +export const registrationApi = { + async createRegistration(registration: CreateRegistrationRequest): Promise { + const response = await fetch(`${API_BASE_URL}/registrations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(registration), + }); + + const apiResponse = await handleResponse>(response); + + if (!apiResponse.success || !apiResponse.data) { + throw new ApiError(apiResponse.error || 'Failed to create registration'); + } + + return apiResponse.data; + }, + + async getRegistrationsByEvent(eventId: string): Promise { + const response = await fetch(`${API_BASE_URL}/events/${eventId}/registrations`); + const apiResponse = await handleResponse>(response); + + if (!apiResponse.success || !apiResponse.data) { + throw new ApiError(apiResponse.error || 'Failed to fetch registrations'); + } + + return apiResponse.data; + }, +}; + +export { ApiError }; \ No newline at end of file diff --git a/frontend/events-app/src/stores/events.ts b/frontend/events-app/src/stores/events.ts new file mode 100644 index 0000000..c7345bc --- /dev/null +++ b/frontend/events-app/src/stores/events.ts @@ -0,0 +1,152 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' +import type { Event, EventFilter, CreateEventRequest, UpdateEventRequest } from '@/types' +import { eventApi, ApiError } from '@/services/api' + +export const useEventsStore = defineStore('events', () => { + const events = ref([]) + const currentEvent = ref(null) + const loading = ref(false) + const error = ref(null) + const totalEvents = ref(0) + const currentFilter = ref({}) + + const filteredEvents = computed(() => { + let filtered = events.value + + if (currentFilter.value.date) { + filtered = filtered.filter(event => event.date === currentFilter.value.date) + } + + if (currentFilter.value.location) { + filtered = filtered.filter(event => + event.location.toLowerCase().includes(currentFilter.value.location!.toLowerCase()) + ) + } + + return filtered + }) + + async function fetchEvents(filter?: EventFilter) { + loading.value = true + error.value = null + + try { + if (filter) { + currentFilter.value = { ...filter } + } + + const response = await eventApi.getEvents(filter) + events.value = response.events + totalEvents.value = response.total + } catch (err) { + error.value = err instanceof ApiError ? err.message : 'Failed to fetch events' + console.error('Error fetching events:', err) + } finally { + loading.value = false + } + } + + async function fetchEvent(id: string) { + loading.value = true + error.value = null + + try { + const event = await eventApi.getEvent(id) + currentEvent.value = event + return event + } catch (err) { + error.value = err instanceof ApiError ? err.message : 'Failed to fetch event' + console.error('Error fetching event:', err) + throw err + } finally { + loading.value = false + } + } + + async function createEvent(eventData: CreateEventRequest) { + loading.value = true + error.value = null + + try { + const newEvent = await eventApi.createEvent(eventData) + events.value.unshift(newEvent) + totalEvents.value += 1 + return newEvent + } catch (err) { + error.value = err instanceof ApiError ? err.message : 'Failed to create event' + console.error('Error creating event:', err) + throw err + } finally { + loading.value = false + } + } + + async function updateEvent(id: string, eventData: UpdateEventRequest) { + loading.value = true + error.value = null + + try { + const updatedEvent = await eventApi.updateEvent(id, eventData) + const index = events.value.findIndex(e => e.id === id) + if (index !== -1) { + events.value[index] = updatedEvent + } + if (currentEvent.value?.id === id) { + currentEvent.value = updatedEvent + } + return updatedEvent + } catch (err) { + error.value = err instanceof ApiError ? err.message : 'Failed to update event' + console.error('Error updating event:', err) + throw err + } finally { + loading.value = false + } + } + + async function deleteEvent(id: string) { + loading.value = true + error.value = null + + try { + await eventApi.deleteEvent(id) + events.value = events.value.filter(e => e.id !== id) + totalEvents.value -= 1 + if (currentEvent.value?.id === id) { + currentEvent.value = null + } + } catch (err) { + error.value = err instanceof ApiError ? err.message : 'Failed to delete event' + console.error('Error deleting event:', err) + throw err + } finally { + loading.value = false + } + } + + function clearError() { + error.value = null + } + + function clearCurrentEvent() { + currentEvent.value = null + } + + return { + events, + currentEvent, + loading, + error, + totalEvents, + currentFilter, + filteredEvents, + fetchEvents, + fetchEvent, + createEvent, + updateEvent, + deleteEvent, + clearError, + clearCurrentEvent + } +}) diff --git a/frontend/events-app/src/types/index.ts b/frontend/events-app/src/types/index.ts new file mode 100644 index 0000000..8ed3575 --- /dev/null +++ b/frontend/events-app/src/types/index.ts @@ -0,0 +1,63 @@ +// Copy of data contract types for frontend +export interface Event { + id: string; + name: string; + location: string; + date: string; // ISO 8601 date string (YYYY-MM-DD) + startTime: string; // HH:mm format + createdAt: string; // ISO 8601 datetime string + updatedAt: string; // ISO 8601 datetime string +} + +export interface Registration { + id: string; + eventId: string; + name: string; + email: string; + pronouns?: string; // Optional + optInCommunication: boolean; + registeredAt: string; // ISO 8601 datetime string +} + +export interface CreateEventRequest { + name: string; + location: string; + date: string; // ISO 8601 date string + startTime: string; // HH:mm format +} + +export interface UpdateEventRequest { + name?: string; + location?: string; + date?: string; // ISO 8601 date string + startTime?: string; // HH:mm format +} + +export interface CreateRegistrationRequest { + eventId: string; + name: string; + email: string; + pronouns?: string; + optInCommunication: boolean; +} + +export interface EventFilter { + date?: string; // ISO 8601 date string + location?: string; + limit?: number; + offset?: number; +} + +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + message?: string; +} + +export interface EventListResponse { + events: Event[]; + total: number; + offset: number; + limit: number; +} \ No newline at end of file diff --git a/frontend/events-app/src/views/AboutView.vue b/frontend/events-app/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/frontend/events-app/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/frontend/events-app/src/views/CreateEventView.vue b/frontend/events-app/src/views/CreateEventView.vue new file mode 100644 index 0000000..be209c5 --- /dev/null +++ b/frontend/events-app/src/views/CreateEventView.vue @@ -0,0 +1,283 @@ + + + + + \ No newline at end of file diff --git a/frontend/events-app/src/views/EventDetailView.vue b/frontend/events-app/src/views/EventDetailView.vue new file mode 100644 index 0000000..9873cdd --- /dev/null +++ b/frontend/events-app/src/views/EventDetailView.vue @@ -0,0 +1,378 @@ + + + + + \ No newline at end of file diff --git a/frontend/events-app/src/views/EventsView.vue b/frontend/events-app/src/views/EventsView.vue new file mode 100644 index 0000000..6143b69 --- /dev/null +++ b/frontend/events-app/src/views/EventsView.vue @@ -0,0 +1,309 @@ + + + + + \ No newline at end of file diff --git a/frontend/events-app/src/views/HomeView.vue b/frontend/events-app/src/views/HomeView.vue new file mode 100644 index 0000000..239cc61 --- /dev/null +++ b/frontend/events-app/src/views/HomeView.vue @@ -0,0 +1,66 @@ + + + diff --git a/frontend/events-app/tsconfig.app.json b/frontend/events-app/tsconfig.app.json new file mode 100644 index 0000000..913b8f2 --- /dev/null +++ b/frontend/events-app/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/frontend/events-app/tsconfig.json b/frontend/events-app/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/frontend/events-app/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/frontend/events-app/tsconfig.node.json b/frontend/events-app/tsconfig.node.json new file mode 100644 index 0000000..a83dfc9 --- /dev/null +++ b/frontend/events-app/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/frontend/events-app/vite.config.ts b/frontend/events-app/vite.config.ts new file mode 100644 index 0000000..4217010 --- /dev/null +++ b/frontend/events-app/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, +})