fix: push notifications — urlBase64ToUint8Array bug, add PushBanner prompt

This commit is contained in:
2026-04-15 12:24:18 +03:00
parent 1641b14f29
commit e6d84e6fd7
3 changed files with 78 additions and 4 deletions

View File

@@ -0,0 +1,73 @@
import { useState, useEffect } from 'react'
import { Bell, X } from 'lucide-react'
import { isPushSupported, subscribeToPush } from '../lib/push'
import { api } from '../lib/api'
const DISMISSED_KEY = 'hotelsync-push-banner-dismissed'
export function PushBanner() {
const [visible, setVisible] = useState(false)
const [loading, setLoading] = useState(false)
useEffect(() => {
if (!isPushSupported()) return
if (typeof Notification === 'undefined') return
if (Notification.permission !== 'default') return
if (localStorage.getItem(DISMISSED_KEY)) return
// Show after 3s so the UI settles
const t = setTimeout(() => setVisible(true), 3000)
return () => clearTimeout(t)
}, [])
if (!visible) return null
const dismiss = () => {
setVisible(false)
localStorage.setItem(DISMISSED_KEY, '1')
}
const enable = async () => {
setLoading(true)
try {
const result = await Notification.requestPermission()
if (result === 'granted') {
const { publicKey } = await api.push.getVapidKey()
if (publicKey) {
const sub = await subscribeToPush(publicKey)
if (sub) {
const json = sub.toJSON()
if (json.endpoint && json.keys?.p256dh && json.keys?.auth) {
await api.push.subscribe({ endpoint: json.endpoint, p256dh: json.keys.p256dh, auth: json.keys.auth })
}
}
}
}
} catch { /**/ }
finally {
setLoading(false)
dismiss()
}
}
return (
<div className="fixed bottom-24 left-4 right-24 z-40 max-w-xs">
<div className="bg-slate-800 dark:bg-slate-900 text-white rounded-2xl shadow-xl px-4 py-3 flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-brand-600 flex items-center justify-center shrink-0">
<Bell size={15} />
</div>
<p className="flex-1 text-sm leading-snug">
Включите уведомления, чтобы не пропускать сообщения
</p>
<div className="flex items-center gap-2 shrink-0">
<button onClick={enable} disabled={loading}
className="text-xs font-semibold text-brand-300 hover:text-brand-200 disabled:opacity-50 transition-colors whitespace-nowrap">
{loading ? '...' : 'Включить'}
</button>
<button onClick={dismiss} className="text-slate-400 hover:text-white transition-colors">
<X size={14} />
</button>
</div>
</div>
</div>
)
}