- 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.
222 lines
No EOL
7.3 KiB
Vue
222 lines
No EOL
7.3 KiB
Vue
<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> |