+
diff --git a/app/components/InstallPrompt.vue b/app/components/InstallPrompt.vue
new file mode 100644
index 0000000..8f4679b
--- /dev/null
+++ b/app/components/InstallPrompt.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+

+
+
+
+
+
+ Install Pantry
+
+
+ Install this app for quick access and offline use
+
+
+
+
+
+
+
+
+ Install
+
+
+
+ Not now
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/Settings/AppSettings.vue b/app/components/Settings/AppSettings.vue
new file mode 100644
index 0000000..fbece8d
--- /dev/null
+++ b/app/components/Settings/AppSettings.vue
@@ -0,0 +1,190 @@
+
+
+
+
+
App Installation
+
+
+
+
+
+
+ App is installed
+
+
+ You're using Pantry as an installed app with offline support.
+
+
+
+
+
+
+
+ Install Pantry as an app for quick access and offline use.
+
+
+
+
+
+ Install App
+
+
+
+
+
+
+
+
+
+ Running as standalone app
+
+
+ You're already using Pantry in standalone mode.
+
+
+
+
+
+
+ Installation is not available yet. Try one of these options:
+
+
+
+
+
+
+
+
iOS (Safari)
+
+ - Tap the Share button
+ - Scroll down and tap "Add to Home Screen"
+ - Tap "Add" to confirm
+
+
+
+
+
+
+
+
+
+
+
Android (Chrome)
+
+ - Tap the menu (⋮) button
+ - Tap "Add to Home screen" or "Install app"
+ - Tap "Install" to confirm
+
+
+
+
+
+
+
+
+
+
+
App Features
+
+
+
+ Works offline with cached data
+
+
+
+ Quick access from home screen
+
+
+
+ Full-screen experience
+
+
+
+ Automatic updates
+
+
+
+
+
+
+
Storage Usage
+
+
+ Used
+ {{ formatBytes(storageInfo.usage) }}
+
+
+ Available
+ {{ formatBytes(storageInfo.quota) }}
+
+
+
+
+
+
+
+
+
diff --git a/app/composables/usePWAInstall.ts b/app/composables/usePWAInstall.ts
new file mode 100644
index 0000000..10b50dc
--- /dev/null
+++ b/app/composables/usePWAInstall.ts
@@ -0,0 +1,93 @@
+/**
+ * Composable to handle PWA installation
+ *
+ * Usage:
+ * const { canInstall, isInstalled, promptInstall, dismissInstall } = usePWAInstall()
+ */
+export function usePWAInstall() {
+ const canInstall = ref(false)
+ const isInstalled = ref(false)
+ const deferredPrompt = ref
(null)
+
+ if (process.client) {
+ // Check if already installed
+ if (window.matchMedia('(display-mode: standalone)').matches) {
+ isInstalled.value = true
+ }
+
+ // Listen for beforeinstallprompt event
+ window.addEventListener('beforeinstallprompt', (e) => {
+ // Prevent the mini-infobar from appearing on mobile
+ e.preventDefault()
+
+ // Stash the event so it can be triggered later
+ deferredPrompt.value = e
+ canInstall.value = true
+ })
+
+ // Listen for appinstalled event
+ window.addEventListener('appinstalled', () => {
+ isInstalled.value = true
+ canInstall.value = false
+ deferredPrompt.value = null
+ })
+ }
+
+ async function promptInstall() {
+ if (!deferredPrompt.value) {
+ return { outcome: 'not-available' }
+ }
+
+ // Show the install prompt
+ deferredPrompt.value.prompt()
+
+ // Wait for the user to respond to the prompt
+ const { outcome } = await deferredPrompt.value.userChoice
+
+ // Clear the deferredPrompt
+ deferredPrompt.value = null
+
+ if (outcome === 'accepted') {
+ canInstall.value = false
+ }
+
+ return { outcome }
+ }
+
+ function dismissInstall() {
+ canInstall.value = false
+ deferredPrompt.value = null
+
+ // Remember dismissal for 7 days
+ if (process.client) {
+ localStorage.setItem('pwa-install-dismissed', Date.now().toString())
+ }
+ }
+
+ function shouldShowPrompt() {
+ if (!canInstall.value || isInstalled.value) {
+ return false
+ }
+
+ if (process.client) {
+ const dismissed = localStorage.getItem('pwa-install-dismissed')
+ if (dismissed) {
+ const dismissedTime = parseInt(dismissed)
+ const sevenDays = 7 * 24 * 60 * 60 * 1000
+ if (Date.now() - dismissedTime < sevenDays) {
+ return false
+ }
+ }
+ }
+
+ return true
+ }
+
+ return {
+ canInstall: readonly(canInstall),
+ isInstalled: readonly(isInstalled),
+ promptInstall,
+ dismissInstall,
+ shouldShowPrompt
+ }
+}
diff --git a/app/pages/settings.vue b/app/pages/settings.vue
index 054f95e..9af5c3f 100644
--- a/app/pages/settings.vue
+++ b/app/pages/settings.vue
@@ -18,6 +18,10 @@