feat(auth): implement option 3 architecture - server InteractiveServer login + client WASM dashboard

- Add Server.AddInteractiveServerComponents() + AddInteractiveServerRenderMode()
- Create Server AuthLayout for login form (MudBlazor)
- Implement LoginSimple.razor with @rendermode InteractiveServer in Server project
- Update App.razor: CascadingAuthenticationState + Router with AppAssembly=Server
- Fix Client MudBlazor Providers in MainLayout + AuthLayout
- Update Playwright tests: use dynamic selectors for MudTextField (auto-generated IDs)
- Build: 0 errors, 0 warnings
- API: /api/auth/login fully operational (200 OK confirmed)
- Playwright E2E test: 1 passed

Architecture:
/login    → Server InteractiveServer (MudBlazor form)
/         → Client WebAssembly (Dashboard)
/api/*    → Server Endpoints

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 23:10:54 +09:00
parent 98501c0d2f
commit 53db2f63e3
27 changed files with 1012 additions and 296 deletions
+13 -8
View File
@@ -36,6 +36,7 @@ builder.Host.UseSerilog();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
// Authentication and Custom State Provider (Shared client components)
@@ -126,15 +127,13 @@ if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
// Redirect status code pages only for non-API routes
app.UseStatusCodePages(async ctx =>
{
if (!ctx.HttpContext.Request.Path.StartsWithSegments("/api"))
ctx.HttpContext.Response.Redirect("/not-found");
});
app.UseHttpsRedirection();
// CRITICAL: Static assets MUST be served before StatusCodePages middleware
// This ensures app.css, _framework/, and other static files are served correctly
app.MapStaticAssets();
// Configure static file MIME types for Blazor
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".wasm"] = "application/wasm";
@@ -152,6 +151,13 @@ app.UseStaticFiles(new StaticFileOptions
DefaultContentType = "application/octet-stream"
});
// Redirect status code pages only for non-API routes (AFTER static files)
app.UseStatusCodePages(async ctx =>
{
if (!ctx.HttpContext.Request.Path.StartsWithSegments("/api"))
ctx.HttpContext.Response.Redirect("/not-found");
});
app.UseAntiforgery();
app.UseAuthentication();
app.UseAuthorization();
@@ -166,8 +172,6 @@ catch (Exception ex)
Log.Warning("Hangfire setup failed: {Message}", ex.Message);
}
app.MapStaticAssets();
app.MapGet("/", () => Results.Redirect("/login"));
// Collection API Endpoints (must be before MapRazorComponents)
@@ -411,6 +415,7 @@ app.MapPost("/api/history/{domain}", async (string domain, JsonElement payload,
});
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly);