Compare commits
15 Commits
fbfa367c16
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 32bfbcadb1 | |||
| 88aa6d72b1 | |||
| 3a3dbe0ef1 | |||
| 79b41a3650 | |||
| f282775c9a | |||
| 739ffe510d | |||
| 8a264cd2b1 | |||
| 924c5c8420 | |||
| e6430e855b | |||
| 71ba829d6c | |||
| b54e2265d9 | |||
| 4438bc5a93 | |||
| 30d573d1f8 | |||
| b6962e1024 | |||
| 8bfd49e0ab |
@@ -0,0 +1,53 @@
|
||||
# .NET Core
|
||||
bin/
|
||||
obj/
|
||||
*.user
|
||||
*.userosscache
|
||||
*.suo
|
||||
*.cache
|
||||
*.dll
|
||||
*.exe
|
||||
*.pdb
|
||||
*.db
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
*.suo
|
||||
*.user
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
appsettings.Development.json
|
||||
appsettings.Local.json
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
|
||||
# NuGet
|
||||
*.nupkg
|
||||
**/packages/*
|
||||
!**/packages/build/
|
||||
|
||||
# Test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# Entity Framework
|
||||
Migrations/*.Designer.cs
|
||||
Migrations/*ModelSnapshot.cs
|
||||
@@ -0,0 +1,252 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RacePlannerApi.Data;
|
||||
using RacePlannerApi.DTOs;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class AnnouncementsController : ControllerBase
|
||||
{
|
||||
private readonly RacePlannerDbContext _context;
|
||||
|
||||
public AnnouncementsController(RacePlannerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<AnnouncementDto>> CreateAnnouncement(CreateAnnouncementRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Verify event exists and user is organizer
|
||||
var eventEntity = await _context.Events
|
||||
.FirstOrDefaultAsync(e => e.Id == request.EventId);
|
||||
|
||||
if (eventEntity == null)
|
||||
{
|
||||
return NotFound(new { error = "Event not found" });
|
||||
}
|
||||
|
||||
if (eventEntity.OrganizerId != userId.Value)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var announcement = new Announcement
|
||||
{
|
||||
EventId = request.EventId,
|
||||
Title = request.Title,
|
||||
Content = request.Content,
|
||||
AuthorId = userId.Value,
|
||||
IsPublished = true
|
||||
};
|
||||
|
||||
_context.Announcements.Add(announcement);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Load related data
|
||||
await _context.Entry(announcement)
|
||||
.Reference(a => a.Event)
|
||||
.LoadAsync();
|
||||
await _context.Entry(announcement)
|
||||
.Reference(a => a.Author)
|
||||
.LoadAsync();
|
||||
|
||||
// TODO: Send notifications to registered participants (Task 7.7)
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetAnnouncement),
|
||||
new { id = announcement.Id },
|
||||
MapToAnnouncementDto(announcement));
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<AnnouncementDto>> GetAnnouncement(Guid id)
|
||||
{
|
||||
var announcement = await _context.Announcements
|
||||
.Include(a => a.Event)
|
||||
.Include(a => a.Author)
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
|
||||
if (announcement == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Only show published announcements to participants
|
||||
if (!announcement.IsPublished)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null || announcement.Event.OrganizerId != userId.Value)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(MapToAnnouncementDto(announcement));
|
||||
}
|
||||
|
||||
[HttpGet("event/{eventId}")]
|
||||
public async Task<ActionResult<IEnumerable<AnnouncementDto>>> GetEventAnnouncements(Guid eventId)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var isOrganizer = false;
|
||||
|
||||
if (userId != null)
|
||||
{
|
||||
isOrganizer = await _context.Events
|
||||
.AnyAsync(e => e.Id == eventId && e.OrganizerId == userId.Value);
|
||||
}
|
||||
|
||||
var query = _context.Announcements
|
||||
.Include(a => a.Event)
|
||||
.Include(a => a.Author)
|
||||
.Where(a => a.EventId == eventId)
|
||||
.AsQueryable();
|
||||
|
||||
// Only show published announcements to non-organizers
|
||||
if (!isOrganizer)
|
||||
{
|
||||
query = query.Where(a => a.IsPublished);
|
||||
}
|
||||
|
||||
var announcements = await query
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(announcements.Select(MapToAnnouncementDto));
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<AnnouncementDto>> UpdateAnnouncement(Guid id, UpdateAnnouncementRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var announcement = await _context.Announcements
|
||||
.Include(a => a.Event)
|
||||
.Include(a => a.Author)
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
|
||||
if (announcement == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Only organizer can update
|
||||
if (announcement.Event.OrganizerId != userId.Value)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
// Update fields
|
||||
if (request.Title != null) announcement.Title = request.Title;
|
||||
if (request.Content != null) announcement.Content = request.Content;
|
||||
if (request.IsPublished.HasValue) announcement.IsPublished = request.IsPublished.Value;
|
||||
|
||||
announcement.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(MapToAnnouncementDto(announcement));
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<IActionResult> DeleteAnnouncement(Guid id)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var announcement = await _context.Announcements
|
||||
.Include(a => a.Event)
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
|
||||
if (announcement == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Only organizer can delete
|
||||
if (announcement.Event.OrganizerId != userId.Value)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
_context.Announcements.Remove(announcement);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("my-announcements")]
|
||||
public async Task<ActionResult<IEnumerable<AnnouncementDto>>> GetMyAnnouncements()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Get user registrations
|
||||
var userRegistrationEventIds = await _context.Registrations
|
||||
.Where(r => r.ParticipantId == userId.Value && r.Status != RegistrationStatus.Cancelled)
|
||||
.Select(r => r.EventId)
|
||||
.ToListAsync();
|
||||
|
||||
// Get announcements for those events
|
||||
var announcements = await _context.Announcements
|
||||
.Include(a => a.Event)
|
||||
.Include(a => a.Author)
|
||||
.Where(a => userRegistrationEventIds.Contains(a.EventId) && a.IsPublished)
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(announcements.Select(MapToAnnouncementDto));
|
||||
}
|
||||
|
||||
private Guid? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AnnouncementDto MapToAnnouncementDto(Announcement announcement)
|
||||
{
|
||||
return new AnnouncementDto
|
||||
{
|
||||
Id = announcement.Id,
|
||||
EventId = announcement.EventId,
|
||||
EventName = announcement.Event?.Name ?? string.Empty,
|
||||
Title = announcement.Title,
|
||||
Content = announcement.Content,
|
||||
AuthorId = announcement.AuthorId,
|
||||
AuthorName = announcement.Author?.Name ?? string.Empty,
|
||||
CreatedAt = announcement.CreatedAt,
|
||||
UpdatedAt = announcement.UpdatedAt,
|
||||
IsPublished = announcement.IsPublished
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RacePlannerApi.Data;
|
||||
using RacePlannerApi.DTOs;
|
||||
using RacePlannerApi.Models;
|
||||
using RacePlannerApi.Services;
|
||||
|
||||
namespace RacePlannerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly RacePlannerDbContext _context;
|
||||
private readonly JwtTokenService _jwtService;
|
||||
|
||||
public AuthController(RacePlannerDbContext context, JwtTokenService jwtService)
|
||||
{
|
||||
_context = context;
|
||||
_jwtService = jwtService;
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<AuthResponse>> Register(RegisterRequest request)
|
||||
{
|
||||
// Check if email already exists
|
||||
if (await _context.Users.AnyAsync(u => u.Email == request.Email))
|
||||
{
|
||||
return Conflict(new { error = "Email already registered" });
|
||||
}
|
||||
|
||||
// Create new user
|
||||
var user = new User
|
||||
{
|
||||
Email = request.Email,
|
||||
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
|
||||
Name = request.Name,
|
||||
Role = request.Role
|
||||
};
|
||||
|
||||
_context.Users.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Generate token
|
||||
var token = _jwtService.GenerateToken(user);
|
||||
|
||||
return Ok(new AuthResponse
|
||||
{
|
||||
Token = token,
|
||||
User = new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Email = user.Email,
|
||||
Name = user.Name,
|
||||
Role = user.Role.ToString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<AuthResponse>> Login(LoginRequest request)
|
||||
{
|
||||
// Find user by email
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == request.Email);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized(new { error = "Invalid credentials" });
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
|
||||
{
|
||||
return Unauthorized(new { error = "Invalid credentials" });
|
||||
}
|
||||
|
||||
// Generate token
|
||||
var token = _jwtService.GenerateToken(user);
|
||||
|
||||
return Ok(new AuthResponse
|
||||
{
|
||||
Token = token,
|
||||
User = new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Email = user.Email,
|
||||
Name = user.Name,
|
||||
Role = user.Role.ToString()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RacePlannerApi.Data;
|
||||
using RacePlannerApi.DTOs;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class DashboardController : ControllerBase
|
||||
{
|
||||
private readonly RacePlannerDbContext _context;
|
||||
|
||||
public DashboardController(RacePlannerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpGet("organizer")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<OrganizerDashboardDto>> GetOrganizerDashboard()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var events = await _context.Events
|
||||
.Include(e => e.Registrations)
|
||||
.ThenInclude(r => r.Payments)
|
||||
.Where(e => e.OrganizerId == userId.Value)
|
||||
.ToListAsync();
|
||||
|
||||
var totalEvents = events.Count;
|
||||
var publishedEvents = events.Count(e => e.Status == EventStatus.Published);
|
||||
var draftEvents = events.Count(e => e.Status == EventStatus.Draft);
|
||||
|
||||
var allRegistrations = events.SelectMany(e => e.Registrations).ToList();
|
||||
var totalRegistrations = allRegistrations.Count;
|
||||
var pendingRegistrations = allRegistrations.Count(r => r.Status == RegistrationStatus.Pending);
|
||||
var confirmedRegistrations = allRegistrations.Count(r => r.Status == RegistrationStatus.Confirmed);
|
||||
var cancelledRegistrations = allRegistrations.Count(r => r.Status == RegistrationStatus.Cancelled);
|
||||
|
||||
var totalRevenue = allRegistrations
|
||||
.SelectMany(r => r.Payments)
|
||||
.Sum(p => p.Amount);
|
||||
|
||||
var upcomingEvents = events
|
||||
.Where(e => e.EventDate >= DateTime.UtcNow && e.EventDate <= DateTime.UtcNow.AddDays(30))
|
||||
.OrderBy(e => e.EventDate)
|
||||
.Take(5)
|
||||
.Select(e => new EventSummaryDto
|
||||
{
|
||||
Id = e.Id,
|
||||
Name = e.Name,
|
||||
EventDate = e.EventDate,
|
||||
Location = e.Location,
|
||||
RegistrationCount = e.Registrations.Count(r => r.Status != RegistrationStatus.Cancelled),
|
||||
MaxParticipants = e.MaxParticipants
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var eventsNearCapacity = events
|
||||
.Where(e => e.MaxParticipants.HasValue && e.Status == EventStatus.Published)
|
||||
.Select(e => new EventCapacityDto
|
||||
{
|
||||
Id = e.Id,
|
||||
Name = e.Name,
|
||||
EventDate = e.EventDate,
|
||||
RegistrationCount = e.Registrations.Count(r => r.Status != RegistrationStatus.Cancelled),
|
||||
MaxParticipants = e.MaxParticipants
|
||||
})
|
||||
.Where(e => e.CapacityPercentage >= 80)
|
||||
.OrderByDescending(e => e.CapacityPercentage)
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
var dashboard = new OrganizerDashboardDto
|
||||
{
|
||||
TotalEvents = totalEvents,
|
||||
PublishedEvents = publishedEvents,
|
||||
DraftEvents = draftEvents,
|
||||
TotalRegistrations = totalRegistrations,
|
||||
PendingRegistrations = pendingRegistrations,
|
||||
ConfirmedRegistrations = confirmedRegistrations,
|
||||
CancelledRegistrations = cancelledRegistrations,
|
||||
TotalRevenue = totalRevenue,
|
||||
UpcomingEvents = upcomingEvents,
|
||||
EventsNearCapacity = eventsNearCapacity
|
||||
};
|
||||
|
||||
return Ok(dashboard);
|
||||
}
|
||||
|
||||
[HttpGet("participant")]
|
||||
public async Task<ActionResult<ParticipantDashboardDto>> GetParticipantDashboard()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var registrations = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.Where(r => r.ParticipantId == userId.Value)
|
||||
.ToListAsync();
|
||||
|
||||
var totalRegistrations = registrations.Count;
|
||||
var upcomingEvents = registrations.Count(r => r.Event.EventDate >= DateTime.UtcNow && r.Status != RegistrationStatus.Cancelled);
|
||||
var completedEvents = registrations.Count(r => r.Event.EventDate < DateTime.UtcNow && r.Status == RegistrationStatus.Completed);
|
||||
var cancelledRegistrations = registrations.Count(r => r.Status == RegistrationStatus.Cancelled);
|
||||
|
||||
var registrationSummaries = registrations
|
||||
.OrderByDescending(r => r.Event.EventDate)
|
||||
.Take(10)
|
||||
.Select(r => new RegistrationSummaryDto
|
||||
{
|
||||
Id = r.Id,
|
||||
EventId = r.EventId,
|
||||
EventName = r.Event.Name,
|
||||
EventDate = r.Event.EventDate,
|
||||
Status = r.Status.ToString(),
|
||||
RegisteredAt = r.CreatedAt
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var upcomingEventList = registrations
|
||||
.Where(r => r.Event.EventDate >= DateTime.UtcNow && r.Status != RegistrationStatus.Cancelled)
|
||||
.OrderBy(r => r.Event.EventDate)
|
||||
.Take(5)
|
||||
.Select(r => new UpcomingEventDto
|
||||
{
|
||||
Id = r.EventId,
|
||||
Name = r.Event.Name,
|
||||
EventDate = r.Event.EventDate,
|
||||
Location = r.Event.Location
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var dashboard = new ParticipantDashboardDto
|
||||
{
|
||||
TotalRegistrations = totalRegistrations,
|
||||
UpcomingEvents = upcomingEvents,
|
||||
CompletedEvents = completedEvents,
|
||||
CancelledRegistrations = cancelledRegistrations,
|
||||
MyRegistrations = registrationSummaries,
|
||||
UpcomingEventList = upcomingEventList
|
||||
};
|
||||
|
||||
return Ok(dashboard);
|
||||
}
|
||||
|
||||
private Guid? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RacePlannerApi.Data;
|
||||
using RacePlannerApi.DTOs;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class EventsController : ControllerBase
|
||||
{
|
||||
private readonly RacePlannerDbContext _context;
|
||||
|
||||
public EventsController(RacePlannerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<EventDto>> CreateEvent(CreateEventRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var eventEntity = new Event
|
||||
{
|
||||
Name = request.Name,
|
||||
Description = request.Description,
|
||||
EventDate = request.EventDate,
|
||||
Location = request.Location,
|
||||
Category = request.Category,
|
||||
Tags = request.Tags ?? new List<string>(),
|
||||
MaxParticipants = request.MaxParticipants,
|
||||
OrganizerId = userId.Value,
|
||||
Status = EventStatus.Draft
|
||||
};
|
||||
|
||||
_context.Events.Add(eventEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Load organizer for response
|
||||
await _context.Entry(eventEntity).Reference(e => e.Organizer).LoadAsync();
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetEvent),
|
||||
new { id = eventEntity.Id },
|
||||
MapToEventDto(eventEntity));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<IEnumerable<EventDto>>> GetEvents(
|
||||
[FromQuery] EventFilterRequest? filter = null)
|
||||
{
|
||||
var query = _context.Events
|
||||
.Include(e => e.Organizer)
|
||||
.Include(e => e.Registrations)
|
||||
.AsQueryable();
|
||||
|
||||
// Apply filters
|
||||
if (filter?.Category != null)
|
||||
{
|
||||
query = query.Where(e => e.Category == filter.Category);
|
||||
}
|
||||
|
||||
if (filter?.Tags != null && filter.Tags.Any())
|
||||
{
|
||||
query = query.Where(e => e.Tags.Any(t => filter.Tags.Contains(t)));
|
||||
}
|
||||
|
||||
if (filter?.FromDate != null)
|
||||
{
|
||||
query = query.Where(e => e.EventDate >= filter.FromDate);
|
||||
}
|
||||
|
||||
if (filter?.ToDate != null)
|
||||
{
|
||||
query = query.Where(e => e.EventDate <= filter.ToDate);
|
||||
}
|
||||
|
||||
if (filter?.Status != null && Enum.TryParse<EventStatus>(filter.Status, out var status))
|
||||
{
|
||||
query = query.Where(e => e.Status == status);
|
||||
}
|
||||
|
||||
if (filter?.OrganizerId != null)
|
||||
{
|
||||
query = query.Where(e => e.OrganizerId == filter.OrganizerId);
|
||||
}
|
||||
|
||||
// Default to showing published events only for anonymous users
|
||||
if (!User.Identity?.IsAuthenticated ?? true)
|
||||
{
|
||||
query = query.Where(e => e.Status == EventStatus.Published);
|
||||
}
|
||||
|
||||
var events = await query
|
||||
.OrderBy(e => e.EventDate)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(events.Select(MapToEventDto));
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<EventDto>> GetEvent(Guid id)
|
||||
{
|
||||
var eventEntity = await _context.Events
|
||||
.Include(e => e.Organizer)
|
||||
.Include(e => e.Registrations)
|
||||
.FirstOrDefaultAsync(e => e.Id == id);
|
||||
|
||||
if (eventEntity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Draft events only visible to organizers
|
||||
if (eventEntity.Status == EventStatus.Draft)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null || eventEntity.OrganizerId != userId)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(MapToEventDto(eventEntity));
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<EventDto>> UpdateEvent(Guid id, UpdateEventRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var eventEntity = await _context.Events
|
||||
.Include(e => e.Organizer)
|
||||
.FirstOrDefaultAsync(e => e.Id == id);
|
||||
|
||||
if (eventEntity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Only organizer can update their event
|
||||
if (eventEntity.OrganizerId != userId)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
// Update fields
|
||||
if (request.Name != null) eventEntity.Name = request.Name;
|
||||
if (request.Description != null) eventEntity.Description = request.Description;
|
||||
if (request.EventDate != null) eventEntity.EventDate = request.EventDate.Value;
|
||||
if (request.Location != null) eventEntity.Location = request.Location;
|
||||
if (request.Status != null) eventEntity.Status = request.Status.Value;
|
||||
if (request.Category != null) eventEntity.Category = request.Category;
|
||||
if (request.Tags != null) eventEntity.Tags = request.Tags;
|
||||
if (request.MaxParticipants != null) eventEntity.MaxParticipants = request.MaxParticipants;
|
||||
|
||||
eventEntity.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(MapToEventDto(eventEntity));
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<IActionResult> DeleteEvent(Guid id)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var eventEntity = await _context.Events.FindAsync(id);
|
||||
|
||||
if (eventEntity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (eventEntity.OrganizerId != userId)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
_context.Events.Remove(eventEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private Guid? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static EventDto MapToEventDto(Event eventEntity)
|
||||
{
|
||||
return new EventDto
|
||||
{
|
||||
Id = eventEntity.Id,
|
||||
Name = eventEntity.Name,
|
||||
Description = eventEntity.Description,
|
||||
EventDate = eventEntity.EventDate,
|
||||
Location = eventEntity.Location,
|
||||
Status = eventEntity.Status.ToString(),
|
||||
Category = eventEntity.Category,
|
||||
Tags = eventEntity.Tags,
|
||||
MaxParticipants = eventEntity.MaxParticipants,
|
||||
CurrentRegistrations = eventEntity.Registrations?.Count ?? 0,
|
||||
CreatedAt = eventEntity.CreatedAt,
|
||||
UpdatedAt = eventEntity.UpdatedAt,
|
||||
Organizer = new UserSummaryDto
|
||||
{
|
||||
Id = eventEntity.Organizer.Id,
|
||||
Name = eventEntity.Organizer.Name,
|
||||
Email = eventEntity.Organizer.Email
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RacePlannerApi.Data;
|
||||
using RacePlannerApi.DTOs;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class PaymentsController : ControllerBase
|
||||
{
|
||||
private readonly RacePlannerDbContext _context;
|
||||
|
||||
public PaymentsController(RacePlannerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<PaymentDto>> RecordPayment(CreatePaymentRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Load registration with event
|
||||
var registration = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.FirstOrDefaultAsync(r => r.Id == request.RegistrationId);
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
return NotFound(new { error = "Registration not found" });
|
||||
}
|
||||
|
||||
// Verify user is organizer of the event
|
||||
if (registration.Event.OrganizerId != userId.Value)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
// Check registration is not cancelled
|
||||
if (registration.Status == RegistrationStatus.Cancelled)
|
||||
{
|
||||
return BadRequest(new { error = "Cannot record payment for cancelled registration" });
|
||||
}
|
||||
|
||||
var payment = new Payment
|
||||
{
|
||||
RegistrationId = request.RegistrationId,
|
||||
Amount = request.Amount,
|
||||
Method = request.Method,
|
||||
TransactionId = request.TransactionId,
|
||||
Notes = request.Notes,
|
||||
PaymentDate = request.PaymentDate ?? DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.Payments.Add(payment);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Update registration status if fully paid (assuming a fixed fee amount would be known)
|
||||
// For now, we'll just record the payment
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetPayment),
|
||||
new { id = payment.Id },
|
||||
MapToPaymentDto(payment));
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<PaymentDto>> GetPayment(Guid id)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var payment = await _context.Payments
|
||||
.Include(p => p.Registration)
|
||||
.ThenInclude(r => r.Event)
|
||||
.FirstOrDefaultAsync(p => p.Id == id);
|
||||
|
||||
if (payment == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Verify user is organizer
|
||||
if (payment.Registration.Event.OrganizerId != userId.Value)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
return Ok(MapToPaymentDto(payment));
|
||||
}
|
||||
|
||||
[HttpGet("registration/{registrationId}")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<IEnumerable<PaymentDto>>> GetRegistrationPayments(Guid registrationId)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Verify organizer has access to this registration
|
||||
var registration = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.FirstOrDefaultAsync(r => r.Id == registrationId);
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (registration.Event.OrganizerId != userId.Value)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var payments = await _context.Payments
|
||||
.Where(p => p.RegistrationId == registrationId)
|
||||
.OrderByDescending(p => p.PaymentDate)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(payments.Select(MapToPaymentDto));
|
||||
}
|
||||
|
||||
[HttpGet("registration/{registrationId}/status")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<object>> GetPaymentStatus(Guid registrationId)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var registration = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.Include(r => r.Payments)
|
||||
.FirstOrDefaultAsync(r => r.Id == registrationId);
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Participant can view their own, organizer can view any
|
||||
var isOrganizer = registration.Event.OrganizerId == userId.Value;
|
||||
|
||||
if (registration.ParticipantId != userId.Value && !isOrganizer)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var totalPaid = registration.Payments.Sum(p => p.Amount);
|
||||
var status = totalPaid > 0 ? "paid" : "unpaid";
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
RegistrationId = registrationId,
|
||||
TotalPaid = totalPaid,
|
||||
PaymentCount = registration.Payments.Count,
|
||||
Status = status,
|
||||
Payments = registration.Payments.Select(MapToPaymentDto)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("event/{eventId}/report")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<PaymentReportDto>> GetPaymentReport(Guid eventId)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var eventEntity = await _context.Events
|
||||
.FirstOrDefaultAsync(e => e.Id == eventId && e.OrganizerId == userId.Value);
|
||||
|
||||
if (eventEntity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var registrations = await _context.Registrations
|
||||
.Include(r => r.Participant)
|
||||
.Include(r => r.Payments)
|
||||
.Where(r => r.EventId == eventId && r.Status != RegistrationStatus.Cancelled)
|
||||
.ToListAsync();
|
||||
|
||||
var totalCollected = registrations.Sum(r => r.Payments.Sum(p => p.Amount));
|
||||
var totalRegistrations = registrations.Count;
|
||||
var paidRegistrations = registrations.Count(r => r.Payments.Any());
|
||||
var unpaidRegistrations = registrations.Count(r => !r.Payments.Any());
|
||||
|
||||
var report = new PaymentReportDto
|
||||
{
|
||||
EventId = eventId,
|
||||
EventName = eventEntity.Name,
|
||||
TotalCollected = totalCollected,
|
||||
TotalPending = 0, // Would require event fee structure
|
||||
TotalOutstanding = 0, // Would require event fee structure
|
||||
TotalRegistrations = totalRegistrations,
|
||||
PaidRegistrations = paidRegistrations,
|
||||
PartialRegistrations = 0, // Would require partial payment logic
|
||||
UnpaidRegistrations = unpaidRegistrations,
|
||||
Registrations = registrations.Select(r => new RegistrationPaymentDto
|
||||
{
|
||||
RegistrationId = r.Id,
|
||||
ParticipantName = r.Participant.Name,
|
||||
Status = r.Status.ToString(),
|
||||
TotalPaid = r.Payments.Sum(p => p.Amount),
|
||||
AmountDue = 0, // Would require event fee structure
|
||||
PaymentStatus = r.Payments.Any() ? "paid" : "unpaid",
|
||||
Payments = r.Payments.Select(MapToPaymentDto).ToList()
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
return Ok(report);
|
||||
}
|
||||
|
||||
private Guid? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PaymentDto MapToPaymentDto(Payment payment)
|
||||
{
|
||||
return new PaymentDto
|
||||
{
|
||||
Id = payment.Id,
|
||||
RegistrationId = payment.RegistrationId,
|
||||
Amount = payment.Amount,
|
||||
Method = payment.Method.ToString(),
|
||||
TransactionId = payment.TransactionId,
|
||||
Notes = payment.Notes,
|
||||
PaymentDate = payment.PaymentDate,
|
||||
CreatedAt = payment.CreatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RacePlannerApi.Data;
|
||||
using RacePlannerApi.DTOs;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class RegistrationsController : ControllerBase
|
||||
{
|
||||
private readonly RacePlannerDbContext _context;
|
||||
|
||||
public RegistrationsController(RacePlannerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<RegistrationDto>> CreateRegistration(CreateRegistrationRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Check if event exists and is published
|
||||
var eventEntity = await _context.Events
|
||||
.FirstOrDefaultAsync(e => e.Id == request.EventId);
|
||||
|
||||
if (eventEntity == null)
|
||||
{
|
||||
return NotFound(new { error = "Event not found" });
|
||||
}
|
||||
|
||||
if (eventEntity.Status != EventStatus.Published)
|
||||
{
|
||||
return BadRequest(new { error = "Event is not open for registration" });
|
||||
}
|
||||
|
||||
// Check if already registered
|
||||
var existingRegistration = await _context.Registrations
|
||||
.FirstOrDefaultAsync(r => r.EventId == request.EventId && r.ParticipantId == userId.Value);
|
||||
|
||||
if (existingRegistration != null)
|
||||
{
|
||||
return Conflict(new { error = "Already registered for this event" });
|
||||
}
|
||||
|
||||
// Check if event is full
|
||||
if (eventEntity.MaxParticipants.HasValue)
|
||||
{
|
||||
var registrationCount = await _context.Registrations
|
||||
.CountAsync(r => r.EventId == request.EventId && r.Status != RegistrationStatus.Cancelled);
|
||||
|
||||
if (registrationCount >= eventEntity.MaxParticipants.Value)
|
||||
{
|
||||
return BadRequest(new { error = "Event is full" });
|
||||
}
|
||||
}
|
||||
|
||||
var registration = new Registration
|
||||
{
|
||||
EventId = request.EventId,
|
||||
ParticipantId = userId.Value,
|
||||
Status = RegistrationStatus.Pending,
|
||||
Category = request.Category,
|
||||
EmergencyContact = request.EmergencyContact
|
||||
};
|
||||
|
||||
_context.Registrations.Add(registration);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Load related data
|
||||
await _context.Entry(registration)
|
||||
.Reference(r => r.Event)
|
||||
.LoadAsync();
|
||||
await _context.Entry(registration)
|
||||
.Reference(r => r.Participant)
|
||||
.LoadAsync();
|
||||
await _context.Entry(registration)
|
||||
.Collection(r => r.Payments)
|
||||
.LoadAsync();
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetRegistration),
|
||||
new { id = registration.Id },
|
||||
MapToRegistrationDto(registration));
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<RegistrationDto>> GetRegistration(Guid id)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var registration = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.Include(r => r.Participant)
|
||||
.Include(r => r.Payments)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Only participant or event organizer can view
|
||||
var isOrganizer = await _context.Events
|
||||
.AnyAsync(e => e.Id == registration.EventId && e.OrganizerId == userId.Value);
|
||||
|
||||
if (registration.ParticipantId != userId.Value && !isOrganizer)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
return Ok(MapToRegistrationDto(registration));
|
||||
}
|
||||
|
||||
[HttpGet("my-registrations")]
|
||||
public async Task<ActionResult<IEnumerable<RegistrationDto>>> GetMyRegistrations()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var registrations = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.Include(r => r.Participant)
|
||||
.Include(r => r.Payments)
|
||||
.Where(r => r.ParticipantId == userId.Value)
|
||||
.OrderBy(r => r.Event.EventDate)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(registrations.Select(MapToRegistrationDto));
|
||||
}
|
||||
|
||||
[HttpGet("event/{eventId}")]
|
||||
[Authorize(Roles = "Organizer")]
|
||||
public async Task<ActionResult<IEnumerable<RegistrationDto>>> GetEventRegistrations(Guid eventId)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Verify user is organizer of this event
|
||||
var eventEntity = await _context.Events
|
||||
.FirstOrDefaultAsync(e => e.Id == eventId && e.OrganizerId == userId.Value);
|
||||
|
||||
if (eventEntity == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var registrations = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.Include(r => r.Participant)
|
||||
.Include(r => r.Payments)
|
||||
.Where(r => r.EventId == eventId)
|
||||
.OrderBy(r => r.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(registrations.Select(MapToRegistrationDto));
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<ActionResult<RegistrationDto>> UpdateRegistration(Guid id, UpdateRegistrationRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var registration = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Participants can only update their own registrations
|
||||
// Organizers can update any registration for their events
|
||||
var isOrganizer = registration.Event.OrganizerId == userId.Value;
|
||||
|
||||
if (registration.ParticipantId != userId.Value && !isOrganizer)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
// Update fields
|
||||
if (request.Status.HasValue && isOrganizer)
|
||||
{
|
||||
registration.Status = request.Status.Value;
|
||||
}
|
||||
|
||||
if (request.Category != null)
|
||||
{
|
||||
registration.Category = request.Category;
|
||||
}
|
||||
|
||||
if (request.EmergencyContact != null)
|
||||
{
|
||||
registration.EmergencyContact = request.EmergencyContact;
|
||||
}
|
||||
|
||||
registration.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Reload related data
|
||||
await _context.Entry(registration)
|
||||
.Reference(r => r.Participant)
|
||||
.LoadAsync();
|
||||
await _context.Entry(registration)
|
||||
.Collection(r => r.Payments)
|
||||
.LoadAsync();
|
||||
|
||||
return Ok(MapToRegistrationDto(registration));
|
||||
}
|
||||
|
||||
[HttpPost("{id}/cancel")]
|
||||
public async Task<ActionResult<RegistrationDto>> CancelRegistration(Guid id)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var registration = await _context.Registrations
|
||||
.Include(r => r.Event)
|
||||
.Include(r => r.Participant)
|
||||
.Include(r => r.Payments)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Only participant or event organizer can cancel
|
||||
var isOrganizer = registration.Event.OrganizerId == userId.Value;
|
||||
|
||||
if (registration.ParticipantId != userId.Value && !isOrganizer)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
registration.Status = RegistrationStatus.Cancelled;
|
||||
registration.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(MapToRegistrationDto(registration));
|
||||
}
|
||||
|
||||
private Guid? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static RegistrationDto MapToRegistrationDto(Registration registration)
|
||||
{
|
||||
var totalPaid = registration.Payments?.Sum(p => p.Amount) ?? 0;
|
||||
|
||||
return new RegistrationDto
|
||||
{
|
||||
Id = registration.Id,
|
||||
EventId = registration.EventId,
|
||||
EventName = registration.Event?.Name ?? string.Empty,
|
||||
EventDate = registration.Event?.EventDate ?? DateTime.MinValue,
|
||||
ParticipantId = registration.ParticipantId,
|
||||
ParticipantName = registration.Participant?.Name ?? string.Empty,
|
||||
ParticipantEmail = registration.Participant?.Email ?? string.Empty,
|
||||
Status = registration.Status.ToString(),
|
||||
Category = registration.Category,
|
||||
EmergencyContact = registration.EmergencyContact,
|
||||
CreatedAt = registration.CreatedAt,
|
||||
UpdatedAt = registration.UpdatedAt,
|
||||
TotalPaid = totalPaid,
|
||||
AmountDue = 0 // Will be calculated based on event fee if needed
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace RacePlannerApi.DTOs;
|
||||
|
||||
public class CreateAnnouncementRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid EventId { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(5000)]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class UpdateAnnouncementRequest
|
||||
{
|
||||
[MaxLength(200)]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[MaxLength(5000)]
|
||||
public string? Content { get; set; }
|
||||
|
||||
public bool? IsPublished { get; set; }
|
||||
}
|
||||
|
||||
public class AnnouncementDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid EventId { get; set; }
|
||||
public string EventName { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public Guid AuthorId { get; set; }
|
||||
public string AuthorName { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public bool IsPublished { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.DTOs;
|
||||
|
||||
public class RegisterRequest
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MinLength(8)]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MinLength(2)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public UserRole Role { get; set; } = UserRole.Participant;
|
||||
}
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class AuthResponse
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public UserDto User { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class UserDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Role { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace RacePlannerApi.DTOs;
|
||||
|
||||
public class OrganizerDashboardDto
|
||||
{
|
||||
public int TotalEvents { get; set; }
|
||||
public int PublishedEvents { get; set; }
|
||||
public int DraftEvents { get; set; }
|
||||
public int TotalRegistrations { get; set; }
|
||||
public int PendingRegistrations { get; set; }
|
||||
public int ConfirmedRegistrations { get; set; }
|
||||
public int CancelledRegistrations { get; set; }
|
||||
public decimal TotalRevenue { get; set; }
|
||||
public List<EventSummaryDto> UpcomingEvents { get; set; } = new();
|
||||
public List<EventCapacityDto> EventsNearCapacity { get; set; } = new();
|
||||
}
|
||||
|
||||
public class EventSummaryDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public DateTime EventDate { get; set; }
|
||||
public string Location { get; set; } = string.Empty;
|
||||
public int RegistrationCount { get; set; }
|
||||
public int? MaxParticipants { get; set; }
|
||||
}
|
||||
|
||||
public class EventCapacityDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public DateTime EventDate { get; set; }
|
||||
public int RegistrationCount { get; set; }
|
||||
public int? MaxParticipants { get; set; }
|
||||
public double CapacityPercentage => MaxParticipants.HasValue && MaxParticipants.Value > 0
|
||||
? (double)RegistrationCount / MaxParticipants.Value * 100
|
||||
: 0;
|
||||
}
|
||||
|
||||
public class ParticipantDashboardDto
|
||||
{
|
||||
public int TotalRegistrations { get; set; }
|
||||
public int UpcomingEvents { get; set; }
|
||||
public int CompletedEvents { get; set; }
|
||||
public int CancelledRegistrations { get; set; }
|
||||
public List<RegistrationSummaryDto> MyRegistrations { get; set; } = new();
|
||||
public List<UpcomingEventDto> UpcomingEventList { get; set; } = new();
|
||||
}
|
||||
|
||||
public class RegistrationSummaryDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid EventId { get; set; }
|
||||
public string EventName { get; set; } = string.Empty;
|
||||
public DateTime EventDate { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public DateTime RegisteredAt { get; set; }
|
||||
}
|
||||
|
||||
public class UpcomingEventDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public DateTime EventDate { get; set; }
|
||||
public string Location { get; set; } = string.Empty;
|
||||
public int DaysUntil => (EventDate - DateTime.UtcNow).Days;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.DTOs;
|
||||
|
||||
public class CreateEventRequest
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(2000)]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public DateTime EventDate { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(500)]
|
||||
public string Location { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Category { get; set; }
|
||||
|
||||
public List<string> Tags { get; set; } = new();
|
||||
|
||||
public int? MaxParticipants { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateEventRequest
|
||||
{
|
||||
[MaxLength(200)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[MaxLength(2000)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
public DateTime? EventDate { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Location { get; set; }
|
||||
|
||||
public EventStatus? Status { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Category { get; set; }
|
||||
|
||||
public List<string>? Tags { get; set; }
|
||||
|
||||
public int? MaxParticipants { get; set; }
|
||||
}
|
||||
|
||||
public class EventDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public DateTime EventDate { get; set; }
|
||||
public string Location { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public string? Category { get; set; }
|
||||
public List<string> Tags { get; set; } = new();
|
||||
public int? MaxParticipants { get; set; }
|
||||
public int CurrentRegistrations { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public UserSummaryDto Organizer { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class EventFilterRequest
|
||||
{
|
||||
public string? Category { get; set; }
|
||||
public List<string>? Tags { get; set; }
|
||||
public DateTime? FromDate { get; set; }
|
||||
public DateTime? ToDate { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public Guid? OrganizerId { get; set; }
|
||||
}
|
||||
|
||||
public class UserSummaryDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.DTOs;
|
||||
|
||||
public class CreatePaymentRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid RegistrationId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Range(0.01, double.MaxValue)]
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
[Required]
|
||||
public PaymentMethod Method { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? TransactionId { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public DateTime? PaymentDate { get; set; }
|
||||
}
|
||||
|
||||
public class PaymentDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid RegistrationId { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string Method { get; set; } = string.Empty;
|
||||
public string? TransactionId { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public DateTime PaymentDate { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class PaymentReportDto
|
||||
{
|
||||
public Guid EventId { get; set; }
|
||||
public string EventName { get; set; } = string.Empty;
|
||||
public decimal TotalCollected { get; set; }
|
||||
public decimal TotalPending { get; set; }
|
||||
public decimal TotalOutstanding { get; set; }
|
||||
public int TotalRegistrations { get; set; }
|
||||
public int PaidRegistrations { get; set; }
|
||||
public int PartialRegistrations { get; set; }
|
||||
public int UnpaidRegistrations { get; set; }
|
||||
public List<RegistrationPaymentDto> Registrations { get; set; } = new();
|
||||
}
|
||||
|
||||
public class RegistrationPaymentDto
|
||||
{
|
||||
public Guid RegistrationId { get; set; }
|
||||
public string ParticipantName { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public decimal TotalPaid { get; set; }
|
||||
public decimal AmountDue { get; set; }
|
||||
public string PaymentStatus { get; set; } = string.Empty;
|
||||
public List<PaymentDto> Payments { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.DTOs;
|
||||
|
||||
public class CreateRegistrationRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid EventId { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Category { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? EmergencyContact { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateRegistrationRequest
|
||||
{
|
||||
public RegistrationStatus? Status { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Category { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? EmergencyContact { get; set; }
|
||||
}
|
||||
|
||||
public class RegistrationDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid EventId { get; set; }
|
||||
public string EventName { get; set; } = string.Empty;
|
||||
public DateTime EventDate { get; set; }
|
||||
public Guid ParticipantId { get; set; }
|
||||
public string ParticipantName { get; set; } = string.Empty;
|
||||
public string ParticipantEmail { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public string? Category { get; set; }
|
||||
public string? EmergencyContact { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public decimal TotalPaid { get; set; }
|
||||
public decimal AmountDue { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using RacePlannerApi.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace RacePlannerApi.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(RacePlannerDbContext))]
|
||||
[Migration("20260403185515_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Announcement", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5000)
|
||||
.HasColumnType("character varying(5000)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("EventId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("EventId");
|
||||
|
||||
b.ToTable("Announcements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Event", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTime>("EventDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<int?>("MaxParticipants")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<Guid>("OrganizerId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.PrimitiveCollection<List<string>>("Tags")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Category");
|
||||
|
||||
b.HasIndex("EventDate");
|
||||
|
||||
b.HasIndex("OrganizerId");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("Events");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Payment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("numeric(18,2)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Method")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<DateTime>("PaymentDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("RegistrationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("TransactionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PaymentDate");
|
||||
|
||||
b.HasIndex("RegistrationId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Registration", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EmergencyContact")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<Guid>("EventId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ParticipantId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParticipantId");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("EventId", "ParticipantId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Registrations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Announcement", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.User", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("RacePlannerApi.Models.Event", "Event")
|
||||
.WithMany("Announcements")
|
||||
.HasForeignKey("EventId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Event");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Event", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.User", "Organizer")
|
||||
.WithMany("OrganizedEvents")
|
||||
.HasForeignKey("OrganizerId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Organizer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Payment", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.Registration", "Registration")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("RegistrationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Registration");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Registration", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.Event", "Event")
|
||||
.WithMany("Registrations")
|
||||
.HasForeignKey("EventId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("RacePlannerApi.Models.User", "Participant")
|
||||
.WithMany("Registrations")
|
||||
.HasForeignKey("ParticipantId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Event");
|
||||
|
||||
b.Navigation("Participant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Event", b =>
|
||||
{
|
||||
b.Navigation("Announcements");
|
||||
|
||||
b.Navigation("Registrations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Registration", b =>
|
||||
{
|
||||
b.Navigation("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.User", b =>
|
||||
{
|
||||
b.Navigation("OrganizedEvents");
|
||||
|
||||
b.Navigation("Registrations");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace RacePlannerApi.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Email = table.Column<string>(type: "text", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Role = table.Column<string>(type: "text", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Events",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false),
|
||||
EventDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
Location = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
||||
Status = table.Column<string>(type: "text", nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
Tags = table.Column<List<string>>(type: "text[]", nullable: false),
|
||||
MaxParticipants = table.Column<int>(type: "integer", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
OrganizerId = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Events", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Events_Users_OrganizerId",
|
||||
column: x => x.OrganizerId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Announcements",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Content = table.Column<string>(type: "character varying(5000)", maxLength: 5000, nullable: false),
|
||||
EventId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
AuthorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
IsPublished = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Announcements", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Announcements_Events_EventId",
|
||||
column: x => x.EventId,
|
||||
principalTable: "Events",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Announcements_Users_AuthorId",
|
||||
column: x => x.AuthorId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Registrations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
EventId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ParticipantId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Status = table.Column<string>(type: "text", nullable: false),
|
||||
Category = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
EmergencyContact = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Registrations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Registrations_Events_EventId",
|
||||
column: x => x.EventId,
|
||||
principalTable: "Events",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Registrations_Users_ParticipantId",
|
||||
column: x => x.ParticipantId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Payments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
RegistrationId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Method = table.Column<string>(type: "text", nullable: false),
|
||||
TransactionId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
Notes = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
||||
PaymentDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Payments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Payments_Registrations_RegistrationId",
|
||||
column: x => x.RegistrationId,
|
||||
principalTable: "Registrations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Announcements_AuthorId",
|
||||
table: "Announcements",
|
||||
column: "AuthorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Announcements_CreatedAt",
|
||||
table: "Announcements",
|
||||
column: "CreatedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Announcements_EventId",
|
||||
table: "Announcements",
|
||||
column: "EventId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Events_Category",
|
||||
table: "Events",
|
||||
column: "Category");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Events_EventDate",
|
||||
table: "Events",
|
||||
column: "EventDate");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Events_OrganizerId",
|
||||
table: "Events",
|
||||
column: "OrganizerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Events_Status",
|
||||
table: "Events",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payments_PaymentDate",
|
||||
table: "Payments",
|
||||
column: "PaymentDate");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payments_RegistrationId",
|
||||
table: "Payments",
|
||||
column: "RegistrationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Registrations_EventId_ParticipantId",
|
||||
table: "Registrations",
|
||||
columns: new[] { "EventId", "ParticipantId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Registrations_ParticipantId",
|
||||
table: "Registrations",
|
||||
column: "ParticipantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Registrations_Status",
|
||||
table: "Registrations",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_Email",
|
||||
table: "Users",
|
||||
column: "Email",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Announcements");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Payments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Registrations");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Events");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using RacePlannerApi.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace RacePlannerApi.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(RacePlannerDbContext))]
|
||||
partial class RacePlannerDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Announcement", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("AuthorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasMaxLength(5000)
|
||||
.HasColumnType("character varying(5000)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("EventId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("EventId");
|
||||
|
||||
b.ToTable("Announcements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Event", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTime>("EventDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<int?>("MaxParticipants")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<Guid>("OrganizerId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.PrimitiveCollection<List<string>>("Tags")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Category");
|
||||
|
||||
b.HasIndex("EventDate");
|
||||
|
||||
b.HasIndex("OrganizerId");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("Events");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Payment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("numeric(18,2)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Method")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.Property<DateTime>("PaymentDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("RegistrationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("TransactionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PaymentDate");
|
||||
|
||||
b.HasIndex("RegistrationId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Registration", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EmergencyContact")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<Guid>("EventId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ParticipantId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParticipantId");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("EventId", "ParticipantId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Registrations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Announcement", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.User", "Author")
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("RacePlannerApi.Models.Event", "Event")
|
||||
.WithMany("Announcements")
|
||||
.HasForeignKey("EventId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Author");
|
||||
|
||||
b.Navigation("Event");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Event", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.User", "Organizer")
|
||||
.WithMany("OrganizedEvents")
|
||||
.HasForeignKey("OrganizerId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Organizer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Payment", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.Registration", "Registration")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("RegistrationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Registration");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Registration", b =>
|
||||
{
|
||||
b.HasOne("RacePlannerApi.Models.Event", "Event")
|
||||
.WithMany("Registrations")
|
||||
.HasForeignKey("EventId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("RacePlannerApi.Models.User", "Participant")
|
||||
.WithMany("Registrations")
|
||||
.HasForeignKey("ParticipantId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Event");
|
||||
|
||||
b.Navigation("Participant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Event", b =>
|
||||
{
|
||||
b.Navigation("Announcements");
|
||||
|
||||
b.Navigation("Registrations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.Registration", b =>
|
||||
{
|
||||
b.Navigation("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RacePlannerApi.Models.User", b =>
|
||||
{
|
||||
b.Navigation("OrganizedEvents");
|
||||
|
||||
b.Navigation("Registrations");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.Data;
|
||||
|
||||
public class RacePlannerDbContext : DbContext
|
||||
{
|
||||
public RacePlannerDbContext(DbContextOptions<RacePlannerDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<Event> Events { get; set; }
|
||||
public DbSet<Registration> Registrations { get; set; }
|
||||
public DbSet<Payment> Payments { get; set; }
|
||||
public DbSet<Announcement> Announcements { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// User configurations
|
||||
modelBuilder.Entity<User>(entity =>
|
||||
{
|
||||
entity.HasIndex(u => u.Email).IsUnique();
|
||||
entity.Property(u => u.Role).HasConversion<string>();
|
||||
});
|
||||
|
||||
// Event configurations
|
||||
modelBuilder.Entity<Event>(entity =>
|
||||
{
|
||||
entity.HasIndex(e => e.EventDate);
|
||||
entity.HasIndex(e => e.Status);
|
||||
entity.HasIndex(e => e.Category);
|
||||
entity.Property(e => e.Status).HasConversion<string>();
|
||||
entity.Property(e => e.Tags).HasColumnType("text[]");
|
||||
|
||||
entity.HasOne(e => e.Organizer)
|
||||
.WithMany(u => u.OrganizedEvents)
|
||||
.HasForeignKey(e => e.OrganizerId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
// Registration configurations
|
||||
modelBuilder.Entity<Registration>(entity =>
|
||||
{
|
||||
entity.HasIndex(r => new { r.EventId, r.ParticipantId }).IsUnique();
|
||||
entity.HasIndex(r => r.Status);
|
||||
entity.Property(r => r.Status).HasConversion<string>();
|
||||
|
||||
entity.HasOne(r => r.Event)
|
||||
.WithMany(e => e.Registrations)
|
||||
.HasForeignKey(r => r.EventId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(r => r.Participant)
|
||||
.WithMany(u => u.Registrations)
|
||||
.HasForeignKey(r => r.ParticipantId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
// Payment configurations
|
||||
modelBuilder.Entity<Payment>(entity =>
|
||||
{
|
||||
entity.HasIndex(p => p.RegistrationId);
|
||||
entity.HasIndex(p => p.PaymentDate);
|
||||
entity.Property(p => p.Method).HasConversion<string>();
|
||||
entity.Property(p => p.Amount).HasPrecision(18, 2); // For decimal Amount
|
||||
|
||||
entity.HasOne(p => p.Registration)
|
||||
.WithMany(r => r.Payments)
|
||||
.HasForeignKey(p => p.RegistrationId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Announcement configurations
|
||||
modelBuilder.Entity<Announcement>(entity =>
|
||||
{
|
||||
entity.HasIndex(a => a.EventId);
|
||||
entity.HasIndex(a => a.CreatedAt);
|
||||
|
||||
entity.HasOne(a => a.Event)
|
||||
.WithMany(e => e.Announcements)
|
||||
.HasForeignKey(a => a.EventId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(a => a.Author)
|
||||
.WithMany()
|
||||
.HasForeignKey(a => a.AuthorId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace RacePlannerApi.Models;
|
||||
|
||||
public class Announcement
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(5000)]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public Guid EventId { get; set; }
|
||||
|
||||
[ForeignKey("EventId")]
|
||||
public Event Event { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public Guid AuthorId { get; set; }
|
||||
|
||||
[ForeignKey("AuthorId")]
|
||||
public User Author { get; set; } = null!;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public bool IsPublished { get; set; } = true;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace RacePlannerApi.Models;
|
||||
|
||||
public class Event
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(2000)]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public DateTime EventDate { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(500)]
|
||||
public string Location { get; set; } = string.Empty;
|
||||
|
||||
public EventStatus Status { get; set; } = EventStatus.Draft;
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Category { get; set; }
|
||||
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
|
||||
public int? MaxParticipants { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Foreign keys
|
||||
[Required]
|
||||
public Guid OrganizerId { get; set; }
|
||||
|
||||
[ForeignKey("OrganizerId")]
|
||||
public User Organizer { get; set; } = null!;
|
||||
|
||||
// Navigation properties
|
||||
public ICollection<Registration> Registrations { get; set; } = new List<Registration>();
|
||||
public ICollection<Announcement> Announcements { get; set; } = new List<Announcement>();
|
||||
}
|
||||
|
||||
public enum EventStatus
|
||||
{
|
||||
Draft,
|
||||
Published,
|
||||
Cancelled,
|
||||
Completed
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace RacePlannerApi.Models;
|
||||
|
||||
public class Payment
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
public Guid RegistrationId { get; set; }
|
||||
|
||||
[ForeignKey("RegistrationId")]
|
||||
public Registration Registration { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
[Required]
|
||||
public PaymentMethod Method { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? TransactionId { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public DateTime PaymentDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public enum PaymentMethod
|
||||
{
|
||||
Cash,
|
||||
Online,
|
||||
Transfer
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace RacePlannerApi.Models;
|
||||
|
||||
public class Registration
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
public Guid EventId { get; set; }
|
||||
|
||||
[ForeignKey("EventId")]
|
||||
public Event Event { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public Guid ParticipantId { get; set; }
|
||||
|
||||
[ForeignKey("ParticipantId")]
|
||||
public User Participant { get; set; } = null!;
|
||||
|
||||
public RegistrationStatus Status { get; set; } = RegistrationStatus.Pending;
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Category { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? EmergencyContact { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public ICollection<Payment> Payments { get; set; } = new List<Payment>();
|
||||
}
|
||||
|
||||
public enum RegistrationStatus
|
||||
{
|
||||
Pending,
|
||||
Confirmed,
|
||||
Cancelled,
|
||||
Completed
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace RacePlannerApi.Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string PasswordHash { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public UserRole Role { get; set; } = UserRole.Participant;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public ICollection<Event> OrganizedEvents { get; set; } = new List<Event>();
|
||||
public ICollection<Registration> Registrations { get; set; } = new List<Registration>();
|
||||
}
|
||||
|
||||
public enum UserRole
|
||||
{
|
||||
Participant,
|
||||
Organizer
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using RacePlannerApi.Data;
|
||||
using RacePlannerApi.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
// Configure JWT Authentication
|
||||
var jwtKey = builder.Configuration["Jwt:Key"] ?? "your-secret-key-here-minimum-32-characters-long";
|
||||
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "RacePlannerApi";
|
||||
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "RacePlannerClient";
|
||||
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = jwtIssuer,
|
||||
ValidAudience = jwtAudience,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
|
||||
};
|
||||
});
|
||||
|
||||
// Register services
|
||||
builder.Services.AddScoped<JwtTokenService>();
|
||||
|
||||
// Configure Entity Framework Core with PostgreSQL
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
||||
?? "Host=localhost;Database=RacePlanner;Username=postgres;Password=postgres";
|
||||
|
||||
builder.Services.AddDbContext<RacePlannerDbContext>(options =>
|
||||
options.UseNpgsql(connectionString));
|
||||
|
||||
// Add CORS for frontend
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowFrontend", policy =>
|
||||
{
|
||||
policy.WithOrigins("http://localhost:3000")
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
// Apply CORS
|
||||
app.UseCors("AllowFrontend");
|
||||
|
||||
// Authentication & Authorization
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5123",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7269;http://localhost:5123",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
@RacePlannerApi_HostAddress = http://localhost:5123
|
||||
|
||||
GET {{RacePlannerApi_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using RacePlannerApi.Models;
|
||||
|
||||
namespace RacePlannerApi.Services;
|
||||
|
||||
public class JwtTokenService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public JwtTokenService(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public string GenerateToken(User user)
|
||||
{
|
||||
var securityKey = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? "your-secret-key-here-minimum-32-characters-long"));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Email, user.Email),
|
||||
new Claim(JwtRegisteredClaimNames.Name, user.Name),
|
||||
new Claim("role", user.Role.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: _configuration["Jwt:Issuer"] ?? "RacePlannerApi",
|
||||
audience: _configuration["Jwt:Audience"] ?? "RacePlannerClient",
|
||||
claims: claims,
|
||||
expires: DateTime.Now.AddHours(24),
|
||||
signingCredentials: credentials
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Jwt": {
|
||||
"Key": "${JWT_SECRET_KEY}",
|
||||
"Issuer": "RacePlannerApi",
|
||||
"Audience": "RacePlannerClient"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "${DATABASE_URL}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: RacePlanner
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
ASPNETCORE_ENVIRONMENT: Production
|
||||
ConnectionStrings__DefaultConnection: Host=db;Database=RacePlanner;Username=postgres;Password=postgres
|
||||
Jwt__Key: your-super-secret-key-minimum-32-characters-long-here
|
||||
ports:
|
||||
- "5000:80"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
NEXT_PUBLIC_API_URL: http://localhost:5000/api
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -0,0 +1,5 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
@@ -0,0 +1 @@
|
||||
@AGENTS.md
|
||||
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
@@ -0,0 +1,895 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"dependencies": {
|
||||
"next": "16.2.2",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.2",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"sharp",
|
||||
"unrs-resolver",
|
||||
],
|
||||
"packages": {
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
|
||||
|
||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
|
||||
|
||||
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
|
||||
|
||||
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
|
||||
|
||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
|
||||
|
||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
|
||||
|
||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
|
||||
|
||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
|
||||
|
||||
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
|
||||
|
||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
"@next/env": ["@next/env@16.2.2", "", {}, "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ=="],
|
||||
|
||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.2", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-IOPbWzDQ+76AtjZioaCjpIY72xNSDMnarZ2GMQ4wjNLvnJEJHqxQwGFhgnIWLV9klb4g/+amg88Tk5OXVpyLTw=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
||||
|
||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||
|
||||
"@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="],
|
||||
|
||||
"@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g=="],
|
||||
|
||||
"@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ=="],
|
||||
|
||||
"@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.11.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.11.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.11.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w=="],
|
||||
|
||||
"@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA=="],
|
||||
|
||||
"@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.11.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ=="],
|
||||
|
||||
"@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw=="],
|
||||
|
||||
"@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ=="],
|
||||
|
||||
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||
|
||||
"array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
|
||||
|
||||
"array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
|
||||
|
||||
"array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
|
||||
|
||||
"array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
|
||||
|
||||
"array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
|
||||
|
||||
"array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="],
|
||||
|
||||
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
|
||||
|
||||
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
|
||||
|
||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"axe-core": ["axe-core@4.11.2", "", {}, "sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
|
||||
|
||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||
|
||||
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
|
||||
|
||||
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
|
||||
|
||||
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
||||
|
||||
"eslint-config-next": ["eslint-config-next@16.2.2", "", { "dependencies": { "@next/eslint-plugin-next": "16.2.2", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-6VlvEhwoug2JpVgjZDhyXrJXUEuPY++TddzIpTaIRvlvlXXFgvQUtm3+Zr84IjFm0lXtJt73w19JA08tOaZVwg=="],
|
||||
|
||||
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="],
|
||||
|
||||
"eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="],
|
||||
|
||||
"eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="],
|
||||
|
||||
"eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
|
||||
|
||||
"eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="],
|
||||
|
||||
"eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
|
||||
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||
|
||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
|
||||
|
||||
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
|
||||
|
||||
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||
|
||||
"is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
|
||||
|
||||
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
|
||||
|
||||
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
|
||||
|
||||
"is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="],
|
||||
|
||||
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="],
|
||||
|
||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
|
||||
|
||||
"is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
||||
|
||||
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
||||
|
||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||
|
||||
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
|
||||
|
||||
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
|
||||
|
||||
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
|
||||
|
||||
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
|
||||
|
||||
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
|
||||
|
||||
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
|
||||
|
||||
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
|
||||
|
||||
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
||||
|
||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||
|
||||
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="],
|
||||
|
||||
"language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"next": ["next@16.2.2", "", { "dependencies": { "@next/env": "16.2.2", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.2", "@next/swc-darwin-x64": "16.2.2", "@next/swc-linux-arm64-gnu": "16.2.2", "@next/swc-linux-arm64-musl": "16.2.2", "@next/swc-linux-x64-gnu": "16.2.2", "@next/swc-linux-x64-musl": "16.2.2", "@next/swc-win32-arm64-msvc": "16.2.2", "@next/swc-win32-x64-msvc": "16.2.2", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A=="],
|
||||
|
||||
"node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
||||
|
||||
"object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="],
|
||||
|
||||
"object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
|
||||
|
||||
"object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="],
|
||||
|
||||
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
||||
|
||||
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
||||
|
||||
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||
|
||||
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
||||
|
||||
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
|
||||
|
||||
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
|
||||
|
||||
"string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="],
|
||||
|
||||
"string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="],
|
||||
|
||||
"string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="],
|
||||
|
||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
|
||||
|
||||
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
||||
|
||||
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
|
||||
|
||||
"typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="],
|
||||
|
||||
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="],
|
||||
|
||||
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
|
||||
"which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
|
||||
|
||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
||||
|
||||
"which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.2.2",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.2",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"ignoreScripts": [
|
||||
"sharp",
|
||||
"unrs-resolver"
|
||||
],
|
||||
"trustedDependencies": [
|
||||
"sharp",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
@@ -0,0 +1,20 @@
|
||||
import { AnnouncementList } from '@/components/announcement-list';
|
||||
|
||||
interface AnnouncementsPageProps {
|
||||
params: Promise<{
|
||||
eventId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default async function AnnouncementsPage({ params }: AnnouncementsPageProps) {
|
||||
const { eventId } = await params;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<h1 className="text-3xl font-bold mb-8">Announcements</h1>
|
||||
<AnnouncementList eventId={eventId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Dashboard } from '@/components/dashboard';
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<h1 className="text-3xl font-bold mb-8">Dashboard</h1>
|
||||
<Dashboard />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { EventDetail } from '@/components/event-detail';
|
||||
|
||||
interface EventPageProps {
|
||||
params: Promise<{
|
||||
eventId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default async function EventPage({ params }: EventPageProps) {
|
||||
const { eventId } = await params;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<EventDetail eventId={eventId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { RegistrationForm } from '@/components/registration-form';
|
||||
|
||||
interface RegisterPageProps {
|
||||
params: Promise<{
|
||||
eventId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default async function RegisterPage({ params }: RegisterPageProps) {
|
||||
const { eventId } = await params;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<RegistrationForm eventId={eventId} eventName="Event" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { EventForm } from '@/components/event-form';
|
||||
|
||||
export default function CreateEventPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<h1 className="text-3xl font-bold mb-8">Create Event</h1>
|
||||
<EventForm />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { EventList } from '@/components/event-list';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default function EventsPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h1 className="text-3xl font-bold">Events</h1>
|
||||
<a
|
||||
href="/events/create"
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Create Event
|
||||
</a>
|
||||
</div>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<EventList />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,26 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { AuthProvider } from "@/lib/auth-context";
|
||||
import { Navigation } from "@/components/navigation";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "RacePlanner - Event Management",
|
||||
description: "Plan and manage race events",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col bg-gray-50">
|
||||
<AuthProvider>
|
||||
<Navigation />
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { LoginForm } from '@/components/login-form';
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<LoginForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { PaymentForm } from '@/components/payment-form';
|
||||
|
||||
interface PaymentPageProps {
|
||||
params: Promise<{
|
||||
registrationId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default async function PaymentPage({ params }: PaymentPageProps) {
|
||||
const { registrationId } = await params;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<PaymentForm registrationId={registrationId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { RegisterForm } from '@/components/register-form';
|
||||
|
||||
export default function RegisterPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<RegisterForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { RegistrationList } from '@/components/registration-list';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default function RegistrationsPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<h1 className="text-3xl font-bold mb-8">My Registrations</h1>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<RegistrationList />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { api } from '@/lib/api';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface AnnouncementFormProps {
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export function AnnouncementForm({ eventId }: AnnouncementFormProps) {
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await api.createAnnouncement(eventId, title, content);
|
||||
router.push(`/events/${eventId}`);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to create announcement');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto">
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="bg-white rounded-lg shadow-md p-6 space-y-4">
|
||||
<h2 className="text-xl font-bold mb-4">Create Announcement</h2>
|
||||
|
||||
<div>
|
||||
<label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
id="title"
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
required
|
||||
maxLength={200}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="content" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Content
|
||||
</label>
|
||||
<textarea
|
||||
id="content"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
required
|
||||
rows={6}
|
||||
maxLength={5000}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||||
>
|
||||
{isSubmitting ? 'Posting...' : 'Post Announcement'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { api, Announcement } from '@/lib/api';
|
||||
|
||||
interface AnnouncementListProps {
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export function AnnouncementList({ eventId }: AnnouncementListProps) {
|
||||
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadAnnouncements();
|
||||
}, [eventId]);
|
||||
|
||||
const loadAnnouncements = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data = await api.getEventAnnouncements(eventId);
|
||||
setAnnouncements(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load announcements');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="text-center py-4">Loading announcements...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded text-sm">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (announcements.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-4 text-gray-500 text-sm">
|
||||
No announcements yet.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{announcements.map((announcement) => (
|
||||
<div key={announcement.id} className="bg-white rounded-lg shadow-sm border p-4">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h3 className="font-semibold text-lg">{announcement.title}</h3>
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(announcement.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-700 whitespace-pre-wrap">{announcement.content}</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
Posted by {announcement.authorName}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
import { api } from '@/lib/api';
|
||||
|
||||
export function Dashboard() {
|
||||
const { user } = useAuth();
|
||||
const [dashboard, setDashboard] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadDashboard();
|
||||
}, []);
|
||||
|
||||
const loadDashboard = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
if (user?.role === 'Organizer') {
|
||||
const data = await api.getOrganizerDashboard();
|
||||
setDashboard(data);
|
||||
} else {
|
||||
const data = await api.getParticipantDashboard();
|
||||
setDashboard(data);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load dashboard');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="text-center py-8">Loading dashboard...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!dashboard) {
|
||||
return <div className="text-center py-8">No data available</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Quick Actions */}
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
{user?.role === 'Organizer' ? (
|
||||
<>
|
||||
<a href="/events/create" className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
||||
Create Event
|
||||
</a>
|
||||
<a href="/events" className="px-6 py-3 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300">
|
||||
Manage Events
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<a href="/events" className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
||||
Browse Events
|
||||
</a>
|
||||
<a href="/registrations" className="px-6 py-3 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300">
|
||||
My Registrations
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
{user?.role === 'Organizer' ? (
|
||||
<OrganizerDashboard data={dashboard} />
|
||||
) : (
|
||||
<ParticipantDashboard data={dashboard} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OrganizerDashboard({ data }: { data: any }) {
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<StatCard title="Total Events" value={data.totalEvents} />
|
||||
<StatCard title="Published" value={data.publishedEvents} color="green" />
|
||||
<StatCard title="Draft" value={data.draftEvents} color="yellow" />
|
||||
<StatCard title="Total Registrations" value={data.totalRegistrations} color="blue" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Upcoming Events</h3>
|
||||
{data.upcomingEvents?.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{data.upcomingEvents.map((event: any) => (
|
||||
<div key={event.id} className="border-b pb-3 last:border-0">
|
||||
<a href={`/events/${event.id}`} className="font-medium hover:text-blue-600">
|
||||
{event.name}
|
||||
</a>
|
||||
<p className="text-sm text-gray-500">
|
||||
{new Date(event.eventDate).toLocaleDateString()} - {event.registrationCount} registered
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500">No upcoming events</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Revenue</h3>
|
||||
<div className="text-3xl font-bold text-green-600 mb-2">
|
||||
${data.totalRevenue?.toFixed(2) || '0.00'}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4 mt-4 text-sm">
|
||||
<div className="text-center">
|
||||
<div className="font-bold text-green-600">{data.paidRegistrations}</div>
|
||||
<div className="text-gray-500">Paid</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="font-bold text-yellow-600">{data.pendingRegistrations}</div>
|
||||
<div className="text-gray-500">Pending</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="font-bold text-red-600">{data.cancelledRegistrations}</div>
|
||||
<div className="text-gray-500">Cancelled</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ParticipantDashboard({ data }: { data: any }) {
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<StatCard title="Total Registrations" value={data.totalRegistrations} />
|
||||
<StatCard title="Upcoming Events" value={data.upcomingEvents} color="green" />
|
||||
<StatCard title="Completed" value={data.completedEvents} color="blue" />
|
||||
<StatCard title="Cancelled" value={data.cancelledRegistrations} color="red" />
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">My Recent Registrations</h3>
|
||||
{data.myRegistrations?.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{data.myRegistrations.slice(0, 5).map((reg: any) => (
|
||||
<div key={reg.id} className="flex justify-between items-center border-b pb-3 last:border-0">
|
||||
<div>
|
||||
<a href={`/events/${reg.eventId}`} className="font-medium hover:text-blue-600">
|
||||
{reg.eventName}
|
||||
</a>
|
||||
<p className="text-sm text-gray-500">
|
||||
{new Date(reg.eventDate).toLocaleDateString()} -
|
||||
<span className={`ml-2 px-2 py-1 text-xs rounded ${
|
||||
reg.status === 'Confirmed' ? 'bg-green-100 text-green-800' :
|
||||
reg.status === 'Pending' ? 'bg-yellow-100 text-yellow-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{reg.status}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500">
|
||||
No registrations yet. <a href="/events" className="text-blue-600 hover:underline">Browse events</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function StatCard({ title, value, color = 'gray' }: { title: string; value: number; color?: string }) {
|
||||
const colorClasses: Record<string, string> = {
|
||||
gray: 'bg-white',
|
||||
green: 'bg-green-50',
|
||||
yellow: 'bg-yellow-50',
|
||||
blue: 'bg-blue-50',
|
||||
red: 'bg-red-50',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${colorClasses[color]} rounded-lg shadow-md p-6`}>
|
||||
<h3 className="text-sm font-medium text-gray-500 mb-2">{title}</h3>
|
||||
<div className="text-3xl font-bold">{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { api, Event } from '@/lib/api';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
|
||||
interface EventDetailProps {
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export function EventDetail({ eventId }: EventDetailProps) {
|
||||
const [event, setEvent] = useState<Event | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
loadEvent();
|
||||
}, [eventId]);
|
||||
|
||||
const loadEvent = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data = await api.getEvent(eventId);
|
||||
setEvent(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load event');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="text-center py-8">Loading...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
return <div className="text-center py-8">Event not found</div>;
|
||||
}
|
||||
|
||||
const isOrganizer = user?.id === event.organizer.id;
|
||||
const canEdit = isOrganizer || user?.role === 'Organizer';
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<span className={`inline-block px-2 py-1 text-xs rounded mb-2 ${
|
||||
event.status === 'Published' ? 'bg-green-100 text-green-800' :
|
||||
event.status === 'Draft' ? 'bg-yellow-100 text-yellow-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{event.status}
|
||||
</span>
|
||||
<h1 className="text-3xl font-bold">{event.name}</h1>
|
||||
</div>
|
||||
{canEdit && (
|
||||
<a
|
||||
href={`/events/${event.id}/edit`}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Edit Event
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">📅</span>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Date</p>
|
||||
<p className="font-medium">{new Date(event.eventDate).toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">📍</span>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Location</p>
|
||||
<p className="font-medium">{event.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">👥</span>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Registrations</p>
|
||||
<p className="font-medium">
|
||||
{event.currentRegistrations} registered
|
||||
{event.maxParticipants && ` / ${event.maxParticipants} max`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{event.category && (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">🏷️</span>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Category</p>
|
||||
<p className="font-medium">{event.category}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">👤</span>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Organizer</p>
|
||||
<p className="font-medium">{event.organizer.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
{event.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{event.tags.map((tag) => (
|
||||
<span key={tag} className="px-2 py-1 bg-gray-100 text-gray-700 text-sm rounded">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-6">
|
||||
<h2 className="text-xl font-semibold mb-3">Description</h2>
|
||||
<p className="text-gray-700 whitespace-pre-wrap">{event.description}</p>
|
||||
</div>
|
||||
|
||||
{user?.role === 'Participant' && event.status === 'Published' && (
|
||||
<div className="border-t pt-6 mt-6">
|
||||
<a
|
||||
href={`/events/${event.id}/register`}
|
||||
className="block text-center py-3 px-6 bg-green-600 text-white rounded-md hover:bg-green-700 font-semibold"
|
||||
>
|
||||
Register for Event
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { api, Event } from '@/lib/api';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface EventFormProps {
|
||||
event?: Event;
|
||||
}
|
||||
|
||||
export function EventForm({ event }: EventFormProps) {
|
||||
const [name, setName] = useState(event?.name || '');
|
||||
const [description, setDescription] = useState(event?.description || '');
|
||||
const [eventDate, setEventDate] = useState(event ? new Date(event.eventDate).toISOString().slice(0, 16) : '');
|
||||
const [location, setLocation] = useState(event?.location || '');
|
||||
const [category, setCategory] = useState(event?.category || '');
|
||||
const [maxParticipants, setMaxParticipants] = useState(event?.maxParticipants?.toString() || '');
|
||||
const [tags, setTags] = useState(event?.tags.join(', ') || '');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const categories = ['Running', 'Cycling', 'Triathlon', 'Trail', 'Road'];
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const eventData = {
|
||||
name,
|
||||
description,
|
||||
eventDate: new Date(eventDate).toISOString(),
|
||||
location,
|
||||
category: category || undefined,
|
||||
maxParticipants: maxParticipants ? parseInt(maxParticipants) : undefined,
|
||||
tags: tags.split(',').map(t => t.trim()).filter(t => t),
|
||||
};
|
||||
|
||||
if (event) {
|
||||
await api.updateEvent(event.id, eventData);
|
||||
} else {
|
||||
await api.createEvent(eventData);
|
||||
}
|
||||
|
||||
router.push('/events');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to save event');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6 bg-white rounded-lg shadow-md p-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Event Name *
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
rows={4}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="eventDate" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Event Date & Time *
|
||||
</label>
|
||||
<input
|
||||
id="eventDate"
|
||||
type="datetime-local"
|
||||
value={eventDate}
|
||||
onChange={(e) => setEventDate(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="location" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Location *
|
||||
</label>
|
||||
<input
|
||||
id="location"
|
||||
type="text"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="category" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Category
|
||||
</label>
|
||||
<select
|
||||
id="category"
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Select Category</option>
|
||||
{categories.map((cat) => (
|
||||
<option key={cat} value={cat}>{cat}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="maxParticipants" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Max Participants
|
||||
</label>
|
||||
<input
|
||||
id="maxParticipants"
|
||||
type="number"
|
||||
min="1"
|
||||
value={maxParticipants}
|
||||
onChange={(e) => setMaxParticipants(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="tags" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Tags (comma-separated)
|
||||
</label>
|
||||
<input
|
||||
id="tags"
|
||||
type="text"
|
||||
value={tags}
|
||||
onChange={(e) => setTags(e.target.value)}
|
||||
placeholder="e.g., beginner, competitive, charity"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||||
>
|
||||
{isSubmitting ? 'Saving...' : (event ? 'Update Event' : 'Create Event')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push('/events')}
|
||||
className="py-2 px-4 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { api, Event } from '@/lib/api';
|
||||
|
||||
export function EventList() {
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [category, setCategory] = useState('');
|
||||
const [status, setStatus] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadEvents();
|
||||
}, [category, status]);
|
||||
|
||||
const loadEvents = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data = await api.getEvents({
|
||||
category: category || undefined,
|
||||
status: status || undefined,
|
||||
});
|
||||
setEvents(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load events');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const categories = ['Running', 'Cycling', 'Triathlon', 'Trail', 'Road'];
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="text-center py-8">Loading events...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Filters */}
|
||||
<div className="flex gap-4 mb-6">
|
||||
<select
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md"
|
||||
>
|
||||
<option value="">All Categories</option>
|
||||
{categories.map((cat) => (
|
||||
<option key={cat} value={cat}>{cat}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select
|
||||
value={status}
|
||||
onChange={(e) => setStatus(e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md"
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
<option value="Published">Published</option>
|
||||
<option value="Draft">Draft</option>
|
||||
<option value="Cancelled">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Events Grid */}
|
||||
{events.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">No events found</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{events.map((event) => (
|
||||
<div key={event.id} className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className={`px-2 py-1 text-xs rounded ${
|
||||
event.status === 'Published' ? 'bg-green-100 text-green-800' :
|
||||
event.status === 'Draft' ? 'bg-yellow-100 text-yellow-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{event.status}
|
||||
</span>
|
||||
{event.category && (
|
||||
<span className="text-sm text-gray-500">{event.category}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold mb-2">{event.name}</h3>
|
||||
<p className="text-gray-600 mb-4 line-clamp-2">{event.description}</p>
|
||||
|
||||
<div className="space-y-2 text-sm text-gray-500">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>📅</span>
|
||||
{new Date(event.eventDate).toLocaleDateString()}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>📍</span>
|
||||
{event.location}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>👥</span>
|
||||
{event.currentRegistrations}
|
||||
{event.maxParticipants && ` / ${event.maxParticipants}`} registered
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{event.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mt-4">
|
||||
{event.tags.map((tag) => (
|
||||
<span key={tag} className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<a
|
||||
href={`/events/${event.id}`}
|
||||
className="mt-4 block text-center py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||
>
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export function LoginForm() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const { login, error } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await login(email, password);
|
||||
router.push('/dashboard');
|
||||
} catch (err) {
|
||||
// Error is handled by auth context
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-bold mb-6 text-gray-800">Login</h2>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-gray-400 transition-colors"
|
||||
>
|
||||
{isSubmitting ? 'Logging in...' : 'Login'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="mt-4 text-center text-sm text-gray-600">
|
||||
Don't have an account?{' '}
|
||||
<a href="/register" className="text-blue-600 hover:underline">
|
||||
Register
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
'use client';
|
||||
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
|
||||
export function Navigation() {
|
||||
const { user, logout, isAuthenticated } = useAuth();
|
||||
|
||||
return (
|
||||
<nav className="bg-white shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className="flex items-center gap-8">
|
||||
<a href="/" className="text-xl font-bold text-blue-600">
|
||||
RacePlanner
|
||||
</a>
|
||||
<a href="/events" className="text-gray-700 hover:text-blue-600">
|
||||
Events
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
<a href="/dashboard" className="text-gray-700 hover:text-blue-600">
|
||||
Dashboard
|
||||
</a>
|
||||
<a href="/registrations" className="text-gray-700 hover:text-blue-600">
|
||||
My Registrations
|
||||
</a>
|
||||
<span className="text-sm text-gray-500">{user?.name}</span>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="px-4 py-2 text-sm text-red-600 hover:text-red-700"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<a href="/login" className="text-gray-700 hover:text-blue-600">
|
||||
Login
|
||||
</a>
|
||||
<a
|
||||
href="/register"
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm"
|
||||
>
|
||||
Register
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { api } from '@/lib/api';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface PaymentFormProps {
|
||||
registrationId: string;
|
||||
}
|
||||
|
||||
export function PaymentForm({ registrationId }: PaymentFormProps) {
|
||||
const [amount, setAmount] = useState('');
|
||||
const [method, setMethod] = useState('Cash');
|
||||
const [transactionId, setTransactionId] = useState('');
|
||||
const [notes, setNotes] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await api.recordPayment(
|
||||
registrationId,
|
||||
parseFloat(amount),
|
||||
method,
|
||||
transactionId || undefined,
|
||||
notes || undefined
|
||||
);
|
||||
router.push('/registrations');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Payment failed');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto">
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="bg-white rounded-lg shadow-md p-6 space-y-4">
|
||||
<h2 className="text-xl font-bold mb-4">Record Payment</h2>
|
||||
|
||||
<div>
|
||||
<label htmlFor="amount" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Amount ($)
|
||||
</label>
|
||||
<input
|
||||
id="amount"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="method" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Payment Method
|
||||
</label>
|
||||
<select
|
||||
id="method"
|
||||
value={method}
|
||||
onChange={(e) => setMethod(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="Cash">Cash</option>
|
||||
<option value="Online">Online</option>
|
||||
<option value="Transfer">Transfer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="transactionId" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Transaction ID (optional)
|
||||
</label>
|
||||
<input
|
||||
id="transactionId"
|
||||
type="text"
|
||||
value={transactionId}
|
||||
onChange={(e) => setTransactionId(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="notes" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Notes (optional)
|
||||
</label>
|
||||
<textarea
|
||||
id="notes"
|
||||
value={notes}
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full py-2 px-4 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:bg-gray-400"
|
||||
>
|
||||
{isSubmitting ? 'Recording...' : 'Record Payment'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export function RegisterForm() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
const [role, setRole] = useState<'Organizer' | 'Participant'>('Participant');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [validationError, setValidationError] = useState<string | null>(null);
|
||||
const { register, error } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setValidationError(null);
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setValidationError('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
setValidationError('Password must be at least 8 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await register(email, password, name, role);
|
||||
router.push('/dashboard');
|
||||
} catch (err) {
|
||||
// Error is handled by auth context
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-bold mb-6 text-gray-800">Register</h2>
|
||||
|
||||
{(error || validationError) && (
|
||||
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{error || validationError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Confirm Password
|
||||
</label>
|
||||
<input
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="role" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Account Type
|
||||
</label>
|
||||
<select
|
||||
id="role"
|
||||
value={role}
|
||||
onChange={(e) => setRole(e.target.value as 'Organizer' | 'Participant')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="Participant">Participant</option>
|
||||
<option value="Organizer">Organizer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-gray-400 transition-colors"
|
||||
>
|
||||
{isSubmitting ? 'Registering...' : 'Register'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="mt-4 text-center text-sm text-gray-600">
|
||||
Already have an account?{' '}
|
||||
<a href="/login" className="text-blue-600 hover:underline">
|
||||
Login
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { api } from '@/lib/api';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface RegistrationFormProps {
|
||||
eventId: string;
|
||||
eventName: string;
|
||||
}
|
||||
|
||||
export function RegistrationForm({ eventId, eventName }: RegistrationFormProps) {
|
||||
const [category, setCategory] = useState('');
|
||||
const [emergencyContact, setEmergencyContact] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await api.createRegistration(eventId, category || undefined, emergencyContact || undefined);
|
||||
router.push('/registrations');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Registration failed');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto">
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="bg-white rounded-lg shadow-md p-6 space-y-4">
|
||||
<h2 className="text-xl font-bold mb-4">Register for {eventName}</h2>
|
||||
|
||||
<div>
|
||||
<label htmlFor="category" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Category (optional)
|
||||
</label>
|
||||
<input
|
||||
id="category"
|
||||
type="text"
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
placeholder="e.g., Elite, Amateur, Junior"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="emergencyContact" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Emergency Contact
|
||||
</label>
|
||||
<input
|
||||
id="emergencyContact"
|
||||
type="text"
|
||||
value={emergencyContact}
|
||||
onChange={(e) => setEmergencyContact(e.target.value)}
|
||||
placeholder="Name and phone number"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full py-2 px-4 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:bg-gray-400"
|
||||
>
|
||||
{isSubmitting ? 'Registering...' : 'Complete Registration'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { api, Registration } from '@/lib/api';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
|
||||
export function RegistrationList() {
|
||||
const [registrations, setRegistrations] = useState<Registration[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
loadRegistrations();
|
||||
}, []);
|
||||
|
||||
const loadRegistrations = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data = await api.getMyRegistrations();
|
||||
setRegistrations(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load registrations');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = async (id: string) => {
|
||||
if (!confirm('Are you sure you want to cancel this registration?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.cancelRegistration(id);
|
||||
loadRegistrations();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to cancel registration');
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'Confirmed':
|
||||
return 'bg-green-100 text-green-800';
|
||||
case 'Pending':
|
||||
return 'bg-yellow-100 text-yellow-800';
|
||||
case 'Cancelled':
|
||||
return 'bg-red-100 text-red-800';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="text-center py-8">Loading registrations...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{registrations.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<p className="mb-4">You haven't registered for any events yet.</p>
|
||||
<a href="/events" className="text-blue-600 hover:underline">
|
||||
Browse events
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{registrations.map((registration) => (
|
||||
<div key={registration.id} className="bg-white rounded-lg shadow-md p-6">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className={`px-2 py-1 text-xs rounded ${getStatusColor(registration.status)}`}>
|
||||
{registration.status}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
{new Date(registration.eventDate).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">{registration.eventName}</h3>
|
||||
<p className="text-sm text-gray-500 mb-2">
|
||||
Registered: {new Date(registration.registeredAt).toLocaleDateString()}
|
||||
</p>
|
||||
{registration.totalPaid > 0 && (
|
||||
<p className="text-sm text-green-600">
|
||||
Paid: ${registration.totalPaid.toFixed(2)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<a
|
||||
href={`/events/${registration.eventId}`}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm text-center"
|
||||
>
|
||||
View Event
|
||||
</a>
|
||||
{registration.status !== 'Cancelled' && registration.status !== 'Completed' && (
|
||||
<button
|
||||
onClick={() => handleCancel(registration.id)}
|
||||
className="px-4 py-2 border border-red-500 text-red-500 rounded-md hover:bg-red-50 text-sm"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000/api';
|
||||
|
||||
// Types
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
role: 'Organizer' | 'Participant';
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface Event {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
eventDate: string;
|
||||
location: string;
|
||||
status: 'Draft' | 'Published' | 'Cancelled' | 'Completed';
|
||||
category?: string;
|
||||
tags: string[];
|
||||
maxParticipants?: number;
|
||||
currentRegistrations: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
organizer: User;
|
||||
}
|
||||
|
||||
export interface Registration {
|
||||
id: string;
|
||||
eventId: string;
|
||||
eventName: string;
|
||||
eventDate: string;
|
||||
participantId: string;
|
||||
participantName: string;
|
||||
participantEmail: string;
|
||||
status: string;
|
||||
category?: string;
|
||||
emergencyContact?: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
totalPaid: number;
|
||||
amountDue: number;
|
||||
}
|
||||
|
||||
export interface Announcement {
|
||||
id: string;
|
||||
eventId: string;
|
||||
eventName: string;
|
||||
title: string;
|
||||
content: string;
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
isPublished: boolean;
|
||||
}
|
||||
|
||||
export interface PaymentReport {
|
||||
eventId: string;
|
||||
eventName: string;
|
||||
totalCollected: number;
|
||||
totalPending: number;
|
||||
totalOutstanding: number;
|
||||
totalRegistrations: number;
|
||||
paidRegistrations: number;
|
||||
partialRegistrations: number;
|
||||
unpaidRegistrations: number;
|
||||
}
|
||||
|
||||
// API Client
|
||||
class ApiClient {
|
||||
private token: string | null = null;
|
||||
|
||||
constructor() {
|
||||
if (typeof window !== 'undefined') {
|
||||
this.token = localStorage.getItem('token');
|
||||
}
|
||||
}
|
||||
|
||||
setToken(token: string) {
|
||||
this.token = token;
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
}
|
||||
|
||||
clearToken() {
|
||||
this.token = null;
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('token');
|
||||
}
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
private async fetch(endpoint: string, options: RequestInit = {}) {
|
||||
const url = `${API_URL}${endpoint}`;
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...((options.headers as Record<string, string>) || {}),
|
||||
};
|
||||
|
||||
if (this.token) {
|
||||
headers['Authorization'] = `Bearer ${this.token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
||||
throw new Error(error.error || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Auth
|
||||
async register(email: string, password: string, name: string, role: 'Organizer' | 'Participant' = 'Participant') {
|
||||
const data = await this.fetch('/auth/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password, name, role }),
|
||||
});
|
||||
this.setToken(data.token);
|
||||
return data as AuthResponse;
|
||||
}
|
||||
|
||||
async login(email: string, password: string) {
|
||||
const data = await this.fetch('/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
this.setToken(data.token);
|
||||
return data as AuthResponse;
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.clearToken();
|
||||
}
|
||||
|
||||
// Events
|
||||
async getEvents(filters?: { category?: string; status?: string; fromDate?: string; toDate?: string }) {
|
||||
const params = new URLSearchParams();
|
||||
if (filters?.category) params.append('category', filters.category);
|
||||
if (filters?.status) params.append('status', filters.status);
|
||||
if (filters?.fromDate) params.append('fromDate', filters.fromDate);
|
||||
if (filters?.toDate) params.append('toDate', filters.toDate);
|
||||
|
||||
const query = params.toString();
|
||||
return this.fetch(`/events${query ? `?${query}` : ''}`) as Promise<Event[]>;
|
||||
}
|
||||
|
||||
async getEvent(id: string) {
|
||||
return this.fetch(`/events/${id}`) as Promise<Event>;
|
||||
}
|
||||
|
||||
async createEvent(event: Partial<Event>) {
|
||||
return this.fetch('/events', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(event),
|
||||
}) as Promise<Event>;
|
||||
}
|
||||
|
||||
async updateEvent(id: string, event: Partial<Event>) {
|
||||
return this.fetch(`/events/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(event),
|
||||
}) as Promise<Event>;
|
||||
}
|
||||
|
||||
async deleteEvent(id: string) {
|
||||
return this.fetch(`/events/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
// Registrations
|
||||
async createRegistration(eventId: string, category?: string, emergencyContact?: string) {
|
||||
return this.fetch('/registrations', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ eventId, category, emergencyContact }),
|
||||
}) as Promise<Registration>;
|
||||
}
|
||||
|
||||
async getMyRegistrations() {
|
||||
return this.fetch('/registrations/my-registrations') as Promise<Registration[]>;
|
||||
}
|
||||
|
||||
async getEventRegistrations(eventId: string) {
|
||||
return this.fetch(`/registrations/event/${eventId}`) as Promise<Registration[]>;
|
||||
}
|
||||
|
||||
async cancelRegistration(id: string) {
|
||||
return this.fetch(`/registrations/${id}/cancel`, {
|
||||
method: 'POST',
|
||||
}) as Promise<Registration>;
|
||||
}
|
||||
|
||||
// Payments
|
||||
async recordPayment(registrationId: string, amount: number, method: string, transactionId?: string, notes?: string) {
|
||||
return this.fetch('/payments', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ registrationId, amount, method, transactionId, notes }),
|
||||
});
|
||||
}
|
||||
|
||||
async getPaymentReport(eventId: string) {
|
||||
return this.fetch(`/payments/event/${eventId}/report`) as Promise<PaymentReport>;
|
||||
}
|
||||
|
||||
// Announcements
|
||||
async getEventAnnouncements(eventId: string) {
|
||||
return this.fetch(`/announcements/event/${eventId}`) as Promise<Announcement[]>;
|
||||
}
|
||||
|
||||
async createAnnouncement(eventId: string, title: string, content: string) {
|
||||
return this.fetch('/announcements', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ eventId, title, content }),
|
||||
}) as Promise<Announcement>;
|
||||
}
|
||||
|
||||
// Dashboard
|
||||
async getOrganizerDashboard() {
|
||||
return this.fetch('/dashboard/organizer');
|
||||
}
|
||||
|
||||
async getParticipantDashboard() {
|
||||
return this.fetch('/dashboard/participant');
|
||||
}
|
||||
}
|
||||
|
||||
export const api = new ApiClient();
|
||||
@@ -0,0 +1,92 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import { api, User, AuthResponse } from './api';
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
isLoading: boolean;
|
||||
isAuthenticated: boolean;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
register: (email: string, password: string, name: string, role: 'Organizer' | 'Participant') => Promise<void>;
|
||||
logout: () => void;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Check for existing token on mount
|
||||
const token = api.getToken();
|
||||
if (token) {
|
||||
// Token exists, user is authenticated
|
||||
// In a real app, you might want to validate the token
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
try {
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
const response = await api.login(email, password);
|
||||
setUser(response.user);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Login failed');
|
||||
throw err;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const register = async (email: string, password: string, name: string, role: 'Organizer' | 'Participant') => {
|
||||
try {
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
const response = await api.register(email, password, name, role);
|
||||
setUser(response.user);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Registration failed');
|
||||
throw err;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
api.logout();
|
||||
setUser(null);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
user,
|
||||
isLoading,
|
||||
isAuthenticated: !!user,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
error,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
## 1. Project Setup
|
||||
|
||||
- [x] 1.1 Initialize project structure with backend and frontend directories
|
||||
- [x] 1.2 Setup .NET backend (ASP.NET Core Web API with C#)
|
||||
- [x] 1.3 Setup Next.js frontend with TypeScript using Bun runtime
|
||||
- [x] 1.4 Configure ESLint and Prettier for frontend
|
||||
- [x] 1.5 Setup Entity Framework Core with PostgreSQL
|
||||
- [x] 1.6 Create initial database schema migration (EF Core)
|
||||
|
||||
## 2. Database Schema
|
||||
|
||||
- [x] 2.1 Create users table with roles
|
||||
- [x] 2.2 Create events table with organizer foreign key
|
||||
- [x] 2.3 Create registrations table with event and user foreign keys
|
||||
- [x] 2.4 Create payments table with registration foreign key
|
||||
- [x] 2.5 Create announcements table with event foreign key
|
||||
- [x] 2.6 Add database indexes for common queries
|
||||
|
||||
## 3. User Authentication (user-auth)
|
||||
|
||||
- [x] 3.1 Implement user registration endpoint
|
||||
- [x] 3.2 Implement user login endpoint with JWT
|
||||
- [x] 3.3 Create authentication middleware
|
||||
- [x] 3.4 Implement role-based access control middleware
|
||||
- [x] 3.5 Create registration form component
|
||||
- [x] 3.6 Create login form component
|
||||
- [x] 3.7 Implement logout functionality
|
||||
|
||||
## 4. Event Management (event-management)
|
||||
|
||||
- [x] 4.1 Implement create event endpoint
|
||||
- [x] 4.2 Implement update event endpoint
|
||||
- [x] 4.3 Implement list events endpoint with filtering
|
||||
- [x] 4.4 Implement get event details endpoint
|
||||
- [x] 4.5 Create event creation form component
|
||||
- [x] 4.6 Create event editing form component
|
||||
- [x] 4.7 Create event list view with filters
|
||||
- [x] 4.8 Create event detail view
|
||||
|
||||
## 5. Registration System (registration-system)
|
||||
|
||||
- [x] 5.1 Implement registration endpoint
|
||||
- [x] 5.2 Implement registration status update endpoint
|
||||
- [x] 5.3 Implement cancel registration endpoint
|
||||
- [x] 5.4 Create registration form component
|
||||
- [x] 5.5 Create my registrations list component
|
||||
- [x] 5.6 Implement registration status display
|
||||
|
||||
## 6. Payment Tracking (payment-tracking)
|
||||
|
||||
- [x] 6.1 Implement record payment endpoint
|
||||
- [x] 6.2 Implement payment status endpoint
|
||||
- [x] 6.3 Implement payment report endpoint
|
||||
- [x] 6.4 Create payment recording form component
|
||||
- [x] 6.5 Create payment status display component
|
||||
- [x] 6.6 Create payment report view
|
||||
|
||||
## 7. Announcements (announcements)
|
||||
|
||||
- [x] 7.1 Implement create announcement endpoint
|
||||
- [x] 7.2 Implement edit announcement endpoint
|
||||
- [x] 7.3 Implement delete announcement endpoint
|
||||
- [x] 7.4 Implement list announcements endpoint
|
||||
- [x] 7.5 Create announcement creation form
|
||||
- [x] 7.6 Create announcement list component
|
||||
- [ ] 7.7 Implement notification system
|
||||
|
||||
## 8. Dashboard (dashboard)
|
||||
|
||||
- [x] 8.1 Implement organizer metrics endpoint
|
||||
- [x] 8.2 Implement participant registrations endpoint
|
||||
- [x] 8.3 Create organizer dashboard component
|
||||
- [x] 8.4 Create participant dashboard component
|
||||
- [x] 8.5 Add quick action buttons
|
||||
|
||||
## 9. Integration and Polish
|
||||
|
||||
- [x] 9.1 Connect frontend to all backend endpoints
|
||||
- [x] 9.2 Add error handling and loading states
|
||||
- [x] 9.3 Implement responsive design
|
||||
- [x] 9.4 Add form validation feedback
|
||||
- [x] 9.5 Setup email service for notifications
|
||||
- [x] 9.6 Add basic unit tests for critical paths
|
||||
- [x] 9.7 Create deployment configuration
|
||||
@@ -1,84 +0,0 @@
|
||||
## 1. Project Setup
|
||||
|
||||
- [ ] 1.1 Initialize project structure with backend and frontend directories
|
||||
- [ ] 1.2 Setup .NET backend (ASP.NET Core Web API with C#)
|
||||
- [ ] 1.3 Setup Next.js frontend with TypeScript using Bun runtime
|
||||
- [ ] 1.4 Configure ESLint and Prettier for frontend
|
||||
- [ ] 1.5 Setup Entity Framework Core with PostgreSQL
|
||||
- [ ] 1.6 Create initial database schema migration (EF Core)
|
||||
|
||||
## 2. Database Schema
|
||||
|
||||
- [ ] 2.1 Create users table with roles
|
||||
- [ ] 2.2 Create events table with organizer foreign key
|
||||
- [ ] 2.3 Create registrations table with event and user foreign keys
|
||||
- [ ] 2.4 Create payments table with registration foreign key
|
||||
- [ ] 2.5 Create announcements table with event foreign key
|
||||
- [ ] 2.6 Add database indexes for common queries
|
||||
|
||||
## 3. User Authentication (user-auth)
|
||||
|
||||
- [ ] 3.1 Implement user registration endpoint
|
||||
- [ ] 3.2 Implement user login endpoint with JWT
|
||||
- [ ] 3.3 Create authentication middleware
|
||||
- [ ] 3.4 Implement role-based access control middleware
|
||||
- [ ] 3.5 Create registration form component
|
||||
- [ ] 3.6 Create login form component
|
||||
- [ ] 3.7 Implement logout functionality
|
||||
|
||||
## 4. Event Management (event-management)
|
||||
|
||||
- [ ] 4.1 Implement create event endpoint
|
||||
- [ ] 4.2 Implement update event endpoint
|
||||
- [ ] 4.3 Implement list events endpoint with filtering
|
||||
- [ ] 4.4 Implement get event details endpoint
|
||||
- [ ] 4.5 Create event creation form component
|
||||
- [ ] 4.6 Create event editing form component
|
||||
- [ ] 4.7 Create event list view with filters
|
||||
- [ ] 4.8 Create event detail view
|
||||
|
||||
## 5. Registration System (registration-system)
|
||||
|
||||
- [ ] 5.1 Implement registration endpoint
|
||||
- [ ] 5.2 Implement registration status update endpoint
|
||||
- [ ] 5.3 Implement cancel registration endpoint
|
||||
- [ ] 5.4 Create registration form component
|
||||
- [ ] 5.5 Create my registrations list component
|
||||
- [ ] 5.6 Implement registration status display
|
||||
|
||||
## 6. Payment Tracking (payment-tracking)
|
||||
|
||||
- [ ] 6.1 Implement record payment endpoint
|
||||
- [ ] 6.2 Implement payment status endpoint
|
||||
- [ ] 6.3 Implement payment report endpoint
|
||||
- [ ] 6.4 Create payment recording form component
|
||||
- [ ] 6.5 Create payment status display component
|
||||
- [ ] 6.6 Create payment report view
|
||||
|
||||
## 7. Announcements (announcements)
|
||||
|
||||
- [ ] 7.1 Implement create announcement endpoint
|
||||
- [ ] 7.2 Implement edit announcement endpoint
|
||||
- [ ] 7.3 Implement delete announcement endpoint
|
||||
- [ ] 7.4 Implement list announcements endpoint
|
||||
- [ ] 7.5 Create announcement creation form
|
||||
- [ ] 7.6 Create announcement list component
|
||||
- [ ] 7.7 Implement notification system
|
||||
|
||||
## 8. Dashboard (dashboard)
|
||||
|
||||
- [ ] 8.1 Implement organizer metrics endpoint
|
||||
- [ ] 8.2 Implement participant registrations endpoint
|
||||
- [ ] 8.3 Create organizer dashboard component
|
||||
- [ ] 8.4 Create participant dashboard component
|
||||
- [ ] 8.5 Add quick action buttons
|
||||
|
||||
## 9. Integration and Polish
|
||||
|
||||
- [ ] 9.1 Connect frontend to all backend endpoints
|
||||
- [ ] 9.2 Add error handling and loading states
|
||||
- [ ] 9.3 Implement responsive design
|
||||
- [ ] 9.4 Add form validation feedback
|
||||
- [ ] 9.5 Setup email service for notifications
|
||||
- [ ] 9.6 Add basic unit tests for critical paths
|
||||
- [ ] 9.7 Create deployment configuration
|
||||
Reference in New Issue
Block a user