106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { Bell, BellOff, Loader2 } from "lucide-react"
|
|
import { toast } from "sonner"
|
|
import { subscribeUser, unsubscribeUser } from "@/app/actions/subscription"
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
function urlBase64ToUint8Array(base64String: string) {
|
|
const padding = '='.repeat((4 - base64String.length % 4) % 4)
|
|
const base64 = (base64String + padding)
|
|
.replace(/\-/g, '+')
|
|
.replace(/_/g, '/')
|
|
|
|
const rawData = window.atob(base64)
|
|
const outputArray = new Uint8Array(rawData.length)
|
|
|
|
for (let i = 0; i < rawData.length; ++i) {
|
|
outputArray[i] = rawData.charCodeAt(i)
|
|
}
|
|
return outputArray
|
|
}
|
|
|
|
export function PushSubscriptionSettings({ planId }: { planId: string }) {
|
|
const [isSupported, setIsSupported] = useState(false)
|
|
const [subscription, setSubscription] = useState<PushSubscription | null>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if ('serviceWorker' in navigator && 'PushManager' in window && process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY) {
|
|
setIsSupported(true)
|
|
registerServiceWorker()
|
|
}
|
|
}, [])
|
|
|
|
async function registerServiceWorker() {
|
|
try {
|
|
const registration = await navigator.serviceWorker.ready
|
|
const sub = await registration.pushManager.getSubscription()
|
|
setSubscription(sub)
|
|
} catch (e) {
|
|
console.error("SW registration error", e)
|
|
}
|
|
}
|
|
|
|
async function subscribe() {
|
|
setLoading(true)
|
|
try {
|
|
const registration = await navigator.serviceWorker.ready
|
|
const sub = await registration.pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey: urlBase64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!)
|
|
})
|
|
setSubscription(sub)
|
|
await subscribeUser(planId, sub.toJSON())
|
|
toast.success("Push Notifications enabled")
|
|
} catch (error) {
|
|
console.error(error)
|
|
toast.error("Failed to enable notifications. check permissions.")
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
async function unsubscribe() {
|
|
setLoading(true)
|
|
try {
|
|
if (subscription) {
|
|
await subscription.unsubscribe()
|
|
await unsubscribeUser(subscription.endpoint)
|
|
setSubscription(null)
|
|
toast.success("Notifications disabled")
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
toast.error("Failed to disable.")
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
if (!isSupported) return null
|
|
|
|
return (
|
|
<div className="flex items-center justify-between p-4 border rounded-md mb-4 bg-muted/50">
|
|
<div className="space-y-0.5">
|
|
<h3 className="font-medium text-sm">Device Notifications</h3>
|
|
<p className="text-xs text-muted-foreground">
|
|
Receive updates on this device
|
|
</p>
|
|
</div>
|
|
{subscription ? (
|
|
<Button variant="outline" size="sm" onClick={unsubscribe} disabled={loading}>
|
|
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <BellOff className="w-4 h-4 mr-2" />}
|
|
Disable
|
|
</Button>
|
|
) : (
|
|
<Button size="sm" onClick={subscribe} disabled={loading} variant="secondary">
|
|
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Bell className="w-4 h-4 mr-2" />}
|
|
Enable
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|