Refactoring

Building out UI
Building out API
Fixing Bugs
This commit is contained in:
Tara Wilson 2024-12-06 17:23:23 -05:00
parent 924cee4e0f
commit cb433ba390
57 changed files with 461 additions and 73 deletions

View File

@ -40,7 +40,8 @@ public class TicketController(
Id = ticketId, Id = ticketId,
QrCode = qrCode, QrCode = qrCode,
Type = mintRequest.Type, Type = mintRequest.Type,
EventId = mintRequest.EventId EventId = mintRequest.EventId,
SeasonId = mintRequest.SeasonId != Guid.Empty ? mintRequest.SeasonId : Guid.Empty,
}; };
//save the minted ticket //save the minted ticket

View File

@ -1,14 +1,16 @@
@api_HostAddress = http://localhost:5168 @api_HostAddress = http://localhost:5168
@eventId = b9f4478b-701b-4223-9aaa-042b6f53b83a @eventId = cdae537c-e5ad-4945-9557-b14ba473c80f
@seasonId = c69552ba-8fcf-43b4-9d4f-23b5203ea40e
POST {{api_HostAddress}}/event POST {{api_HostAddress}}/event
Accept: application/json Accept: application/json
Content-Type: application/json Content-Type: application/json
{ {
"Date": "2024-12-02T15:00:00.991Z", "Date": "2024-12-07T15:00:00.991Z",
"EventName": "Winter Concert", "Name": "Winter Concert",
"EventDescription": "A wintery journey of classical music", "Description": "A wintery journey of classical music",
"SeasonId": "{{seasonId}}",
"Venue": { "Venue": {
"Name": "Valley Forge High School", "Name": "Valley Forge High School",
"Description": "Auditorium", "Description": "Auditorium",
@ -33,8 +35,8 @@ Content-Type: application/json
{ {
"Id": "{{eventId}}", "Id": "{{eventId}}",
"Date": "2024-12-05T15:00:00.991Z", "Date": "2024-12-05T15:00:00.991Z",
"EventName": "Winter Concert", "Name": "Winter Concert",
"EventDescription": "A wintery journey of classical music", "Description": "A wintery journey of classical music",
"Venue": { "Venue": {
"Name": "Valley Forge High School", "Name": "Valley Forge High School",
"Description": "Auditorium", "Description": "Auditorium",

View File

@ -1,5 +1,5 @@
@api_HostAddress = http://localhost:5168 @api_HostAddress = http://localhost:5168
@ticketId = fe21c683-b607-4da7-8e9b-76b199a4b76c @ticketId =
### ###

View File

@ -1,6 +1,6 @@
@api_HostAddress = http://localhost:5168 @api_HostAddress = http://localhost:5168
@seasonId = 76178cf4-3805-4a66-a3d9-a0223d99c2fa @seasonId = c69552ba-8fcf-43b4-9d4f-23b5203ea40e
@eventId = b9f4478b-701b-4223-9aaa-042b6f53b83a @eventId = cdae537c-e5ad-4945-9557-b14ba473c80f
GET {{api_HostAddress}}/season GET {{api_HostAddress}}/season
Accept: application/json Accept: application/json
@ -13,8 +13,8 @@ Accept: application/json
Content-Type: application/json Content-Type: application/json
{ {
"SeasonName": "PSO 25 - 26", "Name": "PSO 25 - 26",
"SeasonDescription": "Parma Symphony Orchestra's 53rd Season", "Description": "Parma Symphony Orchestra's 53rd Season",
"StartDate": "2025-08-01T00:00:00.991Z", "StartDate": "2025-08-01T00:00:00.991Z",
"EndDate": "2026-05-31T00:00:00.991Z" "EndDate": "2026-05-31T00:00:00.991Z"
} }

View File

@ -1,16 +1,31 @@
@api_HostAddress = http://localhost:5168 @api_HostAddress = http://localhost:5168
@ticketId = fe21c683-b607-4da7-8e9b-76b199a4b76c @ticketId =
@eventId = b9f4478b-701b-4223-9aaa-042b6f53b83a @eventId = cdae537c-e5ad-4945-9557-b14ba473c80f
@seasonId = c69552ba-8fcf-43b4-9d4f-23b5203ea40e
### Mint Single Non Season Ticket
POST {{api_HostAddress}}/ticket POST {{api_HostAddress}}/ticket
Accept: application/json Accept: application/json
Content-Type: application/json Content-Type: application/json
{ {
"ticketType": "Single", "type": 0,
"eventId": "{{eventId}}" "eventId": "{{eventId}}"
} }
### Mint Single Season Ticket
POST {{api_HostAddress}}/ticket
Accept: application/json
Content-Type: application/json
{
"type": 1,
"eventId": "{{eventId}}",
"seasonId": "{{seasonId}}"
}
### ###
GET {{api_HostAddress}}/ticket?ticketId={{ticketId}} GET {{api_HostAddress}}/ticket?ticketId={{ticketId}}

View File

@ -12,14 +12,16 @@ public class EventManager : IEventManager
var @event = new Event var @event = new Event
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
EventDescription = request.EventDescription, SeasonId = request.SeasonId,
EventName = request.EventName, Description = request.Description,
Name = request.Name,
Date = request.Date, Date = request.Date,
Talent = request.Talent, Talent = request.Talent,
Venue = request.Venue Venue = request.Venue
}; };
new Save().Execute(@event); new Save().Execute(@event);
new data.Seasons.AddEvent().Execute(@event.Id, request.SeasonId);
} }
public void PatchEvent(PatchEvent request) public void PatchEvent(PatchEvent request)

View File

@ -13,8 +13,8 @@ public class SeasonManager : ISeasonManager
var season = new Season var season = new Season
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Name = request.SeasonName, Name = request.Name,
Description = request.SeasonDescription, Description = request.Description,
StartDate = request.StartDate, StartDate = request.StartDate,
EndDate = request.EndDate, EndDate = request.EndDate,
}; };

View File

@ -17,8 +17,8 @@ public class Update
{ {
Id = request.Id, Id = request.Id,
Date = request.Date, Date = request.Date,
EventName = request.EventName, Name = request.Name,
EventDescription = request.EventDescription, Description = request.Description,
Venue = request.Venue, Venue = request.Venue,
Talent = request.Talent, Talent = request.Talent,
}; };

View File

@ -3,9 +3,10 @@ namespace models.Core;
public class Event public class Event
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid SeasonId { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public required string EventName { get; set; } public required string Name { get; set; }
public string? EventDescription { get; set; } public string? Description { get; set; }
public required Venue Venue { get; set; } public required Venue Venue { get; set; }
public required Talent Talent { get; set; } public required Talent Talent { get; set; }
public List<Guid> TicketIds { get; set; } = []; public List<Guid> TicketIds { get; set; } = [];

View File

@ -5,6 +5,7 @@ namespace models.Core;
public class Ticket public class Ticket
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid SeasonId { get; set; }
public Guid EventId { get; set; } public Guid EventId { get; set; }
public TicketType Type { get; set; } public TicketType Type { get; set; }
public required string QrCode { get; set; } public required string QrCode { get; set; }

View File

@ -5,8 +5,9 @@ namespace models.Request;
public class AddEvent public class AddEvent
{ {
public DateTime Date { get; set; } public DateTime Date { get; set; }
public required string EventName { get; set; } public Guid SeasonId { get; set; }
public string? EventDescription { get; set; } public required string Name { get; set; }
public string? Description { get; set; }
public required Venue Venue { get; set; } public required Venue Venue { get; set; }
public required Talent Talent { get; set; } public required Talent Talent { get; set; }
} }

View File

@ -2,8 +2,8 @@ namespace models.Request;
public class AddSeason public class AddSeason
{ {
public required string SeasonName { get; set; } public required string Name { get; set; }
public string? SeasonDescription { get; set; } public string? Description { get; set; }
public DateTime StartDate { get; set; } public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; } public DateTime EndDate { get; set; }
} }

View File

@ -6,4 +6,5 @@ public class AddTicket
{ {
public TicketType Type { get; set; } public TicketType Type { get; set; }
public Guid EventId { get; set; } public Guid EventId { get; set; }
public Guid SeasonId { get; set; } = Guid.Empty;
} }

View File

@ -1,7 +0,0 @@
namespace models.Request;
public class EventSearch
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}

View File

@ -6,8 +6,8 @@ public class PatchEvent
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public required string EventName { get; set; } public required string Name { get; set; }
public string? EventDescription { get; set; } public string? Description { get; set; }
public required Venue Venue { get; set; } public required Venue Venue { get; set; }
public required Talent Talent { get; set; } public required Talent Talent { get; set; }
} }

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M330-120 120-330v-300l210-210h300l210 210v300L630-120H330Zm36-190 114-114 114 114 56-56-114-114 114-114-56-56-114 114-114-114-56 56 114 114-114 114 56 56Zm-2 110h232l164-164v-232L596-760H364L200-596v232l164 164Zm116-280Z"/></svg>

