Represented user details better, refresh on log in

This commit is contained in:
Gordon Pedersen 2024-06-01 14:38:12 +10:00
parent 5fb95c8305
commit 4fa8440e1f
10 changed files with 256 additions and 67 deletions

View file

@ -1,9 +1,12 @@
namespace Neighbourhood.omg.lol {
public partial class App : Application {
public App() {
public App(NavigatorService navigatorService) {
InitializeComponent();
MainPage = new AppShell();
NavigatorService = navigatorService;
}
internal NavigatorService NavigatorService { get; private set; }
}
}

View file

@ -1,6 +1,16 @@
@inherits LayoutComponentBase
@inject NavigatorService NavigatorService
@inject NavigationManager NavigationManager
@inject State State
<link rel="stylesheet" href="vendor/fluent-emoji/animated.css" />
<NavMenu />
<main class="responsive max">
@Body
</main>
@code {
protected override void OnInitialized() {
base.OnInitialized();
NavigatorService.NavigationManager = NavigationManager;
}
}

View file

@ -1,22 +1,46 @@
@inject CustomAuthenticationStateProvider AuthStateProvider;
@inject State State;
<nav class="left drawer l">
<header>
<nav>
<AuthorizeView>
<Authorized>
<img class="circle medium" src="https://profiles.cache.lol/@FirstAddress/picture" alt="@FirstAddress" />
<div>Hey, @Name.</div>
<button class="transparent circle large">
<img class="responsive" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
<menu class="no-wrap">
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
<img class="tiny circle" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address" />
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
</a>
}
<a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i>
<span>Logout</span>
</a>
</menu>
</button>
<div>
Hey, @State.Name. <br />
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</span>
</div>
</Authorized>
<NotAuthorized>
<img src="https://cdn.cache.lol/img/prami.svg" class="medium">
<button class="transparent circle large">
<img class="responsive" src="https://cdn.cache.lol/img/prami.svg">
<menu class="no-wrap">
<a class="row" href="/login">
<i class="fa-solid fa-door-closed"></i>
<span>Login</span>
</a>
</menu>
</button>
<div>
Hey there. <br/>
Hey there. <br />
<a href="/login">Login?</a>
</div>
</NotAuthorized>
</AuthorizeView>
</nav>
</header>
<NavLink class="row nav-link" href="" Match="NavLinkMatch.All">
@ -27,51 +51,114 @@
<i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="#4dabf7" d="M250 450c-38.388 0-76.775-14.646-106.066-43.934l-100-100c-58.579-58.58-58.579-153.553 0-212.132C100.534 37.336 191.105 35.421 250 88.191c58.898-52.768 149.47-50.853 206.066 5.743 58.58 58.58 58.58 153.553 0 212.132l-100 100C326.778 435.354 288.39 450 250 450" /><path fill="#228be6" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" stroke="#461036" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /><path fill="#1971c2" d="M438.68 212.011c0-32.564-26.399-58.962-58.963-58.962s-58.962 26.398-58.962 58.962 26.398 58.963 58.962 58.963 58.962-26.399 58.962-58.963m-259.433 0c0-32.564-26.398-58.962-58.962-58.962S61.32 179.447 61.32 212.011s26.398 58.963 58.963 58.963 58.962-26.399 58.962-58.963" /><path fill="#4dabf7" stroke="#1864ab" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /></svg></i>
<div>Statuslog</div>
</NavLink>
<div class="row max"></div>
<footer>
<AuthorizeView>
<Authorized><a class="button small" @onclick='() => AuthStateProvider.Logout()'>Logout</a></Authorized>
<NotAuthorized><a class="button small" href="/login">Login</a></NotAuthorized>
</AuthorizeView>
</footer>
</nav>
<nav class="left m">
<header>
<img src="https://cdn.cache.lol/img/prami.svg" class="circle">
<AuthorizeView>
<Authorized>
<button class="transparent circle">
<img class="responsive" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
<menu class="no-wrap">
<a class="row">
<img class="tiny" data-emoji="👋">
<span>Hey, @State.Name.</span>
</a>
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
<img class="tiny circle" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address">
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
</a>
}
<a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i>
<span>Logout</span>
</a>
</menu>
</button>
</Authorized>
<NotAuthorized>
<button class="transparent circle">
<img class="responsive" src="https://cdn.cache.lol/img/prami.svg">
<menu class="no-wrap">
<a class="row">
<i class="medium" data-emoji="👋"></i>
<span>Hey there.</span>
</a>
<a class="row" href="/login">
<i class="fa-solid fa-door-closed"></i>
<span>Login</span>
</a>
</menu>
</button>
</NotAuthorized>
</AuthorizeView>
</header>
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<i class="fa-solid fa-fw fa-home"></i>
<div>Home</div>
<small>Home</small>
</NavLink>
<NavLink class="nav-link" href="/statuslog/latest">
<i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="#4dabf7" d="M250 450c-38.388 0-76.775-14.646-106.066-43.934l-100-100c-58.579-58.58-58.579-153.553 0-212.132C100.534 37.336 191.105 35.421 250 88.191c58.898-52.768 149.47-50.853 206.066 5.743 58.58 58.58 58.58 153.553 0 212.132l-100 100C326.778 435.354 288.39 450 250 450" /><path fill="#228be6" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" stroke="#461036" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /><path fill="#1971c2" d="M438.68 212.011c0-32.564-26.399-58.962-58.963-58.962s-58.962 26.398-58.962 58.962 26.398 58.963 58.962 58.963 58.962-26.399 58.962-58.963m-259.433 0c0-32.564-26.398-58.962-58.962-58.962S61.32 179.447 61.32 212.011s26.398 58.963 58.963 58.963 58.962-26.399 58.962-58.963" /><path fill="#4dabf7" stroke="#1864ab" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /></svg></i>
<div>Statuslog</div>
<small>Statuslog</small>
</NavLink>
</nav>
<nav class="bottom s">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<i class="fa-solid fa-fw fa-home"></i>
<div>Home</div>
<small>Home</small>
</NavLink>
<NavLink class="nav-link" href="/statuslog/latest">
<i><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="#4dabf7" d="M250 450c-38.388 0-76.775-14.646-106.066-43.934l-100-100c-58.579-58.58-58.579-153.553 0-212.132C100.534 37.336 191.105 35.421 250 88.191c58.898-52.768 149.47-50.853 206.066 5.743 58.58 58.58 58.58 153.553 0 212.132l-100 100C326.778 435.354 288.39 450 250 450" /><path fill="#228be6" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" stroke="#461036" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /><path fill="#1971c2" d="M438.68 212.011c0-32.564-26.399-58.962-58.963-58.962s-58.962 26.398-58.962 58.962 26.398 58.963 58.962 58.963 58.962-26.399 58.962-58.963m-259.433 0c0-32.564-26.398-58.962-58.962-58.962S61.32 179.447 61.32 212.011s26.398 58.963 58.963 58.963 58.962-26.399 58.962-58.963" /><path fill="#4dabf7" stroke="#1864ab" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /></svg></i>
<div>Statuslog</div>
<small>Statuslog</small>
</NavLink>
<AuthorizeView>
<Authorized>
<NavLink>
<button class="transparent circle small">
<img class="responsive" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
<menu>
<a class="row">
<img class="tiny" data-emoji="👋">
<span>Hey, @State.Name.</span>
</a>
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
<img class="tiny circle" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address">
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
</a>
}
<a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i>
<span>Logout</span>
</a>
</menu>
</button>
<small class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</small>
</NavLink>
</Authorized>
<NotAuthorized>
<button class="transparent circle">
<img class="responsive" src="https://cdn.cache.lol/img/prami.svg">
<menu>
<a class="row">
<i class="medium" data-emoji="👋"></i>
<span>Hey there.</span>
</a>
<a class="row" href="/login">
<i class="fa-solid fa-door-closed"></i>
<span>Login</span>
</a>
</menu>
</button>
</NotAuthorized>
</AuthorizeView>
</nav>
@code {
private string? Name = null;
private List<string> Addresses = new List<string>();
private string FirstAddress { get => this.Addresses.FirstOrDefault() ?? string.Empty; }
protected override async Task OnInitializedAsync() {
var state = await AuthStateProvider.GetAuthenticationStateAsync();
var identity = state.User.Identity;
Name = identity?.Name ?? string.Empty;
Addresses = state.User.FindFirst("addresses")?.Value?.Split(',')?.ToList() ?? new List<string>();
public void changeAddress(AddressResponseData address) {
State.SelectedAddress = address;
}
}

View file

@ -1,10 +1,14 @@
using Microsoft.AspNetCore.Components.Authorization;
using Neighbourhood.omg.lol.Models;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Neighbourhood.omg.lol {
public class CustomAuthenticationStateProvider : AuthenticationStateProvider {
public CustomAuthenticationStateProvider() {
private State State;
public CustomAuthenticationStateProvider(State _state) {
this.State = _state;
}
public async Task Login(string token) {
@ -14,7 +18,8 @@ namespace Neighbourhood.omg.lol {
public async Task Logout() {
SecureStorage.Remove("accounttoken");
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
Preferences.Default.Clear();
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync() {
@ -22,39 +27,12 @@ namespace Neighbourhood.omg.lol {
try {
var token = await SecureStorage.GetAsync("accounttoken");
if (token != null) {
var name = await SecureStorage.GetAsync("accountname");
var email = await SecureStorage.GetAsync("accountemail");
var addresses = await SecureStorage.GetAsync("accountaddresses");
await State.PopulateAccountDetails(token);
RestService api = new RestService(token);
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(email)) {
AccountResponseData? accountInfo = await api.AccountInfo();
if (accountInfo != null) {
name = accountInfo.Name;
email = accountInfo.Email;
}
}
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(email)) {
if (string.IsNullOrEmpty(addresses)) {
AddressResponseList? addressList = await api.Addresses();
List<string> addressStrings = new List<string>();
if (addressList != null) foreach (var address in addressList) {
if (!address.Expiration.Expired && !string.IsNullOrEmpty(address.Address)) {
addressStrings.Add(address.Address);
}
}
addresses = string.Join(',', addressStrings);
}
if(!string.IsNullOrEmpty(addresses)) {
await SecureStorage.SetAsync("accountname", name);
await SecureStorage.SetAsync("accountemail", email);
await SecureStorage.SetAsync("accountaddresses", addresses);
}
var claims = new[] {
new Claim(ClaimTypes.Name, name),
new Claim(ClaimTypes.Email, email),
new Claim("addresses", addresses)
if(State.AccountInfo != null) {
List<Claim> claims = new List<Claim> {
new Claim(ClaimTypes.Name, State.AccountInfo.Name),
new Claim(ClaimTypes.Email, State.AccountInfo.Email)
};
identity = new ClaimsIdentity(claims, "Server authentication");
}

View file

@ -8,10 +8,12 @@ namespace Neighbourhood.omg.lol;
public partial class LoginWebViewPage : ContentPage
{
private AuthenticationStateProvider AuthStateProvider { get; set; }
private NavigatorService NavigatorService { get; set; }
public LoginWebViewPage(AuthenticationStateProvider authStateProvider)
public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService)
{
this.AuthStateProvider = authStateProvider;
this.NavigatorService = navigatorService;
InitializeComponent();
this.loginwebview.Source = "https://home.omg.lol/oauth/authorize?client_id=ea14dafd3e92cbcf93750c35cd81a031&scope=everything&redirect_uri=https://neatnik.net/adam/bucket/omgloloauth/&response_type=code";
@ -29,6 +31,7 @@ public partial class LoginWebViewPage : ContentPage
if (!string.IsNullOrEmpty(token)) {
Debug.WriteLine($"Fuck yeah, a token! {token}");
await ((CustomAuthenticationStateProvider)this.AuthStateProvider).Login(token);
NavigatorService.NavigationManager.NavigateTo(NavigatorService.NavigationManager.Uri, forceLoad: true);
await Shell.Current.GoToAsync("..");
}
}

View file

@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Components.WebView;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebView;
using Microsoft.AspNetCore.Components.WebView.Maui;
using System.Diagnostics;
namespace Neighbourhood.omg.lol {
public partial class MainPage : ContentPage {
public MainPage() {
InitializeComponent();
}
@ -13,5 +16,10 @@ namespace Neighbourhood.omg.lol {
Shell.Current.GoToAsync(nameof(LoginWebViewPage));
}
}
protected override void OnAppearing() {
base.OnAppearing();
Debug.WriteLine("And now you're back. From outer space.");
}
}
}

View file

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using Neighbourhood.omg.lol.Models;
namespace Neighbourhood.omg.lol {
public static class MauiProgram {
@ -14,6 +15,9 @@ namespace Neighbourhood.omg.lol {
builder.Services.AddMauiBlazorWebView();
builder.Services.AddTransient<LoginWebViewPage>();
builder.Services.AddSingleton<State>();
builder.Services.AddSingleton<NavigatorService>();
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthenticationStateProvider>());

49
Models/State.cs Normal file
View file

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models {
public class State {
public AccountResponseData? AccountInfo { get; set; }
public AddressResponseList? AddressList { get; set; }
public string? Name { get => AccountInfo?.Name; }
public string? Email { get => AccountInfo?.Email; }
public IEnumerable<string>? AddressNames { get => AddressList?.Select(a => a.Address); }
public AddressResponseData? SelectedAddress { get; set; }
public string? SelectedAddressName { get => SelectedAddress?.Address; }
public async Task PopulateAccountDetails(string token) {
RestService api = new RestService(token);
string accountJson = Preferences.Default.Get("accountdetails", string.Empty);
string addressJson = Preferences.Default.Get("accountaddresses", string.Empty);
string selectedAddressJson = Preferences.Default.Get("selectedaddress", string.Empty);
if (!string.IsNullOrEmpty(accountJson)) AccountInfo = JsonSerializer.Deserialize<AccountResponseData>(accountJson);
if (!string.IsNullOrEmpty(addressJson)) AddressList = JsonSerializer.Deserialize<AddressResponseList>(addressJson);
if (!string.IsNullOrEmpty(selectedAddressJson)) SelectedAddress = JsonSerializer.Deserialize<AddressResponseData>(selectedAddressJson);
// if we haven't got account info, attempt to retrieve it.
if (AccountInfo == null) {
AccountInfo = await api.AccountInfo();
if (AccountInfo != null) {
Preferences.Default.Set("accountdetails", JsonSerializer.Serialize(AccountInfo));
}
}
// if we don't have the list of addresses, attempt to retrieve that.
if (AddressList == null) {
AddressList = await api.Addresses();
if (AddressList != null) {
Preferences.Default.Set("accountaddresses", JsonSerializer.Serialize(AddressList));
SelectedAddress = AddressList.FirstOrDefault();
Preferences.Default.Set("selectedaddress", JsonSerializer.Serialize(SelectedAddress));
}
}
}
}
}

12
NavigatorService.cs Normal file
View file

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol {
public class NavigatorService {
internal NavigationManager NavigationManager { get; set; }
}
}

View file

@ -32,8 +32,10 @@ p, li {
line-height: 160%;
}
.author {
.author, .address {
font-family: 'VC Honey Deck', var(--font);
}
.author {
color: inherit;
font-size: 1.2em;
}
@ -102,3 +104,36 @@ main {
flex:1 1 auto;
overflow: hidden;
}
nav header {
z-index: 101;
}
:is(button,.button).tiny {
block-size: 1.5rem;
/*max-inline-size: 1.5rem;*/
font-size: .875rem;
border-radius: .75rem;
}
nav.bottom.s:not(.drawer) > a:not(.button,.chip) {
inline-size: unset;
}
nav.bottom.s:not(.drawer) :is(button,.button) > menu {
position: fixed;
margin-top:auto;
margin-bottom: 5rem;
margin-left:auto;
margin-right:0;
inline-size: auto;
min-inline-size: 12rem;
z-index: 100;
transform: none !important;
inset: auto 0 0 auto;
}
i[class*=fa-at] {
vertical-align:unset;
font-size: .75em;
}