feat: add service worker and native PWA install prompt for Android
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Adds minimal service worker for PWA installability. On Android Chrome, the smart banner and install page now trigger the native install dialog directly instead of showing manual instructions. iOS still shows step-by-step guide (Apple provides no install API). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e8d04c83d9
commit
5ac24009dd
@ -1564,6 +1564,15 @@ def pwa_install():
|
|||||||
return render_template('pwa_install.html')
|
return render_template('pwa_install.html')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/sw.js')
|
||||||
|
def service_worker():
|
||||||
|
"""Service worker served from root scope for PWA installability."""
|
||||||
|
return current_app.send_static_file('sw.js'), 200, {
|
||||||
|
'Content-Type': 'application/javascript',
|
||||||
|
'Service-Worker-Allowed': '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/robots.txt')
|
@bp.route('/robots.txt')
|
||||||
def robots_txt():
|
def robots_txt():
|
||||||
"""Robots.txt for search engine crawlers."""
|
"""Robots.txt for search engine crawlers."""
|
||||||
|
|||||||
14
static/sw.js
Normal file
14
static/sw.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Norda Biznes Partner — minimal service worker for PWA installability
|
||||||
|
// No offline caching — just enough for Chrome to offer install prompt
|
||||||
|
|
||||||
|
self.addEventListener('install', function() {
|
||||||
|
self.skipWaiting();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', function(event) {
|
||||||
|
event.waitUntil(self.clients.claim());
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', function(event) {
|
||||||
|
event.respondWith(fetch(event.request));
|
||||||
|
});
|
||||||
@ -2254,6 +2254,49 @@
|
|||||||
setInterval(updateNotificationBadgeFromAPI, 60000);
|
setInterval(updateNotificationBadgeFromAPI, 60000);
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
// Register Service Worker for PWA installability
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register('/sw.js').catch(function() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// PWA install prompt — capture beforeinstallprompt for Android
|
||||||
|
var deferredInstallPrompt = null;
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
deferredInstallPrompt = e;
|
||||||
|
|
||||||
|
// On Android we can install directly — change banner button behavior
|
||||||
|
var actionBtn = document.querySelector('.pwa-smart-banner-action');
|
||||||
|
if (actionBtn) {
|
||||||
|
actionBtn.href = '#';
|
||||||
|
actionBtn.addEventListener('click', function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
triggerPwaInstall();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('appinstalled', function() {
|
||||||
|
deferredInstallPrompt = null;
|
||||||
|
dismissPwaBanner();
|
||||||
|
});
|
||||||
|
|
||||||
|
function triggerPwaInstall() {
|
||||||
|
if (deferredInstallPrompt) {
|
||||||
|
deferredInstallPrompt.prompt();
|
||||||
|
deferredInstallPrompt.userChoice.then(function(result) {
|
||||||
|
if (result.outcome === 'accepted') {
|
||||||
|
dismissPwaBanner();
|
||||||
|
}
|
||||||
|
deferredInstallPrompt = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback — go to instructions page (iOS or prompt unavailable)
|
||||||
|
window.location.href = '{{ url_for("public.pwa_install") }}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PWA Smart Banner logic
|
// PWA Smart Banner logic
|
||||||
(function() {
|
(function() {
|
||||||
var banner = document.getElementById('pwaSmartBanner');
|
var banner = document.getElementById('pwaSmartBanner');
|
||||||
|
|||||||
@ -318,6 +318,39 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Direct install button for Android */
|
||||||
|
.pwa-direct-install {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
background: var(--surface);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border: 2px solid var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-direct-install-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 28px;
|
||||||
|
padding: 16px 32px;
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
cursor: pointer;
|
||||||
|
animation: pulseGlow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-direct-install-hint {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile-only: show steps, hide desktop msg */
|
/* Mobile-only: show steps, hide desktop msg */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.pwa-desktop-msg { display: none !important; }
|
.pwa-desktop-msg { display: none !important; }
|
||||||
@ -435,6 +468,17 @@
|
|||||||
|
|
||||||
<!-- Android steps -->
|
<!-- Android steps -->
|
||||||
<div class="pwa-steps" id="pwa-android">
|
<div class="pwa-steps" id="pwa-android">
|
||||||
|
<!-- Direct install button (shown only when beforeinstallprompt is available) -->
|
||||||
|
<div class="pwa-direct-install" id="pwaDirectInstall" style="display: none;">
|
||||||
|
<button class="pwa-direct-install-btn" onclick="triggerPwaInstall()">
|
||||||
|
<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||||
|
Zainstaluj teraz
|
||||||
|
</button>
|
||||||
|
<p class="pwa-direct-install-hint">Kliknij i potwierdź — to wszystko!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="pwa-manual-fallback-label" id="pwaManualLabel" style="display: none; text-align: center; color: var(--text-secondary); font-size: var(--font-size-sm); margin-bottom: var(--spacing-md);">Jeśli przycisk nie działa, wykonaj poniższe kroki:</p>
|
||||||
|
|
||||||
<div class="pwa-step">
|
<div class="pwa-step">
|
||||||
<div class="pwa-step-number">1</div>
|
<div class="pwa-step-number">1</div>
|
||||||
<div class="pwa-step-content">
|
<div class="pwa-step-content">
|
||||||
@ -526,4 +570,13 @@
|
|||||||
panel.classList.toggle('active', panel.id === 'pwa-' + platform);
|
panel.classList.toggle('active', panel.id === 'pwa-' + platform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show direct install button if beforeinstallprompt available (Android)
|
||||||
|
// deferredInstallPrompt and triggerPwaInstall are defined in base.html
|
||||||
|
window.addEventListener('beforeinstallprompt', function() {
|
||||||
|
var directBtn = document.getElementById('pwaDirectInstall');
|
||||||
|
var manualLabel = document.getElementById('pwaManualLabel');
|
||||||
|
if (directBtn) directBtn.style.display = 'block';
|
||||||
|
if (manualLabel) manualLabel.style.display = 'block';
|
||||||
|
});
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user