After

Width:  |  Height:  |  Size: 345 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M40-160v-112q0-34 17.5-62.5T104-378q62-31 126-46.5T360-440q66 0 130 15.5T616-378q29 15 46.5 43.5T680-272v112H40Zm720 0v-120q0-44-24.5-84.5T666-434q51 6 96 20.5t84 35.5q36 20 55 44.5t19 53.5v120H760ZM360-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm400-160q0 66-47 113t-113 47q-11 0-28-2.5t-28-5.5q27-32 41.5-71t14.5-81q0-42-14.5-81T544-792q14-5 28-6.5t28-1.5q66 0 113 47t47 113ZM120-240h480v-32q0-11-5.5-20T580-306q-54-27-109-40.5T360-360q-56 0-111 13.5T140-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T440-640q0-33-23.5-56.5T360-720q-33 0-56.5 23.5T280-640q0 33 23.5 56.5T360-560Zm0 320Zm0-400Z"/></svg>

After

Width:  |  Height:  |  Size: 766 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m344-60-76-128-144-32 14-148-98-112 98-112-14-148 144-32 76-128 136 58 136-58 76 128 144 32-14 148 98 112-98 112 14 148-144 32-76 128-136-58-136 58Zm34-102 102-44 104 44 56-96 110-26-10-112 74-84-74-86 10-112-110-24-58-96-102 44-104-44-56 96-110 24 10 112-74 86 74 84-10 114 110 24 58 96Zm102-318Zm-42 142 226-226-56-58-170 170-86-84-56 56 142 142Z"/></svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M440-122q-121-15-200.5-105.5T160-440q0-66 26-126.5T260-672l57 57q-38 34-57.5 79T240-440q0 88 56 155.5T440-202v80Zm80 0v-80q87-16 143.5-83T720-440q0-100-70-170t-170-70h-3l44 44-56 56-140-140 140-140 56 56-44 44h3q134 0 227 93t93 227q0 121-79.5 211.5T520-122Z"/></svg>

