Improved PWA options. Fully implemented auth

This commit is contained in:
Masu-Baumgartner
2024-10-02 16:31:23 +02:00
parent 522d0c1471
commit a0432eec68
17 changed files with 304 additions and 38 deletions

View File

@@ -11,13 +11,15 @@
@if (IsLoading)
{
<LazyLoader Load="Load">
</LazyLoader>
<div class="h-full w-full min-h-[100dvh] flex items-center justify-center">
<div id="loader"></div>
</div>
}
else if (CurrentScreen != null)
{
@CurrentScreen
<CascadingValue Value="this" IsFixed="true">
@CurrentScreen
</CascadingValue>
}
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);
}
}

View File

@@ -1,4 +1,8 @@
@using Moonlight.Client.UI.Layouts
@using Moonlight.Client.Services
@using Moonlight.Client.UI.Layouts
@inject IdentityService IdentityService
@inject ToastService ToastService
<div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 bg-gray-800/60 backdrop-blur px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
@if (Layout.ShowMobileNavigation)
@@ -35,7 +39,7 @@
<!-- Profile dropdown -->
<div class="relative">
<button type="button" class="-m-1.5 flex items-center p-1.5" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<button @onclick="ToggleProfileNav" @onfocusout="ProfileNav_OnFocusOut" type="button" class="-m-1.5 flex items-center p-1.5" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" src="https://masuowo.xyz/assets/images/avatar.png" alt="">
<span class="hidden lg:flex lg:items-center">
@@ -51,17 +55,17 @@
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
Entering: ""
From: "transform scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<div class="hidden absolute right-0 z-10 mt-2.5 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<div class="@(ShowProfileNav ? "opacity-100" : "opacity-0") transition ease-out duration-100 absolute right-0 z-10 mt-2.5 w-44 origin-top-right rounded-md bg-gray-750 py-2 shadow-lg ring-1 ring-gray-100/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<!-- Active: "bg-gray-50", Not Active: "" -->
<a href="#" class="block px-3 py-1 text-sm leading-6 text-gray-900" role="menuitem" tabindex="-1" id="user-menu-item-0">Your profile</a>
<a href="#" class="block px-3 py-1 text-sm leading-6 text-gray-900" role="menuitem" tabindex="-1" id="user-menu-item-1">Sign out</a>
<a href="/admin" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-0">Your profile</a>
<a @onclick="Logout" @onclick:preventDefault href="#" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-1">Sign out</a>
</div>
</div>
</div>
@@ -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();
}
}

View File

@@ -38,8 +38,8 @@
<!-- Sidebar component, swap this element with another sidebar if you like -->
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-gray-900 px-6 pb-4">
<div class="flex h-16 shrink-0 items-center">
<img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
<div class="flex h-16 shrink-0 items-center">a
<img class="h-8 w-auto" src="/logo.svg" alt="Moonlight">
</div>
<nav class="flex flex-1 flex-col">
<ul role="list" class="flex flex-1 flex-col gap-y-7">

View File

@@ -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)
<div class="h-full w-full min-h-[100dvh] flex items-center justify-center px-5 md:px-0">
@if (isRegister)
{
<div class="card card-body w-full max-w-lg">
<div class="sm:mx-auto sm:w-full sm:max-w-sm mb-5">
<img class="mx-auto h-16 w-auto" src="/logolong.webp" alt="Moonlight">
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-100">Register your account</h2>
</div>
<HandleForm @ref="RegisterForm" Model="RegisterRequest" OnValidSubmit="OnSubmitRegister">
<GeneratedForm Model="RegisterRequest" OnConfigure="OnConfigureRegister" Gap="gap-x-6 gap-y-3"/>
</HandleForm>
<div class="mt-5 flex flex-col justify-center">
<WButton OnClick="_ => RegisterForm.Submit()" CssClasses="btn btn-primary">Register</WButton>
<p class="mt-3 text-center text-sm text-gray-500">
Already registered?
<a href="/auth/login" class="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">Login</a>
</p>
</div>
</div>
}
else
{
<div class="card card-body w-full max-w-lg">
<div class="sm:mx-auto sm:w-full sm:max-w-sm mb-5">
<img class="mx-auto h-16 w-auto" src="/logolong.webp" alt="Moonlight">
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-100">Sign in to your account</h2>
</div>
<HandleForm @ref="LoginForm" Model="LoginRequest" OnValidSubmit="OnSubmitLogin">
<GeneratedForm Model="LoginRequest" OnConfigure="OnConfigureLogin" Gap="gap-x-6 gap-y-3"/>
</HandleForm>
<div class="mt-5 flex flex-col justify-center">
<WButton OnClick="_ => LoginForm.Submit()" CssClasses="btn btn-primary">Login</WButton>
<p class="mt-3 text-center text-sm text-gray-500">
Need an account registered?
<a href="/auth/register" class="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">Register</a>
</p>
</div>
</div>
}
</div>
@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<RegisterResponse>("api/auth/register", RegisterRequest);
await IdentityService.Login(response.Token);
await HandleAfterAuthPage();
}
private void OnConfigureRegister(FormConfiguration<RegisterRequest> 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<StringComponent>(component => { component.Type = "password"; });
}
// Login
private LoginRequest LoginRequest = new();
private HandleForm LoginForm;
private async Task OnSubmitLogin()
{
var response = await ApiClient.PostJson<LoginResponse>("api/auth/login", LoginRequest);
await IdentityService.Login(response.Token);
await HandleAfterAuthPage();
}
private void OnConfigureLogin(FormConfiguration<LoginRequest> configuration)
{
configuration.WithField(x => x.Email, fieldConfiguration => { fieldConfiguration.Columns = 6; });
configuration
.WithField(x => x.Password, fieldConfiguration => { fieldConfiguration.Columns = 6; })
.WithComponent<StringComponent>(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();
}
}