Updated front-end authentication.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
@using JSMR.UI.Blazor.Services
|
||||
@using JSMR.UI.Blazor.Components.Authentication
|
||||
@using JSMR.UI.Blazor.Services
|
||||
|
||||
@inject SessionState Session
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
<RadzenTheme Theme="material-dark" />
|
||||
</HeadContent>
|
||||
|
||||
<AuthenticationGate>
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
@@ -18,10 +20,11 @@
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</AuthenticationGate>
|
||||
|
||||
@code {
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await Session.RefreshAsync();
|
||||
//await Session.RefreshAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
@using JSMR.UI.Blazor.Services
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
|
||||
@inject SessionState Session
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@if (!_ready)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
private bool _ready;
|
||||
|
||||
// Add any routes you want public here.
|
||||
// Use absolute-path form (leading slash).
|
||||
private static readonly HashSet<string> _allowAnonymous = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"/login",
|
||||
"/login/", // optional
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Navigation.LocationChanged += OnLocationChanged;
|
||||
|
||||
// One-time refresh at app start
|
||||
await Session.RefreshAsync();
|
||||
_ready = true;
|
||||
|
||||
await EnforceAsync();
|
||||
}
|
||||
|
||||
private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
// If your Session can change based on navigation/cookies, you *may* refresh here,
|
||||
// but avoid doing it on every navigation unless necessary.
|
||||
// await Session.RefreshAsync();
|
||||
|
||||
await EnforceAsync();
|
||||
}
|
||||
|
||||
private Task EnforceAsync()
|
||||
{
|
||||
if (!_ready) return Task.CompletedTask;
|
||||
|
||||
var path = "/" + Navigation.ToBaseRelativePath(Navigation.Uri);
|
||||
var qIndex = path.IndexOf('?', StringComparison.Ordinal);
|
||||
if (qIndex >= 0) path = path[..qIndex];
|
||||
|
||||
// allow anonymous routes
|
||||
if (_allowAnonymous.Contains(path))
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!Session.IsAuthenticated)
|
||||
{
|
||||
var returnUrl = Uri.EscapeDataString(Navigation.Uri);
|
||||
Navigation.NavigateTo($"/login?returnUrl={returnUrl}", forceLoad: false);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> Navigation.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
@@ -5,7 +5,11 @@
|
||||
<div class="@GetClasses()" @onclick="@OnClickAsync">
|
||||
@if (Graphic != null)
|
||||
{
|
||||
<Icon Graphic="@Graphic.Value" Varient="@(IconVarient ?? Enums.IconVarient.None)" Color="@Color"></Icon>
|
||||
<Icon Graphic="@Graphic.Value"
|
||||
Varient="@(IconVarient ?? Enums.IconVarient.None)"
|
||||
Size="@(IconSize ?? Enums.SizeVarient.Small)"
|
||||
Color="@Color">
|
||||
</Icon>
|
||||
}
|
||||
<span>@ChildContent</span>
|
||||
</div>
|
||||
@@ -15,7 +19,12 @@ else
|
||||
<a class="@GetClasses()" href="@Url" target="@Target">
|
||||
@if (Graphic != null)
|
||||
{
|
||||
<Icon Graphic="@Graphic.Value" Varient="@(IconVarient ?? Enums.IconVarient.None)" Color="@Color"></Icon>
|
||||
<Icon
|
||||
Graphic="@Graphic.Value"
|
||||
Varient="@(IconVarient ?? Enums.IconVarient.None)"
|
||||
Size="@(IconSize ?? Enums.SizeVarient.Small)"
|
||||
Color="@Color">
|
||||
</Icon>
|
||||
}
|
||||
<span>@ChildContent</span>
|
||||
</a>
|
||||
@@ -32,6 +41,9 @@ else
|
||||
[Parameter]
|
||||
public IconVarient? IconVarient { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public SizeVarient? IconSize { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ColorVarient Color { get; set; } = ColorVarient.Primary;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
public Graphic Graphic { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public SizeVarient Size { get; set; } = SizeVarient.Medium;
|
||||
public SizeVarient Size { get; set; } = SizeVarient.Small;
|
||||
|
||||
[Parameter]
|
||||
public IconVarient Varient { get; set; } = IconVarient.None;
|
||||
@@ -25,7 +25,7 @@
|
||||
[
|
||||
$"j-icon",
|
||||
$"j-icon-{graphic}",
|
||||
$"j-icon-size-{Size.ToString().ToLower()}",
|
||||
$"size-{Size.ToString().ToLower()}",
|
||||
$"background-color-{Color.ToString().ToLower()}"
|
||||
];
|
||||
|
||||
|
||||
@@ -42,12 +42,12 @@
|
||||
<ProductTag Tag="tag"></ProductTag>
|
||||
}
|
||||
</div>
|
||||
<div class="j-tags">
|
||||
@* <div class="j-tags">
|
||||
@foreach (var tag in Product.Tags)
|
||||
{
|
||||
@* <TagChip Tag="tag"></TagChip> *@
|
||||
<TagChip Tag="tag"></TagChip>
|
||||
}
|
||||
</div>
|
||||
</div> *@
|
||||
</div>
|
||||
<div class="j-voice-work-info">
|
||||
<div class="j-release-date-container">
|
||||
|
||||
@@ -15,6 +15,7 @@ public enum Graphic
|
||||
Circle,
|
||||
Tag,
|
||||
Person,
|
||||
Avatar,
|
||||
Sort,
|
||||
Grid,
|
||||
Age,
|
||||
|
||||
5
JSMR.UI.Blazor/Layout/LoginLayout.razor
Normal file
5
JSMR.UI.Blazor/Layout/LoginLayout.razor
Normal file
@@ -0,0 +1,5 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="login-layout">
|
||||
@Body
|
||||
</div>
|
||||
@@ -1,27 +1,30 @@
|
||||
@using JSMR.UI.Blazor.Services
|
||||
@using JSMR.UI.Blazor.Components
|
||||
@using JSMR.UI.Blazor.Services
|
||||
|
||||
@inject SessionState Session
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="topbar">
|
||||
@if (Session.IsAuthenticated)
|
||||
{
|
||||
<span>Logged in as <b>@Session.Me?.name</b> (@Session.Me?.role)</span>
|
||||
<a href="" @onclick="OnLogout" style="margin-left: 12px;">Logout</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/login">Login</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="1" Dense="@_dense">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@ToggleDrawer" />
|
||||
<MudText>JSMR</MudText>
|
||||
<MudSpacer />
|
||||
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Inherit" Href="https://github.com/MudBlazor/MudBlazor" Target="_blank" />
|
||||
@* <MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Inherit" Href="https://github.com/MudBlazor/MudBlazor" Target="_blank" /> *@
|
||||
|
||||
@if (Session.IsAuthenticated)
|
||||
{
|
||||
@* <span>Logged in as <b>@Session.Me?.Name</b> (@Session.Me?.Role)</span> *@
|
||||
<Chip Graphic="Enums.Graphic.Avatar" IconVarient="Enums.IconVarient.Fill" IconSize="Enums.SizeVarient.Small" Color="Enums.ColorVarient.Blue">
|
||||
<span>@Session.Me?.Name @* (@Session.Me?.Role) *@</span>
|
||||
</Chip>
|
||||
<Chip Graphic="Enums.Graphic.Headphones" Color="Enums.ColorVarient.Primary" Click="@LogoutAsync">Logout</Chip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Chip Graphic="Enums.Graphic.Headphones" Color="Enums.ColorVarient.Primary" Url="/login">Login</Chip>
|
||||
}
|
||||
</MudAppBar>
|
||||
<MudDrawer @bind-Open="@_open" ClipMode="_clipMode" Breakpoint="@_breakpoint" Elevation="1" Variant="@DrawerVariant.Mini">
|
||||
<MudNavMenu>
|
||||
@@ -89,4 +92,10 @@
|
||||
{
|
||||
Session.Changed -= OnSessionChanged;
|
||||
}
|
||||
|
||||
private async Task LogoutAsync()
|
||||
{
|
||||
await Session.LogoutAsync();
|
||||
Navigation.NavigateTo("/login");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/login"
|
||||
@layout LoginLayout
|
||||
|
||||
@using JSMR.UI.Blazor.Services
|
||||
|
||||
@@ -9,29 +10,23 @@
|
||||
|
||||
@if (Session.IsAuthenticated)
|
||||
{
|
||||
<p>You're already logged in as <b>@Session.Me?.name</b>.</p>
|
||||
<p>You're already logged in as <b>@Session.Me?.Name</b>.</p>
|
||||
<button @onclick="Logout">Logout</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="max-width: 360px;">
|
||||
<div>
|
||||
<label>Username</label><br />
|
||||
<input @bind="username" />
|
||||
</div>
|
||||
<div style="margin-top: 8px;">
|
||||
<label>Password</label><br />
|
||||
<input type="password" @bind="password" />
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 12px;">
|
||||
<button @onclick="LoginAsync" disabled="@busy">Login</button>
|
||||
</div>
|
||||
|
||||
<BitCard>
|
||||
<BitStack>
|
||||
<BitTextField Label="Username" @bind-Value="username"></BitTextField>
|
||||
<BitTextField Label="Password" @bind-Value="password" Type="BitInputType.Password"></BitTextField>
|
||||
<BitButton OnClick="LoginAsync" IsEnabled="@(!busy)">Login</BitButton>
|
||||
@if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
<p style="color: crimson; margin-top: 8px;">@error</p>
|
||||
}
|
||||
</BitStack>
|
||||
</BitCard>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
@inherits SearchPageBase<VoiceWorkFilterState, VoiceWorkSearchResult>
|
||||
|
||||
<RequireAuthentication>
|
||||
<PageTitle>Voice Works</PageTitle>
|
||||
|
||||
<h3>Voice Works</h3>
|
||||
@@ -42,7 +41,6 @@
|
||||
</RightContent>
|
||||
</JPagination>
|
||||
}
|
||||
</RequireAuthentication>
|
||||
|
||||
@code {
|
||||
[Inject]
|
||||
|
||||
@@ -25,5 +25,5 @@ public class AuthenticationClient(HttpClient http)
|
||||
return await resp.Content.ReadFromJsonAsync<MeResponse>(cancellationToken: ct);
|
||||
}
|
||||
|
||||
public sealed record MeResponse(string? name, string? id, string? role);
|
||||
public sealed record MeResponse(string? Name, string? Id, string? Role);
|
||||
}
|
||||
@@ -659,6 +659,12 @@ code {
|
||||
.j-chip.is-clickable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
--chip-hover-alpha: 0.2;
|
||||
transition: .2s linear;
|
||||
}
|
||||
|
||||
.j-chip.is-clickable:hover {
|
||||
background: rgb(var(--chip-rgb) / var(--chip-hover-alpha));
|
||||
}
|
||||
|
||||
.j-chip.varient-filled {
|
||||
@@ -703,6 +709,21 @@ code {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.j-icon.size-small {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.j-icon.size-medium {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.j-icon.size-large {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.j-icon-color-yellow {
|
||||
background: #ffe073;
|
||||
}
|
||||
@@ -784,6 +805,14 @@ code {
|
||||
mask-image: url("../svg/person-fill.svg");
|
||||
}
|
||||
|
||||
.j-icon-avatar {
|
||||
mask-image: url("../svg/person-circle.svg");
|
||||
}
|
||||
|
||||
.j-icon-avatar-fill {
|
||||
mask-image: url("../svg/person-circle.svg");
|
||||
}
|
||||
|
||||
.j-icon-sort {
|
||||
mask-image: url("../svg/sort.svg");
|
||||
}
|
||||
|
||||
4
JSMR.UI.Blazor/wwwroot/svg/person-circle.svg
Normal file
4
JSMR.UI.Blazor/wwwroot/svg/person-circle.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16">
|
||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
|
||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 342 B |
Reference in New Issue
Block a user