After

Width:  |  Height:  |  Size: 382 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m320-40-64-48 104-139v-213q0-31 5-67.5t15-67.5l-60 33v142h-80v-188l176-100q25-14 43.5-21.5T494-717q25 0 45.5 21.5T587-628q32 54 58 81t56 41q11-8 19-11t19-3q25 0 43 18t18 42v420h-40v-420q0-8-6-14t-14-6q-8 0-14 6t-6 14v50h-40v-19q-54-23-84-51.5T543-557q-11 28-17.5 68.5T521-412l79 112v260h-80v-200l-71-102-9 142L320-40Zm220-700q-33 0-56.5-23.5T460-820q0-33 23.5-56.5T540-900q33 0 56.5 23.5T620-820q0 33-23.5 56.5T540-740Z"/></svg>

After

Width:  |  Height:  |  Size: 544 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z"/></svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@ -4,4 +4,4 @@
<div class="main"> <div class="main">
<router-outlet/> <router-outlet/>
</div> </div>
<app-snackbar />

View File

@ -1,12 +1,14 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {SidebarComponent} from './sidebar/sidebar.component'; import {SidebarComponent} from './sidebar/sidebar.component';
import {RouterOutlet} from '@angular/router'; import {RouterOutlet} from '@angular/router';
import {SnackbarComponent} from './snackbar/snackbar.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [ imports: [
SidebarComponent, SidebarComponent,
RouterOutlet RouterOutlet,
SnackbarComponent,
], ],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss' styleUrl: './app.component.scss'

View File

@ -0,0 +1,8 @@
<div class="card">
<label for="events">Events</label>
<select name="Events" id="events">
@for(event of events$(); track event.id) {
<option [value]="event.id">{{event.name}}</option>
}
</select>
</div>

