diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index c3acad43..cd92652b 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -17,6 +17,7 @@ + diff --git a/Moonlight.Client/Implementations/AuthenticationUiHandler.cs b/Moonlight.Client/Implementations/AuthenticationUiHandler.cs index fef8e2ad..95de0d66 100644 --- a/Moonlight.Client/Implementations/AuthenticationUiHandler.cs +++ b/Moonlight.Client/Implementations/AuthenticationUiHandler.cs @@ -14,7 +14,7 @@ public class AuthenticationUiHandler : IAppLoader, IAppScreen { var identityService = serviceProvider.GetRequiredService(); - return Task.FromResult(identityService.IsLoggedIn); + return Task.FromResult(!identityService.IsLoggedIn); // Only show the screen when we are not logged in } public RenderFragment Render() => ComponentHelper.FromType(); diff --git a/Moonlight.Client/Interfaces/IAppLoader.cs b/Moonlight.Client/Interfaces/IAppLoader.cs index f1e2b5e0..4cc8c280 100644 --- a/Moonlight.Client/Interfaces/IAppLoader.cs +++ b/Moonlight.Client/Interfaces/IAppLoader.cs @@ -1,3 +1,5 @@ +using Moonlight.Client.UI.Layouts; + namespace Moonlight.Client.Interfaces; public interface IAppLoader diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj index bc3f6ec7..bc029be0 100644 --- a/Moonlight.Client/Moonlight.Client.csproj +++ b/Moonlight.Client/Moonlight.Client.csproj @@ -11,7 +11,7 @@ - + @@ -29,4 +29,10 @@ + + + ..\..\..\..\GitHub\Marcel-Baumgartner\MoonCore\MoonCore\MoonCore.Blazor.Tailwind\bin\Debug\net8.0\MoonCore.Blazor.Tailwind.dll + + + diff --git a/Moonlight.Client/Program.cs b/Moonlight.Client/Program.cs index b947e06c..696c47d9 100644 --- a/Moonlight.Client/Program.cs +++ b/Moonlight.Client/Program.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MoonCore.Blazor.Tailwind.Extensions; +using MoonCore.Blazor.Tailwind.Forms; +using MoonCore.Blazor.Tailwind.Forms.Components; using MoonCore.Extensions; using MoonCore.Helpers; using MoonCore.PluginFramework.Services; @@ -48,8 +50,13 @@ builder.RootComponents.Add("head::after"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddScoped(sp => new HttpApiClient(sp.GetRequiredService())); +builder.Services.AutoAddServices(); + builder.Services.AddMoonCoreBlazorTailwind(); +FormComponentRepository.Set(); +FormComponentRepository.Set(); + // Implementation service var implementationService = new ImplementationService(); diff --git a/Moonlight.Client/Services/IdentityService.cs b/Moonlight.Client/Services/IdentityService.cs index 46fdb9ff..a5491d8c 100644 --- a/Moonlight.Client/Services/IdentityService.cs +++ b/Moonlight.Client/Services/IdentityService.cs @@ -1,3 +1,4 @@ +using MoonCore.Attributes; using MoonCore.Blazor.Tailwind.Services; using MoonCore.Exceptions; using MoonCore.Helpers; @@ -5,6 +6,7 @@ using Moonlight.Shared.Http.Responses.Auth; namespace Moonlight.Client.Services; +[Scoped] public class IdentityService { public string Username { get; private set; } @@ -40,7 +42,7 @@ public class IdentityService IsLoggedIn = false; } - await OnStateChanged(); + //await OnStateChanged?.Invoke(); } public async Task Login(string token) diff --git a/Moonlight.Client/Styles/style.css b/Moonlight.Client/Styles/style.css index ca2a44da..a82a8fff 100644 --- a/Moonlight.Client/Styles/style.css +++ b/Moonlight.Client/Styles/style.css @@ -12,4 +12,66 @@ #blazor-error-ui { display: none; +} + +#loader { + display: block; + width: 10rem; + height: 10rem; + border-radius: 50%; + border: 3px solid transparent; + border-top-color: #9370DB; + -webkit-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; + @apply border-t-primary-500 +} +#loader:before { + content: ""; + position: absolute; + top: 5px; + left: 5px; + right: 5px; + bottom: 5px; + border-radius: 50%; + border: 3px solid transparent; + -webkit-animation: spin 3s linear infinite; + animation: spin 3s linear infinite; + @apply border-t-tertiary-500 +} +#loader:after { + content: ""; + position: absolute; + top: 15px; + left: 15px; + right: 15px; + bottom: 15px; + border-radius: 50%; + border: 3px solid transparent; + -webkit-animation: spin 1.5s linear infinite; + animation: spin 1.5s linear infinite; + @apply border-t-info-500 +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } } \ No newline at end of file diff --git a/Moonlight.Client/UI/Layouts/MainLayout.razor b/Moonlight.Client/UI/Layouts/MainLayout.razor index 9a37c48f..63224017 100644 --- a/Moonlight.Client/UI/Layouts/MainLayout.razor +++ b/Moonlight.Client/UI/Layouts/MainLayout.razor @@ -11,13 +11,15 @@ @if (IsLoading) { - - - + + + } else if (CurrentScreen != null) { - @CurrentScreen + + @CurrentScreen + } else { @@ -60,21 +62,28 @@ else private bool IsLoading = true; private RenderFragment? CurrentScreen; - private async Task Load(LazyLoader lazyLoader) + protected override async Task OnAfterRenderAsync(bool firstRender) { - await lazyLoader.UpdateText("Retrieving data"); - await RunLoaders(); + if(!firstRender) + return; - await RenderScreens(); - - IsLoading = false; - await InvokeAsync(StateHasChanged); + await Load(); } - public async Task Reload() + public async Task Load() { IsLoading = true; await InvokeAsync(StateHasChanged); + + // + await RunLoaders(); + + // Screens + await RenderScreens(); + + // + IsLoading = false; + await InvokeAsync(StateHasChanged); } private async Task RunLoaders() @@ -87,6 +96,7 @@ else { try { + Logger.LogDebug("Running application loader '{name}'", loader.GetType().Name); await loader.Load(ServiceProvider); } catch (Exception e) @@ -110,9 +120,11 @@ else continue; CurrentScreen = screen.Render(); + await InvokeAsync(StateHasChanged); - return; } + + await InvokeAsync(StateHasChanged); } } \ No newline at end of file diff --git a/Moonlight.Client/UI/Partials/AppHeader.razor b/Moonlight.Client/UI/Partials/AppHeader.razor index 6e137ea0..0da86ce0 100644 --- a/Moonlight.Client/UI/Partials/AppHeader.razor +++ b/Moonlight.Client/UI/Partials/AppHeader.razor @@ -1,4 +1,8 @@ -@using Moonlight.Client.UI.Layouts +@using Moonlight.Client.Services +@using Moonlight.Client.UI.Layouts + +@inject IdentityService IdentityService +@inject ToastService ToastService @if (Layout.ShowMobileNavigation) @@ -35,7 +39,7 @@ - + Open user menu @@ -51,17 +55,17 @@ - + - Your profile - Sign out + Your profile + Sign out @@ -72,11 +76,42 @@ { [Parameter] public MainLayout Layout { get; set; } - protected override async Task OnAfterRenderAsync(bool firstRender) + private bool ShowProfileNav = false; + + protected override Task OnAfterRenderAsync(bool firstRender) { if(!firstRender) - return; + return Task.CompletedTask; Layout.OnStateChanged += () => InvokeAsync(StateHasChanged); + + return Task.CompletedTask; + } + + private async Task ToggleProfileNav() + { + ShowProfileNav = !ShowProfileNav; + await InvokeAsync(StateHasChanged); + } + + private Task ProfileNav_OnFocusOut() + { + Task.Run(async () => + { + await Task.Delay(200); + + ShowProfileNav = false; + await InvokeAsync(StateHasChanged); + }); + + return Task.CompletedTask; + } + + private async Task Logout() + { + await IdentityService.Logout(); + await ToastService.Info("Successfully logged out"); + + await Layout.Load(); } } diff --git a/Moonlight.Client/UI/Partials/AppSidebar.razor b/Moonlight.Client/UI/Partials/AppSidebar.razor index 277d6349..a354ac01 100644 --- a/Moonlight.Client/UI/Partials/AppSidebar.razor +++ b/Moonlight.Client/UI/Partials/AppSidebar.razor @@ -38,8 +38,8 @@ - - + a + diff --git a/Moonlight.Client/UI/Screens/AuthenticationScreen.razor b/Moonlight.Client/UI/Screens/AuthenticationScreen.razor index 3b1cbbf6..9d6c6584 100644 --- a/Moonlight.Client/UI/Screens/AuthenticationScreen.razor +++ b/Moonlight.Client/UI/Screens/AuthenticationScreen.razor @@ -1,18 +1,141 @@ @page "/auth/register" @page "/auth/login" +@using MoonCore.Blazor.Tailwind.Forms.Components +@using MoonCore.Helpers +@using Moonlight.Client.Services +@using Moonlight.Client.UI.Layouts +@using Moonlight.Shared.Http.Requests.Auth +@using Moonlight.Shared.Http.Responses.Auth + +@implements IDisposable + @inject NavigationManager Navigation +@inject HttpApiClient ApiClient +@inject IdentityService IdentityService @{ var url = new Uri(Navigation.Uri); var isRegister = url.LocalPath.StartsWith("/auth/register"); } -@if (isRegister) + + + @if (isRegister) + { + + + + Register your account + + + + + + + + Register + + Already registered? + Login + + + + } + else + { + + + + Sign in to your account + + + + + + + + Login + + Need an account registered? + Register + + + + } + + + +@code { - -} -else -{ - + [CascadingParameter] public MainLayout Layout { get; set; } + + // Page change handling + protected override void OnInitialized() + { + Navigation.LocationChanged += NavigationOnLocationChanged; + } + + private async void NavigationOnLocationChanged(object? sender, LocationChangedEventArgs e) + => await InvokeAsync(StateHasChanged); + + public void Dispose() + { + Navigation.LocationChanged -= NavigationOnLocationChanged; + } + + // Register + private RegisterRequest RegisterRequest = new(); + private HandleForm RegisterForm; + + private async Task OnSubmitRegister() + { + var response = await ApiClient.PostJson("api/auth/register", RegisterRequest); + await IdentityService.Login(response.Token); + + await HandleAfterAuthPage(); + } + + private void OnConfigureRegister(FormConfiguration configuration) + { + configuration.WithField(x => x.Username, fieldConfiguration => { fieldConfiguration.Columns = 6; }); + + configuration.WithField(x => x.Email, fieldConfiguration => { fieldConfiguration.Columns = 6; }); + + configuration + .WithField(x => x.Password, fieldConfiguration => { fieldConfiguration.Columns = 6; }) + .WithComponent(component => { component.Type = "password"; }); + } + + // Login + private LoginRequest LoginRequest = new(); + private HandleForm LoginForm; + + private async Task OnSubmitLogin() + { + var response = await ApiClient.PostJson("api/auth/login", LoginRequest); + await IdentityService.Login(response.Token); + + await HandleAfterAuthPage(); + } + + private void OnConfigureLogin(FormConfiguration configuration) + { + configuration.WithField(x => x.Email, fieldConfiguration => { fieldConfiguration.Columns = 6; }); + + configuration + .WithField(x => x.Password, fieldConfiguration => { fieldConfiguration.Columns = 6; }) + .WithComponent(component => { component.Type = "password"; }); + } + + // Navigation handling + private async Task HandleAfterAuthPage() + { + var url = new Uri(Navigation.Uri); + + if (url.LocalPath.StartsWith("/auth/login") || url.LocalPath.StartsWith("/auth/register")) + Navigation.NavigateTo("/"); + + await Layout.Load(); + } } \ No newline at end of file diff --git a/Moonlight.Client/wwwroot/icon-192.png b/Moonlight.Client/wwwroot/icon-192.png index 166f56da..1b417d98 100644 Binary files a/Moonlight.Client/wwwroot/icon-192.png and b/Moonlight.Client/wwwroot/icon-192.png differ diff --git a/Moonlight.Client/wwwroot/icon-512.png b/Moonlight.Client/wwwroot/icon-512.png index c2dd4842..437487cf 100644 Binary files a/Moonlight.Client/wwwroot/icon-512.png and b/Moonlight.Client/wwwroot/icon-512.png differ diff --git a/Moonlight.Client/wwwroot/index.html b/Moonlight.Client/wwwroot/index.html index 170711bf..21ef0215 100644 --- a/Moonlight.Client/wwwroot/index.html +++ b/Moonlight.Client/wwwroot/index.html @@ -12,10 +12,12 @@ - + - - + + + + diff --git a/Moonlight.Client/wwwroot/logo.svg b/Moonlight.Client/wwwroot/logo.svg new file mode 100644 index 00000000..193ebfae --- /dev/null +++ b/Moonlight.Client/wwwroot/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Moonlight.Client/wwwroot/logolong.webp b/Moonlight.Client/wwwroot/logolong.webp new file mode 100644 index 00000000..e5e7926e Binary files /dev/null and b/Moonlight.Client/wwwroot/logolong.webp differ diff --git a/Moonlight.Client/wwwroot/manifest.webmanifest b/Moonlight.Client/wwwroot/manifest.webmanifest index 031440d7..f3d69b34 100644 --- a/Moonlight.Client/wwwroot/manifest.webmanifest +++ b/Moonlight.Client/wwwroot/manifest.webmanifest @@ -1,11 +1,11 @@ { - "name": "Moonlight.Client", + "name": "Moonlight Client", "short_name": "Moonlight.Client", "id": "./", "start_url": "./", "display": "standalone", "background_color": "#ffffff", - "theme_color": "#03173d", + "theme_color": "#030b1f", "prefer_related_applications": false, "icons": [ {
+ Already registered? + Login +
+ Need an account registered? + Register +