-
+}
@code {
private OnboardingModel model = new();
private bool isSaving;
private bool emailReadOnly;
private string? errorMessage;
+ private bool checkCompleted;
+ private bool existingUserFound;
- protected override void OnInitialized()
+ protected override async Task OnInitializedAsync()
{
- var context = AccessService?.Context;
- if (context != null)
- {
- model.Email = context.Email ?? "";
+ var context = AccessService?.Context ?? AccessService?.CircuitContext;
+ if (context == null) { checkCompleted = true; return; }
- // Only lock the email field if we got a valid email from OAuth
- emailReadOnly = !string.IsNullOrEmpty(context.Email) && context.Email.Contains('@');
+ model.Email = context.Email ?? "";
- // Pre-populate full name from OAuth claims
- if (!string.IsNullOrEmpty(context.Name) && context.Name != "Unknown")
- model.FullName = context.Name;
+ // Only lock the email field if we got a valid email from OAuth
+ emailReadOnly = !string.IsNullOrEmpty(context.Email) && context.Email.Contains('@');
- // Suggest username from email prefix (lowercase)
- if (!string.IsNullOrEmpty(context.Email) && context.Email.Contains('@'))
- model.Username = context.Email.Split('@')[0].ToLowerInvariant();
+ // Pre-populate full name from OAuth claims
+ if (!string.IsNullOrEmpty(context.Name) && context.Name != "Unknown")
+ model.FullName = context.Name;
+
+ // Suggest username from email prefix (lowercase)
+ if (!string.IsNullOrEmpty(context.Email) && context.Email.Contains('@'))
+ model.Username = context.Email.Split('@')[0].ToLowerInvariant();
+
+ // Check if a User node already exists for this email (e.g., created via another portal)
+ if (!string.IsNullOrEmpty(context.Email))
+ {
+ using (AccessService!.ImpersonateAsHub(PortalApplication!.Hub))
+ {
+ var existing = await MeshQuery.QueryAsync
(
+ $"nodeType:User namespace:User content.email:{context.Email} limit:1")
+ .FirstOrDefaultAsync();
+
+ if (existing is { State: MeshNodeState.Active })
+ {
+ // Adopt existing identity and skip onboarding
+ var updated = (AccessService.Context ?? new AccessContext()) with
+ {
+ ObjectId = existing.Id,
+ Name = existing.Name ?? existing.Id,
+ Email = context.Email
+ };
+ AccessService.SetContext(updated);
+ // Update the circuit-level context so subsequent client-side
+ // navigations (e.g., to Index.razor) see the resolved identity.
+ CircuitAccessHandler.UpdateUserContext(updated);
+ existingUserFound = true;
+ checkCompleted = true;
+ return;
+ }
+ }
+ }
+ checkCompleted = true;
+ }
+
+ protected override Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender && existingUserFound)
+ {
+ Navigation.NavigateTo("/"); // Client-side only, no forceLoad
}
+ return Task.CompletedTask;
}
private async Task HandleSubmit()
@@ -119,7 +173,8 @@
try
{
- var userId = AccessService?.Context?.ObjectId;
+ var userId = AccessService?.Context?.ObjectId
+ ?? AccessService?.CircuitContext?.ObjectId;
if (string.IsNullOrEmpty(userId))
{
errorMessage = "Not authenticated. Please sign in again.";
diff --git a/memex/Memex.Portal.Shared/Pages/Welcome.razor b/memex/Memex.Portal.Shared/Pages/Welcome.razor
index 111ed6a00..ed70f5a51 100644
--- a/memex/Memex.Portal.Shared/Pages/Welcome.razor
+++ b/memex/Memex.Portal.Shared/Pages/Welcome.razor
@@ -1,5 +1,7 @@
@page "/welcome"
+@using Microsoft.AspNetCore.Components.Authorization
@using MeshWeaver.Blazor.Layouts
+@inject NavigationManager Navigation
@layout EmptyLayout
Welcome - Memex Portal
@@ -75,6 +77,32 @@