diff --git a/src/PowderCoating.Web/Controllers/PasskeyController.cs b/src/PowderCoating.Web/Controllers/PasskeyController.cs
index af5ab56..e8f0334 100644
--- a/src/PowderCoating.Web/Controllers/PasskeyController.cs
+++ b/src/PowderCoating.Web/Controllers/PasskeyController.cs
@@ -255,6 +255,28 @@ public class PasskeyController : Controller
// ─── Management ───────────────────────────────────────────────────────────
+ // ─── Post-login enrollment prompt ─────────────────────────────────────────
+
+ ///
+ /// Shown immediately after password login. If the user already has a passkey,
+ /// redirects straight to returnUrl. Otherwise presents the "Enable Face ID" page.
+ ///
+ [Authorize]
+ [HttpGet("/Passkey/EnrollPrompt")]
+ public async Task
EnrollPrompt(string? returnUrl)
+ {
+ var user = await _userManager.GetUserAsync(User);
+ if (user == null) return Unauthorized();
+
+ // Skip prompt for users who already have at least one passkey
+ var hasPasskey = await _db.UserPasskeys.AnyAsync(p => p.UserId == user.Id);
+ if (hasPasskey)
+ return Redirect(returnUrl ?? "/");
+
+ ViewBag.ReturnUrl = returnUrl ?? "/";
+ return View();
+ }
+
/// Shows all passkeys registered by the current user.
[Authorize]
[HttpGet("/Passkey/Manage")]
diff --git a/src/PowderCoating.Web/Views/Passkey/EnrollPrompt.cshtml b/src/PowderCoating.Web/Views/Passkey/EnrollPrompt.cshtml
new file mode 100644
index 0000000..83f9fc0
--- /dev/null
+++ b/src/PowderCoating.Web/Views/Passkey/EnrollPrompt.cshtml
@@ -0,0 +1,63 @@
+@{
+ ViewData["Title"] = "Enable Biometric Login";
+ Layout = "/Views/Shared/_AuthLayout.cshtml";
+ var returnUrl = ViewBag.ReturnUrl as string ?? "/";
+}
+
+
+
+
+

