Files
MilkyShots/MilkStream.Client/Components/Pages/User.razor
REDCODE 3d2c14ef41 fix(ui): modal icon order, fill all outline buttons
- ModalFrame: move HeaderActions before title so icons appear left of text
- Replace all btn-outline-* with btn-* (secondary/primary/danger/success/
  warning/light/info) across all .razor components for filled buttons
2026-07-10 20:33:08 +02:00

210 lines
7.6 KiB
Plaintext

@page "/User/{UserId:guid}"
@using Butter.Dtos.User
@using Butter.Types
@using MilkStream.Client.Services
@inject LoginService LoginService
@inject UserService UserService
<div class="d-flex justify-content-center">
<div class="card mb-3" style="max-width: 600px; width: 100%;">
<div class="card-header d-flex align-items-center gap-3">
<div class="profile-avatar">@_initials</div>
<div class="min-width-0">
<h5 class="mb-0 text-truncate">@(user?.Username ?? "...")</h5>
@if (isSelf || AdminAccess) {
<small class="text-muted text-truncate d-block">@user?.Email</small>
}
</div>
<span class="badge @RoleBadge ms-auto">@(user?.AccessLevel.ToString() ?? "")</span>
</div>
<div class="card-body">
<dl class="row mb-0 g-2">
@if (isSelf || AdminAccess) {
<dt class="col-sm-4 text-muted small">Email</dt>
<dd class="col-sm-8">@user?.Email</dd>
}
<dt class="col-sm-4 text-muted small">Role</dt>
<dd class="col-sm-8">@user?.AccessLevel.ToString()</dd>
<dt class="col-sm-4 text-muted small">Created</dt>
<dd class="col-sm-8">@user?.CreatedAt.ToString("D")</dd>
<dt class="col-sm-4 text-muted small">Last Login</dt>
<dd class="col-sm-8">@(user?.LastLogin?.ToString("D") ?? "Never")</dd>
<dt class="col-sm-4 text-muted small">Banned</dt>
<dd class="col-sm-8">@(user is {IsBanned: true} ? $"Yes ({user.BannedAt.ToString()})" : "No")</dd>
@if (AdminAccess) {
<dt class="col-sm-4 text-muted small">Deleted</dt>
<dd class="col-sm-8">@(user is not {DeletedAt: null} ? user!.DeletedAt.ToString() : "No")</dd>
}
</dl>
</div>
@if (canEdit) {
<div class="card-footer d-flex align-items-center gap-2 flex-wrap">
@if (!string.IsNullOrEmpty(_pageMessage)) {
<div class="alert alert-success py-1 mb-0 flex-grow-1">@_pageMessage</div>
}
<button class="btn btn-primary btn-sm" @onclick="OpenEditModal">
<i class="bi bi-pencil"></i> Edit Profile
</button>
@if (isSelf) {
<button class="btn btn-primary btn-sm" @onclick="() => _showPasswordModal = true">
<i class="bi bi-key"></i> Change Password
</button>
}
</div>
}
</div>
</div>
<ModalFrame Title="Edit Profile" Show="_showEditModal" OnClose="CloseEditModal">
<HeaderActions><i class="bi bi-pencil me-1"></i></HeaderActions>
<Body>
@if (!string.IsNullOrEmpty(_editError)) {
<div class="alert alert-danger py-1">@_editError</div>
}
<div class="mb-3">
<label class="form-label">Username</label>
<input type="text" class="form-control" @bind="_editUsername" @bind:event="oninput" />
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" @bind="_editEmail" @bind:event="oninput" />
</div>
</Body>
<Footer>
<button class="btn btn-secondary" @onclick="CloseEditModal">Cancel</button>
<button class="btn btn-primary" disabled="@(string.IsNullOrWhiteSpace(_editUsername) || string.IsNullOrWhiteSpace(_editEmail))" @onclick="SubmitEditProfile">
Save
</button>
</Footer>
</ModalFrame>
<ModalFrame Title="Change Password" Show="_showPasswordModal" OnClose="ClosePasswordModal">
<HeaderActions><i class="bi bi-key me-1"></i></HeaderActions>
<Body>
@if (!string.IsNullOrEmpty(_passwordError)) {
<div class="alert alert-danger py-1">@_passwordError</div>
}
<PasswordField @ref="_passwordField"
ShowOldPassword="true"
PasswordLabel="New Password"
ConfirmPasswordLabel="Confirm New Password"
@bind-OldPassword="_oldPassword"
@bind-Password="_newPassword"
@bind-ConfirmPassword="_confirmPassword" />
</Body>
<Footer>
<button class="btn btn-secondary" @onclick="ClosePasswordModal">Cancel</button>
<button class="btn btn-primary" disabled="@(!_canSubmit)" @onclick="SubmitPasswordChange">
Update Password
</button>
</Footer>
</ModalFrame>
@code {
[Parameter]
public Guid UserId { get; set; }
bool isSelf;
bool AdminAccess => LoginService.LoggedUser?.AccessLevel == EAccessLevel.Admin;
bool canEdit => isSelf || AdminAccess;
UserInfoDto? user;
string _initials => user?.Username is { Length: > 0 } u ? u[..1].ToUpper() : "?";
string RoleBadge => user?.AccessLevel switch {
EAccessLevel.Admin => "bg-danger",
EAccessLevel.Curator => "bg-warning text-dark",
_ => "bg-secondary"
};
bool _showEditModal;
bool _showPasswordModal;
string _editUsername = "";
string _editEmail = "";
string _editError = "";
string _pageMessage = "";
string _oldPassword = "";
string _newPassword = "";
string _confirmPassword = "";
string _passwordError = "";
PasswordField? _passwordField;
bool _canSubmit => _passwordField?.IsValid == true
&& !string.IsNullOrEmpty(_oldPassword)
&& !string.IsNullOrEmpty(_newPassword);
protected async override Task OnInitializedAsync() {
await base.OnInitializedAsync();
if (LoginService.IsLoggedIn) await LoadUser();
LoginService.AuthInfoChanged += async (_, _) => await LoadUser();
}
async Task LoadUser() {
if (UserId == LoginService.AuthInfo?.UserId) isSelf = true;
user = await UserService.GetUserAsync();
StateHasChanged();
}
void OpenEditModal() {
_editUsername = user?.Username ?? "";
_editEmail = user?.Email ?? "";
_editError = "";
_showEditModal = true;
}
async Task CloseEditModal() {
_showEditModal = false;
_editError = "";
}
async Task SubmitEditProfile() {
_editError = "";
var dto = new UserUpdateDto {
Id = UserId,
Username = _editUsername,
Email = _editEmail
};
var (result, error) = await UserService.UpdateUserAsync(dto);
if (result != null) {
_pageMessage = "Profile updated successfully.";
await CloseEditModal();
await LoadUser();
} else {
_editError = error ?? "Failed to update profile.";
}
}
async Task ClosePasswordModal() {
_showPasswordModal = false;
_oldPassword = "";
_newPassword = "";
_confirmPassword = "";
_passwordError = "";
}
async Task SubmitPasswordChange() {
_passwordError = "";
var dto = new UserUpdateDto {
Id = UserId,
OldPassword = _oldPassword,
Password = _newPassword
};
var (result, error) = await UserService.UpdateUserAsync(dto);
if (result != null) {
_pageMessage = "Password updated successfully.";
await ClosePasswordModal();
} else {
_passwordError = error ?? "Failed to update password.";
}
}
}