Unified · Split
| @@ -8,41 +8,6 @@ import { useSession } from "@/lib/session-context"; · 7 unmodified lines | |||
| 8 | import { convexApi } from "@/lib/convex-api"; | 8 | import { convexApi } from "@/lib/convex-api"; |
| 9 | import type { PendingApprovalRecord } from "@/lib/types"; | 9 | import type { PendingApprovalRecord } from "@/lib/types"; |
| 10 | 10 | ||
| 11 | const NOTIFICATION_PROMPT_KEY = "executor_approval_desktop_notifications_prompted_v1"; | ||
| 12 | |||
| 13 | function supportsDesktopNotifications(): boolean { | ||
| 14 | return typeof window !== "undefined" && "Notification" in window; | ||
| 15 | } | ||
| 16 | |||
| 17 | function notifyDesktop(approval: PendingApprovalRecord) { | ||
| 18 | if (!supportsDesktopNotifications()) { | ||
| 19 | return; | ||
| 20 | } | ||
| 21 | |||
| 22 | if (Notification.permission !== "granted") { | ||
| 23 | return; | ||
| 24 | } | ||
| 25 | |||
| 26 | if (document.visibilityState === "visible") { | ||
| 27 | return; | ||
| 28 | } | ||
| 29 | |||
| 30 | const notification = new Notification("Approval required", { | ||
| 31 | body: `${approval.toolPath} is waiting for review`, | ||
| 32 | tag: `approval-${approval.id}`, | ||
| 33 | }); | ||
| 34 | |||
| 35 | notification.onclick = () => { | ||
| 36 | window.focus(); | ||
| 37 | window.location.assign("/approvals"); | ||
| 38 | notification.close(); | ||
| 39 | }; | ||
| 40 | |||
| 41 | window.setTimeout(() => { | ||
| 42 | notification.close(); | ||
| 43 | }, 12_000); | ||
| 44 | } | ||
| 45 | |||
| 46 | export function ApprovalNotifier() { | 11 | export function ApprovalNotifier() { |
| 47 | const router = useRouter(); | 12 | const router = useRouter(); |
| 48 | const { context } = useSession(); | 13 | const { context } = useSession(); |
| @@ -98,59 +63,7 @@ export function ApprovalNotifier() { · 49 unmodified lines | |||
| 98 | }, | 63 | }, |
| 99 | ); | 64 | ); |
| 100 | 65 | ||
| 101 | for (const approval of newApprovals) { | ||
| 102 | notifyDesktop(approval); | ||
| 103 | } | ||
| 104 | }, [approvals, context, router]); | 66 | }, [approvals, context, router]); |
| 105 | 67 | ||
| 106 | useEffect(() => { | ||
| 107 | if (!context) { | ||
| 108 | return; | ||
| 109 | } | ||
| 110 | |||
| 111 | if (!supportsDesktopNotifications()) { | ||
| 112 | return; | ||
| 113 | } | ||
| 114 | |||
| 115 | if (Notification.permission !== "default") { | ||
| 116 | return; | ||
| 117 | } | ||
| 118 | |||
| 119 | if (localStorage.getItem(NOTIFICATION_PROMPT_KEY)) { | ||
| 120 | return; | ||
| 121 | } | ||
| 122 | |||
| 123 | localStorage.setItem(NOTIFICATION_PROMPT_KEY, "1"); | ||
| 124 | |||
| 125 | toast.message("Enable desktop approval alerts?", { | ||
| 126 | description: "Get browser notifications when new approvals arrive.", | ||
| 127 | duration: 14_000, | ||
| 128 | action: { | ||
| 129 | label: "Enable", | ||
| 130 | onClick: async () => { | ||
| 131 | if (!supportsDesktopNotifications()) { | ||
| 132 | return; | ||
| 133 | } | ||
| 134 | |||
| 135 | try { | ||
| 136 | const permission = await Notification.requestPermission(); | ||
| 137 | |||
| 138 | if (permission === "granted") { | ||
| 139 | toast.success("Desktop notifications enabled"); | ||
| 140 | } else if (permission === "denied") { | ||
| 141 | toast.error("Desktop notifications blocked in this browser"); | ||
| 142 | } | ||
| 143 | } catch { | ||
| 144 | toast.error("Failed to request notification permission"); | ||
| 145 | } | ||
| 146 | }, | ||
| 147 | }, | ||
| 148 | cancel: { | ||
| 149 | label: "Later", | ||
| 150 | onClick: () => {}, | ||
| 151 | }, | ||
| 152 | }); | ||
| 153 | }, [context]); | ||
| 154 | |||
| 155 | return null; | 68 | return null; |
| 156 | } | 69 | } |