@inherits LayoutComponentBase
@implements IDisposable
@inject IUserSessionService Session
@inject IUserContextAccessor UserContext
@inject ISessionCredentialStore CredStore
@inject ISessionManager SessionManager
@inject NavigationManager Nav
@inject IJSRuntime JS
@inject SharepointToolbox.Web.Services.OAuth.IOAuthFlowCache OAuthCache
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.WebUtilities
@using Microsoft.JSInterop
@using SharepointToolbox.Web.Core.Models
@using SharepointToolbox.Web.Services.Session
@if (UserContext.IsAuthenticated)
{
@Body
}
else
{
Loadingβ¦
}
@code {
private bool _sidebarCollapsed;
private bool _hasCredentials;
private string _credUsername = string.Empty;
private SessionCredentialsModal? _credModal;
protected override void OnInitialized()
{
Session.ProfileChanged += OnProfileChanged;
UserContext.Initialized += OnUserContextInitialized;
}
private void OnUserContextInitialized() => InvokeAsync(StateHasChanged);
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
// Apply persisted theme preference
await ApplyThemeAsync();
// Pick up token_key from OAuth callback redirect before checking credential state
await HandleOAuthCallbackAsync();
await RefreshCredentialState();
// Check for connect_error query param
var uri = new Uri(Nav.Uri);
var query = QueryHelpers.ParseQuery(uri.Query);
if (query.TryGetValue("connect_error", out var err) && !string.IsNullOrEmpty(err))
{
Nav.NavigateTo(uri.GetLeftPart(UriPartial.Path), replace: true);
if (_credModal is not null)
{
// Surface the failure reason instead of silently reopening the modal
await _credModal.ShowAsync(err!);
}
}
// If profile selected but no credentials β show modal
if (Session.HasProfile && !_hasCredentials && _credModal is not null)
await _credModal.ShowAsync();
}
private async Task HandleOAuthCallbackAsync()
{
var uri = new Uri(Nav.Uri);
var query = QueryHelpers.ParseQuery(uri.Query);
if (!query.TryGetValue("token_key", out var tokenKey) || string.IsNullOrEmpty(tokenKey))
return;
var tokens = OAuthCache.GetAndRemoveTokens(tokenKey!);
if (tokens is not null)
{
await CredStore.SetAsync(tokens);
await SessionManager.ClearAllAsync();
}
// Strip token_key from URL bar
Nav.NavigateTo(uri.GetLeftPart(UriPartial.Path), replace: true);
}
private async Task RefreshCredentialState()
{
var tokens = await CredStore.GetAsync();
_hasCredentials = tokens is not null && !string.IsNullOrEmpty(tokens.RefreshToken);
_credUsername = tokens?.UserPrincipalName ?? string.Empty;
await InvokeAsync(StateHasChanged);
}
private async Task ReconnectAsync()
{
await CredStore.ClearAsync();
await SessionManager.ClearAllAsync();
_hasCredentials = false;
_credUsername = string.Empty;
if (Session.HasProfile && _credModal is not null)
await _credModal.ShowAsync();
}
private async Task OnCredentialsConnected()
{
await RefreshCredentialState();
}
private void OnProfileChanged()
{
InvokeAsync(async () =>
{
StateHasChanged();
// New profile selected β prompt for credentials if none
if (Session.HasProfile && !_hasCredentials && _credModal is not null)
await _credModal.ShowAsync();
});
}
private async Task ApplyThemeAsync()
{
try
{
await JS.InvokeVoidAsync("sptb.setTheme", Session.Settings.Theme);
}
catch (JSException) { /* best-effort; JS not yet available */ }
}
private void ToggleSidebar() => _sidebarCollapsed = !_sidebarCollapsed;
private static string RoleChipClass(UserRole role) => role switch
{
UserRole.Admin => "chip-red",
UserRole.TechN1 => "chip-green",
_ => "chip-blue"
};
public void Dispose()
{
Session.ProfileChanged -= OnProfileChanged;
UserContext.Initialized -= OnUserContextInitialized;
}
}