feat: Enhance admin announcement functionality and introduce PWA installation prompt

- Update sendAdminAnnouncement to support multiple configured admin pubkeys and improve console output for clarity.
- Add new script send_posts_from_configured_admins.js to send posts from multiple admins for testing purposes.
- Implement PWAInstallPrompt component to guide users in installing the app, including handling installation status and browser compatibility.
- Integrate PWAInstallPrompt into Home.vue for improved user experience.
This commit is contained in:
padreug 2025-07-07 01:52:39 +02:00
parent 8a9ffc5918
commit 0c90af01b1
4 changed files with 399 additions and 7 deletions

View file

@ -0,0 +1,283 @@
<template>
<div v-if="showInstallPrompt" class="bg-primary/10 border border-primary/20 rounded-lg p-4 mb-4">
<div class="flex items-start gap-3">
<Download class="h-5 w-5 text-primary mt-0.5" />
<div class="flex-1">
<h3 class="font-semibold text-sm mb-1">
Install Ario App
</h3>
<p class="text-sm text-muted-foreground mb-3">
Install Ario as an app on your device for the best experience with offline support and push notifications.
</p>
<div class="flex gap-2">
<Button
v-if="browserInfo !== 'Firefox' && deferredPrompt"
@click="installPWA"
:disabled="isInstalling"
size="sm"
class="text-xs"
>
<Download class="h-3 w-3 mr-1" />
{{ isInstalling ? 'Installing...' : 'Install App' }}
</Button>
<Button
@click="dismissPrompt"
variant="outline"
size="sm"
class="text-xs"
>
Not Now
</Button>
</div>
</div>
<Button
@click="dismissPrompt"
variant="ghost"
size="icon"
class="h-6 w-6"
>
<X class="h-3 w-3" />
</Button>
</div>
</div>
<!-- Status messages -->
<div v-if="installStatus === 'installed' && !(isStandaloneMode || hasPwaParam)" class="bg-green-50 dark:bg-green-950 border border-green-200 dark:border-green-800 rounded-lg p-3 mb-4">
<div class="flex items-center gap-2">
<CheckCircle class="h-4 w-4 text-green-600 dark:text-green-400" />
<span class="text-sm text-green-800 dark:text-green-200">
App installed successfully! Look for Ario on your home screen.
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Button } from '@/components/ui/button'
import { Download, X, CheckCircle } from 'lucide-vue-next'
import { toast } from 'vue-sonner'
const showInstallPrompt = ref(false)
const isInstalling = ref(false)
const installStatus = ref<'unknown' | 'supported' | 'not-supported' | 'installed' | 'dismissed'>('unknown')
const showDebugInfo = ref(false)
const browserInfo = ref('')
// Track the deferred prompt event
let deferredPrompt: any = null
// Computed properties for template
const isStandaloneMode = computed(() => window.matchMedia('(display-mode: standalone)').matches)
const hasPwaParam = computed(() => window.location.search.includes('pwa=true'))
const isServiceWorkerRegistered = ref(false)
const isManifestValid = ref(false)
const props = defineProps<{
autoShow?: boolean
}>()
onMounted(async () => {
// Detect browser info
browserInfo.value = navigator.userAgent.includes('Firefox') ? 'Firefox' :
navigator.userAgent.includes('Chrome') ? 'Chrome' :
navigator.userAgent.includes('Safari') ? 'Safari' : 'Unknown'
// Show debug info in development or when debugging
showDebugInfo.value = window.location.hostname === 'localhost' ||
window.location.hostname.includes('192.168') ||
window.location.search.includes('debug=true')
console.log('PWA Install Prompt mounted')
console.log('User Agent:', navigator.userAgent)
console.log('Window location:', window.location.href)
console.log('Display mode:', window.matchMedia('(display-mode: standalone)').matches)
// Check service worker registration
if ('serviceWorker' in navigator) {
try {
const registrations = await navigator.serviceWorker.getRegistrations()
isServiceWorkerRegistered.value = registrations.length > 0
console.log('Service worker registrations:', registrations.length)
} catch (error) {
console.error('Error checking service worker:', error)
}
}
// Check manifest validity
try {
const manifestResponse = await fetch('/manifest.webmanifest')
if (manifestResponse.ok) {
const manifest = await manifestResponse.json()
console.log('Manifest:', manifest)
isManifestValid.value = !!(manifest.name && manifest.start_url && manifest.display && manifest.icons)
}
} catch (error) {
console.error('Error checking manifest:', error)
}
await checkInstallStatus()
// Listen for the beforeinstallprompt event
window.addEventListener('beforeinstallprompt', (e) => {
console.log('✅ beforeinstallprompt event fired')
console.log('Event details:', e)
// Prevent the default mini-infobar from appearing
e.preventDefault()
// Store the event so we can trigger it later
deferredPrompt = e
if (props.autoShow && !isDismissed()) {
showInstallPrompt.value = true
console.log('Auto-showing install prompt')
}
installStatus.value = 'supported'
})
// Listen for successful installation
window.addEventListener('appinstalled', () => {
console.log('✅ PWA was installed')
installStatus.value = 'installed'
showInstallPrompt.value = false
deferredPrompt = null
toast.success('App installed successfully!')
})
// Additional debugging
console.log('Service worker support:', 'serviceWorker' in navigator)
console.log('BeforeInstallPromptEvent support:', 'BeforeInstallPromptEvent' in window)
console.log('Is dismissed:', isDismissed())
console.log('Current install status:', installStatus.value)
})
async function checkInstallStatus() {
console.log('🔍 Checking install status...')
// Check if already installed
if (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) {
console.log('✅ Already installed (standalone mode)')
installStatus.value = 'installed'
return
}
// Check if running in PWA mode
if (window.location.search.includes('pwa=true')) {
console.log('✅ Already installed (PWA mode)')
installStatus.value = 'installed'
return
}
// Check if browser supports PWA installation
const hasServiceWorker = 'serviceWorker' in navigator
const hasBeforeInstallPrompt = 'BeforeInstallPromptEvent' in window
const isFirefox = browserInfo.value === 'Firefox'
if (hasServiceWorker && (hasBeforeInstallPrompt || isFirefox)) {
console.log('✅ Browser supports PWA installation')
installStatus.value = 'supported'
if (props.autoShow && !isDismissed()) {
console.log('📅 Scheduling delayed prompt check...')
// Show prompt after a delay if no beforeinstallprompt event
setTimeout(() => {
console.log('⏰ Delayed check - deferredPrompt:', deferredPrompt)
console.log('⏰ Delayed check - installStatus:', installStatus.value)
console.log('⏰ Delayed check - isFirefox:', isFirefox)
if ((!deferredPrompt && installStatus.value === 'supported') || isFirefox) {
console.log('📱 Showing install prompt (delayed)')
showInstallPrompt.value = true
}
}, 3000)
}
} else {
console.log('❌ Browser does not support PWA installation')
console.log('ServiceWorker support:', hasServiceWorker)
console.log('BeforeInstallPromptEvent support:', hasBeforeInstallPrompt)
console.log('Firefox browser:', isFirefox)
installStatus.value = 'not-supported'
}
}
async function installPWA() {
console.log('🚀 Install PWA button clicked')
console.log('deferredPrompt available:', !!deferredPrompt)
console.log('deferredPrompt value:', deferredPrompt)
if (!deferredPrompt) {
console.log('❌ No deferred prompt - showing manual instructions')
// Fallback: Show manual instructions
showManualInstallInstructions()
return
}
try {
console.log('⏳ Starting installation process...')
isInstalling.value = true
// Show the install prompt
console.log('📱 Calling deferredPrompt.prompt()')
deferredPrompt.prompt()
// Wait for the user to respond to the prompt
console.log('⏳ Waiting for user choice...')
const { outcome } = await deferredPrompt.userChoice
console.log(`✅ User response to the install prompt: ${outcome}`)
if (outcome === 'accepted') {
console.log('✅ User accepted installation')
toast.success('Installing app...')
installStatus.value = 'installed'
} else {
console.log('❌ User cancelled installation')
toast.info('Installation cancelled')
}
// Clear the deferred prompt
deferredPrompt = null
showInstallPrompt.value = false
} catch (error) {
console.error('❌ Error during PWA installation:', error)
toast.error('Installation failed. Try using your browser\'s install option.')
showManualInstallInstructions()
} finally {
isInstalling.value = false
console.log('🏁 Installation process finished')
}
}
function showManualInstallInstructions() {
console.log('📋 Showing manual install instructions')
installStatus.value = 'supported'
showInstallPrompt.value = false
}
function dismissPrompt() {
showInstallPrompt.value = false
installStatus.value = 'dismissed'
// Store dismissal in localStorage
localStorage.setItem('pwa-install-dismissed', Date.now().toString())
}
function isDismissed(): boolean {
const dismissed = localStorage.getItem('pwa-install-dismissed')
if (!dismissed) return false
// Show again after 7 days
const dismissedTime = parseInt(dismissed)
const weekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000)
return dismissedTime > weekAgo
}
// Expose methods for parent components
defineExpose({
installPWA,
checkInstallStatus
})
</script>

View file

@ -1,5 +1,6 @@
<template>
<div class="container py-8 space-y-6">
<PWAInstallPrompt auto-show />
<NotificationPermission auto-show />
<NostrFeed feed-type="announcements" />
</div>
@ -8,4 +9,5 @@
<script setup lang="ts">
import NostrFeed from '@/components/nostr/NostrFeed.vue'
import NotificationPermission from '@/components/notifications/NotificationPermission.vue'
import PWAInstallPrompt from '@/components/pwa/PWAInstallPrompt.vue'
</script>