Implementar sistema de toast con detección de PWA
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 27s

Cambios:
- Crear componente Toast.vue con soporte para posiciones (top/bottom, left/center/right)
- Crear composable useToast.js para manejar notificaciones
- Integrar sistema de toast en App.vue
- Implementar detección de PWA:
  * Detecta si el usuario está en modo standalone (PWA instalada)
  * Si puede instalar, muestra toast con botón de instalación
  * Si ya está instalada pero no se usa, sugiere abrir en app
- Toast persistente hasta que el usuario interactúe
- Soporte para tema claro/oscuro
- Animaciones suaves y diseño moderno
- Responsive para móviles

El sistema permite mostrar toasts de tipo: success, error, warning, info, pwa
con opciones de posición, duración, acciones personalizadas y modo persistente.
This commit is contained in:
2025-10-17 04:26:16 -06:00
parent dc2c373a4f
commit cba732fbca
3 changed files with 426 additions and 0 deletions

View File

@@ -156,6 +156,9 @@
</div>
</div>
</Modal>
<!-- Toast System -->
<Toast position="top-center" />
</template>
<script setup>
@@ -168,6 +171,12 @@ import UserForm from './components/UserForm.vue';
import RawDbViewer from './components/RawDbViewer.vue';
import VlanForm from './components/VlanForm.vue';
import DeviceForm from './components/DeviceForm.vue';
import Toast from './components/Toast.vue';
import { createToastSystem, useToast } from './composables/useToast.js';
// Initialize toast system
createToastSystem();
const { toast } = useToast();
const users = ref([]);
const requests = ref([]);
@@ -292,8 +301,74 @@ onMounted(async () => {
await fetchRequests();
setupSse();
applyTheme();
checkPWAStatus();
});
// PWA Detection and Toast
function checkPWAStatus() {
// Don't show if already dismissed
if (localStorage.getItem('pwa_toast_dismissed')) return;
// Check if running in standalone mode (PWA installed and active)
const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true;
// If NOT in standalone mode, user is in browser
if (!isStandalone) {
// Check if PWA can be installed
let deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
// Show install prompt
setTimeout(() => {
toast.pwa('Instala RADIUS Nucleo como aplicación para una mejor experiencia', {
title: '📱 Instalar Aplicación',
position: 'top-center',
duration: 0, // Persistent
persistent: true,
action: {
label: 'Instalar',
handler: async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
localStorage.setItem('pwa_toast_dismissed', 'true');
}
deferredPrompt = null;
}
}
}
});
}, 2000);
});
// If already installed but not in standalone, prompt to open in app
if ('getInstalledRelatedApps' in navigator) {
navigator.getInstalledRelatedApps().then((apps) => {
if (apps.length > 0) {
setTimeout(() => {
toast.pwa('Abre RADIUS Nucleo en la aplicación para una mejor experiencia', {
title: '📱 Abrir en App',
position: 'top-center',
duration: 10000,
action: {
label: 'Entendido',
handler: () => {
localStorage.setItem('pwa_toast_dismissed', 'true');
}
}
});
}, 2000);
}
});
}
}
}
const filteredRequests = computed(() => {
return requests.value.filter(ev => {
if (eventFilters.type && ev.type !== eventFilters.type) return false;