aboutsummaryrefslogtreecommitdiffstats
path: root/site/src/components/NotificationButton.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'site/src/components/NotificationButton.tsx')
-rw-r--r--site/src/components/NotificationButton.tsx196
1 files changed, 196 insertions, 0 deletions
diff --git a/site/src/components/NotificationButton.tsx b/site/src/components/NotificationButton.tsx
new file mode 100644
index 0000000..8f4fb61
--- /dev/null
+++ b/site/src/components/NotificationButton.tsx
@@ -0,0 +1,196 @@
+import { useState, useEffect } from "react";
+import { messaging, initializeForegroundNotifications } from "../firebase.ts";
+import { getToken, deleteToken } from "firebase/messaging";
+
+const VAPID_KEY =
+ "BK7tpLF5Loy8Ew8bKxhTi-vOEJdxJSnu-jPyagWecLdD_SrEAt_OQS7nu0Xu3hR7AQpn0cOmgcdeeQd5zq5-Gyo";
+
+interface NotificationButtonProps {
+ className?: string;
+ isMoe?: boolean;
+}
+
+export default function NotificationButton({ className = "", isMoe = false }: NotificationButtonProps) {
+ const [permission, setPermission] = useState<NotificationPermission>("default");
+ const [isRegistered, setIsRegistered] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ // Check initial permission status
+ setPermission(Notification.permission);
+
+ // Check if service worker is registered
+ const checkRegistration = async () => {
+ if ('serviceWorker' in navigator) {
+ const registration = await navigator.serviceWorker.getRegistration('/firebase-messaging-sw.js');
+ setIsRegistered(!!registration);
+
+ // Initialize foreground notifications if already registered
+ if (registration && Notification.permission === "granted") {
+ initializeForegroundNotifications();
+ }
+ }
+ };
+
+ checkRegistration();
+ }, []);
+
+ const handleEnableNotifications = async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const permissionResult = await Notification.requestPermission();
+ setPermission(permissionResult);
+
+ if (permissionResult === "granted") {
+ // Register service worker
+ const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
+ console.log("Service Worker registered:", registration);
+ const token = await getToken(messaging, { vapidKey: VAPID_KEY });
+ console.log("FCM Token:", token);
+ // Store token locally (you might want to send this to your server)
+ localStorage.setItem('fcm_token', token);
+
+ // Initialize foreground notification handler
+ initializeForegroundNotifications();
+
+ setIsRegistered(true);
+ } else {
+ setError("Notification permission was denied");
+ }
+ } catch (err) {
+ console.error("Error enabling notifications:", err);
+ setError("Failed to enable notifications. Please try again.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleDisableNotifications = async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ await deleteToken(messaging);
+ console.log("FCM token deleted");
+ localStorage.removeItem('fcm_token');
+ if ('serviceWorker' in navigator) {
+ const registration = await navigator.serviceWorker.getRegistration('/firebase-messaging-sw.js');
+ if (registration) {
+ await registration.unregister();
+ console.log("Service Worker unregistered");
+ }
+ }
+
+ setIsRegistered(false);
+ } catch (err) {
+ console.error("Error disabling notifications:", err);
+ setError("Failed to disable notifications. Please try again.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Determine button state and action
+ const getButtonContent = () => {
+ if (loading) {
+ return (
+ <>
+ <svg className="animate-spin h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
+ </svg>
+ {isRegistered ? "Disabling..." : "Enabling..."}
+ </>
+ );
+ }
+
+ if (permission === "denied") {
+ return (
+ <>
+ <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
+ </svg>
+ Notifications Blocked
+ </>
+ );
+ }
+
+ if (isRegistered && permission === "granted") {
+ return (
+ <>
+ <svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
+ <path d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
+ </svg>
+ Disable Notifications
+ </>
+ );
+ }
+
+ return (
+ <>
+ <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
+ </svg>
+ Enable Notifications
+ </>
+ );
+ };
+
+ const handleClick = () => {
+ if (permission === "denied") {
+ // Can't re-request permission if denied
+ alert("Notifications are blocked. Please enable them in your browser settings.");
+ return;
+ }
+
+ if (isRegistered && permission === "granted") {
+ handleDisableNotifications();
+ } else {
+ handleEnableNotifications();
+ }
+ };
+
+ // Determine button styles
+ const getButtonStyles = () => {
+ if (loading || permission === "denied") {
+ return isMoe
+ ? `bg-pink-300 cursor-not-allowed opacity-60`
+ : `bg-gray-600 cursor-not-allowed opacity-60`;
+ }
+
+ if (isMoe) {
+ return isRegistered
+ ? `bg-pink-600 text-white hover:bg-pink-700`
+ : `bg-pink-500 text-white hover:bg-pink-600`;
+ } else {
+ return isRegistered
+ ? `bg-purple-700 text-white hover:bg-purple-800`
+ : `bg-purple-600 text-white hover:bg-purple-700`;
+ }
+ };
+
+ return (
+ <div className="flex flex-col items-center gap-2">
+ <button
+ onClick={handleClick}
+ disabled={loading || permission === "denied"}
+ className={`flex items-center justify-center px-4 py-2 rounded-lg font-semibold transition-colors ${getButtonStyles()} ${className}`}
+ >
+ {getButtonContent()}
+ </button>
+ {error && (
+ <p className={`text-sm ${isMoe ? "text-pink-600" : "text-red-500"}`}>
+ {error}
+ </p>
+ )}
+ {permission === "denied" && (
+ <p className={`text-xs ${isMoe ? "text-pink-600" : "text-gray-400"}`}>
+ To enable notifications, update your browser settings
+ </p>
+ )}
+ </div>
+ );
+}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage