diff --git a/src/AzureOpenAIProxy.PlaygroundApp/Components/Pages/AdminUpdateEvent.razor b/src/AzureOpenAIProxy.PlaygroundApp/Components/Pages/AdminUpdateEvent.razor
new file mode 100644
index 00000000..0e115dbc
--- /dev/null
+++ b/src/AzureOpenAIProxy.PlaygroundApp/Components/Pages/AdminUpdateEvent.razor
@@ -0,0 +1,14 @@
+@page "/admin/events/edit/{EventId:guid}"
+
+@using AzureOpenAIProxy.PlaygroundApp.Models;
+
+Update Event
+
+
Update Event
+
+
+
+@code {
+ [Parameter]
+ public Guid EventId { get; set; }
+}
\ No newline at end of file
diff --git a/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/AdminEventsComponent.razor b/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/AdminEventsComponent.razor
index 09c43446..a0ff196c 100644
--- a/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/AdminEventsComponent.razor
+++ b/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/AdminEventsComponent.razor
@@ -1,5 +1,7 @@
@using AzureOpenAIProxy.PlaygroundApp.Models
+@inject NavigationManager NavigationManager
+
@if (eventDetails == null)
{
@@ -21,7 +23,7 @@
-
+ NavigateToEdit(((AdminEventDetails)@context).EventId))" />
@@ -89,4 +91,9 @@
private string? AriaCurrentValue(int pageIndex)
=> pagination.CurrentPageIndex == pageIndex ? "page" : null;
+
+ private void NavigateToEdit(Guid eventId)
+ {
+ NavigationManager.NavigateTo($"/admin/events/edit/{eventId}");
+ }
}
diff --git a/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/UpdateEventDetailsComponent.razor b/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/UpdateEventDetailsComponent.razor
new file mode 100644
index 00000000..b9bff807
--- /dev/null
+++ b/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/UpdateEventDetailsComponent.razor
@@ -0,0 +1,195 @@
+@using AzureOpenAIProxy.PlaygroundApp.Models
+@using NodaTime
+@using NodaTime.Extensions
+@using System.Globalization
+
+@inject NavigationManager NavigationManager
+
+
+ @if(adminEventDetails == null)
+ {
+ Loading...
+ }
+ else
+ {
+ Update Event
+
+
+ Event Infomation
+
+
+ Title
+
+
+
+
+ Summary
+
+
+
+
+ Description
+
+
+
+
+ Event Start Date
+
+
+
+
+
+ Event End Date
+
+
+
+
+
+ Time Zone
+
+ @foreach (var timeZone in timeZoneList)
+ {
+ @timeZone.Id
+ }
+
+
+
+
+
+ Event Organizer
+
+
+ Organizer Name
+
+
+
+
+
+ Organizer Email
+
+
+
+
+
+ Event Coorganizers
+
+
+ Coorgnizer Name
+
+
+
+
+ Coorgnizer Email
+
+
+
+
+
+ Event Configuration
+
+
+ Max Token Cap
+
+
+
+
+ Daily Request Cap
+
+
+
+
+
+
+ }
+
+
+@code {
+ [Parameter]
+ public string? Id { get; set; }
+
+ [Parameter]
+ public Guid EventId { get; set;}
+
+ private List? timeZoneList = [];
+ private AdminEventDetails? adminEventDetails;
+
+ protected override void OnInitialized()
+ {
+ timeZoneList = DateTimeZoneProviders.Tzdb.GetAllZones().ToList();
+
+ CultureInfo customCulture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
+ customCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd";
+ customCulture.DateTimeFormat.ShortTimePattern = "HH:mm";
+
+ CultureInfo.DefaultThreadCurrentCulture = customCulture;
+ CultureInfo.DefaultThreadCurrentUICulture = customCulture;
+ }
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if(firstRender)
+ {
+ // TODO: GET AdminEventDetails through the /admin/events/{eventId} API.
+ await Task.Delay(1000);
+ var timezoneId = GetIanaTimezoneId();
+ var currentTime = GetCurrentDateTimeOffset(timezoneId);
+
+ // Make dummy data
+ adminEventDetails = new AdminEventDetails
+ {
+ EventId = EventId,
+ Title = "dummy title",
+ Summary = "dummy summary",
+ Description = "dummy description",
+ DateStart = currentTime.AddHours(1).AddMinutes(-currentTime.Minute),
+ DateEnd = currentTime.AddDays(1).AddHours(1).AddMinutes(-currentTime.Minute),
+ TimeZone = timezoneId,
+ IsActive = true,
+ OrganizerName = $"dummy organizer",
+ OrganizerEmail = $"dummy_user@gmail.com",
+ CoorganizerName = $"dummy coorganizer",
+ CoorganizerEmail = $"dummy_supprot@gmail.com",
+ MaxTokenCap = 10000,
+ DailyRequestCap = 1000
+ };
+
+ await InvokeAsync(StateHasChanged);
+ }
+ }
+
+ private async Task UpdateEvent()
+ {
+ // TODO: PUT AdminEventDetails through the /admin/events/{eventId} API.
+ await Task.CompletedTask;
+ }
+
+ private void CancelUpdate()
+ {
+ NavigationManager.NavigateTo("/admin/events", forceLoad: false);
+ }
+
+ private string GetIanaTimezoneId()
+ {
+ string timezoneId = TimeZoneInfo.Local.Id;
+
+ if (OperatingSystem.IsWindows())
+ {
+ if (TimeZoneInfo.TryConvertWindowsIdToIanaId(timezoneId, out var ianaTimezoneId))
+ {
+ timezoneId = ianaTimezoneId;
+ }
+ }
+
+ return timezoneId;
+ }
+
+ private DateTimeOffset GetCurrentDateTimeOffset(string timezoneId)
+ {
+ var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timezoneId);
+
+ return TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, timeZoneInfo);
+ }
+}
\ No newline at end of file
diff --git a/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/UpdateEventDetailsComponent.razor.css b/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/UpdateEventDetailsComponent.razor.css
new file mode 100644
index 00000000..76e38e4d
--- /dev/null
+++ b/src/AzureOpenAIProxy.PlaygroundApp/Components/UI/Admin/UpdateEventDetailsComponent.razor.css
@@ -0,0 +1,25 @@
+section {
+ margin-bottom: 100px
+}
+
+::deep .update-input-label {
+ width: 200px;
+ --type-ramp-base-font-size: 22px;
+}
+
+::deep .update-fluent-stack {
+ height: 100px;
+}
+
+.button-section {
+ display: flex;
+ justify-content: center;
+ gap: 50px;
+}
+
+.button {
+ width: 150px;
+ height: 50px;
+ font-size: 16px;
+ margin: 0 10px;
+}
diff --git a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminNewEventPageTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminNewEventPageTests.cs
index 2cc2a3f3..31a51ff3 100644
--- a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminNewEventPageTests.cs
+++ b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminNewEventPageTests.cs
@@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}
[Theory]
- [InlineData("")]
+ [InlineData("")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
diff --git a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminPageTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminPageTests.cs
index b1b2f936..1a7f912d 100644
--- a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminPageTests.cs
+++ b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminPageTests.cs
@@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}
[Theory]
- [InlineData("")]
+ [InlineData("")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
diff --git a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminUpdateEventTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminUpdateEventTests.cs
new file mode 100644
index 00000000..f00b2195
--- /dev/null
+++ b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/AdminUpdateEventTests.cs
@@ -0,0 +1,81 @@
+using System.Net;
+
+using AzureOpenAIProxy.AppHost.Tests.Fixtures;
+
+using FluentAssertions;
+
+namespace AzureOpenAIProxy.AppHost.Tests.PlaygroundApp.Pages;
+
+public class AdminUpdateEventPageTests(AspireAppHostFixture host) : IClassFixture
+{
+ [Fact]
+ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_OK()
+ {
+ // Arrange
+ using var httpClient = host.App!.CreateHttpClient("playgroundapp");
+ await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
+
+ var eventId = Guid.NewGuid();
+ var expectedUrl = $"/admin/events/edit/{eventId}";
+
+ // Act
+ var response = await httpClient.GetAsync(expectedUrl);
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ [Theory]
+ [InlineData("_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css")]
+ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_CSS_Elements(string expected)
+ {
+ // Arrange
+ using var httpClient = host.App!.CreateHttpClient("playgroundapp");
+ await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
+
+ var eventId = Guid.NewGuid();
+ var expectedUrl = $"/admin/events/edit/{eventId}";
+
+ // Act
+ var html = await httpClient.GetStringAsync(expectedUrl);
+
+ // Assert
+ html.Should().Contain(expected);
+ }
+
+ [Theory]
+ [InlineData("_content/Microsoft.FluentUI.AspNetCore.Components/Microsoft.FluentUI.AspNetCore.Components.lib.module.js")]
+ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_JavaScript_Elements(string expected)
+ {
+ // Arrange
+ using var httpClient = host.App!.CreateHttpClient("playgroundapp");
+ await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
+
+ var eventId = Guid.NewGuid();
+ var expectedUrl = $"/admin/events/edit/{eventId}";
+
+ // Act
+ var html = await httpClient.GetStringAsync(expectedUrl);
+
+ // Assert
+ html.Should().Contain(expected);
+ }
+
+ [Theory]
+ [InlineData("")]
+ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
+ {
+ // Arrange
+ using var httpClient = host.App!.CreateHttpClient("playgroundapp");
+ await host.ResourceNotificationService.WaitForResourceAsync("playgroundapp", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
+
+ var eventId = Guid.NewGuid();
+ var expectedUrl = $"/admin/events/edit/{eventId}";
+
+ // Act
+ var html = await httpClient.GetStringAsync(expectedUrl);
+
+ // Assert
+ html.Should().Contain(expected);
+ }
+}
\ No newline at end of file
diff --git a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/EventsPageTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/EventsPageTests.cs
index 45377403..e25bdd7c 100644
--- a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/EventsPageTests.cs
+++ b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/EventsPageTests.cs
@@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}
[Theory]
- [InlineData("")]
+ [InlineData("")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
diff --git a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/HomePageTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/HomePageTests.cs
index 9f276ff5..c316cf81 100644
--- a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/HomePageTests.cs
+++ b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/HomePageTests.cs
@@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}
[Theory]
- [InlineData("")]
+ [InlineData("")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
diff --git a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/PlaygroundPageTests.cs b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/PlaygroundPageTests.cs
index bb680273..6e2bf859 100644
--- a/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/PlaygroundPageTests.cs
+++ b/test/AzureOpenAIProxy.AppHost.Tests/PlaygroundApp/Pages/PlaygroundPageTests.cs
@@ -51,7 +51,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}
[Theory]
- [InlineData("")]
+ [InlineData("")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
diff --git a/test/AzureOpenAIProxy.PlaygroundApp.Tests/Pages/AdminUpdateEventPageTests.cs b/test/AzureOpenAIProxy.PlaygroundApp.Tests/Pages/AdminUpdateEventPageTests.cs
new file mode 100644
index 00000000..70e6fad2
--- /dev/null
+++ b/test/AzureOpenAIProxy.PlaygroundApp.Tests/Pages/AdminUpdateEventPageTests.cs
@@ -0,0 +1,159 @@
+using FluentAssertions;
+
+using Microsoft.Playwright;
+using Microsoft.Playwright.NUnit;
+
+namespace AzureOpenAIProxy.PlaygroundApp.Tests.Pages;
+
+[Parallelizable(ParallelScope.Self)]
+[TestFixture]
+[Property("Category", "Integration")]
+public class AdminUpdateEventPageTests : PageTest
+{
+ public override BrowserNewContextOptions ContextOptions() => new() { IgnoreHTTPSErrors = true, };
+
+ [SetUp]
+ public async Task Init()
+ {
+ var eventId = Guid.NewGuid();
+ await Page.GotoAsync($"https://localhost:5001/admin/events/edit/{eventId}");
+ await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+ }
+
+ [Test]
+ [TestCase("event-title")]
+ [TestCase("event-summary")]
+ [TestCase("event-description")]
+ [TestCase("event-start-date")]
+ [TestCase("event-start-time")]
+ [TestCase("event-end-date")]
+ [TestCase("event-end-time")]
+ [TestCase("event-timezone")]
+ [TestCase("event-organizer-name")]
+ [TestCase("event-organizer-email")]
+ [TestCase("event-coorgnizer-name")]
+ [TestCase("event-coorgnizer-email")]
+ [TestCase("event-max-token-cap")]
+ [TestCase("event-daily-request-cap")]
+ [TestCase("admin-event-detail-update")]
+ [TestCase("admin-event-detail-cancel")]
+ public async Task Given_Update_Event_Details_Page_When_Navigated_Then_It_Should_Load_Correctly(string id)
+ {
+ // Act
+ var element = Page.Locator($"#{id}");
+
+ // Assert
+ await Expect(element).ToBeVisibleAsync();
+ }
+
+ [Test]
+ public async Task Given_Input_Event_Timezone_When_Initialized_Timezone_Then_It_Should_Update_Value()
+ {
+ // Arrange
+ var inputTimezone = Page.Locator("#event-timezone");
+
+ string timeZone = GetIanaTimezoneId();
+
+ // Act
+ string inputTimezoneValue = await inputTimezone.GetAttributeAsync("current-value");
+
+ // Assert
+ inputTimezoneValue.Should().Be(timeZone);
+ }
+
+ [Test]
+ public async Task Given_Input_Event_Start_Date_When_Initialized_Timezone_Then_It_Should_Update_Value()
+ {
+ // Arrange
+ var inputStartDate = Page.Locator("#event-start-date");
+
+ string timezoneId = GetIanaTimezoneId();
+ DateTimeOffset currentTime = GetCurrentDateTimeOffset(timezoneId);
+ var startTime = currentTime.AddHours(1).AddMinutes(-currentTime.Minute);
+
+ // Act
+ var inputStartDateValue = await inputStartDate.GetAttributeAsync("current-value");
+
+ // Assert
+ inputStartDateValue.Should().Be(startTime.ToString("yyyy-MM-dd"));
+ }
+
+ [Test]
+ public async Task Given_Input_Event_Start_Time_When_Initialized_Timezone_Then_It_Should_Update_Value()
+ {
+ // Arrange
+ var inputStartTime = Page.Locator("#event-start-time");
+
+ string timezoneId = GetIanaTimezoneId();
+ DateTimeOffset currentTime = GetCurrentDateTimeOffset(timezoneId);
+ var startTime = currentTime.AddHours(1).AddMinutes(-currentTime.Minute);
+
+ // Act
+ var inputStartTimeValue = await inputStartTime.GetAttributeAsync("current-value");
+
+ // Assert
+ inputStartTimeValue.Should().Be(startTime.ToString("HH:mm"));
+ }
+
+ [Test]
+ public async Task Given_Input_Event_End_Date_When_Initialized_Timezone_Then_It_Should_Update_Value()
+ {
+ // Arrange
+ var inputEndDate = Page.Locator("#event-end-date");
+
+ string timezoneId = GetIanaTimezoneId();
+ DateTimeOffset currentTime = GetCurrentDateTimeOffset(timezoneId);
+ var endTime = currentTime.AddDays(1).AddHours(1).AddMinutes(-currentTime.Minute);
+
+ // Act
+ var inputEndDateValue = await inputEndDate.GetAttributeAsync("current-value");
+
+ // Assert
+ inputEndDateValue.Should().Be(endTime.ToString("yyyy-MM-dd"));
+ }
+
+ [Test]
+ public async Task Given_Input_Event_End_Time_When_Initialized_Timezone_Then_It_Should_Update_Value()
+ {
+ // Arrange
+ var inputEndTime = Page.Locator("#event-end-time");
+
+ string timezoneId = GetIanaTimezoneId();
+ DateTimeOffset currentTime = GetCurrentDateTimeOffset(timezoneId);
+ var endTime = currentTime.AddDays(1).AddHours(1).AddMinutes(-currentTime.Minute);
+
+ // Act
+ var inputEndTimeValue = await inputEndTime.GetAttributeAsync("current-value");
+
+ // Assert
+ inputEndTimeValue.Should().Be(endTime.ToString("HH:mm"));
+ }
+
+ [TearDown]
+ public async Task CleanUp()
+ {
+ await Page.CloseAsync();
+ }
+
+ private string GetIanaTimezoneId()
+ {
+ string timezoneId = TimeZoneInfo.Local.Id;
+
+ if (OperatingSystem.IsWindows())
+ {
+ if (TimeZoneInfo.TryConvertWindowsIdToIanaId(timezoneId, out var ianaTimezoneId))
+ {
+ timezoneId = ianaTimezoneId;
+ }
+ }
+
+ return timezoneId;
+ }
+
+ private DateTimeOffset GetCurrentDateTimeOffset(string timezoneId)
+ {
+ var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timezoneId);
+
+ return TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, timeZoneInfo);
+ }
+}
\ No newline at end of file