240 lines
5.7 KiB
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>
|