<template> <router-view /> <!-- Messages in the upper-right - https://github.com/emmanuelsw/notiwind --> <NotificationGroup group="alert"> <div class="fixed top-4 right-4 w-full max-w-sm flex flex-col items-start justify-end" > <Notification v-slot="{ notifications, close }" enter="transform ease-out duration-300 transition" enter-from="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-4" enter-to="translate-y-0 opacity-100 sm:translate-x-0" leave="transition ease-in duration-500" leave-from="opacity-100" leave-to="opacity-0" move="transition duration-500" move-delay="delay-300" > <div v-for="notification in notifications" :key="notification.id" class="w-full" role="alert" > <div v-if="notification.type === 'toast'" class="w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-900/90 text-white rounded-lg shadow-md" > <div class="w-full px-4 py-3"> <span class="font-semibold">{{ notification.title }}</span> <p class="text-sm">{{ notification.text }}</p> </div> </div> <div v-if="notification.type === 'info'" class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-100 rounded-lg shadow-md" > <div class="flex items-center justify-center w-12 bg-slate-600 text-slate-100" > <fa icon="circle-info" class="fa-fw fa-xl"></fa> </div> <div class="relative w-full pl-4 pr-8 py-2 text-slate-900"> <span class="font-semibold">{{ notification.title }}</span> <p class="text-sm">{{ notification.text }}</p> <button @click="close(notification.id)" class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600" > <fa icon="xmark" class="fa-fw"></fa> </button> </div> </div> <div v-if="notification.type === 'success'" class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-emerald-100 rounded-lg shadow-md" > <div class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100" > <fa icon="circle-info" class="fa-fw fa-xl"></fa> </div> <div class="relative w-full pl-4 pr-8 py-2 text-emerald-900"> <span class="font-semibold">{{ notification.title }}</span> <p class="text-sm">{{ notification.text }}</p> <button @click="close(notification.id)" class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600" > <fa icon="xmark" class="fa-fw"></fa> </button> </div> </div> <div v-if="notification.type === 'warning'" class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-amber-100 rounded-lg shadow-md" > <div class="flex items-center justify-center w-12 bg-amber-600 text-amber-100" > <fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa> </div> <div class="relative w-full pl-4 pr-8 py-2 text-amber-900"> <span class="font-semibold">{{ notification.title }}</span> <p class="text-sm">{{ notification.text }}</p> <button @click="close(notification.id)" class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600" > <fa icon="xmark" class="fa-fw"></fa> </button> </div> </div> <div v-if="notification.type === 'danger'" class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-rose-100 rounded-lg shadow-md" > <div class="flex items-center justify-center w-12 bg-rose-600 text-rose-100" > <fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa> </div> <div class="relative w-full pl-4 pr-8 py-2 text-rose-900"> <span class="font-semibold">{{ notification.title }}</span> <p class="text-sm">{{ notification.text }}</p> <button @click="close(notification.id)" class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600" > <fa icon="xmark" class="fa-fw"></fa> </button> </div> </div> </div> </Notification> </div> </NotificationGroup> <!-- This "group" of "modal" is the prompt for an answer. Set "type" as follows: "confirm" for yes/no, and "notification" ones: "-permission", "-mute", "-off" --> <NotificationGroup group="modal"> <div class="fixed z-[100] top-0 inset-x-0 w-full"> <Notification v-slot="{ notifications, close }" enter="transform ease-out duration-300 transition" enter-from="translate-y-2 opacity-0 sm:translate-y-4" enter-to="translate-y-0 opacity-100 sm:translate-y-0" leave="transition ease-in duration-500" leave-from="opacity-100" leave-to="opacity-0" move="transition duration-500" move-delay="delay-300" > <!-- see NotificationIface in constants/app.ts --> <div v-for="notification in notifications" :key="notification.id" class="w-full" role="alert" > <!-- Type of "confirm" will post a message. With onYes function, show a "Yes" button to call that function. With onNo function, show a "No" button to call that function, and pass it state of "askAgain" field shown if you set promptToStopAsking. --> <div v-if="notification.type === 'confirm'" class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50" > <div class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg" > <div class="w-full px-6 py-6 text-slate-900 text-center"> <span class="font-semibold text-lg"> {{ notification.title }} </span> <p class="text-sm mb-2">{{ notification.text }}</p> <button v-if="notification.onYes" @click=" notification.onYes(); close(notification.id); " class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2" > Yes{{ notification.yesText ? ", " + notification.yesText : "" }} </button> <button v-if="notification.onNo" @click=" notification.onNo(stopAsking); close(notification.id); stopAsking = false; // reset value " class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2" > No{{ notification.noText ? ", " + notification.noText : "" }} </button> <label v-if="notification.promptToStopAsking && notification.onNo" for="toggleStopAsking" class="flex items-center justify-between cursor-pointer my-4" @click="stopAsking = !stopAsking" > <!-- label --> <span class="ml-2">... and do not ask again.</span> <!-- toggle --> <div class="relative ml-2"> <!-- input --> <input type="checkbox" v-model="stopAsking" name="stopAsking" class="sr-only" /> <!-- line --> <div class="block bg-slate-500 w-14 h-8 rounded-full"></div> <!-- dot --> <div class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition" ></div> </div> </label> <button @click=" notification.onCancel ? notification.onCancel(stopAsking) : null; close(notification.id); stopAsking = false; // reset value " class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md" > {{ notification.onYes ? "Cancel" : "Close" }} </button> </div> </div> </div> <div v-if="notification.type === 'notification-mute'" class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50" > <div class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg" > <div class="w-full px-6 py-6 text-slate-900 text-center"> <p class="text-lg mb-4">Mute app notifications:</p> <button class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2" > For 1 Day </button> <button class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2" > For 2 Days </button> <button class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2" > For 1 Week </button> <button class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2" > Until I turn it back on </button> <button @click="close(notification.id)" class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md" > Cancel </button> </div> </div> </div> <div v-if="notification.type === 'notification-off'" class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50" > <div class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg" > <div class="w-full px-6 py-6 text-slate-900 text-center"> <p class="text-lg mb-4"> Would you like to <b>turn off</b> notifications for this app? </p> <button @click=" close(notification.id); turnOffNotifications(); " class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2" > Turn Off Notifications </button> <button @click="close(notification.id)" class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md" > Leave it On </button> </div> </div> </div> </div> </Notification> </div> </NotificationGroup> </template> <style></style> <script lang="ts"> import { Vue, Component } from "vue-facing-decorator"; import { logConsoleAndDb } from "@/db/index"; @Component export default class App extends Vue { stopAsking = false; async turnOffNotifications() { let subscription; const pushProviderSuccess = await navigator.serviceWorker?.ready .then((registration) => { return registration.pushManager.getSubscription(); }) .then((subscript) => { subscription = subscript; if (subscription) { return subscription.unsubscribe(); } else { logConsoleAndDb("Subscription object is not available."); return false; } }) .catch((error) => { logConsoleAndDb( "Push provider server communication failed: " + JSON.stringify(error), true, ); return false; }); const pushServerSuccess = await fetch("/web-push/unsubscribe", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(subscription), }) .then((response) => { return response.ok; }) .catch((error) => { logConsoleAndDb( "Push server communication failed: " + JSON.stringify(error), true, ); return false; }); alert( "Notifications are off. Push provider unsubscribe " + (pushProviderSuccess ? "succeeded" : "failed") + (pushProviderSuccess === pushServerSuccess ? " and" : " but") + " push server unsubscribe " + (pushServerSuccess ? "succeeded" : "failed") + ".", ); } } </script>