fix: PWA install button UX improvements and CSP compliance

Changes:
- Add user feedback when PWA installation unavailable
- Remove all inline event handlers (onclick=) for CSP compliance
- Show helpful messages: "Already Installed" vs "Browser Not Supported"
- Auto-dismiss unavailable message after 8 seconds
- All buttons now use addEventListener (CSP compliant)

Fixes: Non-responsive install button when prompt unavailable
Security: Full CSP compliance - no inline event handlers
This commit is contained in:
TheFlow 2025-10-15 08:39:47 +13:00
parent a15e67fb36
commit 72251385cb

View file

@ -134,14 +134,14 @@ class VersionManager {
</div>
<div class="flex gap-3">
${versionInfo.forceUpdate ? `
<button onclick="window.versionManager.applyUpdate()" class="bg-white text-blue-600 px-6 py-2 rounded-lg font-semibold hover:bg-blue-50 transition">
<button id="update-now-btn" class="bg-white text-blue-600 px-6 py-2 rounded-lg font-semibold hover:bg-blue-50 transition">
Update Now
</button>
` : `
<button onclick="window.versionManager.dismissUpdate()" class="text-white hover:text-blue-100 transition px-3">
<button id="update-later-btn" class="text-white hover:text-blue-100 transition px-3">
Later
</button>
<button onclick="window.versionManager.applyUpdate()" class="bg-white text-blue-600 px-6 py-2 rounded-lg font-semibold hover:bg-blue-50 transition">
<button id="update-reload-btn" class="bg-white text-blue-600 px-6 py-2 rounded-lg font-semibold hover:bg-blue-50 transition">
Reload
</button>
`}
@ -151,6 +151,21 @@ class VersionManager {
document.body.appendChild(notification);
// Add event listeners (CSP compliant)
const updateNowBtn = document.getElementById('update-now-btn');
const updateLaterBtn = document.getElementById('update-later-btn');
const updateReloadBtn = document.getElementById('update-reload-btn');
if (updateNowBtn) {
updateNowBtn.addEventListener('click', () => this.applyUpdate());
}
if (updateLaterBtn) {
updateLaterBtn.addEventListener('click', () => this.dismissUpdate());
}
if (updateReloadBtn) {
updateReloadBtn.addEventListener('click', () => this.applyUpdate());
}
// Animate in
setTimeout(() => {
notification.classList.remove('translate-y-full');
@ -250,10 +265,10 @@ class VersionManager {
</div>
</div>
<div class="flex gap-3">
<button onclick="window.versionManager.dismissInstallPrompt()" class="text-white hover:text-purple-100 transition px-3">
<button id="dismiss-install-btn" class="text-white hover:text-purple-100 transition px-3">
Not Now
</button>
<button onclick="window.versionManager.installApp()" class="bg-white text-purple-600 px-6 py-2 rounded-lg font-semibold hover:bg-purple-50 transition">
<button id="install-app-btn" class="bg-white text-purple-600 px-6 py-2 rounded-lg font-semibold hover:bg-purple-50 transition">
Install
</button>
</div>
@ -262,6 +277,17 @@ class VersionManager {
document.body.appendChild(prompt);
// Add event listeners (CSP compliant)
const dismissBtn = document.getElementById('dismiss-install-btn');
const installBtn = document.getElementById('install-app-btn');
if (dismissBtn) {
dismissBtn.addEventListener('click', () => this.dismissInstallPrompt());
}
if (installBtn) {
installBtn.addEventListener('click', () => this.installApp());
}
// Animate in
setTimeout(() => {
prompt.classList.remove('translate-y-full');
@ -270,6 +296,8 @@ class VersionManager {
async installApp() {
if (!this.deferredInstallPrompt) {
// Show helpful feedback if installation isn't available
this.showInstallUnavailableMessage();
return;
}
@ -287,6 +315,88 @@ class VersionManager {
this.dismissInstallPrompt();
}
showInstallUnavailableMessage() {
// Check if app is already installed
const isInstalled = window.matchMedia('(display-mode: standalone)').matches;
// Don't show message if it already exists
if (document.getElementById('tractatus-install-unavailable')) {
return;
}
const message = document.createElement('div');
message.id = 'tractatus-install-unavailable';
message.className = 'fixed bottom-0 left-0 right-0 bg-gray-800 text-white px-4 py-3 shadow-lg z-50 transform transition-transform duration-300 translate-y-full';
if (isInstalled) {
message.innerHTML = `
<div class="max-w-7xl mx-auto flex items-center justify-between flex-wrap gap-4">
<div class="flex items-start flex-1">
<svg class="w-6 h-6 mr-3 flex-shrink-0 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div>
<p class="font-semibold">Already Installed</p>
<p class="text-sm text-gray-300">
Tractatus is already installed on your device. You're using it right now!
</p>
</div>
</div>
<button id="dismiss-unavailable-btn" class="text-white hover:text-gray-300 transition px-3">
Okay
</button>
</div>
`;
} else {
message.innerHTML = `
<div class="max-w-7xl mx-auto flex items-center justify-between flex-wrap gap-4">
<div class="flex items-start flex-1">
<svg class="w-6 h-6 mr-3 flex-shrink-0 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div>
<p class="font-semibold">Installation Not Available</p>
<p class="text-sm text-gray-300">
Your browser doesn't currently support app installation. Try using Chrome, Edge, or Safari on a supported device.
</p>
</div>
</div>
<button id="dismiss-unavailable-btn" class="text-white hover:text-gray-300 transition px-3">
Okay
</button>
</div>
`;
}
document.body.appendChild(message);
// Add event listener for dismiss button
const dismissBtn = document.getElementById('dismiss-unavailable-btn');
if (dismissBtn) {
dismissBtn.addEventListener('click', () => {
message.classList.add('translate-y-full');
setTimeout(() => {
message.remove();
}, 300);
});
}
// Animate in
setTimeout(() => {
message.classList.remove('translate-y-full');
}, 100);
// Auto-dismiss after 8 seconds
setTimeout(() => {
if (message.parentElement) {
message.classList.add('translate-y-full');
setTimeout(() => {
message.remove();
}, 300);
}
}, 8000);
}
dismissInstallPrompt() {
const prompt = document.getElementById('tractatus-install-prompt');
if (prompt) {