Share Mark Complete modal as partial view; hide install button after PWA install

- Extract _CompleteJobModal.cshtml partial; Details.cshtml uses PartialAsync
- Job board COMPLETED drop fetches partial via AJAX and shows modal in-place
- Add GET Jobs/CompleteJobModal action to load job data for the board modal
- install-app.js: persist installed state in localStorage; clears automatically when browser re-fires beforeinstallprompt after uninstall

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 19:55:37 -04:00
parent bbedaedeaa
commit 2cfe093780
5 changed files with 207 additions and 159 deletions
@@ -1,12 +1,15 @@
// Browser-aware install UX for supported PWAs.
// Shows a real install button only when the browser exposes the install prompt,
// and falls back to platform-specific instructions for iOS Safari.
// After install the button is hidden; if the user later uninstalls, the browser
// re-fires beforeinstallprompt which clears the flag and shows the button again.
(function () {
'use strict';
const installBtn = document.getElementById('installAppBtn');
if (!installBtn) return;
const INSTALLED_KEY = 'pclAppInstalled';
const ua = navigator.userAgent || '';
const isIos = /iphone|ipad|ipod/i.test(ua);
const isIosSafari = isIos && /safari/i.test(ua) && !/crios|fxios|edgios/i.test(ua);
@@ -51,6 +54,13 @@
return;
}
// Hide if already installed and browser hasn't re-offered the prompt
// (flag is cleared automatically when beforeinstallprompt fires again after uninstall)
if (localStorage.getItem(INSTALLED_KEY) === '1') {
hideInstallButton();
return;
}
if (deferredPrompt) {
showInstallButton('prompt', 'Install App', 'bi-download');
return;
@@ -64,14 +74,18 @@
hideInstallButton();
}
// Browser fires this when the app becomes installable (including after an uninstall).
// Clearing the flag here ensures the button reappears if the user previously uninstalled.
window.addEventListener('beforeinstallprompt', function (event) {
event.preventDefault();
deferredPrompt = event;
localStorage.removeItem(INSTALLED_KEY);
refreshInstallUi();
});
window.addEventListener('appinstalled', function () {
deferredPrompt = null;
localStorage.setItem(INSTALLED_KEY, '1');
hideInstallButton();
});
@@ -84,7 +98,10 @@
deferredPrompt.prompt();
try {
await deferredPrompt.userChoice;
const choice = await deferredPrompt.userChoice;
if (choice.outcome === 'accepted') {
localStorage.setItem(INSTALLED_KEY, '1');
}
} catch {
// Ignore prompt result errors; the browser owns the final install flow.
}