198 lines
7.9 KiB
Plaintext
198 lines
7.9 KiB
Plaintext
@page "/User/{UserId:guid}"
|
|
@using Butter.Dtos.User
|
|
@using Butter.Types
|
|
@using MilkStream.Client.Services
|
|
@inject LoginService LoginService
|
|
@inject UserService UserService
|
|
|
|
<h3>@(user?.Username ?? "Username")</h3>
|
|
|
|
<div class="row">
|
|
@if (isSelf || AdminAccess) {
|
|
<div class="col-5 col-sm-3 col-md-2"><i class="bi bi-envelope-at"></i> EMail:</div>
|
|
<div class="col-7 col-sm-9 col-md-10">@user?.Email</div>
|
|
}
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-5 col-sm-3 col-md-2"><i class="bi bi-person-fill-lock"></i> Role:</div>
|
|
<div class="col-7 col-sm-9 col-md-10">@user?.AccessLevel.ToString()</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-5 col-sm-3 col-md-2"><i class="bi bi-calendar-date"></i> Creation Date:</div>
|
|
<div class="col-7 col-sm-9 col-md-10">@user?.CreatedAt.ToString("D")</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-5 col-sm-3 col-md-2"><i class="bi bi-door-open"></i> Last Login:</div>
|
|
<div class="col-7 col-sm-9 col-md-10">@(user?.LastLogin?.ToString("D") ?? "Never")</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-5 col-sm-3 col-md-2"><i class="bi bi-ban"></i>Banned:</div>
|
|
<div class="col-7 col-sm-9 col-md-10">@(user is {IsBanned: true} ? $"Yes ({user.BannedAt.ToString()})" : "No")</div>
|
|
</div>
|
|
<div class="row">
|
|
@if (AdminAccess) {
|
|
<div class="col-5 col-sm-3 col-md-2"><i class="bi bi-trash3"></i>Deleted:</div>
|
|
<div class="col-7 col-sm-9 col-md-10">@(user is not {DeletedAt: null} ? user!.DeletedAt.ToString() : "No")</div>
|
|
}
|
|
</div>
|
|
|
|
@if (isSelf) {
|
|
<hr />
|
|
@if (!string.IsNullOrEmpty(_pageMessage)) {
|
|
<div class="alert alert-success py-1">@_pageMessage</div>
|
|
}
|
|
<button class="btn btn-outline-primary btn-sm" @onclick="() => _showModal = true">
|
|
<i class="bi bi-key"></i> Change Password
|
|
</button>
|
|
}
|
|
|
|
@if (_showModal) {
|
|
<div class="modal-overlay" @onclick="CloseModal">
|
|
<div class="modal-content" @onclick:stopPropagation="true">
|
|
<div class="modal-header">
|
|
<h5><i class="bi bi-key"></i> Change Password</h5>
|
|
<button class="btn-close" @onclick="CloseModal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
@if (!string.IsNullOrEmpty(_errorMessage)) {
|
|
<div class="alert alert-danger py-1">@_errorMessage</div>
|
|
}
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Current Password</label>
|
|
<input type="password" class="form-control"
|
|
@bind="_oldPassword" @bind:event="oninput" />
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">New Password</label>
|
|
<input type="password" class="form-control"
|
|
@bind="_newPassword" @bind:event="oninput" />
|
|
<div class="mt-2" style="height: 6px; background: #333; border-radius: 3px;">
|
|
<div style="height: 100%; width: @(_passwordStrengthPercent)%; border-radius: 3px;
|
|
background: @(_passwordStrengthColor); transition: all 0.2s;"></div>
|
|
</div>
|
|
<small class="text-muted">@_passwordStrengthLabel</small>
|
|
<ul class="list-unstyled small mb-0 mt-1">
|
|
<li class="@(HasMinLength ? "text-success" : "text-danger")">
|
|
<i class="bi @(HasMinLength ? "bi-check-circle" : "bi-x-circle")"></i> At least 8 characters
|
|
</li>
|
|
<li class="@(HasUpperCase ? "text-success" : "text-danger")">
|
|
<i class="bi @(HasUpperCase ? "bi-check-circle" : "bi-x-circle")"></i> At least one uppercase letter
|
|
</li>
|
|
<li class="@(HasLowerCase ? "text-success" : "text-danger")">
|
|
<i class="bi @(HasLowerCase ? "bi-check-circle" : "bi-x-circle")"></i> At least one lowercase letter
|
|
</li>
|
|
<li class="@(HasNumberOrSpecial ? "text-success" : "text-danger")">
|
|
<i class="bi @(HasNumberOrSpecial ? "bi-check-circle" : "bi-x-circle")"></i> At least one number or special character
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Confirm New Password</label>
|
|
<input type="password" class="form-control"
|
|
@bind="_confirmPassword" @bind:event="oninput" />
|
|
@if (!string.IsNullOrEmpty(_confirmPassword) && _newPassword != _confirmPassword) {
|
|
<small class="text-danger">Passwords do not match</small>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" @onclick="CloseModal">Cancel</button>
|
|
<button class="btn btn-primary" disabled="@(!_canSubmit)" @onclick="SubmitPasswordChange">
|
|
Update Password
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
|
|
[Parameter]
|
|
public Guid UserId { get; set; }
|
|
|
|
bool isSelf;
|
|
bool AdminAccess => LoginService.LoggedUser?.AccessLevel == EAccessLevel.Admin;
|
|
UserInfoDto? user;
|
|
|
|
bool _showModal;
|
|
string _oldPassword = "";
|
|
string _newPassword = "";
|
|
string _confirmPassword = "";
|
|
string _errorMessage = "";
|
|
string _pageMessage = "";
|
|
|
|
bool HasMinLength => _newPassword.Length >= 8;
|
|
bool HasUpperCase => _newPassword.Any(char.IsUpper);
|
|
bool HasLowerCase => _newPassword.Any(char.IsLower);
|
|
bool HasNumberOrSpecial => _newPassword.Any(c => char.IsDigit(c) || char.IsPunctuation(c) || char.IsSymbol(c));
|
|
bool PasswordsMatch => _newPassword == _confirmPassword;
|
|
|
|
int _passwordStrengthPercent => _newPassword.Length == 0 ? 0 : _strengthScore * 25;
|
|
string _passwordStrengthColor => _newPassword.Length == 0 ? "transparent" : _strengthScore switch {
|
|
1 => "#dc3545",
|
|
2 => "#fd7e14",
|
|
3 => "#ffc107",
|
|
4 => "#198754",
|
|
_ => "transparent"
|
|
};
|
|
string _passwordStrengthLabel => _newPassword.Length == 0 ? "" : _strengthScore switch {
|
|
1 => "Weak",
|
|
2 => "Fair",
|
|
3 => "Good",
|
|
4 => "Strong",
|
|
_ => ""
|
|
};
|
|
|
|
int _strengthScore => (HasMinLength ? 1 : 0)
|
|
+ (HasUpperCase ? 1 : 0)
|
|
+ (HasLowerCase ? 1 : 0)
|
|
+ (HasNumberOrSpecial ? 1 : 0);
|
|
|
|
bool _canSubmit => _strengthScore == 4
|
|
&& PasswordsMatch
|
|
&& !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();
|
|
}
|
|
|
|
async Task CloseModal() {
|
|
_showModal = false;
|
|
_oldPassword = "";
|
|
_newPassword = "";
|
|
_confirmPassword = "";
|
|
_errorMessage = "";
|
|
}
|
|
|
|
async Task SubmitPasswordChange() {
|
|
_errorMessage = "";
|
|
|
|
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 CloseModal();
|
|
} else {
|
|
_errorMessage = error ?? "Failed to update password.";
|
|
}
|
|
}
|
|
|
|
}
|