firebase implementation
Gitea Actions / Deploy (push) Successful in 1m14s

This commit is contained in:
vadim committed 2026-10-16 16:56:34 +03:00
1 parent a0c557f9b2
commit 2d4b8ea9cf
17 files changed
+1542 -12

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import {
preferenceSet,
preferencesList,
testPushSend,
type NotificationChannel,
type NotificationPreference,
} from '@/api/notification.api.ts'
import { usePushNotifications } from '@/composables/usePushNotifications.ts'
import { useSnackbarStore } from '@/stores/snackbar.ts'
const CHANNEL_LABELS: Record<NotificationChannel, string> = {
EMAIL: 'Email',
PUSH: 'Push',
}
const snackbarStore = useSnackbarStore()
const push = usePushNotifications()
const preferences = ref<NotificationPreference[]>([])
const loading = ref(false)
const saving = ref<NotificationChannel | null>(null)
const permission = ref<NotificationPermission | 'unsupported'>(
'Notification' in window ? Notification.permission : 'unsupported',
)
const deviceSaving = ref(false)
const deviceEnabled = computed(() => !!push.installationId.value)
function showError(text: string, e: unknown) {
snackbarStore.add({ title: text, text: e instanceof Error ? e.message : '', color: 'error' })
}
async function loadPreferences() {
loading.value = true
try {
preferences.value = await preferencesList()
} catch (e) {
showError('Failed to load notification settings', e)
} finally {
loading.value = false
}
}
async function togglePreference(preference: NotificationPreference, enabled: boolean | null) {
const previous = preference.enabled
preference.enabled = !!enabled
saving.value = preference.channel
try {
await preferenceSet(preference.channel, preference.enabled)
} catch (e) {
preference.enabled = previous
showError('Failed to save notification settings', e)
} finally {
saving.value = null
}
}
async function toggleDevice(enabled: boolean | null) {
deviceSaving.value = true
try {
if (enabled) permission.value = await push.enable()
else await push.disable(true)
} catch (e) {
showError('Failed to change push in this browser', e)
} finally {
deviceSaving.value = false
}
}
const testSending = ref(false)
async function sendTestPush() {
testSending.value = true
try {
await testPushSend()
snackbarStore.add({
title: 'Test push sent',
text: 'It should arrive on all your devices shortly',
color: 'success',
})
} catch (e) {
showError('Failed to send test push', e)
} finally {
testSending.value = false
}
}
onMounted(async () => {
await loadPreferences()
if (!navigator.permissions) return
const perm = await navigator.permissions.query({ name: 'notifications' })
perm.onchange = () => {
permission.value = Notification.permission
}
})
</script>
<template>
<div>
<div class="text-uppercase">Notifications</div>
<v-skeleton-loader v-if="loading && !preferences.length" type="list-item, list-item" />
<div v-else class="d-flex flex-column">
<v-switch
v-for="preference in preferences"
:key="preference.channel"
:model-value="preference.enabled"
:label="CHANNEL_LABELS[preference.channel] ?? preference.channel"
:loading="saving === preference.channel"
:disabled="saving === preference.channel"
color="primary"
density="compact"
hide-details
@update:model-value="togglePreference(preference, $event)"
/>
</div>
<div class="text-uppercase mt-4">Push in this browser</div>
<div v-if="permission === 'unsupported'" class="text-medium-emphasis">
This browser does not support push notifications
</div>
<div v-else-if="permission === 'denied'" class="text-medium-emphasis">
Notifications are blocked — allow them in the browser site settings
</div>
<v-switch
v-else
:model-value="deviceEnabled"
:label="deviceEnabled ? 'Enabled' : 'Disabled'"
:loading="deviceSaving"
:disabled="deviceSaving"
color="primary"
density="compact"
hide-details
@update:model-value="toggleDevice"
/>
<v-btn
class="mt-2"
variant="tonal"
size="small"
prepend-icon="mdi-bell-ring-outline"
:loading="testSending"
@click="sendTestPush"
>
Send test push
</v-btn>
</div>
</template>
<style scoped lang="scss"></style>