This commit is contained in:
1 parent
a0c557f9b2
commit
2d4b8ea9cf
17 files changed
+1542
-12
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user