feat: add PWA install prompt UI (#35)
Some checks failed
Deploy to Coolify / Code Quality (pull_request) Has been cancelled
Deploy to Coolify / Run Tests (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Development (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Production (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Test (pull_request) Has been cancelled
Pull Request Checks / Validate PR (pull_request) Has been cancelled
Some checks failed
Deploy to Coolify / Code Quality (pull_request) Has been cancelled
Deploy to Coolify / Run Tests (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Development (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Production (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Test (pull_request) Has been cancelled
Pull Request Checks / Validate PR (pull_request) Has been cancelled
- Create usePWAInstall composable for install management - Add InstallPrompt banner component with auto-show after 3s - Add App Settings tab in settings page - Show install button with loading state - Display installation status and instructions - Handle dismissal with 7-day cooldown - Add iOS/Android installation guides - Show PWA features list - Display storage usage with visual progress - Auto-hide prompt after successful install Features: - Automatic install prompt after 3 seconds - Manual install from settings - Platform-specific instructions - Smart dismissal tracking - Storage info visualization Closes #35
This commit is contained in:
104
app/components/InstallPrompt.vue
Normal file
104
app/components/InstallPrompt.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<Transition
|
||||
enter-active-class="transition ease-out duration-300"
|
||||
enter-from-class="opacity-0 translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in duration-200"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-if="showPrompt"
|
||||
class="fixed bottom-4 left-4 right-4 md:left-auto md:right-4 md:max-w-md z-50"
|
||||
>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- App Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<img
|
||||
src="/icon-192x192.png"
|
||||
alt="Pantry icon"
|
||||
class="w-16 h-16 rounded-lg shadow-md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-1">
|
||||
Install Pantry
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
Install this app for quick access and offline use
|
||||
</p>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-2">
|
||||
<UButton
|
||||
color="emerald"
|
||||
size="sm"
|
||||
@click="install"
|
||||
:loading="installing"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-heroicons-arrow-down-tray" />
|
||||
</template>
|
||||
Install
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="dismiss"
|
||||
>
|
||||
Not now
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Close button -->
|
||||
<button
|
||||
@click="dismiss"
|
||||
class="flex-shrink-0 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition"
|
||||
>
|
||||
<UIcon name="i-heroicons-x-mark" class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { canInstall, promptInstall, dismissInstall, shouldShowPrompt } = usePWAInstall()
|
||||
const installing = ref(false)
|
||||
const showPrompt = ref(false)
|
||||
|
||||
// Show prompt after a delay if conditions are met
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (shouldShowPrompt()) {
|
||||
showPrompt.value = true
|
||||
}
|
||||
}, 3000) // Wait 3 seconds after page load
|
||||
})
|
||||
|
||||
async function install() {
|
||||
installing.value = true
|
||||
try {
|
||||
const { outcome } = await promptInstall()
|
||||
if (outcome === 'accepted') {
|
||||
showPrompt.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Install failed:', error)
|
||||
} finally {
|
||||
installing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
dismissInstall()
|
||||
showPrompt.value = false
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user