feat: Implement push notification system for admin announcements
- Add a notification manager to handle push notifications and integrate with Nostr events. - Create a push notification service to manage subscription and permission requests. - Introduce components for notification settings and permission prompts in the UI. - Update Nostr store to manage push notification state and enable/disable functionality. - Enhance NostrFeed to send notifications for new admin announcements. - Implement test notification functionality for development purposes.
This commit is contained in:
parent
6c1caac84b
commit
c05f40f1ec
17 changed files with 1316 additions and 13 deletions
222
src/components/notifications/NotificationSettings.vue
Normal file
222
src/components/notifications/NotificationSettings.vue
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<template>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Bell class="h-5 w-5" />
|
||||
Notification Settings
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Configure when and how you receive push notifications
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="space-y-6">
|
||||
<!-- Main notification toggle -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label class="text-base font-medium">Push Notifications</Label>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Receive notifications even when the app is closed
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
v-if="!notificationsEnabled && !isBlocked"
|
||||
@click="enableNotifications"
|
||||
:disabled="isLoading"
|
||||
size="sm"
|
||||
>
|
||||
<Bell class="h-3 w-3 mr-1" />
|
||||
{{ isLoading ? 'Enabling...' : 'Enable' }}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
v-else-if="notificationsEnabled"
|
||||
@click="disableNotifications"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<BellOff class="h-3 w-3 mr-1" />
|
||||
Disable
|
||||
</Button>
|
||||
|
||||
<Badge v-else-if="isBlocked" variant="destructive">
|
||||
Blocked
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification type preferences -->
|
||||
<div v-if="notificationsEnabled" class="space-y-4">
|
||||
<div class="border-t pt-4">
|
||||
<Label class="text-sm font-medium mb-3 block">Notification Types</Label>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label class="text-sm">Admin Announcements</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Important updates from community administrators
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="options.adminAnnouncements"
|
||||
@change="updateOptions"
|
||||
class="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label class="text-sm">Mentions</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
When someone mentions you in a post
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="options.mentions"
|
||||
@change="updateOptions"
|
||||
class="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label class="text-sm">Replies</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
When someone replies to your posts
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="options.replies"
|
||||
@change="updateOptions"
|
||||
class="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test notification -->
|
||||
<div class="border-t pt-4">
|
||||
<Button
|
||||
@click="sendTestNotification"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="isTestLoading"
|
||||
>
|
||||
<TestTube class="h-3 w-3 mr-1" />
|
||||
{{ isTestLoading ? 'Sending...' : 'Send Test Notification' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help text for blocked notifications -->
|
||||
<div v-if="isBlocked" class="bg-destructive/10 border border-destructive/20 rounded-lg p-3">
|
||||
<div class="flex items-start gap-2">
|
||||
<AlertCircle class="h-4 w-4 text-destructive mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="text-destructive font-medium mb-1">Notifications Blocked</p>
|
||||
<p class="text-destructive/80">
|
||||
You've blocked notifications for this site. To enable them:
|
||||
</p>
|
||||
<ol class="list-decimal list-inside mt-2 text-destructive/80 space-y-1">
|
||||
<li>Click the lock icon in your browser's address bar</li>
|
||||
<li>Select "Allow" for notifications</li>
|
||||
<li>Refresh this page</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Not supported message -->
|
||||
<div v-if="!isSupported" class="bg-orange-50 dark:bg-orange-950 border border-orange-200 dark:border-orange-800 rounded-lg p-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<AlertCircle class="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
||||
<span class="text-sm text-orange-800 dark:text-orange-200">
|
||||
Push notifications are not supported by your browser
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Bell, BellOff, TestTube, AlertCircle } from 'lucide-vue-next'
|
||||
import { useNostrStore } from '@/stores/nostr'
|
||||
import { notificationManager } from '@/lib/notifications/manager'
|
||||
import { pushService } from '@/lib/notifications/push'
|
||||
import { configUtils } from '@/lib/config'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const nostrStore = useNostrStore()
|
||||
const { notificationsEnabled } = storeToRefs(nostrStore)
|
||||
|
||||
const isLoading = ref(false)
|
||||
const isTestLoading = ref(false)
|
||||
const options = ref(notificationManager.getOptions())
|
||||
|
||||
const isSupported = pushService.isSupported() && configUtils.hasPushConfig()
|
||||
const isBlocked = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
await checkStatus()
|
||||
})
|
||||
|
||||
async function checkStatus() {
|
||||
isBlocked.value = pushService.getPermission() === 'denied'
|
||||
await nostrStore.checkPushNotificationStatus()
|
||||
}
|
||||
|
||||
async function enableNotifications() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
await nostrStore.enablePushNotifications()
|
||||
toast.success('Push notifications enabled!')
|
||||
await checkStatus()
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to enable notifications'
|
||||
toast.error(message)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function disableNotifications() {
|
||||
try {
|
||||
await nostrStore.disablePushNotifications()
|
||||
toast.success('Push notifications disabled')
|
||||
await checkStatus()
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to disable notifications'
|
||||
toast.error(message)
|
||||
}
|
||||
}
|
||||
|
||||
function updateOptions() {
|
||||
notificationManager.updateOptions(options.value)
|
||||
toast.success('Notification preferences updated')
|
||||
}
|
||||
|
||||
async function sendTestNotification() {
|
||||
try {
|
||||
isTestLoading.value = true
|
||||
await notificationManager.sendTestNotification()
|
||||
toast.success('Test notification sent!')
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to send test notification'
|
||||
toast.error(message)
|
||||
} finally {
|
||||
isTestLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue