vn6295337 Claude Opus 4.5 commited on
Commit
1ad22ef
·
1 Parent(s): 8d5bad3

Replace toast with Browser Notifications API

Browse files

- Notifications appear even when user is in other apps/tabs
- Request permission on first stock selection
- Remove Sonner toast dependency from App.tsx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. frontend/src/App.tsx +25 -17
frontend/src/App.tsx CHANGED
@@ -4,7 +4,6 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
4
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
5
  import { Badge } from "@/components/ui/badge"
6
  import { Toaster } from "@/components/ui/toaster"
7
- import { Toaster as Sonner, toast } from "@/components/ui/sonner"
8
  import { TooltipProvider } from "@/components/ui/tooltip"
9
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
10
  import { BrowserRouter, Routes, Route } from "react-router-dom"
@@ -59,7 +58,6 @@ const App = () => (
59
  <QueryClientProvider client={queryClient}>
60
  <TooltipProvider>
61
  <Toaster />
62
- <Sonner />
63
  <BrowserRouter>
64
  <Routes>
65
  <Route path="/" element={<Index />} />
@@ -129,6 +127,24 @@ const Index = () => {
129
  setUserEvents(prev => [...prev, { timestamp: new Date().toISOString(), message }])
130
  }
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  // Extracted polling logic to avoid duplication
133
  const startPolling = (workflowIdToUse: string) => {
134
  if (pollingRef.current) {
@@ -214,11 +230,8 @@ const Index = () => {
214
  setShowResults(true)
215
  setMainTab("results")
216
 
217
- // Notify user that report is ready
218
- toast.success("Analysis complete!", {
219
- description: `SWOT report for ${selectedStock?.symbol || 'company'} is ready.`,
220
- duration: 5000
221
- })
222
  } else if (status.status === "aborted") {
223
  clearInterval(pollingRef.current!)
224
  pollingRef.current = null
@@ -226,22 +239,16 @@ const Index = () => {
226
  setIsAborted(true)
227
  setAbortReason(status.error || 'Critical failure - workflow aborted')
228
 
229
- // Notify user of abort
230
- toast.error("Analysis aborted", {
231
- description: status.error || "Critical failure - workflow aborted",
232
- duration: 8000
233
- })
234
  } else if (status.status === "error") {
235
  clearInterval(pollingRef.current!)
236
  pollingRef.current = null
237
  setIsLoading(false)
238
  setHasError(true)
239
 
240
- // Notify user of error
241
- toast.error("Analysis failed", {
242
- description: "An error occurred during analysis.",
243
- duration: 8000
244
- })
245
  }
246
  } catch (error) {
247
  console.error("Polling error:", error)
@@ -513,6 +520,7 @@ Generated by Instant SWOT Agent`
513
  onSelect={(stock) => {
514
  setSelectedStock(stock)
515
  addUserEvent(`Selected: ${stock.name} (${stock.symbol})`)
 
516
  }}
517
  selectedStock={selectedStock}
518
  onClear={handleStockClear}
 
4
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
5
  import { Badge } from "@/components/ui/badge"
6
  import { Toaster } from "@/components/ui/toaster"
 
7
  import { TooltipProvider } from "@/components/ui/tooltip"
8
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
9
  import { BrowserRouter, Routes, Route } from "react-router-dom"
 
58
  <QueryClientProvider client={queryClient}>
59
  <TooltipProvider>
60
  <Toaster />
 
61
  <BrowserRouter>
62
  <Routes>
63
  <Route path="/" element={<Index />} />
 
127
  setUserEvents(prev => [...prev, { timestamp: new Date().toISOString(), message }])
128
  }
129
 
130
+ // Browser Notification helper
131
+ const notify = (title: string, body: string, type: 'success' | 'error' = 'success') => {
132
+ if (Notification.permission === 'granted') {
133
+ new Notification(title, {
134
+ body,
135
+ icon: type === 'success' ? '/favicon.ico' : undefined,
136
+ tag: 'swot-notification', // Prevents duplicate notifications
137
+ })
138
+ }
139
+ }
140
+
141
+ // Request notification permission on first stock selection
142
+ const requestNotificationPermission = () => {
143
+ if ('Notification' in window && Notification.permission === 'default') {
144
+ Notification.requestPermission()
145
+ }
146
+ }
147
+
148
  // Extracted polling logic to avoid duplication
149
  const startPolling = (workflowIdToUse: string) => {
150
  if (pollingRef.current) {
 
230
  setShowResults(true)
231
  setMainTab("results")
232
 
233
+ // Notify user that report is ready (works even if tab is in background)
234
+ notify("Analysis complete!", `SWOT report for ${selectedStock?.symbol || 'company'} is ready.`, 'success')
 
 
 
235
  } else if (status.status === "aborted") {
236
  clearInterval(pollingRef.current!)
237
  pollingRef.current = null
 
239
  setIsAborted(true)
240
  setAbortReason(status.error || 'Critical failure - workflow aborted')
241
 
242
+ // Notify user of abort (works even if tab is in background)
243
+ notify("Analysis aborted", status.error || "Critical failure - workflow aborted", 'error')
 
 
 
244
  } else if (status.status === "error") {
245
  clearInterval(pollingRef.current!)
246
  pollingRef.current = null
247
  setIsLoading(false)
248
  setHasError(true)
249
 
250
+ // Notify user of error (works even if tab is in background)
251
+ notify("Analysis failed", "An error occurred during analysis.", 'error')
 
 
 
252
  }
253
  } catch (error) {
254
  console.error("Polling error:", error)
 
520
  onSelect={(stock) => {
521
  setSelectedStock(stock)
522
  addUserEvent(`Selected: ${stock.name} (${stock.symbol})`)
523
+ requestNotificationPermission()
524
  }}
525
  selectedStock={selectedStock}
526
  onClear={handleStockClear}