Adding Emailing
Adding Email Verification on Patron Form Addressing #1 - tickets will span next to ticket form
This commit is contained in:
parent
1d7315e063
commit
84431ede36
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -47,3 +47,5 @@ testem.log
|
|||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
source/ticketAPI/api/appsettings.Development.json
|
||||
source/ticketAPI/api/appsettings.json
|
||||
|
|
|
|||
BIN
source/ticketAPI/api/Assets/pso-logo.png
Normal file
BIN
source/ticketAPI/api/Assets/pso-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
|
|
@ -56,6 +56,11 @@ public class TicketController(
|
|||
Details = eventManager.GetEvent(ticket.EventId)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(ticket.Patron.Email))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ public static class ServiceCollectionExtensions
|
|||
services.AddScoped<ITicketManager, TicketManager>();
|
||||
services.AddScoped<IEventManager, EventManager>();
|
||||
services.AddScoped<ISeasonManager, SeasonManager>();
|
||||
services.AddScoped<IEmailService, EmailService>();
|
||||
}
|
||||
}
|
||||
9
source/ticketAPI/api/Interfaces/IEmailService.cs
Normal file
9
source/ticketAPI/api/Interfaces/IEmailService.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using models.Core;
|
||||
using models.Response;
|
||||
|
||||
namespace api.Interfaces;
|
||||
|
||||
public interface IEmailService
|
||||
{
|
||||
void SendEmail(Ticket ticket, EventDetails details);
|
||||
}
|
||||
71
source/ticketAPI/api/Services/EmailService.cs
Normal file
71
source/ticketAPI/api/Services/EmailService.cs
Normal file
|
|
@ -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($"<div #ticket style=\"{cardStyle} {columnStyle}\">");
|
||||
|
||||
sb.AppendLine($"<div #talentLogo style=\"{cardStyle} {rowStyle}\">");
|
||||
sb.AppendLine($"<img src=\"{imagePath}\" width=\"250\" height=\"100\" alt=\"Parma Symphony Orchestra Logo\"/>");
|
||||
sb.AppendLine("</div>"); //closes #talentLogo
|
||||
|
||||
sb.AppendLine($"<div #qrCode style=\"{rowStyle}\">");
|
||||
sb.AppendLine($"<img style=\"{qrCodeStyle}\" alt=\"QR Code\" src=\"data:image/png;base64,{ticket.QrCode}\"/>");
|
||||
sb.AppendLine("</div>"); //closes #qrCode
|
||||
|
||||
sb.AppendLine($"<div #venue style=\"{rowStyle}\">");
|
||||
sb.AppendLine($"<div style=\"{cardStyle}\">");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Talent.Name}</span>");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Name}</span>");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Description}</span>");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Date.ToString("F")}</span>");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Venue.Name}</span>");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Venue.Description}</span>");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Venue.AddressOne}</span>");
|
||||
sb.AppendLine($"<span style=\"{rowStyle}\">{@event.Venue.City} {@event.Venue.State} {@event.Venue.Zip}</span>");
|
||||
sb.AppendLine("</div>"); //closes #venue nested card
|
||||
sb.AppendLine("</div>"); //closes #venue
|
||||
|
||||
sb.AppendLine("</div>"); //closes #ticket
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -21,4 +21,8 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
</div>
|
||||
<div class="row row-space-between row-bottom-margin">
|
||||
<label class="label" for="email">Email</label>
|
||||
<input id="email" [formControl]="email" type="text" class="input"/>
|
||||
<input id="email" [formControl]="email" type="email" class="input"
|
||||
[ngClass]="{'invalid': email.invalid && email.dirty}"/>
|
||||
</div>
|
||||
<div class="row row-space-between row-bottom-margin">
|
||||
<label class="label" for="phoneNumber">Phone</label>
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
<div class="card">
|
||||
<h3>Generate Tickets</h3>
|
||||
<div class="column">
|
||||
<app-ticket-type-selector #ticketType></app-ticket-type-selector>
|
||||
<app-event-browser />
|
||||
@if(IsSeasonTicket(ticketType.selectedTicketType$())) {
|
||||
<app-season-browser />
|
||||
}
|
||||
<app-patron-info />
|
||||
<div [ngClass]="{'row': ticket$() !== null}">
|
||||
<div class="card">
|
||||
<h3>Generate Tickets</h3>
|
||||
<div class="column">
|
||||
<app-ticket-type-selector #ticketType></app-ticket-type-selector>
|
||||
<app-event-browser />
|
||||
@if(IsSeasonTicket(ticketType.selectedTicketType$())) {
|
||||
<app-season-browser />
|
||||
}
|
||||
<app-patron-info />
|
||||
</div>
|
||||
<button class="button" (click)="saveTicket()">Save Ticket</button>
|
||||
</div>
|
||||
<button class="button" (click)="saveTicket()">Save Ticket</button>
|
||||
</div>
|
||||
|
||||
@if(ticket$() !== null) {
|
||||
<app-generated-ticket />
|
||||
}
|
||||
@if(ticket$() !== null) {
|
||||
<app-generated-ticket />
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -99,3 +99,7 @@ body {
|
|||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.invalid {
|
||||
border: 1px solid rgba(255, 43, 43, 0.5);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user