diff --git a/.gitignore b/.gitignore index 56d3edb..9ae288a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ testem.log # System files .DS_Store Thumbs.db +source/ticketAPI/api/appsettings.Development.json +source/ticketAPI/api/appsettings.json diff --git a/source/ticketAPI/api/Assets/pso-logo.png b/source/ticketAPI/api/Assets/pso-logo.png new file mode 100644 index 0000000..fe4967e Binary files /dev/null and b/source/ticketAPI/api/Assets/pso-logo.png differ diff --git a/source/ticketAPI/api/Controllers/TicketController.cs b/source/ticketAPI/api/Controllers/TicketController.cs index 4edaca8..e2a47ac 100644 --- a/source/ticketAPI/api/Controllers/TicketController.cs +++ b/source/ticketAPI/api/Controllers/TicketController.cs @@ -56,6 +56,11 @@ public class TicketController( Details = eventManager.GetEvent(ticket.EventId) }; + if (!string.IsNullOrEmpty(ticket.Patron.Email)) + { + + } + return Ok(response); } catch (Exception e) diff --git a/source/ticketAPI/api/IServiceCollectionExtensions.cs b/source/ticketAPI/api/IServiceCollectionExtensions.cs index 7f49db6..0c9d745 100644 --- a/source/ticketAPI/api/IServiceCollectionExtensions.cs +++ b/source/ticketAPI/api/IServiceCollectionExtensions.cs @@ -11,5 +11,6 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } \ No newline at end of file diff --git a/source/ticketAPI/api/Interfaces/IEmailService.cs b/source/ticketAPI/api/Interfaces/IEmailService.cs new file mode 100644 index 0000000..bfda7e6 --- /dev/null +++ b/source/ticketAPI/api/Interfaces/IEmailService.cs @@ -0,0 +1,9 @@ +using models.Core; +using models.Response; + +namespace api.Interfaces; + +public interface IEmailService +{ + void SendEmail(Ticket ticket, EventDetails details); +} \ No newline at end of file diff --git a/source/ticketAPI/api/Services/EmailService.cs b/source/ticketAPI/api/Services/EmailService.cs new file mode 100644 index 0000000..4ba86fc --- /dev/null +++ b/source/ticketAPI/api/Services/EmailService.cs @@ -0,0 +1,71 @@ +using System.Net; +using System.Net.Mail; +using System.Text; +using api.Interfaces; +using models.Core; +using models.Response; + +namespace api.Services; + +public class EmailService(IConfiguration config) : IEmailService +{ + public void SendEmail(Ticket ticket, EventDetails @event) + { + using var client = new SmtpClient(config.GetSection("Email:Server").Value); + + client.UseDefaultCredentials = false; + var auth = new NetworkCredential(config.GetSection("Email:UserName").Value, + config.GetSection("Email:Password").Value); + + client.Credentials = auth; + var from = new MailAddress(config.GetSection("Email:From").Value); + var to = new MailAddress(ticket.Patron.Email); + + var emailMessage = new MailMessage(from, to); + emailMessage.ReplyToList.Add(from); + emailMessage.Subject = $"Tickets - {@event.Name}"; + emailMessage.SubjectEncoding = Encoding.UTF8; + + emailMessage.IsBodyHtml = true; + emailMessage.Body = GenerateTicketBody(ticket, @event); + + client.Send(emailMessage); + } + + private string GenerateTicketBody(Ticket ticket, EventDetails @event) + { + var sb = new StringBuilder(); + const string cardStyle = "margin: 15px; padding: 5px; backdrop-filter: blur(25px) saturate(112%); -webkit-backdrop-filter: blur(25px) saturate(112%); background-color: rgba(255, 255, 255, 0.11); border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.125); width: fit-content;"; + const string columnStyle = "display: flex; flex-direction: column; justify-content: center;"; + const string rowStyle = "display: flex; flex-direction: row; justify-content: center; align-items: baseline;"; + const string qrCodeStyle = "height: 150px; width: 150px; padding: 10px;"; + const string imagePath = "Assets/pso-logo.png"; + + sb.AppendLine($"
"); + + sb.AppendLine($"
"); + sb.AppendLine($"\"Parma"); + sb.AppendLine("
"); //closes #talentLogo + + sb.AppendLine($"
"); + sb.AppendLine($"\"QR"); + sb.AppendLine("
"); //closes #qrCode + + sb.AppendLine($"
"); + sb.AppendLine($"
"); + sb.AppendLine($"{@event.Talent.Name}"); + sb.AppendLine($"{@event.Name}"); + sb.AppendLine($"{@event.Description}"); + sb.AppendLine($"{@event.Date.ToString("F")}"); + sb.AppendLine($"{@event.Venue.Name}"); + sb.AppendLine($"{@event.Venue.Description}"); + sb.AppendLine($"{@event.Venue.AddressOne}"); + sb.AppendLine($"{@event.Venue.City} {@event.Venue.State} {@event.Venue.Zip}"); + sb.AppendLine("
"); //closes #venue nested card + sb.AppendLine("
"); //closes #venue + + sb.AppendLine("
"); //closes #ticket + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/source/ticketAPI/api/api.csproj b/source/ticketAPI/api/api.csproj index eef141a..86777b5 100644 --- a/source/ticketAPI/api/api.csproj +++ b/source/ticketAPI/api/api.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/source/ticketUI/src/app/components/patron-info/patron-info.component.html b/source/ticketUI/src/app/components/patron-info/patron-info.component.html index 425587d..7aad8ab 100644 --- a/source/ticketUI/src/app/components/patron-info/patron-info.component.html +++ b/source/ticketUI/src/app/components/patron-info/patron-info.component.html @@ -15,7 +15,8 @@
- +
diff --git a/source/ticketUI/src/app/components/patron-info/patron-info.component.ts b/source/ticketUI/src/app/components/patron-info/patron-info.component.ts index 0ab0c74..32c0127 100644 --- a/source/ticketUI/src/app/components/patron-info/patron-info.component.ts +++ b/source/ticketUI/src/app/components/patron-info/patron-info.component.ts @@ -1,11 +1,13 @@ import {Component} from '@angular/core'; import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; import {Patron} from '../../../models/core/patron'; +import {NgClass} from '@angular/common'; @Component({ selector: 'app-patron-info', imports: [ - ReactiveFormsModule + ReactiveFormsModule, + NgClass ], templateUrl: './patron-info.component.html', styleUrl: './patron-info.component.scss' @@ -14,7 +16,9 @@ export class PatronInfoComponent { public firstName = new FormControl('', {nonNullable: true, validators: [Validators.required]}); public middleName = new FormControl(''); public lastName = new FormControl('', {nonNullable: true, validators: [Validators.required]}); - public email = new FormControl('', {nonNullable: true, validators: [Validators.required]}); + public email = new FormControl('', { + nonNullable: true, validators: [Validators.required, Validators.email] + }); public phoneNumber = new FormControl(''); public addressOne = new FormControl('', {nonNullable: true, validators: [Validators.required]}); public addressTwo = new FormControl(''); diff --git a/source/ticketUI/src/app/page/ticket/ticket.component.html b/source/ticketUI/src/app/page/ticket/ticket.component.html index ffc7b0b..e78ac4c 100644 --- a/source/ticketUI/src/app/page/ticket/ticket.component.html +++ b/source/ticketUI/src/app/page/ticket/ticket.component.html @@ -1,16 +1,18 @@ -
-

Generate Tickets

-
- - - @if(IsSeasonTicket(ticketType.selectedTicketType$())) { - - } - +
+
+

Generate Tickets

+
+ + + @if(IsSeasonTicket(ticketType.selectedTicketType$())) { + + } + +
+
- -
-@if(ticket$() !== null) { - -} + @if(ticket$() !== null) { + + } +
diff --git a/source/ticketUI/src/app/page/ticket/ticket.component.ts b/source/ticketUI/src/app/page/ticket/ticket.component.ts index 5be922f..2f85196 100644 --- a/source/ticketUI/src/app/page/ticket/ticket.component.ts +++ b/source/ticketUI/src/app/page/ticket/ticket.component.ts @@ -8,6 +8,8 @@ import {SeasonBrowserComponent} from '../../components/season-browser/season-bro import {IsSeasonTicket} from '../../../models/enums/ticket-type.enum'; import {GeneratedTicketComponent} from '../../components/generated-ticket/generated-ticket.component'; import {Guid} from 'guid-typescript'; +import {SnackbarService} from '../../services/snackbar.service'; +import {NgClass} from '@angular/common'; @Component({ selector: 'app-ticket', @@ -16,7 +18,8 @@ import {Guid} from 'guid-typescript'; TicketTypeSelectorComponent, PatronInfoComponent, SeasonBrowserComponent, - GeneratedTicketComponent + GeneratedTicketComponent, + NgClass ], templateUrl: './ticket.component.html', styleUrl: './ticket.component.scss' @@ -28,9 +31,15 @@ export class TicketComponent { @ViewChild(SeasonBrowserComponent) seasonBrowserComponent!: SeasonBrowserComponent; public ticketService = inject(TicketService); + public snackBar = inject(SnackbarService); public ticket$ = this.ticketService.mintResponse$; public saveTicket(): void { + if(this.patronInfoComponent.email.invalid && this.patronInfoComponent.email.dirty) { + this.snackBar.showMessage("Please enter a valid email address"); + return; + } + const addTicket: AddTicket = { eventId: this.eventBrowserComponent.selectedEventId, type: this.ticketTypeSelector.selectedTicketType, diff --git a/source/ticketUI/src/styles.scss b/source/ticketUI/src/styles.scss index 5105936..c152b24 100644 --- a/source/ticketUI/src/styles.scss +++ b/source/ticketUI/src/styles.scss @@ -99,3 +99,7 @@ body { justify-content: space-between; } } + +.invalid { + border: 1px solid rgba(255, 43, 43, 0.5); +}