@@ -51,6 +52,50 @@
}
+ @if (_Event.TicketTypes != null && _Event.TicketTypes.Any())
+ {
+
@Localize["Administration.Events.Event.Tickets"]
+
+
+
+ @foreach (var ticketType in _Event.TicketTypes.Where(t => t.SellEndDate < DateTime.Now))
+ {
+
+
+
+ @ticketType.Name
+
+
+ @ticketType.Currency.ToCurrencyValue(ticketType.Price) @ticketType.Currency.ToSymbol()
+
+
+
+
+
+ @Localize["Events.Event.Tickets.Available", "TODO remainingTickets / " + @ticketType.AvailableTickets]
+
+
+
+ @if (ticketType.SellStartDate >= DateTime.Now)
+ {
+ @Localize["Events.Event.Tickets.AvailableFrom", ticketType.SellStartDate]
+ }
+ else
+ {
+ @Localize["Events.Event.Tickets.AvailableUntil", ticketType.SellEndDate]
+ }
+
+
+ @Localize["Events.Event.Tickets.Buy"]
+
+
+ }
+
+
+
+
+ }
+
@if (_Event.Description != null)
{
@((MarkupString)_Event.Description)
diff --git a/NetEvent/Client/Pages/Events/Event.razor.cs b/NetEvent/Client/Pages/Events/Event.razor.cs
index d1619bbd..98011c33 100644
--- a/NetEvent/Client/Pages/Events/Event.razor.cs
+++ b/NetEvent/Client/Pages/Events/Event.razor.cs
@@ -25,6 +25,9 @@ public partial class Event
[Inject]
private IStringLocalizer
Localizer { get; set; } = default!;
+ [Inject]
+ private IPaymentService CartService { get; set; } = default!;
+
#endregion
[Parameter]
@@ -56,5 +59,13 @@ private static Color GetStateColor(PublishStateDto state)
_ => throw new($"PublishState {state} is not supported!"),
};
}
+
+ private Task BuyTicketAsync(EventTicketTypeDto eventTicketType)
+ {
+ // /checkout/ticket/{tickettypeid}/{count}
+ NavigationManager.NavigateTo($"checkout/ticket/{eventTicketType.Id}");
+ return Task.CompletedTask;
+ //CartService.AddToCart(eventTicketType);
+ }
}
}
diff --git a/NetEvent/Client/Program.cs b/NetEvent/Client/Program.cs
index d02e6986..fd9c499b 100644
--- a/NetEvent/Client/Program.cs
+++ b/NetEvent/Client/Program.cs
@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading.Tasks;
+using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
@@ -39,6 +40,9 @@ private static async Task Main(string[] args)
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+ builder.Services.AddScoped();
+
+ builder.Services.AddSingleton();
builder.Services.AddHttpClient(Constants.BackendApiHttpClientName)
.ConfigureHttpClient(client =>
@@ -70,9 +74,11 @@ private static async Task Main(string[] args)
config.SnackbarConfiguration.SnackbarVariant = Variant.Filled;
#pragma warning restore S109
});
+ builder.Services.AddBlazoredLocalStorage();
var app = builder.Build();
await app.SetDefaultCultureAsync();
+ await app.InitializeNavigationService();
await app.RunAsync();
}
diff --git a/NetEvent/Client/Resources/App.resx b/NetEvent/Client/Resources/App.resx
index 7a003f8d..3f7980d3 100644
--- a/NetEvent/Client/Resources/App.resx
+++ b/NetEvent/Client/Resources/App.resx
@@ -471,12 +471,6 @@
Start date
-
- Error deleting event
-
-
- Event deleted successfully
-
Edit
@@ -678,6 +672,123 @@
Hide name of organization in navbar
+
+ Error creating
+
+
+ Created successfully
+
+
+ Error deleting
+
+
+ Deleted successfully
+
+
+ Error updating
+
+
+ Updated successfully
+
+
+ Tickets
+
+
+ Available Tickets
+
+
+ Create Ticket Type
+
+
+ Currency
+
+
+ Euro (€)
+
+
+ Is giftable
+
+
+ Name
+
+
+ Price
+
+
+ Sell End
+
+
+ Sell Start
+
+
+ Available Tickets
+
+
+ Is Giftable
+
+
+ Name
+
+
+ Price
+
+
+ Sell End
+
+
+ Sell Start
+
+
+ Sort by
+
+
+ Cancel
+
+
+ Edit
+
+
+ Id
+
+
+ Save
+
+
+ Buy
+
+
+ Available from {0}
+
+
+ Available until {0}
+
+
+ Buy now
+
+
+ available {0}
+
+
+ Payment
+
+
+ https://docs.adyen.com/development-resources/api-credentials#generate-api-key
+
+
+ Adyen Api Key
+
+
+ https://docs.adyen.com/development-resources/client-side-authentication#get-your-client-key
+
+
+ Adyen Client Key
+
+
+ https://ca-test.adyen.com/ca/ca/accounts/show.shtml?accountTypeCode=MerchantAccount
+
+
+ Adyen Merchant Account
+
Event not found
diff --git a/NetEvent/Client/Services/EventService.cs b/NetEvent/Client/Services/EventService.cs
index 0a630fe4..ae9d43ba 100644
--- a/NetEvent/Client/Services/EventService.cs
+++ b/NetEvent/Client/Services/EventService.cs
@@ -35,14 +35,14 @@ public async Task CreateEventAsync(EventDto eventDto, Cancellatio
eventDto.Id = long.Parse(await response.Content.ReadAsStringAsync(cancellationToken), CultureInfo.InvariantCulture);
- return ServiceResult.Success("EventService.AddEventAsync.Success");
+ return ServiceResult.Success("EventService.AddAsync.Success");
}
catch (Exception ex)
{
_Logger.LogError(ex, "Unable to create event in backend.");
}
- return ServiceResult.Error("EventService.AddEventAsync.Error");
+ return ServiceResult.Error("EventService.AddAsync.Error");
}
public async Task GetEventAsync(string slug, CancellationToken cancellationToken)
@@ -128,14 +128,14 @@ public async Task UpdateEventAsync(EventDto eventDto, Cancellatio
response.EnsureSuccessStatusCode();
- return ServiceResult.Success("EventService.UpdateEventAsync.Success");
+ return ServiceResult.Success("EventService.UpdateAsync.Success");
}
catch (Exception ex)
{
_Logger.LogError(ex, "Unable to update event in backend.");
}
- return ServiceResult.Error("EventService.UpdateEventAsync.Error");
+ return ServiceResult.Error("EventService.UpdateAsync.Error");
}
public async Task DeleteEventAsync(long id, CancellationToken cancellationToken)
@@ -148,14 +148,96 @@ public async Task DeleteEventAsync(long id, CancellationToken can
response.EnsureSuccessStatusCode();
- return ServiceResult.Success("EventService.DeleteEventAsync.Success");
+ return ServiceResult.Success("EventService.DeleteAsync.Success");
}
catch (Exception ex)
{
_Logger.LogError(ex, "Unable to delete event in backend.");
}
- return ServiceResult.Error("EventService.DeleteEventAsync.Error");
+ return ServiceResult.Error("EventService.DeleteAsync.Error");
}
- }
+
+ public async Task CreateEventTicketTypeAsync(long eventId, EventTicketTypeDto eventTicketTypeDto, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var client = _HttpClientFactory.CreateClient(Constants.BackendApiHttpClientName);
+
+ var response = await client.PostAsJsonAsync($"api/events/tickettype/{eventId}", eventTicketTypeDto, cancellationToken);
+ response.EnsureSuccessStatusCode();
+
+ eventTicketTypeDto.Id = long.Parse(await response.Content.ReadAsStringAsync(cancellationToken), CultureInfo.InvariantCulture);
+
+ return ServiceResult.Success("EventService.AddAsync.Success");
+ }
+ catch (Exception ex)
+ {
+ _Logger.LogError(ex, "Unable to create eventTicketType in backend.");
+ }
+
+ return ServiceResult.Error("EventService.AddAsync.Error");
+ }
+
+ public async Task UpdateEventTicketTypeAsync(EventTicketTypeDto eventTicketTypeDto, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var client = _HttpClientFactory.CreateClient(Constants.BackendApiHttpClientName);
+
+ var response = await client.PutAsJsonAsync($"api/events/tickettype/{eventTicketTypeDto.Id}", eventTicketTypeDto, cancellationToken);
+ response.EnsureSuccessStatusCode();
+
+ return ServiceResult.Success("EventService.UpdateAsync.Success");
+ }
+ catch (Exception ex)
+ {
+ _Logger.LogError(ex, "Unable to update eventTicketType in backend.");
+ }
+
+ return ServiceResult.Error("EventService.UpdateAsync.Error");
+ }
+
+ public async Task DeleteEventTicketTypeAsync(long id, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var client = _HttpClientFactory.CreateClient(Constants.BackendApiHttpClientName);
+
+ var response = await client.DeleteAsync($"api/events/tickettype/{id}", cancellationToken);
+ response.EnsureSuccessStatusCode();
+
+ return ServiceResult.Success("EventService.DeleteAsync.Success");
+ }
+ catch (Exception ex)
+ {
+ _Logger.LogError(ex, "Unable to delete eventTicketType in backend.");
+ }
+
+ return ServiceResult.Error("EventService.DeleteAsync.Error");
+ }
+
+ public async Task GetEventTicketTypeAsync(long id, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var client = _HttpClientFactory.CreateClient(Constants.BackendApiHttpClientName);
+
+ var eventTicketTypeDto = await client.GetFromJsonAsync($"/api/events/tickettype/{id}", cancellationToken).ConfigureAwait(false);
+
+ if (eventTicketTypeDto == null)
+ {
+ _Logger.LogError("Unable to get eventtickettype data from backend");
+ return null;
+ }
+
+ return eventTicketTypeDto;
+ }
+ catch (Exception ex)
+ {
+ _Logger.LogError(ex, "Unable to get eventtickettype data from backend");
+ return null;
+ }
+ }
+ }
}
diff --git a/NetEvent/Client/Services/IEventService.cs b/NetEvent/Client/Services/IEventService.cs
index 4e5ef235..5fc97c96 100644
--- a/NetEvent/Client/Services/IEventService.cs
+++ b/NetEvent/Client/Services/IEventService.cs
@@ -20,5 +20,11 @@ public interface IEventService
Task UpdateEventAsync(EventDto eventDto, CancellationToken cancellationToken);
Task CreateEventAsync(EventDto eventDto, CancellationToken cancellationToken);
+
+ Task GetEventTicketTypeAsync(long id, CancellationToken cancellationToken);
+
+ Task UpdateEventTicketTypeAsync(EventTicketTypeDto eventTicketTypeDto, CancellationToken cancellationToken);
+
+ Task CreateEventTicketTypeAsync(long eventId, EventTicketTypeDto eventTicketTypeDto, CancellationToken cancellationToken);
}
}
diff --git a/NetEvent/Client/Services/IPaymentService.cs b/NetEvent/Client/Services/IPaymentService.cs
new file mode 100644
index 00000000..7fc1ca8b
--- /dev/null
+++ b/NetEvent/Client/Services/IPaymentService.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NetEvent.Shared.Dto;
+using NetEvent.Shared.Dto.Event;
+
+namespace NetEvent.Client.Services
+{
+ public interface IPaymentService
+ {
+ Task> BuyTicketAsync(long id, int amount, CancellationToken cancellationToken);
+
+ Task LoadCart(string cartId);
+
+ Task?>> LoadPaymentMethodsAsync(long amount, CurrencyDto currency, CancellationToken cancellationToken);
+
+ Task> MakePaymentAsync(string purchaseId, string paymentDataJson, CancellationToken cancellationToken);
+ }
+}
diff --git a/NetEvent/Client/Services/NavigationService.cs b/NetEvent/Client/Services/NavigationService.cs
new file mode 100644
index 00000000..d2f82fc9
--- /dev/null
+++ b/NetEvent/Client/Services/NavigationService.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Routing;
+
+namespace NetEvent.Client.Services
+{
+ public sealed class NavigationService : IDisposable
+ {
+ private const int MinHistorySize = 256;
+ private const int AdditionalHistorySize = 64;
+
+ private readonly NavigationManager _NavigationManager;
+ private readonly List _History;
+
+ public NavigationService(NavigationManager navigationManager)
+ {
+ _NavigationManager = navigationManager;
+ _History = new List(MinHistorySize + AdditionalHistorySize)
+ {
+ _NavigationManager.Uri
+ };
+ _NavigationManager.LocationChanged += OnLocationChanged;
+ }
+
+ ///
+ /// Navigates to the specified url.
+ ///
+ /// The destination url (relative or absolute).
+ public void NavigateTo(string url)
+ {
+ _NavigationManager.NavigateTo(url);
+ }
+
+ ///
+ /// Returns true if it is possible to navigate to the previous url.
+ ///
+ public bool CanNavigateBack => _History.Count >= 2;
+
+ ///
+ /// Navigates to the previous url if possible or does nothing if it is not.
+ ///
+ public void NavigateBack()
+ {
+ if (!CanNavigateBack)
+ {
+ return;
+ }
+
+ var backPageUrl = _History[^2];
+ _History.RemoveRange(_History.Count - 2, 2);
+ _NavigationManager.NavigateTo(backPageUrl);
+ }
+
+ private void OnLocationChanged(object sender, LocationChangedEventArgs e)
+ {
+ EnsureSize();
+ _History.Add(e.Location);
+ }
+
+ private void EnsureSize()
+ {
+ if (_History.Count < MinHistorySize + AdditionalHistorySize)
+ {
+ return;
+ }
+
+ _History.RemoveRange(0, _History.Count - MinHistorySize);
+ }
+
+ #region Implementation of Dispose
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _NavigationManager.LocationChanged -= OnLocationChanged;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ #endregion
+ }
+}
diff --git a/NetEvent/Client/Services/NavigationServiceExtension.cs b/NetEvent/Client/Services/NavigationServiceExtension.cs
new file mode 100644
index 00000000..4d86d6f2
--- /dev/null
+++ b/NetEvent/Client/Services/NavigationServiceExtension.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace NetEvent.Client.Services
+{
+ public static class NavigationServiceExtension
+ {
+ public static Task InitializeNavigationService(this WebAssemblyHost app)
+ {
+ var navigationService = app.Services.GetRequiredService();
+ return navigationService == null
+ ? throw new NotSupportedException("Start without NavigationServce is not possible!")
+ : Task.CompletedTask;
+ }
+ }
+}
diff --git a/NetEvent/Client/Services/PaymentService.cs b/NetEvent/Client/Services/PaymentService.cs
new file mode 100644
index 00000000..de827bf5
--- /dev/null
+++ b/NetEvent/Client/Services/PaymentService.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Blazored.LocalStorage;
+using Microsoft.Extensions.Logging;
+using NetEvent.Shared.Dto;
+using NetEvent.Shared.Dto.Event;
+
+namespace NetEvent.Client.Services
+{
+ public class PaymentService : IPaymentService
+ {
+ private const string _CartKey = "cart";
+
+ private readonly IHttpClientFactory _HttpClientFactory;
+ private readonly ILogger _Logger;
+ private readonly ILocalStorageService _LocalStorage;
+
+ public PaymentService(ILocalStorageService localStorage, IHttpClientFactory httpClientFactory, ILogger logger)
+ {
+ _LocalStorage = localStorage;
+ _HttpClientFactory = httpClientFactory;
+ _Logger = logger;
+ }
+
+ public async Task LoadCart(string cartId)
+ {
+ if (await _LocalStorage.ContainKeyAsync(cartId).ConfigureAwait(false))
+ {
+ return await _LocalStorage.GetItemAsync(_CartKey);
+ }
+
+ var cart = new CartDto();
+ await _LocalStorage.SetItemAsync(_CartKey, cart);
+ return cart;
+ }
+
+ public async Task> BuyTicketAsync(long id, int amount, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var ticketCart = new CartDto { CartEntries = new[] { new CartEntryDto { TicketId = id, Amount = amount } } };
+
+ var client = _HttpClientFactory.CreateClient(Constants.BackendApiHttpClientName);
+
+ var response = await client.PostAsJsonAsync($"api/payment/checkout/buy", ticketCart, cancellationToken);
+ response.EnsureSuccessStatusCode();
+
+ var sessionResponse = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ return ServiceResult.Success(sessionResponse, "EventService.AddAsync.Success");
+ }
+ catch (Exception ex)
+ {
+ _Logger.LogError(ex, "Unable to create eventTicketType in backend.");
+ }
+
+ return ServiceResult.Error("EventService.AddAsync.Error");
+ }
+
+ public async Task?>> LoadPaymentMethodsAsync(long amount, CurrencyDto currency, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var client = _HttpClientFactory.CreateClient(Constants.BackendApiHttpClientName);
+
+ var response = await client.GetFromJsonAsync>($"api/payment/paymentmethods/{amount}/{currency}", cancellationToken);
+
+ return ServiceResult?>.Success(response);
+ }
+ catch (Exception ex)
+ {
+ _Logger.LogError(ex, "Unable to create eventTicketType in backend.");
+ }
+
+ return ServiceResult?>.Error("PaymentService.LoadPaymentMethodsAsync.Error");
+ }
+
+ public async Task> MakePaymentAsync(string purchaseId, string paymentDataJson, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var client = _HttpClientFactory.CreateClient(Constants.BackendApiHttpClientName);
+
+ var response = await client.PostAsJsonAsync($"api/payment/checkout/{purchaseId}/payments", paymentDataJson, cancellationToken);
+ response.EnsureSuccessStatusCode();
+ var paymentResponse = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+ return ServiceResult.Success(paymentResponse);
+ }
+ catch (Exception ex)
+ {
+ _Logger.LogError(ex, "Unable to create eventTicketType in backend.");
+ }
+
+ return ServiceResult.Error("PaymentService.LoadPaymentMethodsAsync.Error");
+ }
+ }
+}
diff --git a/NetEvent/Client/wwwroot/index.html b/NetEvent/Client/wwwroot/index.html
index 7cc649be..26b56a3f 100644
--- a/NetEvent/Client/wwwroot/index.html
+++ b/NetEvent/Client/wwwroot/index.html
@@ -29,8 +29,15 @@
-
-
+
+
+