Updated WebUI with Radzen and tailwind css

This commit is contained in:
2026-01-26 00:19:59 +02:00
parent bc86195b5a
commit 4653e6eb03
24 changed files with 1586 additions and 722 deletions
+55 -17
View File
@@ -1,21 +1,59 @@
@using WebUI.Components.Notification
@inherits LayoutComponentBase
@inherits LayoutComponentBase
<NotificationDisplay />
<div class="page">
<div class="sidebar">
<NavMenu/>
</div>
<main>
<article class="content px-4">
<RadzenLayout>
<RadzenHeader>
<MobileHeader />
</RadzenHeader>
<RadzenSidebar @bind-Expanded="@_sidebarExpanded" class="!bg-gradient-to-b !from-indigo-950 !via-indigo-900 !to-purple-950 !border-0 shadow-2xl shadow-purple-900/20">
<div class="flex flex-col h-full">
<!-- Logo Section -->
<div class="p-5 border-b border-white/10">
<div class="flex items-center gap-3.5">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-lg shadow-primary/40 transition-transform hover:scale-105">
<i class="bi bi-robot text-white text-xl"></i>
</div>
<div>
<h1 class="text-white font-bold text-lg m-0 tracking-tight">Seth Bot</h1>
<p class="text-white/60 text-xs m-0">Discord Bot Manager</p>
</div>
</div>
</div>
<!-- Navigation -->
<div class="flex-1 py-4 px-2 overflow-y-auto scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent">
<NavMenu />
</div>
<!-- Footer -->
<div class="p-4 border-t border-white/10 mt-auto">
<SidebarFooter />
</div>
</div>
</RadzenSidebar>
<RadzenBody class="!bg-dark-primary">
<div class="p-6 md:p-8 min-h-screen">
@Body
</article>
</main>
</div>
</RadzenBody>
</RadzenLayout>
<!-- Setup Wizard -->
<SetupWizardWrapper />
<!-- Radzen Components -->
<RadzenDialog />
<RadzenNotification />
<RadzenTooltip />
<RadzenContextMenu />
<div id="blazor-error-ui" class="fixed bottom-0 left-0 w-full bg-amber-500 text-black p-4 z-[9999] text-center hidden">
An unhandled error has occurred.
<a href="" class="reload underline ml-2">Reload</a>
<a class="dismiss absolute right-4 top-1/2 -translate-y-1/2 cursor-pointer">🗙</a>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
@code {
private bool _sidebarExpanded = true;
}
@@ -1,96 +0,0 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@@ -0,0 +1,71 @@
@rendermode InteractiveServer
<div class="lg:hidden block p-3 bg-dark-secondary border-b border-slate-700/50">
<div class="flex items-center justify-between">
<button @onclick="ToggleMobileSidebar"
class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/10 transition-colors">
<RadzenIcon Icon="menu" />
</button>
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-md shadow-primary/30">
<i class="bi bi-robot text-white text-sm"></i>
</div>
<span class="font-semibold text-white">Seth Bot</span>
</div>
<SidebarFooter Compact="true" />
</div>
</div>
<!-- Mobile Sidebar Drawer -->
@if (_sidebarOpen)
{
<div class="fixed inset-0 z-50 lg:hidden">
<!-- Overlay -->
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm" @onclick="ToggleMobileSidebar"></div>
<!-- Sidebar -->
<div class="fixed left-0 top-0 h-full w-72 bg-gradient-to-b from-indigo-950 via-indigo-900 to-purple-950 shadow-2xl z-10">
<div class="flex flex-col h-full">
<!-- Logo Section -->
<div class="p-5 border-b border-white/10">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-lg shadow-primary/40">
<i class="bi bi-robot text-white"></i>
</div>
<div>
<h1 class="text-white font-bold text-base m-0">Seth Bot</h1>
<p class="text-white/60 text-xs m-0">Discord Bot Manager</p>
</div>
</div>
<button @onclick="ToggleMobileSidebar"
class="p-2 rounded-lg text-white/70 hover:text-white hover:bg-white/10 transition-colors">
<RadzenIcon Icon="close" />
</button>
</div>
</div>
<!-- Navigation -->
<div class="flex-1 py-4 px-2 overflow-y-auto">
<NavMenu />
</div>
<!-- Footer with Theme Toggle -->
<div class="p-4 border-t border-white/10 mt-auto">
<SidebarFooter />
</div>
</div>
</div>
</div>
}
@code {
private bool _sidebarOpen;
private void ToggleMobileSidebar()
{
_sidebarOpen = !_sidebarOpen;
}
}
+9 -42
View File
@@ -1,44 +1,11 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid align-items-center">
<a class="navbar-brand align-content-center" href="">Seth Discord Bot</a>
</div>
</div>
@rendermode InteractiveServer
<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>
<RadzenPanelMenu>
<RadzenPanelMenuItem Text="Home" Icon="home" Path="" />
<RadzenPanelMenuItem Text="Settings" Icon="settings" Path="settings" />
<RadzenPanelMenuItem Text="Plugins" Icon="extension">
<RadzenPanelMenuItem Text="Online Plugins" Icon="cloud_download" Path="plugins/online" />
<RadzenPanelMenuItem Text="Local Plugins" Icon="folder" Path="plugins/local" />
</RadzenPanelMenuItem>
</RadzenPanelMenu>
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="settings">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Settings
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link d-flex align-items-center" href="plugins/online">
<span class="bi bi-list" aria-hidden="true" style="
display: inline-flex;
align-items: center;
justify-content: center;
height: 100%;
margin-right: 0.5rem;
font-size: 1.2rem;
"></span>
Online Plugins
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="plugins/local">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Local Plugins
</NavLink>
</div>
</nav>
</div>
-106
View File
@@ -1,106 +0,0 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
+286
View File
@@ -0,0 +1,286 @@
@using DiscordBotCore.Configuration
@using DiscordBotCore.Logging
@inject IConfiguration Configuration
@inject ILogger Logger
<div class="fixed inset-0 z-50 flex items-center justify-center bg-gradient-to-br from-indigo-950 via-indigo-900 to-purple-950 overflow-hidden">
<!-- Static background blobs (no animation) -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="absolute top-[10%] left-[10%] w-[500px] h-[500px] rounded-full bg-primary/40 blur-[100px] opacity-60"></div>
<div class="absolute bottom-[10%] right-[10%] w-[600px] h-[600px] rounded-full bg-pink-500/30 blur-[100px] opacity-60"></div>
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[400px] h-[400px] rounded-full bg-blue-500/25 blur-[100px] opacity-60"></div>
</div>
<!-- Setup Card -->
<div class="relative w-full max-w-[480px] mx-4 bg-white/[0.08] backdrop-blur-2xl rounded-3xl border border-white/[0.12] overflow-hidden">
<!-- Progress Bar -->
<div class="h-1 bg-white/10">
<div class="h-full bg-gradient-to-r from-primary to-accent transition-all duration-300 ease-out" style="width: @(_currentStep * 100 / TotalSteps)%"></div>
</div>
<!-- Content -->
<div class="p-8">
@switch (_currentStep)
{
case 1:
<!-- Welcome Step -->
<div class="flex flex-col items-center gap-6">
<div class="w-20 h-20 rounded-2xl bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-2xl shadow-primary/40">
<i class="bi bi-robot text-white text-3xl"></i>
</div>
<div class="text-center space-y-2">
<h2 class="text-2xl font-bold text-white">Welcome to Seth Bot!</h2>
<p class="text-white/70">Let's get your Discord bot up and running in just a few steps.</p>
</div>
<div class="flex gap-4 mt-2">
<div class="p-4 rounded-xl bg-white/[0.08] border border-white/10 text-center min-w-[100px]">
<RadzenIcon Icon="bolt" class="text-amber-400 text-2xl" />
<p class="text-white text-xs mt-2 font-medium">Fast Setup</p>
</div>
<div class="p-4 rounded-xl bg-white/[0.08] border border-white/10 text-center min-w-[100px]">
<RadzenIcon Icon="extension" class="text-emerald-400 text-2xl" />
<p class="text-white text-xs mt-2 font-medium">Plugins</p>
</div>
<div class="p-4 rounded-xl bg-white/[0.08] border border-white/10 text-center min-w-[100px]">
<RadzenIcon Icon="security" class="text-blue-400 text-2xl" />
<p class="text-white text-xs mt-2 font-medium">Secure</p>
</div>
</div>
</div>
break;
case 2:
<!-- Token Step -->
<div class="flex flex-col items-center gap-6">
<div class="w-20 h-20 rounded-2xl bg-gradient-to-br from-primary to-purple-500 flex items-center justify-center shadow-2xl shadow-primary/40">
<RadzenIcon Icon="vpn_key" class="text-white text-3xl" />
</div>
<div class="text-center space-y-2">
<h2 class="text-xl font-bold text-white">Bot Token</h2>
<p class="text-white/70">Enter your Discord bot token to continue</p>
</div>
<div class="w-full space-y-4">
<RadzenPassword @bind-Value="@_token"
Placeholder="Paste your bot token here..."
class="!w-full !bg-white/[0.08] !border-white/15 hover:!border-primary focus-within:!border-primary !text-white !rounded-xl !placeholder:text-white/40" />
<div class="p-4 rounded-xl bg-blue-500/10 border border-blue-500/20">
<p class="text-white text-sm">
<span class="font-semibold">Where to find your token?</span><br/>
<span class="text-white/70">Go to Discord Developer Portal → Your Application → Bot → Token</span>
</p>
</div>
</div>
</div>
break;
case 3:
<!-- Prefix Step -->
<div class="flex flex-col items-center gap-6">
<div class="w-20 h-20 rounded-2xl bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center shadow-2xl shadow-emerald-500/40">
<RadzenIcon Icon="chat" class="text-white text-3xl" />
</div>
<div class="text-center space-y-2">
<h2 class="text-xl font-bold text-white">Command Prefix</h2>
<p class="text-white/70">Choose a prefix for your bot commands</p>
</div>
<div class="w-full space-y-4 flex flex-col items-center">
<RadzenTextBox @bind-Value="@_prefix"
Placeholder="!"
MaxLength="5"
class="!w-24 !text-center !text-2xl !font-mono !bg-white/[0.08] !border-white/15 hover:!border-primary focus-within:!border-primary !text-white !rounded-xl" />
<div class="flex gap-2 justify-center">
@foreach (var prefix in new[] { "!", "?", ".", "$", "/" })
{
var p = prefix;
<button type="button"
@onclick="@(() => _prefix = p)"
class="w-12 h-12 rounded-xl font-mono text-xl transition-colors @(_prefix == p ? "bg-gradient-to-r from-primary to-accent text-white shadow-lg shadow-primary/30" : "bg-white/[0.08] text-white/70 border border-white/15 hover:border-primary hover:text-white")">
@p
</button>
}
</div>
<p class="text-white/60 text-sm">
Example: <code class="bg-white/10 px-2 py-0.5 rounded text-white font-mono">@(_prefix)help</code>
</p>
</div>
</div>
break;
case 4:
<!-- Server IDs Step -->
<div class="flex flex-col items-center gap-6">
<div class="w-20 h-20 rounded-2xl bg-gradient-to-br from-orange-500 to-amber-500 flex items-center justify-center shadow-2xl shadow-orange-500/40">
<RadzenIcon Icon="dns" class="text-white text-3xl" />
</div>
<div class="text-center space-y-2">
<h2 class="text-xl font-bold text-white">Server IDs</h2>
<p class="text-white/70">Add the Discord server IDs where your bot will operate</p>
</div>
<div class="w-full space-y-4">
<RadzenTextArea @bind-Value="@_serverIds"
Placeholder="Enter server IDs separated by commas..."
Rows="3"
class="!w-full !bg-white/[0.08] !border-white/15 hover:!border-primary focus-within:!border-primary !text-white !rounded-xl !placeholder:text-white/40" />
<div class="p-4 rounded-xl bg-amber-500/10 border border-amber-500/20">
<p class="text-white text-sm">
<span class="font-semibold">How to get Server ID?</span><br/>
<span class="text-white/70">Enable Developer Mode in Discord Settings → Right-click your server → Copy Server ID</span>
</p>
</div>
<p class="text-white/50 text-xs text-center">Optional: You can add this later in Settings</p>
</div>
</div>
break;
case 5:
<!-- Complete Step -->
<div class="flex flex-col items-center gap-6">
<div class="w-20 h-20 rounded-full bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center shadow-2xl shadow-emerald-500/40">
<RadzenIcon Icon="check" class="text-white text-4xl" />
</div>
<div class="text-center space-y-2">
<h2 class="text-2xl font-bold text-white">You're All Set!</h2>
<p class="text-white/70">Your bot is configured and ready to go.</p>
</div>
<div class="w-full p-5 rounded-xl bg-white/[0.08] border border-white/10">
<div class="flex items-center gap-2 mb-4 text-white">
<RadzenIcon Icon="list" class="text-white/70" />
<span class="font-semibold">Configuration Summary</span>
</div>
<div class="space-y-3 text-sm">
<div class="flex justify-between items-center">
<span class="text-white/60">Token</span>
<code class="text-white bg-white/10 px-2 py-0.5 rounded font-mono">••••••••@(_token.Length > 8 ? _token.Substring(_token.Length - 4) : "••••")</code>
</div>
<div class="flex justify-between items-center">
<span class="text-white/60">Prefix</span>
<code class="text-white bg-white/10 px-2 py-0.5 rounded font-mono">@_prefix</code>
</div>
<div class="flex justify-between items-center">
<span class="text-white/60">Server IDs</span>
<span class="text-white">@(string.IsNullOrEmpty(_serverIds) ? "None specified" : _serverIds.Split(',').Length + " server(s)")</span>
</div>
</div>
</div>
<p class="text-white/50 text-xs">You can change these settings anytime</p>
</div>
break;
}
<!-- Navigation Buttons -->
<div class="flex justify-between mt-8 gap-4">
@if (_currentStep > 1 && _currentStep < TotalSteps)
{
<button @onclick="PreviousStep"
class="px-5 py-2.5 rounded-xl border border-white/30 text-white hover:bg-white/10 transition-all duration-200 flex items-center gap-2">
<RadzenIcon Icon="arrow_back" class="text-sm" />
Back
</button>
}
else
{
<div></div>
}
@if (_currentStep < TotalSteps)
{
<RadzenButton Text="@(_currentStep == 1 ? "Get Started" : "Continue")"
Icon="arrow_forward"
IconPosition="ButtonIconPosition.Right"
ButtonStyle="ButtonStyle.Primary"
Disabled="@(!CanProceed())"
Click="@NextStep"
class="!bg-gradient-to-r !from-primary !to-accent hover:!shadow-lg hover:!shadow-primary/30 !transition-all !duration-300 !rounded-xl" />
}
else
{
<RadzenButton Text="Launch Bot"
Icon="rocket_launch"
ButtonStyle="ButtonStyle.Success"
Click="@FinishSetup"
class="!bg-gradient-to-r !from-emerald-500 !to-teal-500 !rounded-xl" />
}
</div>
<!-- Step Indicators -->
<div class="flex justify-center gap-2 mt-6">
@for (int i = 1; i <= TotalSteps; i++)
{
var step = i;
<div class="w-2 h-2 rounded-full transition-all duration-300 @(step <= _currentStep ? "bg-white scale-125" : "bg-white/25")"></div>
}
</div>
</div>
</div>
</div>
@code {
[Parameter] public EventCallback OnSetupComplete { get; set; }
private int _currentStep = 1;
private const int TotalSteps = 5;
private string _token = string.Empty;
private string _prefix = "!";
private string _serverIds = string.Empty;
private void NextStep()
{
if (_currentStep < TotalSteps && CanProceed())
{
_currentStep++;
}
}
private void PreviousStep()
{
if (_currentStep > 1)
{
_currentStep--;
}
}
private bool CanProceed()
{
return _currentStep switch
{
1 => true,
2 => !string.IsNullOrWhiteSpace(_token),
3 => !string.IsNullOrWhiteSpace(_prefix),
4 => true,
_ => true
};
}
private async Task FinishSetup()
{
// Save configuration
Configuration.Set("token", _token);
Configuration.Set("prefix", _prefix);
var serverIdList = _serverIds
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(id => ulong.TryParse(id.Trim(), out var v) ? v : 0)
.Where(v => v != 0)
.ToList();
Configuration.Set("ServerIds", serverIdList);
await Configuration.SaveToFile();
Logger.Log("Initial setup completed successfully!", this);
await OnSetupComplete.InvokeAsync();
}
}
@@ -0,0 +1,31 @@
@using DiscordBotCore.Configuration
@inject IJSRuntime JS
@inject IConfiguration Configuration
@rendermode InteractiveServer
@if (_showSetupWizard)
{
<SetupWizard OnSetupComplete="HandleSetupComplete" />
}
@code {
private bool _showSetupWizard;
protected override void OnInitialized()
{
// Check if token is configured - if not, show setup wizard
var token = Configuration.Get<string>("token");
if (string.IsNullOrWhiteSpace(token))
{
_showSetupWizard = true;
}
}
private async Task HandleSetupComplete()
{
await JS.InvokeVoidAsync("setupWizard.completeSetup");
_showSetupWizard = false;
StateHasChanged();
}
}
@@ -0,0 +1,21 @@
@if (Compact)
{
<div class="p-2 rounded-lg text-white/80">
<RadzenIcon Icon="dark_mode" />
</div>
}
else
{
<div class="flex items-center justify-between p-3 bg-white/5 rounded-xl border border-white/10">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg flex items-center justify-center bg-gradient-to-br from-indigo-500 to-primary">
<RadzenIcon Icon="dark_mode" class="text-white text-sm" />
</div>
<span class="text-white/90 text-sm font-medium">Copyright @DateTime.Now.Year</span>
</div>
</div>
}
@code {
[Parameter] public bool Compact { get; set; }
}