monacousa-portal/components/PWAInstallBanner.vue

240 lines
5.7 KiB
Vue

<template>
<v-card
v-if="showBanner"
class="pwa-install-banner"
elevation="8"
color="primary"
variant="elevated"
>
<v-card-text class="pa-4">
<v-row align="center" no-gutters>
<v-col cols="auto" class="mr-3">
<v-avatar size="48" color="white">
<v-img src="/icon-192x192.png" alt="MonacoUSA Portal" />
</v-avatar>
</v-col>
<v-col>
<div class="text-white">
<div class="text-subtitle-1 font-weight-bold mb-1">
Install MonacoUSA Portal
</div>
<div class="text-body-2 text-grey-lighten-2">
{{ installMessage }}
</div>
</div>
</v-col>
<v-col cols="auto">
<v-btn
v-if="canInstall"
@click="installPWA"
color="white"
variant="elevated"
size="small"
class="mr-2"
:loading="installing"
>
<v-icon start>mdi-download</v-icon>
Install
</v-btn>
<v-btn
@click="dismissBanner"
color="white"
variant="text"
size="small"
icon
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script setup lang="ts">
interface BeforeInstallPromptEvent extends Event {
prompt(): Promise<void>;
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
}
// Reactive state
const showBanner = ref(false);
const canInstall = ref(false);
const installing = ref(false);
const installMessage = ref('Add to your home screen for quick access');
let deferredPrompt: BeforeInstallPromptEvent | null = null;
// Device detection
const isIOS = computed(() => {
if (process.client) {
return /iPad|iPhone|iPod/.test(navigator.userAgent);
}
return false;
});
const isAndroid = computed(() => {
if (process.client) {
return /Android/.test(navigator.userAgent);
}
return false;
});
const isStandalone = computed(() => {
if (process.client) {
return window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true;
}
return false;
});
// Install messages based on platform
const getInstallMessage = () => {
if (isIOS.value) {
return 'Tap Share → Add to Home Screen to install';
} else if (isAndroid.value) {
return 'Add to your home screen for quick access';
} else {
return 'Install this app for a better experience';
}
};
// PWA installation logic
const installPWA = async () => {
if (!deferredPrompt) return;
installing.value = true;
try {
// Show the install prompt
await deferredPrompt.prompt();
// Wait for the user to respond to the prompt
const { outcome } = await deferredPrompt.userChoice;
console.log(`PWA install prompt outcome: ${outcome}`);
if (outcome === 'accepted') {
console.log('✅ PWA installation accepted');
showBanner.value = false;
localStorage.setItem('pwa-install-dismissed', 'true');
}
// Clear the deferredPrompt
deferredPrompt = null;
canInstall.value = false;
} catch (error) {
console.error('❌ PWA installation error:', error);
} finally {
installing.value = false;
}
};
const dismissBanner = () => {
showBanner.value = false;
localStorage.setItem('pwa-install-dismissed', 'true');
localStorage.setItem('pwa-install-dismissed-date', new Date().toISOString());
};
const shouldShowBanner = () => {
// Don't show if already dismissed recently (within 7 days)
const dismissedDate = localStorage.getItem('pwa-install-dismissed-date');
if (dismissedDate) {
const daysSinceDismissed = (Date.now() - new Date(dismissedDate).getTime()) / (1000 * 60 * 60 * 24);
if (daysSinceDismissed < 7) {
return false;
}
}
// Don't show if permanently dismissed
if (localStorage.getItem('pwa-install-dismissed') === 'true' && !dismissedDate) {
return false;
}
// Don't show if already installed
if (isStandalone.value) {
return false;
}
return true;
};
// Setup event listeners
onMounted(() => {
if (!process.client) return;
installMessage.value = getInstallMessage();
// Listen for the beforeinstallprompt event
window.addEventListener('beforeinstallprompt', (e: Event) => {
console.log('🔔 PWA install prompt available');
// Prevent the mini-infobar from appearing on mobile
e.preventDefault();
// Save the event so it can be triggered later
deferredPrompt = e as BeforeInstallPromptEvent;
canInstall.value = true;
// Show banner if conditions are met
if (shouldShowBanner()) {
showBanner.value = true;
}
});
// Listen for successful installation
window.addEventListener('appinstalled', () => {
console.log('✅ PWA was installed successfully');
showBanner.value = false;
deferredPrompt = null;
canInstall.value = false;
});
// For iOS devices, show banner if not installed and not dismissed
if (isIOS.value && shouldShowBanner()) {
showBanner.value = true;
}
});
</script>
<style scoped>
.pwa-install-banner {
position: fixed;
bottom: 20px;
left: 20px;
right: 20px;
z-index: 1000;
max-width: 400px;
margin: 0 auto;
border-radius: 12px !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
}
@media (max-width: 600px) {
.pwa-install-banner {
left: 16px;
right: 16px;
bottom: 16px;
}
}
/* Animation */
.pwa-install-banner {
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
transform: translateY(100px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>