View File

@ -0,0 +1,17 @@
import {Component, inject, OnInit} from '@angular/core';
import {EventService} from '../services/event.service';
@Component({
selector: 'app-event-browser',
imports: [],
templateUrl: './event-browser.component.html',
styleUrl: './event-browser.component.scss'
})
export class EventBrowserComponent implements OnInit {
private eventService = inject(EventService);
public events$ = this.eventService.events$;
public ngOnInit() {
this.eventService.searchAllEvents();
}
}

View File

@ -1,7 +1,7 @@
import {Component, inject} from '@angular/core'; import {Component, inject} from '@angular/core';
import {TicketComponent} from "../../ticket/ticket.component"; import {TicketComponent} from "../../ticket/ticket.component";
import {TicketService} from '../../services/ticket.service'; import {TicketService} from '../../services/ticket.service';
import {MintRequest} from '../../../models/request/mint-request'; import {AddTicket} from '../../../models/request/add-ticket';
import {TicketTypeEnum} from '../../../models/enums/ticket-type.enum'; import {TicketTypeEnum} from '../../../models/enums/ticket-type.enum';
@Component({ @Component({
@ -16,8 +16,9 @@ export class DebugComponent {
private ticketMinter = inject(TicketService); private ticketMinter = inject(TicketService);
public getQrCode(): void { public getQrCode(): void {
const mintRequest: MintRequest = { const mintRequest: AddTicket = {
ticketType: TicketTypeEnum.Single ticketType: TicketTypeEnum.Single,
eventId: ''
}; };
this.ticketMinter.addTicket(mintRequest); this.ticketMinter.addTicket(mintRequest);

View File

@ -1 +1,44 @@
<p>event works!</p> <div class="card">
<!-- TODO: Move this to a separate component -->
<h3>Add Event</h3>
<div class="column">
<span class="row row-space-between">
<label>Event Name</label>
<input [formControl]="eventName" type="text"/>
</span>
<span class="row row-space-between">
<label>Event Description</label>
<input [formControl]="eventDescription" type="text"/>
</span>
<span class="row row-space-between">
<label>Event Date</label>
<input [formControl]="eventDate" type="date"/>
</span>
</div>
<app-season-browser />
<button class="button" (click)="saveEvent()">Save</button>
</div>
<div class="card">
<!-- TODO: Move this to a separate component -->
<h3>Add Season</h3>
<div class="column">
<span class="row row-space-between">
<label>Season Name</label>
<input [formControl]="seasonName" type="text"/>
</span>
<span class="row row-space-between">
<label>Season Description</label>
<input [formControl]="seasonDescription" type="text"/>
</span>
<span class="row row-space-between">
<label>Season Start Date</label>
<input [formControl]="seasonStartDate" type="date"/>
</span>
<span class="row row-space-between">
<label>Season End Date</label>
<input [formControl]="seasonEndDate" type="date"/>
</span>
</div>
<button class="button" (click)="saveSeason()">Save</button>
</div>

View File

@ -0,0 +1,3 @@
label {
padding-right: 15px;
}

View File

@ -1,11 +1,33 @@
import { Component } from '@angular/core'; import {Component, inject} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {EventService} from '../../services/event.service';
import {SeasonService} from '../../services/season.service';
import {SeasonBrowserComponent} from '../../season-browser/season-browser.component';
@Component({ @Component({
selector: 'app-event', selector: 'app-event',
imports: [], imports: [ReactiveFormsModule, SeasonBrowserComponent],
templateUrl: './event.component.html', templateUrl: './event.component.html',
styleUrl: './event.component.scss' styleUrl: './event.component.scss'
}) })
export class EventComponent { export class EventComponent {
public eventName = new FormControl('');
public eventDescription = new FormControl('');
public eventDate = new FormControl(new Date);
public seasonName = new FormControl('');
public seasonDescription = new FormControl('');
public seasonStartDate = new FormControl(new Date);
public seasonEndDate = new FormControl(new Date);
private eventService = inject(EventService);
private seasonService = inject(SeasonService);
public saveEvent(): void {
}
public saveSeason(): void {
}
} }

View File

@ -1 +1,4 @@
<p>ticket works!</p> <div class="card">
<h3>Generate Tickets</h3>
<app-event-browser />
</div>

View File

@ -1,8 +1,11 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import {EventBrowserComponent} from '../../event-browser/event-browser.component';
@Component({ @Component({
selector: 'app-ticket', selector: 'app-ticket',
imports: [], imports: [
EventBrowserComponent
],
templateUrl: './ticket.component.html', templateUrl: './ticket.component.html',
styleUrl: './ticket.component.scss' styleUrl: './ticket.component.scss'
}) })

View File

@ -1,5 +1,57 @@
@if(ticketValidity() !== TicketValidity.Invalid && ticketType() !== TicketTypeEnum.Null) { @if (ticketValidity() !== TicketValidity.Invalid && ticketType() !== TicketTypeEnum.Null) {
<div class="card"> <div class="card column">
<div class="card">
@if (ticketValidity() == TicketValidity.Valid) {
<img ngSrc="pass.svg" width="100px" height="100px" alt="Ticket Valid"/>
<span>Valid</span>
} @else {
<img ngSrc="fail.svg" width="100px" height="100px" alt="Ticket Invalid"/>
<span>Invalid</span>
}
</div>
@if (ticketValidity() == TicketValidity.Valid) {
<div class="card">
@switch (ticketType()){
@case (TicketTypeEnum.Family) {
<img ngSrc="group.svg" width="25px" height="25px" alt="Family"/>
<span>Family</span>
}
@case (TicketTypeEnum.FamilySeason) {
<img ngSrc="group.svg" width="25px" height="25px" alt="Family"/>
<span>Family</span>
}
@case (TicketTypeEnum.Single) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Single</span>
}
@case (TicketTypeEnum.SingleSeason) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Single</span>
}
@case (TicketTypeEnum.Senior) {
<img ngSrc="senior.svg" width="25px" height="25px" alt="Senior"/>
<span>Senior</span>
}
@case (TicketTypeEnum.SeniorSeason) {
<img ngSrc="senior.svg" width="25px" height="25px" alt="Senior"/>
<span>Senior</span>
}
}
</div>
<div class="card">
@if (ticketType() !== TicketTypeEnum.FamilySeason) {
<img ngSrc="group.svg" width="25px" height="25px" alt="Family"/>
<span>Season</span>
}
@if (ticketType() !== TicketTypeEnum.SingleSeason) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Season</span>
}
@if (ticketType() !== TicketTypeEnum.SeniorSeason) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Season</span>
}
</div>
}
</div> </div>
} }

View File

@ -2,10 +2,13 @@ import {Component, inject} from '@angular/core';
import {TicketValidity} from '../../models/enums/ticket-validity.enum'; import {TicketValidity} from '../../models/enums/ticket-validity.enum';
import {TicketTypeEnum} from '../../models/enums/ticket-type.enum'; import {TicketTypeEnum} from '../../models/enums/ticket-type.enum';
import {ScanService} from '../services/scan.service'; import {ScanService} from '../services/scan.service';
import {NgOptimizedImage} from '@angular/common';
@Component({ @Component({
selector: 'app-scan-result', selector: 'app-scan-result',
imports: [], imports: [
NgOptimizedImage
],
templateUrl: './scan-result.component.html', templateUrl: './scan-result.component.html',
styleUrl: './scan-result.component.scss' styleUrl: './scan-result.component.scss'
}) })

View File

@ -0,0 +1,8 @@
<div class="card">
<label for="seasons">Seasons</label>
<select name="Seasons" id="seasons">
@for(season of seasons$(); track season.id) {
<option [value]="season.id">{{season.name}}</option>
}
</select>
</div>

View File

@ -0,0 +1,17 @@
import {Component, inject, OnInit} from '@angular/core';
import {SeasonService} from '../services/season.service';
@Component({
selector: 'app-season-browser',
imports: [],
templateUrl: './season-browser.component.html',
styleUrl: './season-browser.component.scss'
})
export class SeasonBrowserComponent implements OnInit {
private seasonService = inject(SeasonService);
public seasons$ = this.seasonService.seasons$;
public ngOnInit() {
this.seasonService.getSeasons();
}
}

View File

@ -4,14 +4,17 @@ import {AddEventRequest} from '../../models/request/add-event-request';
import {ApiUtils} from './api-utils'; import {ApiUtils} from './api-utils';
import {environment} from '../../environments/environment'; import {environment} from '../../environments/environment';
import {Endpoints} from '../../models/endpoints'; import {Endpoints} from '../../models/endpoints';
import {catchError, of} from 'rxjs'; import {catchError, map, of} from 'rxjs';
import {SnackbarService} from './snackbar.service';
import {Event} from '../../models/core/event';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class EventService extends ApiUtils { export class EventService extends ApiUtils {
public eventSignal: WritableSignal<Event | null> = signal(null);
private httpClient = inject(HttpClient); private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService);
public events$: WritableSignal<Array<Event>> = signal([]);
constructor() { constructor() {
super(); super();
@ -41,22 +44,41 @@ export class EventService extends ApiUtils {
this.httpClient.post<AddEventRequest>(url, JSON.stringify(request), options) this.httpClient.post<AddEventRequest>(url, JSON.stringify(request), options)
.pipe( .pipe(
catchError(error => { catchError(error => {
console.log(error); this.snackbar.showError(error.error);
return of(undefined); return of(undefined);
}) })
).subscribe(); ).subscribe();
} }
public searchEvents(startDate: Date, endDate: Date): void { public searchAllEvents(): void {
const options = this.setHttpRequestOptions(); const options = this.setHttpRequestOptions();
const url = environment.apiBase + Endpoints.EVENT; const url = environment.apiBase + Endpoints.EVENT;
this.httpClient.get(url, options) this.httpClient.get(url, options)
.pipe( .pipe(
map((response: any) => response.body),
catchError(error => { catchError(error => {
console.log(error); this.snackbar.showError(error.error);
return of(undefined); return of(undefined);
}) })
).subscribe(); ).subscribe(res => {
this.events$.set(res);
});
}
public searchEvents(startDate: Date, endDate: Date): void {
const options = this.setHttpRequestOptions();
const url = environment.apiBase + Endpoints.EVENT_DATE_SEARCH(startDate, endDate);
this.httpClient.get(url, options)
.pipe(
map((response: any) => response.body),
catchError(error => {
this.snackbar.showError(error.error);
return of(undefined);
})
).subscribe(res => {
this.events$.set(res);
});
} }
} }

View File

@ -7,6 +7,7 @@ import {Endpoints} from '../../models/endpoints';
import {TicketSearch} from '../../models/response/ticket-search'; import {TicketSearch} from '../../models/response/ticket-search';
import {catchError, map, of} from 'rxjs'; import {catchError, map, of} from 'rxjs';
import {ApiUtils} from './api-utils'; import {ApiUtils} from './api-utils';
import {SnackbarService} from './snackbar.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -15,6 +16,7 @@ export class ScanService extends ApiUtils {
public ticketValid$: WritableSignal<TicketValidity> = signal(TicketValidity.Null); public ticketValid$: WritableSignal<TicketValidity> = signal(TicketValidity.Null);
public ticketType$: WritableSignal<TicketTypeEnum> = signal(TicketTypeEnum.Null); public ticketType$: WritableSignal<TicketTypeEnum> = signal(TicketTypeEnum.Null);
private httpClient = inject(HttpClient); private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService);
constructor() { constructor() {
super(); super();
@ -28,7 +30,7 @@ export class ScanService extends ApiUtils {
.pipe( .pipe(
map((response: any) => response.body), map((response: any) => response.body),
catchError(error => { catchError(error => {
console.log(error); this.snackbar.showError(error.error);
return of(undefined); return of(undefined);
}) })
).subscribe((res: TicketSearch) => { ).subscribe((res: TicketSearch) => {

View File

@ -0,0 +1,66 @@
import {inject, Injectable, signal, WritableSignal} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {SnackbarService} from './snackbar.service';
import {ApiUtils} from './api-utils';
import {Season} from '../../models/core/season';
import {environment} from '../../environments/environment';
import {Endpoints} from '../../models/endpoints';
import {catchError, map, of} from 'rxjs';
import {AddSeason} from '../../models/request/add-season';
@Injectable({
providedIn: 'root'
})
export class SeasonService extends ApiUtils {
private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService);
public seasons$: WritableSignal<Array<Season>> = signal([]);
constructor() {
super();
}
public getSeasons(): void {
const options = this.setHttpRequestOptions();
const url = environment.apiBase + Endpoints.SEASON;
this.httpClient.get(url, options)
.pipe(
map((response: any) => response.body),
catchError(error => {
this.snackbar.showError(error.error);
return of(undefined);
})
).subscribe(res => {
if (res !== null) {
this.seasons$.set(res);
}
})
}
public saveSeason(request: AddSeason): void {
const options = this.setHttpRequestOptions(request);
const url = environment.apiBase + Endpoints.SEASON;
this.httpClient.post(url, JSON.stringify(request), options)
.pipe(
catchError(error => {
this.snackbar.showError(error.error);
return of(undefined);
})
).subscribe();
}
public addEventToSeason(eventId: string, seasonId: string): void {
const options = this.setHttpRequestOptions();
const url = environment.apiBase + Endpoints.SEASON_ADD_EVENT(eventId, seasonId);
this.httpClient.put(url, options)
.pipe(
catchError(error => {
this.snackbar.showError(error.error);
return of(undefined);
})
).subscribe();
}
}

View File

@ -0,0 +1,19 @@
import {Injectable, signal, WritableSignal} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SnackbarService {
public showSnackbar$: WritableSignal<boolean> = signal(false);
public snackbarMessage$: WritableSignal<string> = signal('');
public showError(message: string): void {
this.showSnackbar$.set(true);
this.snackbarMessage$.set(message);
setTimeout(() => {
this.showSnackbar$.set(false);
this.snackbarMessage$.set('');
}, 5000);
}
}

View File

@ -4,9 +4,10 @@ import {catchError, map, of} from 'rxjs';
import {environment} from '../../environments/environment'; import {environment} from '../../environments/environment';
import {MintResponse} from '../../models/response/mint-response'; import {MintResponse} from '../../models/response/mint-response';
import {Endpoints} from '../../models/endpoints'; import {Endpoints} from '../../models/endpoints';
import {MintRequest} from '../../models/request/mint-request'; import {AddTicket} from '../../models/request/add-ticket';
import {ApiUtils} from './api-utils'; import {ApiUtils} from './api-utils';
import {TicketSearch} from '../../models/response/ticket-search'; import {TicketSearch} from '../../models/response/ticket-search';
import {SnackbarService} from './snackbar.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -14,12 +15,13 @@ import {TicketSearch} from '../../models/response/ticket-search';
export class TicketService extends ApiUtils { export class TicketService extends ApiUtils {
public dataSignal$: WritableSignal<string> = signal(''); public dataSignal$: WritableSignal<string> = signal('');
private httpClient = inject(HttpClient); private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService);
constructor() { constructor() {
super(); super();
} }
public addTicket(request: MintRequest): void { public addTicket(request: AddTicket): void {
const options = this.setHttpRequestOptions(); const options = this.setHttpRequestOptions();
const url = environment.apiBase + Endpoints.TICKET; const url = environment.apiBase + Endpoints.TICKET;
@ -27,7 +29,7 @@ export class TicketService extends ApiUtils {
.pipe( .pipe(
map((response: any) => response.body), map((response: any) => response.body),
catchError(error => { catchError(error => {
console.log(error); this.snackbar.showError(error.error);
return of(undefined); return of(undefined);
}) })
).subscribe((res: MintResponse) => { ).subscribe((res: MintResponse) => {

View File

@ -1,7 +1,7 @@
<div class="column"> <div class="column">
<button class="sidebar-button" [routerLink]="['/ticket']">Generate Tickets</button> <button class="sidebar-button" [routerLink]="['/ticket']">Generate Tickets</button>
<button class="sidebar-button" [routerLink]="['/scan']">Scan Tickets</button> <button class="sidebar-button" [routerLink]="['/scan']">Scan Tickets</button>
<button class="sidebar-button" [routerLink]="['/event']">Manage Events</button> <button class="sidebar-button" [routerLink]="['/event']">Manage</button>
@if(!environment.production) { @if(!environment.production) {
<button class="sidebar-button" [routerLink]="['/debug']">Debug</button> <button class="sidebar-button" [routerLink]="['/debug']">Debug</button>
} }

View File

@ -0,0 +1,3 @@
@if(showSnackbar$()) {
<div class="snackbar">{{snackbarMessage$()}}</div>
}

View File

@ -0,0 +1,16 @@
.snackbar {
min-width: 250px; /* Set a default minimum width */
margin-left: -125px; /* Divide value of min-width by 2 */
backdrop-filter: blur(25px) saturate(112%);
-webkit-backdrop-filter: blur(25px) saturate(112%);
background-color: rgba(255, 255, 255, 0.11);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.125);
font-size: 28px;
text-align: center; /* Centered text */
padding: 16px; /* Padding */
position: fixed; /* Sit on top of the screen */
z-index: 1; /* Add a z-index if needed */
left: 50%; /* Center the snackbar */
bottom: 30px; /* 30px from the bottom */
}

View File

@ -0,0 +1,14 @@
import {Component, inject} from '@angular/core';
import {SnackbarService} from '../services/snackbar.service';
@Component({
selector: 'app-snackbar',
imports: [],
templateUrl: './snackbar.component.html',
styleUrl: './snackbar.component.scss'
})
export class SnackbarComponent {
public snackbar = inject(SnackbarService);
public showSnackbar$ = this.snackbar.showSnackbar$;
public snackbarMessage$ = this.snackbar.snackbarMessage$;
}

View File

@ -0,0 +1,13 @@
import {Talent} from './talent';
import {Venue} from './venue';
export interface Event {
id: string,
seasonId: string,
date: Date,
name: string,
description: string | null,
venue: Venue,
talent: Talent,
ticketIds: Array<string>
}

View File

@ -0,0 +1,7 @@
export interface Season {
id: string;
name: string,
description: string | null,
startDate: Date,
endDate: Date
}

View File

@ -1,4 +1,4 @@
export interface Talent { export interface Talent {
name: string; name: string;
description: string; description: string | null;
} }

View File

@ -1,6 +1,6 @@
export interface Venue { export interface Venue {
name: string, name: string,
description: string, description: string | null,
addressOne: string, addressOne: string,
addressTwo: string | null, addressTwo: string | null,
city: string, city: string,

View File

@ -15,10 +15,18 @@ export class Endpoints {
} }
/* Event Routes */ /* Event Routes */
public static readonly EVENT: string = 'events'; public static readonly EVENT: string = 'event';
/* Calculated Routes */ /* Calculated Routes */
public static EVENT_DATE_SEARCH(startDate: Date, endDate: Date): string { public static EVENT_DATE_SEARCH(startDate: Date, endDate: Date): string {
return `${Endpoints.EVENT}?startDate=${startDate}&endDate=${endDate}`; return `${Endpoints.EVENT}?startDate=${startDate}&endDate=${endDate}`;
} }
/* Season Routes */
public static readonly SEASON: string = 'season';
/* Calculated Routes */
public static SEASON_ADD_EVENT(eventId: string, seasonId: string): string {
return `${Endpoints.SEASON}?eventId=${eventId}&seasonId=${seasonId}`;
}
} }

View File

@ -3,8 +3,8 @@ import {Talent} from '../core/talent';
export interface AddEventRequest { export interface AddEventRequest {
date: Date, date: Date,
eventName: string, name: string,
eventDescription: string, description: string,
venue: Venue, venue: Venue,
talent: Talent, talent: Talent,
} }

View File

@ -0,0 +1,6 @@
export interface AddSeason {
name: string,
description: string | null,
startDate: Date,
endDate: Date
}

View File

@ -0,0 +1,6 @@
import {TicketTypeEnum} from '../enums/ticket-type.enum';
export interface AddTicket {
ticketType: TicketTypeEnum,
eventId: string
}

View File

@ -1,5 +0,0 @@
import {TicketTypeEnum} from '../enums/ticket-type.enum';
export interface MintRequest {
ticketType: TicketTypeEnum
}

View File

@ -4,8 +4,8 @@ import {Talent} from '../core/talent';
export interface PatchEvent { export interface PatchEvent {
id: string, id: string,
date: Date, date: Date,
eventName: string, name: string,
eventDescription: string, description: string,
venue: Venue, venue: Venue,
talent: Talent talent: Talent
} }

View File

@ -43,6 +43,10 @@ body {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
&-space-between {
justify-content: space-between;
}
} }
.column { .column {