forked from jsnbuchanan/crowd-funder-for-time-pwa
Enable full PWA install experience in all web modes
- Add PWAInstallPrompt component for custom install UI and event handling - Register PWAInstallPrompt in App.vue for global visibility - Enable PWA features and install prompt in dev, test, and prod (vite.config.web.mts) - Update service worker registration to work in all environments - Update docs/build-web-script-integration.md with PWA install guidance and visual cues - Add scripts/build-web.sh for unified web build/dev workflow PWA is now installable and testable in all web environments, with clear user prompts and desktop support.
This commit is contained in:
181
src/components/PWAInstallPrompt.vue
Normal file
181
src/components/PWAInstallPrompt.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<transition
|
||||
enter-active-class="transform ease-out duration-300 transition"
|
||||
enter-from-class="translate-y-2 opacity-0 sm:translate-y-4"
|
||||
enter-to-class="translate-y-0 opacity-100 sm:translate-y-0"
|
||||
leave-active-class="transition ease-in duration-500"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="showInstallPrompt"
|
||||
class="fixed z-[100] top-4 right-4 max-w-sm bg-white rounded-lg shadow-lg border border-gray-200"
|
||||
>
|
||||
<div class="p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<font-awesome
|
||||
icon="download"
|
||||
class="h-6 w-6 text-blue-600"
|
||||
title="Install App"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<h3 class="text-sm font-medium text-gray-900">
|
||||
Install Time Safari
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Install this app on your device for a better experience
|
||||
</p>
|
||||
<div class="mt-4 flex space-x-3">
|
||||
<button
|
||||
@click="installPWA"
|
||||
class="flex-1 bg-blue-600 text-white text-sm font-medium px-3 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
>
|
||||
Install
|
||||
</button>
|
||||
<button
|
||||
@click="dismissPrompt"
|
||||
class="flex-1 bg-gray-100 text-gray-700 text-sm font-medium px-3 py-2 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
|
||||
>
|
||||
Later
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 flex-shrink-0">
|
||||
<button
|
||||
@click="dismissPrompt"
|
||||
class="text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
>
|
||||
<font-awesome icon="times" class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { logger } from "@/utils/logger";
|
||||
|
||||
interface BeforeInstallPromptEvent extends Event {
|
||||
prompt(): Promise<void>;
|
||||
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
|
||||
}
|
||||
|
||||
@Component({ name: "PWAInstallPrompt" })
|
||||
export default class PWAInstallPrompt extends Vue {
|
||||
private showInstallPrompt = false;
|
||||
private deferredPrompt: BeforeInstallPromptEvent | null = null;
|
||||
private dismissed = false;
|
||||
|
||||
mounted() {
|
||||
this.setupInstallPrompt();
|
||||
}
|
||||
|
||||
private setupInstallPrompt() {
|
||||
// Only show install prompt if PWA is enabled
|
||||
if (process.env.VITE_PWA_ENABLED !== "true") {
|
||||
logger.debug("[PWA] Install prompt disabled - PWA not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already installed
|
||||
if (this.isPWAInstalled()) {
|
||||
logger.debug("[PWA] Install prompt disabled - PWA already installed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen for the beforeinstallprompt event
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
logger.debug("[PWA] beforeinstallprompt event fired");
|
||||
|
||||
// Prevent the mini-infobar from appearing on mobile
|
||||
e.preventDefault();
|
||||
|
||||
// Stash the event so it can be triggered later
|
||||
this.deferredPrompt = e as BeforeInstallPromptEvent;
|
||||
|
||||
// Show the install prompt
|
||||
this.showInstallPrompt = true;
|
||||
});
|
||||
|
||||
// Listen for successful installation
|
||||
window.addEventListener('appinstalled', () => {
|
||||
logger.debug("[PWA] App installed successfully");
|
||||
this.showInstallPrompt = false;
|
||||
this.deferredPrompt = null;
|
||||
|
||||
// Show success notification
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "App Installed!",
|
||||
text: "Time Safari has been installed on your device.",
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
private isPWAInstalled(): boolean {
|
||||
// Check if running in standalone mode (installed PWA)
|
||||
if (window.matchMedia('(display-mode: standalone)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if running in fullscreen mode (installed PWA)
|
||||
if (window.matchMedia('(display-mode: fullscreen)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if running in minimal-ui mode (installed PWA)
|
||||
if (window.matchMedia('(display-mode: minimal-ui)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async installPWA() {
|
||||
if (!this.deferredPrompt) {
|
||||
logger.warn("[PWA] No install prompt available");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Show the install prompt
|
||||
this.deferredPrompt.prompt();
|
||||
|
||||
// Wait for the user to respond to the prompt
|
||||
const { outcome } = await this.deferredPrompt.userChoice;
|
||||
|
||||
logger.debug(`[PWA] User response to install prompt: ${outcome}`);
|
||||
|
||||
if (outcome === 'accepted') {
|
||||
logger.debug("[PWA] User accepted the install prompt");
|
||||
this.showInstallPrompt = false;
|
||||
} else {
|
||||
logger.debug("[PWA] User dismissed the install prompt");
|
||||
this.dismissed = true;
|
||||
this.showInstallPrompt = false;
|
||||
}
|
||||
|
||||
// Clear the deferred prompt
|
||||
this.deferredPrompt = null;
|
||||
|
||||
} catch (error) {
|
||||
logger.error("[PWA] Error during install prompt:", error);
|
||||
this.showInstallPrompt = false;
|
||||
}
|
||||
}
|
||||
|
||||
private dismissPrompt() {
|
||||
this.dismissed = true;
|
||||
this.showInstallPrompt = false;
|
||||
|
||||
// Don't show again for this session
|
||||
sessionStorage.setItem('pwa-install-dismissed', 'true');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user