+
Powder Coating Logix
+
The complete management platform for powder coating businesses
+
+ - Fast biometric login
+ - Secure — your biometrics never leave your device
+ - Works with Face ID, Touch ID & Windows Hello
+ - Remove any device at any time
+
+
+
+
+
+
+
+@section Scripts {
+
+
+}
diff --git a/src/PowderCoating.Web/Views/Shared/_Layout.cshtml b/src/PowderCoating.Web/Views/Shared/_Layout.cshtml
index 41b5ca5..449d017 100644
--- a/src/PowderCoating.Web/Views/Shared/_Layout.cshtml
+++ b/src/PowderCoating.Web/Views/Shared/_Layout.cshtml
@@ -895,33 +895,6 @@
@TempData["Info"]
}
- @* Passkey setup prompt — shown once per session to authenticated users who have no passkeys yet *@
- @if (User.Identity?.IsAuthenticated == true && !User.IsInRole("SuperAdmin"))
- {
-
-
-
-
-
-
-
Enable Face ID / Biometric Login
-
Skip the password next time — use your fingerprint or Face ID.
-
-
-
-
-
-
-
-
- }
-
@* Hidden container for ModelState errors (read by toast-notifications.js) *@
@if (!ViewData.ModelState.IsValid && ViewData.ModelState.ErrorCount > 0)
{
diff --git a/src/PowderCoating.Web/wwwroot/js/passkey-enroll.js b/src/PowderCoating.Web/wwwroot/js/passkey-enroll.js
new file mode 100644
index 0000000..2df8b3d
--- /dev/null
+++ b/src/PowderCoating.Web/wwwroot/js/passkey-enroll.js
@@ -0,0 +1,52 @@
+document.addEventListener('DOMContentLoaded', async () => {
+ const enableBtn = document.getElementById('pk-enable-btn');
+ const btnLabel = document.getElementById('pk-btn-label');
+ const statusEl = document.getElementById('pk-status');
+ const promptStep = document.getElementById('prompt-step');
+ const successStep = document.getElementById('success-step');
+
+ if (!enableBtn) return;
+
+ // Set platform-specific label
+ const label = passkeyLabel();
+ if (btnLabel) btnLabel.textContent = `Enable ${label.replace('Use ', '')}`;
+
+ const supported = await passkeySupported();
+ if (!supported) {
+ enableBtn.disabled = true;
+ enableBtn.textContent = 'Not supported on this browser';
+ if (statusEl) {
+ statusEl.textContent = 'Your browser does not support biometric login. You can enable it later from Settings → Passkeys & Biometrics on a supported device.';
+ statusEl.className = 'small mb-3 text-muted';
+ }
+ return;
+ }
+
+ enableBtn.addEventListener('click', async () => {
+ enableBtn.disabled = true;
+ enableBtn.innerHTML = 'Follow the prompt on your device…';
+ if (statusEl) { statusEl.textContent = ''; }
+
+ const ua = navigator.userAgent;
+ const deviceName = /iphone/i.test(ua) ? 'iPhone'
+ : /ipad/i.test(ua) ? 'iPad'
+ : /android/i.test(ua) ? 'Android device'
+ : /macintosh/i.test(ua) ? 'Mac'
+ : /windows/i.test(ua) ? 'Windows PC'
+ : 'This device';
+
+ const result = await registerPasskey(deviceName);
+
+ if (result.success) {
+ promptStep.classList.add('d-none');
+ successStep.classList.remove('d-none');
+ } else {
+ enableBtn.disabled = false;
+ enableBtn.innerHTML = `Enable ${label.replace('Use ', '')}`;
+ if (statusEl) {
+ statusEl.textContent = result.error || 'Setup failed. Please try again.';
+ statusEl.className = 'small mb-3 text-danger';
+ }
+ }
+ });
+});
diff --git a/src/PowderCoating.Web/wwwroot/js/passkey.js b/src/PowderCoating.Web/wwwroot/js/passkey.js
index 47826d3..c3c7fe4 100644
--- a/src/PowderCoating.Web/wwwroot/js/passkey.js
+++ b/src/PowderCoating.Web/wwwroot/js/passkey.js
@@ -272,60 +272,3 @@ document.addEventListener('DOMContentLoaded', async () => {
});
});
-// ─── Post-login prompt wiring (layout) ───────────────────────────────────────
-
-document.addEventListener('DOMContentLoaded', async () => {
- const prompt = document.getElementById('passkey-setup-prompt');
- if (!prompt) return;
-
- const supported = await passkeySupported();
- if (!supported) { prompt.remove(); return; }
-
- const label = passkeyLabel();
- const enableBtn = document.getElementById('passkey-enable-btn');
- if (enableBtn) enableBtn.innerHTML = `Enable ${label.replace('Use ', '')}`;
-
- prompt.classList.remove('d-none');
-
- const dismissBtn = document.getElementById('passkey-dismiss-btn');
- const statusEl = document.getElementById('passkey-setup-status');
-
- enableBtn?.addEventListener('click', async () => {
- enableBtn.disabled = true;
- if (statusEl) statusEl.textContent = 'Follow the prompt on your device…';
-
- const ua = navigator.userAgent;
- const deviceName = /iphone/i.test(ua) ? 'iPhone'
- : /ipad/i.test(ua) ? 'iPad'
- : /android/i.test(ua) ? 'Android device'
- : /macintosh/i.test(ua) ? 'Mac'
- : /windows/i.test(ua) ? 'Windows PC'
- : 'This device';
-
- const result = await registerPasskey(deviceName);
-
- if (result.success) {
- if (statusEl) {
- statusEl.textContent = `✓ ${label.replace('Use ', '')} enabled for this device!`;
- statusEl.className = 'small mb-0 text-success';
- }
- enableBtn.classList.add('d-none');
- if (dismissBtn) dismissBtn.textContent = 'Close';
- setTimeout(() => prompt.classList.add('d-none'), 3000);
- } else {
- enableBtn.disabled = false;
- enableBtn.innerHTML = `Enable ${label.replace('Use ', '')}`;
- if (statusEl) statusEl.textContent = result.error || 'Setup failed. Try again.';
- }
- });
-
- dismissBtn?.addEventListener('click', () => {
- prompt.classList.add('d-none');
- // Remember dismissal for this session so it doesn't re-appear on every page load
- sessionStorage.setItem('passkey-prompt-dismissed', '1');
- });
-
- if (sessionStorage.getItem('passkey-prompt-dismissed')) {
- prompt.classList.add('d-none');
- }
-});