feat(pwa): add install prompt banner and register in all POS templates
- pwa-install.js: captures beforeinstallprompt, shows dismissible banner with 7-day localStorage cooldown, handles appinstalled - Registered in 12 POS templates alongside existing service worker
This commit is contained in:
101
pos/static/js/pwa-install.js
Normal file
101
pos/static/js/pwa-install.js
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* Nexus POS — PWA Install Prompt
|
||||||
|
* Captures beforeinstallprompt and shows a dismissible banner.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'nexus_pwa_install_dismissed';
|
||||||
|
let deferredPrompt = null;
|
||||||
|
|
||||||
|
function createBanner() {
|
||||||
|
if (document.getElementById('pwa-install-banner')) return;
|
||||||
|
|
||||||
|
const banner = document.createElement('div');
|
||||||
|
banner.id = 'pwa-install-banner';
|
||||||
|
banner.innerHTML = `
|
||||||
|
<span>📲 Instala Nexus POS para acceso rápido y modo offline.</span>
|
||||||
|
<button id="pwa-install-btn">Instalar</button>
|
||||||
|
<button id="pwa-dismiss-btn" aria-label="Cerrar">×</button>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(banner);
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
#pwa-install-banner {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0; left: 0; right: 0;
|
||||||
|
background: var(--color-surface, #1a1a1a);
|
||||||
|
color: var(--color-text, #eee);
|
||||||
|
border-top: 1px solid var(--color-border, #333);
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 9999;
|
||||||
|
box-shadow: 0 -4px 12px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
#pwa-install-banner span { flex: 1; }
|
||||||
|
#pwa-install-btn {
|
||||||
|
background: var(--color-primary, #F5A623);
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#pwa-dismiss-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-muted, #888);
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
document.getElementById('pwa-install-btn').addEventListener('click', function () {
|
||||||
|
if (!deferredPrompt) return;
|
||||||
|
deferredPrompt.prompt();
|
||||||
|
deferredPrompt.userChoice.then(function (choice) {
|
||||||
|
if (choice.outcome === 'accepted') {
|
||||||
|
console.log('[PWA] User accepted install');
|
||||||
|
}
|
||||||
|
deferredPrompt = null;
|
||||||
|
banner.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('pwa-dismiss-btn').addEventListener('click', function () {
|
||||||
|
localStorage.setItem(STORAGE_KEY, Date.now().toString());
|
||||||
|
banner.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
deferredPrompt = e;
|
||||||
|
|
||||||
|
const dismissed = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (dismissed && (Date.now() - parseInt(dismissed)) < 7 * 24 * 60 * 60 * 1000) {
|
||||||
|
return; // dismissed within 7 days
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for DOM to be ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', createBanner);
|
||||||
|
} else {
|
||||||
|
createBanner();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('appinstalled', function () {
|
||||||
|
console.log('[PWA] App installed');
|
||||||
|
deferredPrompt = null;
|
||||||
|
const b = document.getElementById('pwa-install-banner');
|
||||||
|
if (b) b.remove();
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -495,6 +495,7 @@
|
|||||||
<script src="/pos/static/js/accounting.js" defer></script>
|
<script src="/pos/static/js/accounting.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -279,5 +279,6 @@
|
|||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script src="/pos/static/js/onboarding.js" defer></script>
|
<script src="/pos/static/js/onboarding.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -688,6 +688,7 @@
|
|||||||
<script src="/pos/static/js/config.js" defer></script>
|
<script src="/pos/static/js/config.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -624,6 +624,7 @@
|
|||||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -352,6 +352,33 @@
|
|||||||
</div><!-- end chart-card -->
|
</div><!-- end chart-card -->
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- =================================================================
|
||||||
|
ESTADÍSTICAS EN TIEMPO REAL
|
||||||
|
================================================================= -->
|
||||||
|
<section>
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-title">Rendimiento Hoy</span>
|
||||||
|
</div>
|
||||||
|
<div class="two-col-grid">
|
||||||
|
<div class="rank-card">
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4);">
|
||||||
|
<div style="font-family:var(--font-heading);font-weight:var(--heading-weight-secondary);font-size:var(--text-body-sm);letter-spacing:var(--tracking-wider);text-transform:uppercase;color:var(--color-text-secondary);">
|
||||||
|
Ventas por Hora
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<canvas id="hourlySalesChart" height="180"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="rank-card">
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4);">
|
||||||
|
<div style="font-family:var(--font-heading);font-weight:var(--heading-weight-secondary);font-size:var(--text-body-sm);letter-spacing:var(--tracking-wider);text-transform:uppercase;color:var(--color-text-secondary);">
|
||||||
|
Top Productos (Hoy)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<canvas id="topProductsChart" height="180"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- =================================================================
|
<!-- =================================================================
|
||||||
TOP PRODUCTOS / TOP CLIENTES
|
TOP PRODUCTOS / TOP CLIENTES
|
||||||
================================================================= -->
|
================================================================= -->
|
||||||
@@ -453,13 +480,16 @@
|
|||||||
</div><!-- end app-shell -->
|
</div><!-- end app-shell -->
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/pos/static/js/chart.umd.min.js" defer></script>
|
||||||
<script src="/pos/static/js/i18n.js" defer></script>
|
<script src="/pos/static/js/i18n.js" defer></script>
|
||||||
<script src="/pos/static/js/app-init.js" defer></script>
|
<script src="/pos/static/js/app-init.js" defer></script>
|
||||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||||
|
<script src="/pos/static/js/dashboard-stats.js" defer></script>
|
||||||
<script src="/pos/static/js/dashboard.js" defer></script>
|
<script src="/pos/static/js/dashboard.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -154,6 +154,7 @@
|
|||||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||||
<script src="/pos/static/js/diagrams.js" defer></script>
|
<script src="/pos/static/js/diagrams.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -308,6 +308,7 @@
|
|||||||
<script src="/pos/static/js/fleet.js" defer></script>
|
<script src="/pos/static/js/fleet.js" defer></script>
|
||||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -819,6 +819,7 @@
|
|||||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1057,6 +1057,7 @@
|
|||||||
<script src="/pos/static/js/invoicing.js" defer></script>
|
<script src="/pos/static/js/invoicing.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -532,6 +532,7 @@
|
|||||||
<script src="/pos/static/js/kiosk.js" defer></script>
|
<script src="/pos/static/js/kiosk.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -560,5 +560,6 @@
|
|||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -323,6 +323,7 @@
|
|||||||
<script src="/pos/static/js/reports.js" defer></script>
|
<script src="/pos/static/js/reports.js" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|
||||||
<script src="/pos/static/js/chat.js" defer></script>
|
<script src="/pos/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user