Files
cannamanage/cannamanage-frontend/src/components/pwa-install-prompt.tsx
T
Patrick Plate 599514c0db
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
feat(sprint-6): Phase 6 — Notifications (WebSocket) + PWA
- WebSocket: Spring STOMP + SockJS, NotificationService, persistent notifications table
- NotificationController: GET/PUT endpoints for notification management
- Frontend: notification bell with unread badge, dropdown panel, real-time via STOMP
- PWA: manifest.json, service worker (manual sw.js), offline page, install prompt
- PWA icons (192+512), dark theme colors, standalone display
- Full i18n (de/en) for notifications and PWA
- Flyway V10 migration for notifications table
- spring-boot-starter-websocket dependency added
2026-06-12 23:02:44 +02:00

77 lines
2.3 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { useTranslations } from "next-intl"
import { Download, X } from "lucide-react"
import { Button } from "@/components/ui/button"
interface BeforeInstallPromptEvent extends Event {
prompt: () => Promise<void>
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>
}
export function PwaInstallPrompt() {
const t = useTranslations("pwa")
const [deferredPrompt, setDeferredPrompt] =
useState<BeforeInstallPromptEvent | null>(null)
const [dismissed, setDismissed] = useState(false)
useEffect(() => {
// Check if already dismissed permanently
if (localStorage.getItem("pwa-install-dismissed") === "true") {
setDismissed(true)
return
}
const handler = (e: Event) => {
e.preventDefault()
setDeferredPrompt(e as BeforeInstallPromptEvent)
}
window.addEventListener("beforeinstallprompt", handler)
return () => window.removeEventListener("beforeinstallprompt", handler)
}, [])
const handleInstall = async () => {
if (!deferredPrompt) return
await deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
if (outcome === "accepted") {
setDeferredPrompt(null)
}
}
const handleDismiss = () => {
setDismissed(true)
localStorage.setItem("pwa-install-dismissed", "true")
setDeferredPrompt(null)
}
if (!deferredPrompt || dismissed) return null
return (
<div className="fixed bottom-4 left-4 right-4 z-50 mx-auto max-w-md animate-in slide-in-from-bottom-4 rounded-lg border bg-card p-4 shadow-lg">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10">
<Download className="h-5 w-5 text-primary" />
</div>
<div className="flex-1">
<p className="text-sm font-medium">{t("install")}</p>
<p className="mt-1 text-xs text-muted-foreground">
{t("installDesc")}
</p>
<div className="mt-3 flex gap-2">
<Button size="sm" onClick={handleInstall}>
{t("install")}
</Button>
<Button size="sm" variant="ghost" onClick={handleDismiss}>
<X className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
)
}