Initial commit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
@model PowderCoating.Application.DTOs.User.CreateSuperAdminDto
|
||||
@{
|
||||
ViewData["Title"] = "Create SuperAdmin";
|
||||
}
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4 class="mb-0">
|
||||
<i class="bi bi-shield-check"></i> Create New SuperAdmin
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
<strong>Important:</strong> SuperAdmins have full platform access across all companies. Use this feature carefully.
|
||||
</div>
|
||||
|
||||
<form asp-action="CreateSuperAdmin" method="post">
|
||||
<partial name="_ValidationSummary" />
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="FirstName" class="form-label">First Name <span class="text-danger">*</span></label>
|
||||
<input asp-for="FirstName" class="form-control" placeholder="Enter first name" required />
|
||||
<span asp-validation-for="FirstName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="LastName" class="form-label">Last Name <span class="text-danger">*</span></label>
|
||||
<input asp-for="LastName" class="form-control" placeholder="Enter last name" required />
|
||||
<span asp-validation-for="LastName" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Email" class="form-label">Email <span class="text-danger">*</span></label>
|
||||
<input asp-for="Email" type="email" class="form-control" placeholder="admin@example.com" required />
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="EmployeeNumber" class="form-label">Employee Number</label>
|
||||
<input asp-for="EmployeeNumber" class="form-control" placeholder="SA-002" />
|
||||
<span asp-validation-for="EmployeeNumber" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Department" class="form-label">Department</label>
|
||||
<input asp-for="Department" class="form-control" placeholder="Platform" />
|
||||
<span asp-validation-for="Department" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Position" class="form-label">Position</label>
|
||||
<input asp-for="Position" class="form-control" placeholder="Super Administrator" />
|
||||
<span asp-validation-for="Position" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Phone" class="form-label">Phone</label>
|
||||
<input asp-for="Phone" type="tel" class="form-control" placeholder="(555) 123-4567" />
|
||||
<span asp-validation-for="Phone" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="HireDate" class="form-label">Hire Date <span class="text-danger">*</span></label>
|
||||
<input asp-for="HireDate" type="date" class="form-control" required />
|
||||
<span asp-validation-for="HireDate" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<h5 class="mb-3">
|
||||
<i class="bi bi-key"></i> Password Setup
|
||||
</h5>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Password" class="form-label">Password <span class="text-danger">*</span></label>
|
||||
<input asp-for="Password" type="password" class="form-control" placeholder="Enter strong password" required />
|
||||
<span asp-validation-for="Password" class="text-danger"></span>
|
||||
<small class="form-text text-muted">
|
||||
Minimum 8 characters, must include uppercase, lowercase, number, and special character
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="ConfirmPassword" class="form-label">Confirm Password <span class="text-danger">*</span></label>
|
||||
<input asp-for="ConfirmPassword" type="password" class="form-control" placeholder="Re-enter password" required />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-shield-check"></i> Create SuperAdmin
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
@model PowderCoating.Application.DTOs.User.SuperAdminDetailsDto
|
||||
@{
|
||||
ViewData["Title"] = "User Details";
|
||||
var isSuperAdmin = ViewBag.IsSuperAdmin as bool? ?? false;
|
||||
var companyRole = ViewBag.CompanyRole as string;
|
||||
var companyName = ViewBag.CompanyName as string;
|
||||
var isCompanyUser = !isSuperAdmin && !string.IsNullOrEmpty(companyRole);
|
||||
var detailsReturnUrl = Url.Action("Details", "PlatformUsers", new { id = Model.Id });
|
||||
}
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<h4 class="mb-0">
|
||||
<i class="bi bi-person-circle"></i> User Details
|
||||
</h4>
|
||||
<div class="d-flex gap-1">
|
||||
@if (isSuperAdmin)
|
||||
{
|
||||
<span class="badge bg-danger">
|
||||
<i class="bi bi-shield-check"></i> SuperAdmin
|
||||
</span>
|
||||
}
|
||||
else if (companyRole == "CompanyAdmin")
|
||||
{
|
||||
<span class="badge bg-warning text-dark">
|
||||
<i class="bi bi-person-gear"></i> Company Admin
|
||||
</span>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(companyRole))
|
||||
{
|
||||
<span class="badge bg-light text-dark">
|
||||
<i class="bi bi-person-badge"></i> @companyRole
|
||||
</span>
|
||||
}
|
||||
@if (Model.IsBanned)
|
||||
{
|
||||
<span class="badge bg-danger"><i class="bi bi-slash-circle"></i> Banned</span>
|
||||
}
|
||||
else if (Model.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12 text-center">
|
||||
<div class="avatar-circle-large mx-auto mb-3">
|
||||
@Model.FirstName[0]@Model.LastName[0]
|
||||
</div>
|
||||
<h3>@Model.FullName</h3>
|
||||
@if (!string.IsNullOrEmpty(Model.Position))
|
||||
{
|
||||
<p class="text-muted mb-0">@Model.Position</p>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.Department))
|
||||
{
|
||||
<p class="text-muted">@Model.Department</p>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(companyName))
|
||||
{
|
||||
<p class="text-muted mb-0"><i class="bi bi-building"></i> @companyName</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Contact Information</h6>
|
||||
<p class="mb-1">
|
||||
<strong><i class="bi bi-envelope"></i> Email:</strong><br />
|
||||
@Model.Email
|
||||
@if (Model.EmailConfirmed)
|
||||
{
|
||||
<i class="bi bi-check-circle-fill text-success" title="Email Confirmed"></i>
|
||||
}
|
||||
</p>
|
||||
@if (!string.IsNullOrEmpty(Model.Phone))
|
||||
{
|
||||
<p class="mb-1">
|
||||
<strong><i class="bi bi-telephone"></i> Phone:</strong><br />
|
||||
@Model.Phone
|
||||
</p>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.EmployeeNumber))
|
||||
{
|
||||
<p class="mb-1">
|
||||
<strong><i class="bi bi-badge-4k"></i> Employee Number:</strong><br />
|
||||
@Model.EmployeeNumber
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted mb-2">Employment Information</h6>
|
||||
<p class="mb-1">
|
||||
<strong><i class="bi bi-calendar-check"></i> Hire Date:</strong><br />
|
||||
@Model.HireDate.ToString("MMMM dd, yyyy")
|
||||
</p>
|
||||
@if (Model.TerminationDate.HasValue)
|
||||
{
|
||||
<p class="mb-1">
|
||||
<strong><i class="bi bi-calendar-x"></i> Termination Date:</strong><br />
|
||||
@Model.TerminationDate.Value.ToString("MMMM dd, yyyy")
|
||||
</p>
|
||||
}
|
||||
@if (Model.LastLoginDate.HasValue)
|
||||
{
|
||||
<p class="mb-1">
|
||||
<strong><i class="bi bi-clock-history"></i> Last Login:</strong><br />
|
||||
@Model.LastLoginDate.Value.ToString("MMMM dd, yyyy HH:mm")
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="mb-1">
|
||||
<strong><i class="bi bi-clock-history"></i> Last Login:</strong><br />
|
||||
<span class="text-muted">Never</span>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.IsBanned)
|
||||
{
|
||||
<hr />
|
||||
<div class="alert alert-danger mb-0">
|
||||
<i class="bi bi-slash-circle-fill me-2"></i>
|
||||
<strong>This account is banned.</strong>
|
||||
@if (Model.BannedAt.HasValue)
|
||||
{
|
||||
<span class="ms-1 text-muted small">Since @Model.BannedAt.Value.ToString("MMMM dd, yyyy HH:mm")</span>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.BanReason))
|
||||
{
|
||||
<div class="mt-1">Reason: @Model.BanReason</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (isCompanyUser)
|
||||
{
|
||||
<hr />
|
||||
<h6 class="text-muted mb-3">Permissions</h6>
|
||||
<div class="row g-2 mb-3">
|
||||
@{
|
||||
var perms = new[]
|
||||
{
|
||||
("Can Manage Jobs", (bool)(ViewBag.CanManageJobs ?? false)),
|
||||
("Can Manage Inventory", (bool)(ViewBag.CanManageInventory ?? false)),
|
||||
("Can Manage Customers", (bool)(ViewBag.CanManageCustomers ?? false)),
|
||||
("Can Create Quotes", (bool)(ViewBag.CanCreateQuotes ?? false)),
|
||||
("Can Approve Quotes", (bool)(ViewBag.CanApproveQuotes ?? false)),
|
||||
("Can Manage Calendar", (bool)(ViewBag.CanManageCalendar ?? false)),
|
||||
("Can View Calendar", (bool)(ViewBag.CanViewCalendar ?? false)),
|
||||
("Can Manage Products", (bool)(ViewBag.CanManageProducts ?? false)),
|
||||
("Can View Products", (bool)(ViewBag.CanViewProducts ?? false)),
|
||||
("Can Manage Equipment", (bool)(ViewBag.CanManageEquipment ?? false)),
|
||||
("Can Manage Vendors", (bool)(ViewBag.CanManageVendors ?? false)),
|
||||
("Can Manage Maintenance", (bool)(ViewBag.CanManageMaintenance ?? false)),
|
||||
};
|
||||
foreach (var (label, granted) in perms)
|
||||
{
|
||||
<div class="col-md-6">
|
||||
<span class="@(granted ? "text-success" : "text-muted")">
|
||||
<i class="bi @(granted ? "bi-check-circle-fill" : "bi-x-circle")"></i>
|
||||
@label
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1 text-muted small">
|
||||
<strong>Account Created:</strong> @Model.CreatedAt.ToString("MMMM dd, yyyy HH:mm")
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1 text-muted small">
|
||||
<strong>Last Updated:</strong> @(Model.UpdatedAt?.ToString("MMMM dd, yyyy HH:mm") ?? "Never")
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to List
|
||||
</a>
|
||||
<div class="d-flex gap-2">
|
||||
@if (!Model.IsBanned)
|
||||
{
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
data-bs-toggle="modal" data-bs-target="#banModal">
|
||||
<i class="bi bi-slash-circle"></i> Ban User
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form asp-controller="PlatformUsers" asp-action="UnbanUser" asp-route-id="@Model.Id"
|
||||
asp-route-returnUrl="@detailsReturnUrl" method="post" class="d-inline"
|
||||
onsubmit="return confirm('Lift the ban on @Html.Encode(Model.FullName)?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="bi bi-check-circle"></i> Unban User
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
@if (isSuperAdmin)
|
||||
{
|
||||
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-primary">
|
||||
<i class="bi bi-pencil"></i> Edit SuperAdmin
|
||||
</a>
|
||||
}
|
||||
else if (isCompanyUser)
|
||||
{
|
||||
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#resetPasswordModal"
|
||||
title="Set password manually">
|
||||
<i class="bi bi-key"></i> Reset Password
|
||||
</button>
|
||||
<form asp-controller="CompanyUsers"
|
||||
asp-action="SendPasswordResetEmail"
|
||||
asp-route-id="@Model.Id"
|
||||
method="post"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Send a password reset link to @Html.Encode(Model.Email)?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-outline-info" title="Email password reset link">
|
||||
<i class="bi bi-envelope-arrow-up me-1"></i>Send Reset Link
|
||||
</button>
|
||||
</form>
|
||||
<a asp-controller="CompanyUsers" asp-action="Edit" asp-route-id="@Model.Id" asp-route-returnUrl="@detailsReturnUrl" class="btn btn-primary">
|
||||
<i class="bi bi-pencil"></i> Edit User
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isCompanyUser)
|
||||
{
|
||||
<!-- Reset Password Modal -->
|
||||
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="resetPasswordModalLabel">
|
||||
<i class="bi bi-key"></i> Reset Password
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-action="ResetPassword" method="post" id="resetPasswordForm">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" value="@Model.Id" />
|
||||
<input type="hidden" name="returnUrl" value="@detailsReturnUrl" />
|
||||
<div class="modal-body">
|
||||
<p class="mb-3">
|
||||
Resetting password for: <strong>@Model.FullName</strong>
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" class="form-label">New Password *</label>
|
||||
<input type="password" class="form-control" id="newPassword" name="newPassword"
|
||||
placeholder="Minimum 8 characters" required minlength="8" />
|
||||
<div class="invalid-feedback">Password must be at least 8 characters.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirmNewPassword" class="form-label">Confirm New Password *</label>
|
||||
<input type="password" class="form-control" id="confirmNewPassword"
|
||||
placeholder="Re-enter new password" required />
|
||||
<div class="invalid-feedback" id="confirmPasswordError">Passwords do not match.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="bi bi-key"></i> Reset Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Ban User Modal -->
|
||||
<div class="modal fade" id="banModal" tabindex="-1" aria-labelledby="banModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="banModalLabel"><i class="bi bi-slash-circle"></i> Ban User</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-controller="PlatformUsers" asp-action="BanUser" asp-route-id="@Model.Id"
|
||||
asp-route-returnUrl="@detailsReturnUrl" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
Banning <strong>@Model.FullName</strong> will immediately prevent them from logging in.
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="banReason" class="form-label">Reason <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="banReason" name="reason"
|
||||
placeholder="e.g. Unauthorized access attempt" required maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger"><i class="bi bi-slash-circle"></i> Ban User</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.avatar-circle-large {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@if (isCompanyUser)
|
||||
{
|
||||
@section Scripts {
|
||||
<script>
|
||||
document.getElementById('resetPasswordForm').addEventListener('submit', function (e) {
|
||||
var pwd = document.getElementById('newPassword');
|
||||
var confirm = document.getElementById('confirmNewPassword');
|
||||
var valid = true;
|
||||
|
||||
if (pwd.value.length < 8) {
|
||||
pwd.classList.add('is-invalid');
|
||||
valid = false;
|
||||
} else {
|
||||
pwd.classList.remove('is-invalid');
|
||||
}
|
||||
|
||||
if (pwd.value !== confirm.value) {
|
||||
confirm.classList.add('is-invalid');
|
||||
document.getElementById('confirmPasswordError').textContent = 'Passwords do not match.';
|
||||
valid = false;
|
||||
} else {
|
||||
confirm.classList.remove('is-invalid');
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
@model PowderCoating.Application.DTOs.User.UpdateSuperAdminDto
|
||||
@{
|
||||
ViewData["Title"] = "Edit SuperAdmin";
|
||||
}
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4 class="mb-0">
|
||||
<i class="bi bi-pencil"></i> Edit SuperAdmin
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form asp-action="Edit" method="post">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<partial name="_ValidationSummary" />
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="FirstName" class="form-label">First Name <span class="text-danger">*</span></label>
|
||||
<input asp-for="FirstName" class="form-control" required />
|
||||
<span asp-validation-for="FirstName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="LastName" class="form-label">Last Name <span class="text-danger">*</span></label>
|
||||
<input asp-for="LastName" class="form-control" required />
|
||||
<span asp-validation-for="LastName" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Email" class="form-label">Email <span class="text-danger">*</span></label>
|
||||
<input asp-for="Email" type="email" class="form-control" readonly />
|
||||
<small class="form-text text-muted">Email cannot be changed</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="EmployeeNumber" class="form-label">Employee Number</label>
|
||||
<input asp-for="EmployeeNumber" class="form-control" />
|
||||
<span asp-validation-for="EmployeeNumber" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Department" class="form-label">Department</label>
|
||||
<input asp-for="Department" class="form-control" />
|
||||
<span asp-validation-for="Department" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Position" class="form-label">Position</label>
|
||||
<input asp-for="Position" class="form-control" />
|
||||
<span asp-validation-for="Position" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Phone" class="form-label">Phone</label>
|
||||
<input asp-for="Phone" type="tel" class="form-control" />
|
||||
<span asp-validation-for="Phone" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="HireDate" class="form-label">Hire Date <span class="text-danger">*</span></label>
|
||||
<input asp-for="HireDate" type="date" class="form-control" required />
|
||||
<span asp-validation-for="HireDate" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="TerminationDate" class="form-label">Termination Date</label>
|
||||
<input asp-for="TerminationDate" type="date" class="form-control" />
|
||||
<span asp-validation-for="TerminationDate" class="text-danger"></span>
|
||||
<small class="form-text text-muted">Leave blank if currently employed</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check mt-4 pt-2">
|
||||
<input asp-for="IsActive" class="form-check-input" type="checkbox" />
|
||||
<label asp-for="IsActive" class="form-check-label">
|
||||
Account is Active
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a asp-action="Index" asp-route-filter="superadmins" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
@{
|
||||
ViewData["Title"] = "Grant SuperAdmin Access";
|
||||
ViewData["PageIcon"] = "bi-shield-plus";
|
||||
}
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-end mb-4">
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Users
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<i class="bi bi-shield-exclamation me-2"></i>
|
||||
<strong>Caution:</strong> SuperAdmins have full platform access across <em>all companies</em>. Only promote users you trust completely with platform-level administration.
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-transparent">
|
||||
<h5 class="mb-0"><i class="bi bi-search me-2"></i>Search for a User</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-8">
|
||||
<label for="searchInput" class="form-label">Name or Email</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0">
|
||||
<i class="bi bi-search text-muted"></i>
|
||||
</span>
|
||||
<input type="text" id="searchInput" class="form-control border-start-0"
|
||||
placeholder="Start typing a name or email address..." />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<button type="button" class="btn btn-primary" onclick="searchUsers()">
|
||||
<i class="bi bi-search"></i> Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div id="searchResults" style="display:none;">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-transparent d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-people me-2"></i>Search Results</h5>
|
||||
<span id="resultCount" class="badge bg-secondary"></span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="spinner" class="text-center py-4" style="display:none;">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Searching...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="noResults" class="text-center py-5" style="display:none;">
|
||||
<i class="bi bi-inbox display-4 text-muted"></i>
|
||||
<p class="text-muted mt-2">No eligible users found. Users already holding SuperAdmin are excluded.</p>
|
||||
</div>
|
||||
<div class="table-responsive" id="resultsTable" style="display:none;">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Company</th>
|
||||
<th>Current Role</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="resultsBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grant Confirmation Modal -->
|
||||
<div class="modal fade" id="grantModal" tabindex="-1" aria-labelledby="grantModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="grantModalLabel">
|
||||
<i class="bi bi-shield-plus"></i> Confirm SuperAdmin Grant
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-action="GrantSuperAdmin" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="userId" id="grantUserId" />
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
<strong>Warning:</strong> This user will gain full platform access across all companies.
|
||||
</div>
|
||||
<p class="mb-0">
|
||||
Grant SuperAdmin access to <strong id="grantUserName"></strong>?
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-shield-plus"></i> Grant SuperAdmin
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
// Trigger search on Enter key
|
||||
document.getElementById('searchInput').addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
searchUsers();
|
||||
}
|
||||
});
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function searchUsers() {
|
||||
const term = document.getElementById('searchInput').value.trim();
|
||||
|
||||
document.getElementById('searchResults').style.display = 'block';
|
||||
document.getElementById('spinner').style.display = 'block';
|
||||
document.getElementById('noResults').style.display = 'none';
|
||||
document.getElementById('resultsTable').style.display = 'none';
|
||||
document.getElementById('resultCount').textContent = '';
|
||||
|
||||
try {
|
||||
const url = '@Url.Action("SearchUsersForGrant", "PlatformUsers")' + (term ? '?term=' + encodeURIComponent(term) : '');
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
document.getElementById('spinner').style.display = 'none';
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
document.getElementById('noResults').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('resultCount').textContent = data.length + ' result' + (data.length !== 1 ? 's' : '');
|
||||
const tbody = document.getElementById('resultsBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
data.forEach(function (user) {
|
||||
const statusBadge = user.isActive
|
||||
? '<span class="badge bg-success"><i class="bi bi-check-circle"></i> Active</span>'
|
||||
: '<span class="badge bg-secondary"><i class="bi bi-x-circle"></i> Inactive</span>';
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML =
|
||||
'<td><strong>' + escapeHtml(user.fullName) + '</strong></td>' +
|
||||
'<td>' + escapeHtml(user.email) + '</td>' +
|
||||
'<td><small>' + escapeHtml(user.companyName) + '</small></td>' +
|
||||
'<td><span class="badge bg-primary"><i class="bi bi-person-gear"></i> ' + escapeHtml(user.companyRole) + '</span></td>' +
|
||||
'<td>' + statusBadge + '</td>' +
|
||||
'<td><button type="button" class="btn btn-sm btn-outline-danger" ' +
|
||||
'onclick="openGrantModal(\'' + escapeHtml(user.id) + '\', \'' + escapeHtml(user.fullName).replace(/'/g, "\\'") + '\')">' +
|
||||
'<i class="bi bi-shield-plus"></i> Grant</button></td>';
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
document.getElementById('resultsTable').style.display = 'block';
|
||||
} catch (err) {
|
||||
document.getElementById('spinner').style.display = 'none';
|
||||
document.getElementById('noResults').style.display = 'block';
|
||||
console.error('Search error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function openGrantModal(userId, userName) {
|
||||
document.getElementById('grantUserId').value = userId;
|
||||
document.getElementById('grantUserName').textContent = userName;
|
||||
var modal = new bootstrap.Modal(document.getElementById('grantModal'));
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
@model PagedResult<PowderCoating.Application.DTOs.User.PlatformUserListDto>
|
||||
|
||||
@section Styles {
|
||||
<style>
|
||||
.avatar-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
[data-bs-theme="dark"] a.text-dark { color: var(--bs-body-color) !important; }
|
||||
</style>
|
||||
}
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Platform Users";
|
||||
ViewData["PageIcon"] = "bi-people-fill";
|
||||
var currentFilter = ViewBag.CurrentFilter as string;
|
||||
var returnUrl = Url.Action("Index", "PlatformUsers");
|
||||
const string RootUserEmail = "artemis@powdercoatinglogix.com";
|
||||
}
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a asp-action="GrantSuperAdmin" class="btn btn-outline-danger">
|
||||
<i class="bi bi-shield-plus"></i> Grant SuperAdmin Access
|
||||
</a>
|
||||
<a asp-action="CreateSuperAdmin" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Create SuperAdmin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-7">
|
||||
<div class="btn-group" role="group">
|
||||
<a asp-action="Index" class="btn btn-outline-primary @(string.IsNullOrEmpty(currentFilter) ? "active" : "")">
|
||||
<i class="bi bi-people"></i> All Users
|
||||
</a>
|
||||
<a asp-action="Index" asp-route-filter="superadmins" class="btn btn-outline-danger @(currentFilter == "superadmins" ? "active" : "")">
|
||||
<i class="bi bi-shield-check"></i> SuperAdmins
|
||||
</a>
|
||||
<a asp-action="Index" asp-route-filter="companyadmins" class="btn btn-outline-primary @(currentFilter == "companyadmins" ? "active" : "")">
|
||||
<i class="bi bi-building"></i> Company Admins
|
||||
</a>
|
||||
<a asp-action="Index" asp-route-filter="active" class="btn btn-outline-success @(currentFilter == "active" ? "active" : "")">
|
||||
<i class="bi bi-check-circle"></i> Active
|
||||
</a>
|
||||
<a asp-action="Index" asp-route-filter="inactive" class="btn btn-outline-secondary @(currentFilter == "inactive" ? "active" : "")">
|
||||
<i class="bi bi-x-circle"></i> Inactive
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<form method="get" class="d-flex gap-2">
|
||||
<input type="hidden" name="filter" value="@currentFilter" />
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0">
|
||||
<i class="bi bi-search text-muted"></i>
|
||||
</span>
|
||||
<input type="text" name="searchTerm" class="form-control border-start-0"
|
||||
placeholder="Search by name or email..." value="@ViewBag.SearchTerm">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-search"></i></button>
|
||||
@if (!string.IsNullOrEmpty(ViewBag.SearchTerm))
|
||||
{
|
||||
<a asp-action="Index" asp-route-filter="@currentFilter" class="btn btn-outline-secondary">Clear</a>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
@if (Model.Items.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th sortable="Name" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Name</th>
|
||||
<th sortable="Email" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Email</th>
|
||||
<th>Role</th>
|
||||
<th>Company</th>
|
||||
<th sortable="IsActive" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Status</th>
|
||||
<th sortable="LastLoginDate" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Last Login</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var user in Model.Items)
|
||||
{
|
||||
<tr style="cursor:pointer" onclick="window.location='@Url.Action("Details", new { id = user.Id })'">
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="avatar-circle bg-primary text-white me-2">
|
||||
@user.FirstName[0]@user.LastName[0]
|
||||
</div>
|
||||
<div>
|
||||
<strong>@user.FullName</strong>
|
||||
@if (!string.IsNullOrEmpty(user.Position))
|
||||
{
|
||||
<br /><small class="text-muted">@user.Position</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@user.Email
|
||||
@if (!string.IsNullOrEmpty(user.EmployeeNumber))
|
||||
{
|
||||
<br /><small class="text-muted">ID: @user.EmployeeNumber</small>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (user.IsSuperAdmin)
|
||||
{
|
||||
<span class="badge bg-danger">
|
||||
<i class="bi bi-shield-check"></i> SuperAdmin
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-person-gear"></i> Company Admin
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(user.CompanyName))
|
||||
{
|
||||
<small>@user.CompanyName</small>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (user.IsBanned)
|
||||
{
|
||||
<span class="badge bg-danger">
|
||||
<i class="bi bi-slash-circle"></i> Banned
|
||||
</span>
|
||||
}
|
||||
else if (user.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-check-circle"></i> Active
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">
|
||||
<i class="bi bi-x-circle"></i> Inactive
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (user.LastLoginDate.HasValue)
|
||||
{
|
||||
@user.LastLoginDate.Value.ToString("MMM dd, yyyy")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">Never</span>
|
||||
}
|
||||
</td>
|
||||
<td onclick="event.stopPropagation()">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a asp-action="Details" asp-route-id="@user.Id" class="btn btn-outline-info" title="View Details">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
@if (string.Equals(user.Email, RootUserEmail, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@if (string.Equals(User.Identity?.Name, RootUserEmail, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
<a asp-action="Edit" asp-route-id="@user.Id" class="btn btn-outline-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="btn btn-outline-secondary disabled" title="Root account — protected">
|
||||
<i class="bi bi-shield-lock"></i>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
else if (user.IsSuperAdmin)
|
||||
{
|
||||
<a asp-action="Edit" asp-route-id="@user.Id" class="btn btn-outline-primary" title="Edit SuperAdmin">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
title="Revoke SuperAdmin Access"
|
||||
onclick="openRevokeModal('@user.Id', '@Html.Encode(user.FullName)')">
|
||||
<i class="bi bi-shield-x"></i>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-controller="CompanyUsers" asp-action="Edit" asp-route-id="@user.Id" asp-route-returnUrl="@returnUrl" class="btn btn-outline-primary" title="Edit User">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-warning"
|
||||
title="Reset Password (set manually)"
|
||||
onclick="openResetPasswordModal('@user.Id', '@Html.Encode(user.FullName)')">
|
||||
<i class="bi bi-key"></i>
|
||||
</button>
|
||||
<form asp-controller="CompanyUsers"
|
||||
asp-action="SendPasswordResetEmail"
|
||||
asp-route-id="@user.Id"
|
||||
method="post"
|
||||
class="d-inline"
|
||||
onsubmit="return confirm('Send a password reset link to @Html.Encode(user.Email)?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-outline-info" title="Email password reset link">
|
||||
<i class="bi bi-envelope-arrow-up"></i>
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Mobile card view — shown on screens < 992px (table-responsive hidden by mobile-cards.css) -->
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var user in Model.Items)
|
||||
{
|
||||
<a href="@Url.Action("Details", new { id = user.Id })" class="mobile-data-card text-decoration-none">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon bg-primary"><i class="bi bi-person-fill"></i></div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@user.FullName</h6>
|
||||
<small class="text-muted">@user.Email</small>
|
||||
</div>
|
||||
@if (user.IsBanned)
|
||||
{
|
||||
<span class="badge bg-danger"><i class="bi bi-slash-circle"></i> Banned</span>
|
||||
}
|
||||
else if (user.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Role</span>
|
||||
<span class="mobile-card-value">
|
||||
@if (user.IsSuperAdmin)
|
||||
{
|
||||
<span class="badge bg-danger"><i class="bi bi-shield-check"></i> SuperAdmin</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-primary"><i class="bi bi-person-gear"></i> Company Admin</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(user.CompanyName))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Company</span>
|
||||
<span class="mobile-card-value">@user.CompanyName</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Last Login</span>
|
||||
<span class="mobile-card-value">
|
||||
@if (user.LastLoginDate.HasValue)
|
||||
{
|
||||
@user.LastLoginDate.Value.ToString("MMM dd, yyyy")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">Never</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<span class="btn btn-sm btn-outline-primary">View →</span>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-inbox display-1 text-muted"></i>
|
||||
<p class="text-muted mt-3">No users found</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.TotalCount > 0)
|
||||
{
|
||||
@await Html.PartialAsync("_Pagination", Model)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Modal -->
|
||||
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="resetPasswordModalLabel">
|
||||
<i class="bi bi-key"></i> Reset Password
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-action="ResetPassword" method="post" id="resetPasswordForm">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" id="resetUserId" />
|
||||
<input type="hidden" name="returnUrl" value="@returnUrl" />
|
||||
<div class="modal-body">
|
||||
<p class="mb-3">
|
||||
Resetting password for: <strong id="resetUserName"></strong>
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" class="form-label">New Password *</label>
|
||||
<input type="password" class="form-control" id="newPassword" name="newPassword"
|
||||
placeholder="Minimum 8 characters" required minlength="8" />
|
||||
<div class="invalid-feedback">Password must be at least 8 characters.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirmNewPassword" class="form-label">Confirm New Password *</label>
|
||||
<input type="password" class="form-control" id="confirmNewPassword"
|
||||
placeholder="Re-enter new password" required />
|
||||
<div class="invalid-feedback" id="confirmPasswordError">Passwords do not match.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="bi bi-key"></i> Reset Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Revoke SuperAdmin Modal -->
|
||||
<div class="modal fade" id="revokeModal" tabindex="-1" aria-labelledby="revokeModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="revokeModalLabel">
|
||||
<i class="bi bi-shield-x"></i> Revoke SuperAdmin Access
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-action="RevokeSuperAdmin" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="id" id="revokeUserId" />
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
<strong>Warning:</strong> This will remove platform-wide access. The user will be demoted to Company Admin.
|
||||
</div>
|
||||
<p class="mb-0">
|
||||
Are you sure you want to revoke SuperAdmin access from <strong id="revokeUserName"></strong>?
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-shield-x"></i> Revoke Access
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function openResetPasswordModal(userId, userName) {
|
||||
document.getElementById('resetUserId').value = userId;
|
||||
document.getElementById('resetUserName').textContent = userName;
|
||||
document.getElementById('newPassword').value = '';
|
||||
document.getElementById('confirmNewPassword').value = '';
|
||||
document.getElementById('newPassword').classList.remove('is-invalid');
|
||||
document.getElementById('confirmNewPassword').classList.remove('is-invalid');
|
||||
var modal = new bootstrap.Modal(document.getElementById('resetPasswordModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function openRevokeModal(userId, userName) {
|
||||
document.getElementById('revokeUserId').value = userId;
|
||||
document.getElementById('revokeUserName').textContent = userName;
|
||||
var modal = new bootstrap.Modal(document.getElementById('revokeModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
document.getElementById('resetPasswordForm').addEventListener('submit', function (e) {
|
||||
var pwd = document.getElementById('newPassword');
|
||||
var confirm = document.getElementById('confirmNewPassword');
|
||||
var valid = true;
|
||||
|
||||
if (pwd.value.length < 8) {
|
||||
pwd.classList.add('is-invalid');
|
||||
valid = false;
|
||||
} else {
|
||||
pwd.classList.remove('is-invalid');
|
||||
}
|
||||
|
||||
if (pwd.value !== confirm.value) {
|
||||
confirm.classList.add('is-invalid');
|
||||
document.getElementById('confirmPasswordError').textContent = 'Passwords do not match.';
|
||||
valid = false;
|
||||
} else {
|
||||
confirm.classList.remove('is-invalid');
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
Reference in New Issue
Block a user