Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@page "/admin/events/edit/{EventId:guid}"

@using AzureOpenAIProxy.PlaygroundApp.Models;

<PageTitle>Update Event</PageTitle>

<h1>Update Event</h1>

<UpdateEventDetailsComponent Id="admin-update-event" EventId = "@EventId" @rendermode="InteractiveServer" />

@code {
[Parameter]
public Guid EventId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@using AzureOpenAIProxy.PlaygroundApp.Models

@inject NavigationManager NavigationManager

<FluentLayout Id="@Id">
@if (eventDetails == null)
{
Expand All @@ -21,7 +23,7 @@
<AdminEventIsActiveComponent IsActive="@(((AdminEventDetails)@context).IsActive)" />
</TemplateColumn>
<TemplateColumn Class="fluent-datagrid-cell" Title="Actions" Align="@Align.Center">
<FluentButton aria-label="Edit item" IconEnd="@(new Icons.Regular.Size16.Edit())" />
<FluentButton aria-label="Edit item" IconEnd="@(new Icons.Regular.Size16.Edit())" @onclick="@(() => NavigateToEdit(((AdminEventDetails)@context).EventId))" />
<FluentButton aria-label="Delete item" IconEnd="@(new Icons.Regular.Size16.Delete())" />
</TemplateColumn>
</FluentDataGrid>
Expand Down Expand Up @@ -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}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
@using AzureOpenAIProxy.PlaygroundApp.Models
@using NodaTime
@using NodaTime.Extensions
@using System.Globalization

@inject NavigationManager NavigationManager

<FluentLayout Id="@Id">
@if(adminEventDetails == null)
{
<p><em>Loading...</em></p>
}
else
{
<FluentHeader>Update Event</FluentHeader>
<FluentBodyContent>
<section>
<h2>Event Infomation</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-title" Class="update-input-label" >Title</FluentLabel>
<FluentTextField Id="event-title" Name="title" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.Title" Required/>
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-summary" Class="update-input-label">Summary</FluentLabel>
<FluentTextField id="event-summary" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.Summary" Required/>
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-description" Class="update-input-label">Description</FluentLabel>
<FluentTextArea Id="event-description" Style="width:300px" @bind-Value="adminEventDetails.Description"/>
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-start-date" Class="update-input-label">Event Start Date</FluentLabel>
<FluentDatePicker Id="event-start-date" Value="@adminEventDetails.DateStart.DateTime" ValueChanged="@(e => adminEventDetails.DateStart = e.Value)" Culture="System.Globalization.CultureInfo.CurrentCulture" />
<FluentTimePicker Id="event-start-time" Value="@adminEventDetails.DateStart.DateTime" ValueChanged="@(e => adminEventDetails.DateStart = e.Value)" />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-end-date" Class="update-input-label">Event End Date</FluentLabel>
<FluentDatePicker Id="event-end-date" Value="@adminEventDetails.DateEnd.DateTime" ValueChanged="@(e => adminEventDetails.DateEnd = e.Value)" Culture="System.Globalization.CultureInfo.CurrentCulture" />
<FluentTimePicker Id="event-end-time" Value="@adminEventDetails.DateEnd.DateTime" ValueChanged="@(e => adminEventDetails.DateEnd = e.Value)" />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-timezone" Class="update-input-label">Time Zone</FluentLabel>
<FluentSelect Id="event-timezone" @bind-Value="@adminEventDetails.TimeZone" Height="500px" TOption="string" Required>
@foreach (var timeZone in timeZoneList)
{
<FluentOption Value="@timeZone.Id">@timeZone.Id</FluentOption>
}
</FluentSelect>
</FluentStack>
</section>

<section>
<h2>Event Organizer</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-organizer-name" Class="update-input-label">Organizer Name</FluentLabel>
<FluentTextField Id="event-organizer-name" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.OrganizerName" Required/>
</FluentStack>


<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-organizer-email" Class="update-input-label">Organizer Email</FluentLabel>
<FluentTextField Id="event-organizer-email" TextFieldType="TextFieldType.Email" @bind-Value="adminEventDetails.OrganizerEmail" Required/>
</FluentStack>
</section>

<section>
<h2>Event Coorganizers</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-coorgnizer-name" Class="update-input-label">Coorgnizer Name</FluentLabel>
<FluentTextField Id="event-coorgnizer-name" TextFieldType="TextFieldType.Text" @bind-Value="adminEventDetails.CoorganizerName" Required />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-coorgnizer-email" Class="update-input-label">Coorgnizer Email</FluentLabel>
<FluentTextField Id="event-coorgnizer-email" TextFieldType="TextFieldType.Email" @bind-Value="adminEventDetails.CoorganizerEmail" Required/>
</FluentStack>
</section>

<section>
<h2>Event Configuration</h2>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-max-token-cap" Class="update-input-label">Max Token Cap</FluentLabel>
<FluentNumberField Id="event-max-token-cap" @bind-Value="adminEventDetails.MaxTokenCap" Required />
</FluentStack>

<FluentStack Class="update-fluent-stack" Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<FluentLabel For="event-daily-request-cap" Class="update-input-label">Daily Request Cap</FluentLabel>
<FluentNumberField Id="event-daily-request-cap" @bind-Value="adminEventDetails.DailyRequestCap" Required/>
</FluentStack>
</section>

<section class="button-section">
<FluentButton Id="admin-event-detail-update" Appearance="Appearance.Accent" Class="button" @onclick="UpdateEvent">Update Event</FluentButton>
<FluentButton Id="admin-event-detail-cancel" Appearance="Appearance.Outline" Class="button" @onclick="CancelUpdate">Cancel</FluentButton>
</section>
</FluentBodyContent>
}
</FluentLayout>

@code {
[Parameter]
public string? Id { get; set; }

[Parameter]
public Guid EventId { get; set;}

private List<DateTimeZone>? 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 = $"[email protected]",
CoorganizerName = $"dummy coorganizer",
CoorganizerEmail = $"[email protected]",
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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AspireAppHostFixture>
{
[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("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_Jav
}

[Theory]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"display: fixed;\"></div>")]
[InlineData("<div class=\"fluent-tooltip-provider\" style=\"position: fixed; z-index: 9999;\"></div>")]
public async Task Given_Resource_When_Invoked_Endpoint_Then_It_Should_Return_HTML_Elements(string expected)
{
// Arrange
Expand Down
Loading
Loading