Restore all zeroed views + add bulk gift certificate creation
The HTML entity sweep script had a bug where it wrote empty files for any
view that contained no target Unicode characters, zeroing out 215 view files.
All views restored from the pre-sweep commit (cefdf3e).
Bulk gift certificate feature:
- BulkCreateGiftCertificateDto with Quantity (1-500), Amount, Reason, Expiry, Notes
- GenerateBulkGiftCertificatePdfAsync on IPdfService / PdfService: one Letter page
per cert, reusing the same purple/gold branded ComposeGiftCertificateContent helper
- GiftCertificatesController: BulkCreate GET/POST, BulkResult GET, BulkDownloadPdf POST
- Views: BulkCreate.cshtml (form with live total preview), BulkResult.cshtml (table +
Download All PDF button that POSTs cert IDs to avoid URL length limits)
- gift-certificate-bulk.js: live preview + spinner/disable on submit
- Index.cshtml: Bulk Create button added alongside New Certificate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
@{
|
||||
ViewData["Title"] = "Enable Biometric Login";
|
||||
Layout = "/Views/Shared/_AuthLayout.cshtml";
|
||||
var returnUrl = ViewBag.ReturnUrl as string ?? "/";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<style>
|
||||
.auth-brand-panel {
|
||||
background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 3rem 2.5rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.auth-brand-panel h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-brand-panel .tagline {
|
||||
font-size: 1rem;
|
||||
color: rgba(255,255,255,0.65);
|
||||
margin-bottom: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 0;
|
||||
font-size: 0.95rem;
|
||||
color: rgba(255,255,255,0.82);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
|
||||
.feature-list li:last-child { border-bottom: none; }
|
||||
|
||||
.feature-list li i {
|
||||
color: #4fc3f7;
|
||||
font-size: 1.1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.auth-form-panel {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2.5rem 1.5rem;
|
||||
background-color: #ffffff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.auth-form-container {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.auth-form-container h2 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.auth-form-container .subtext {
|
||||
color: #64748b;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
<div class="d-flex" style="min-height:100vh;">
|
||||
<!-- Left brand panel — hidden on mobile, same as login page -->
|
||||
<div class="col-lg-5 d-none d-lg-flex auth-brand-panel">
|
||||
<img src="/images/pcl-logo.png" alt="Powder Coating Logix" style="max-width:220px; margin-bottom:1.5rem;" />
|
||||
<h1>Powder Coating Logix</h1>
|
||||
<p class="tagline">The complete management platform for powder coating businesses</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="bi bi-fingerprint"></i> Fast biometric login</li>
|
||||
<li><i class="bi bi-shield-check-fill"></i> Secure — your biometrics never leave your device</li>
|
||||
<li><i class="bi bi-phone-fill"></i> Works with Face ID, Touch ID & Windows Hello</li>
|
||||
<li><i class="bi bi-trash3-fill"></i> Remove any device at any time</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Right prompt panel -->
|
||||
<div class="auth-form-panel col-lg-7">
|
||||
<div class="auth-form-container text-center">
|
||||
|
||||
<div id="prompt-step">
|
||||
<div class="mb-4" style="font-size:4rem; color:#0284c7; line-height:1;">
|
||||
<i class="bi bi-fingerprint"></i>
|
||||
</div>
|
||||
<h2 class="mb-2">Speed up future logins</h2>
|
||||
<p class="subtext mb-4">
|
||||
Enable biometric login so next time you can sign in with a single tap —
|
||||
no password needed.
|
||||
</p>
|
||||
|
||||
<p id="pk-status" class="small mb-3" style="min-height:1.25em;"></p>
|
||||
|
||||
<div class="d-grid gap-2" style="max-width:320px; margin:0 auto;">
|
||||
<button id="pk-enable-btn" type="button" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-fingerprint me-2"></i><span id="pk-btn-label">Enable Biometric Login</span>
|
||||
</button>
|
||||
<a id="pk-skip-link" href="@returnUrl" class="btn btn-outline-secondary btn-lg">
|
||||
Maybe later
|
||||
</a>
|
||||
<form method="post" action="/Passkey/DismissPrompt">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="returnUrl" value="@returnUrl" />
|
||||
<button type="submit" class="btn btn-link text-muted w-100" style="font-size:0.85rem;">
|
||||
Don't ask me again
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="success-step" class="d-none">
|
||||
<div class="mb-4" style="font-size:4rem; color:#16a34a; line-height:1;">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</div>
|
||||
<h2 class="mb-2">All set!</h2>
|
||||
<p class="subtext mb-4">Biometric login is enabled for this device.</p>
|
||||
<a href="@returnUrl" class="btn btn-primary btn-lg px-5">Continue</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="~/js/passkey.js"></script>
|
||||
<script src="~/js/passkey-enroll.js"></script>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
@model IEnumerable<PowderCoating.Core.Entities.UserPasskey>
|
||||
@{
|
||||
ViewData["Title"] = "My Passkeys";
|
||||
}
|
||||
|
||||
<div class="container-fluid py-4" style="max-width:760px;">
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<div class="rounded-circle d-flex align-items-center justify-content-center"
|
||||
style="width:48px;height:48px;background:#e0f2fe;">
|
||||
<i class="bi bi-fingerprint" style="font-size:1.5rem;color:#0284c7;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="mb-0 fw-semibold">Passkeys & Biometric Login</h4>
|
||||
<p class="text-muted small mb-0">
|
||||
Passkeys let you sign in with Face ID, fingerprint, or your device PIN — no password needed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (TempData["Success"] is string msg)
|
||||
{
|
||||
<div class="alert alert-success alert-permanent">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>@msg
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Add new passkey -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title mb-1">Add a passkey for this device</h6>
|
||||
<p class="text-muted small mb-3">
|
||||
You'll be prompted to authenticate using Face ID, Touch ID, Windows Hello, or a security key.
|
||||
</p>
|
||||
<div class="d-flex gap-2 align-items-center flex-wrap">
|
||||
<input type="text" id="pk-device-name" class="form-control" style="max-width:220px;"
|
||||
placeholder="Device name (e.g. iPhone 15)" maxlength="64" />
|
||||
<button type="button" id="pk-add-btn" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle me-1"></i>Add Passkey
|
||||
</button>
|
||||
</div>
|
||||
<p id="pk-add-status" class="mt-2 small mb-0"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing passkeys -->
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-fingerprint" style="font-size:3rem;opacity:.3;"></i>
|
||||
<p class="mt-3">No passkeys registered yet.<br />Add one above to enable biometric login on this device.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group shadow-sm">
|
||||
@foreach (var pk in Model)
|
||||
{
|
||||
<div class="list-group-item list-group-item-action d-flex align-items-center gap-3">
|
||||
<i class="bi bi-phone" style="font-size:1.4rem;color:#64748b;flex-shrink:0;"></i>
|
||||
<div class="flex-grow-1 min-width-0">
|
||||
<div class="fw-medium text-truncate">
|
||||
@(pk.DeviceFriendlyName ?? "Unnamed device")
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Added @pk.CreatedAt.ToLocalTime().ToString("MMM d, yyyy")
|
||||
@if (pk.LastUsedAt.HasValue)
|
||||
{
|
||||
<span class="ms-2">• Last used @pk.LastUsedAt.Value.ToLocalTime().ToString("MMM d, yyyy")</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" asp-action="Remove" asp-route-id="@pk.Id"
|
||||
onsubmit="return confirm('Remove this passkey?');">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||||
<i class="bi bi-trash3"></i> Remove
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<p class="text-muted small mt-3">
|
||||
Removing a passkey means you'll need to use your password on that device next time.
|
||||
</p>
|
||||
}
|
||||
|
||||
<div class="mt-4">
|
||||
<a asp-controller="CompanySettings" asp-action="Index" class="text-decoration-none">
|
||||
<i class="bi bi-arrow-left me-1"></i>Back to Settings
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="~/js/passkey.js"></script>
|
||||
<script src="~/js/passkey-manage.js"></script>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user