1+ 'use client' ;
2+
3+ import React , { useState } from 'react' ;
4+ import { useNotificationStore } from '@/store/notificationStore' ;
5+ import { Button } from '@/components/ui/button' ;
6+ import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui/card' ;
7+ import { Badge } from '@/components/ui/badge' ;
8+ import { Input } from '@/components/ui/input' ;
9+ import { Label } from '@/components/ui/label' ;
10+ import { Switch } from '@/components/ui/switch' ;
11+ import { Tabs , TabsContent , TabsList , TabsTrigger } from '@/components/ui/tabs' ;
12+ import {
13+ Bell ,
14+ BellRing ,
15+ Trash2 ,
16+ TrendingUp ,
17+ TrendingDown ,
18+ AlertCircle ,
19+ Mail ,
20+ MailOff ,
21+ Settings ,
22+ X ,
23+ ArrowLeft
24+ } from 'lucide-react' ;
25+ import { formatPrice } from '@/utils/searchUtils' ;
26+ import { PRICE_ALERT_TYPE_LABELS , type PriceAlertType } from '@/types/property' ;
27+ import Link from 'next/link' ;
28+ import { toast } from 'sonner' ;
29+ import { WalletConnector } from '@/components/WalletConnector' ;
30+ import { PriceAlertBell } from '@/components/PriceAlertBell' ;
31+
32+ export default function AlertsPage ( ) {
33+ const {
34+ priceAlerts,
35+ priceAlertNotifications,
36+ removePriceAlert,
37+ togglePriceAlert,
38+ markPriceAlertNotificationAsRead,
39+ clearPriceAlertNotification,
40+ clearAllPriceAlertNotifications,
41+ settings,
42+ updateSettings,
43+ } = useNotificationStore ( ) ;
44+
45+ const [ activeTab , setActiveTab ] = useState ( 'alerts' ) ;
46+
47+ const unreadNotificationsCount = priceAlertNotifications . filter ( n => ! n . isRead ) . length ;
48+
49+ const handleRemoveAlert = ( alertId : string ) => {
50+ removePriceAlert ( alertId ) ;
51+ toast . success ( 'Alert removed successfully' ) ;
52+ } ;
53+
54+ const handleToggleAlert = ( alertId : string ) => {
55+ togglePriceAlert ( alertId ) ;
56+ } ;
57+
58+ const handleMarkAsRead = ( notificationId : string ) => {
59+ markPriceAlertNotificationAsRead ( notificationId ) ;
60+ } ;
61+
62+ const handleClearNotification = ( notificationId : string ) => {
63+ clearPriceAlertNotification ( notificationId ) ;
64+ } ;
65+
66+ const getAlertTypeIcon = ( type : PriceAlertType ) => {
67+ switch ( type ) {
68+ case 'above' :
69+ return < TrendingUp className = "w-5 h-5 text-green-600" /> ;
70+ case 'below' :
71+ return < TrendingDown className = "w-5 h-5 text-red-600" /> ;
72+ case 'change' :
73+ return < AlertCircle className = "w-5 h-5 text-orange-600" /> ;
74+ }
75+ } ;
76+
77+ return (
78+ < div className = "min-h-screen bg-gray-50 dark:bg-gray-900" >
79+ { /* Header */ }
80+ < header className = "bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700 sticky top-0 z-30" >
81+ < div className = "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8" >
82+ < div className = "flex justify-between items-center h-16" >
83+ < div className = "flex items-center gap-4" >
84+ < Link href = "/properties" >
85+ < Button variant = "ghost" size = "sm" >
86+ < ArrowLeft className = "w-4 h-4 mr-2" />
87+ Properties
88+ </ Button >
89+ </ Link >
90+ < div className = "flex items-center gap-2" >
91+ < div className = "w-7 h-7 bg-blue-600 rounded-lg flex items-center justify-center" >
92+ < span className = "text-white font-bold text-xs" > PC</ span >
93+ </ div >
94+ < span className = "font-bold text-gray-900 dark:text-white" > PropChain</ span >
95+ </ div >
96+ </ div >
97+ < div className = "flex items-center gap-2" >
98+ < PriceAlertBell />
99+ < WalletConnector />
100+ </ div >
101+ </ div >
102+ </ div >
103+ </ header >
104+
105+ < div className = "container mx-auto py-8 px-4" >
106+ < div className = "max-w-4xl mx-auto" >
107+ { /* Page Header */ }
108+ < div className = "mb-8" >
109+ < h1 className = "text-3xl font-bold text-gray-900 dark:text-white mb-2" >
110+ Price Alerts
111+ </ h1 >
112+ < p className = "text-gray-600 dark:text-gray-400" >
113+ Manage your property price alerts and notification preferences
114+ </ p >
115+ </ div >
116+
117+ < Tabs value = { activeTab } onValueChange = { setActiveTab } >
118+ < TabsList className = "grid w-full grid-cols-3" >
119+ < TabsTrigger value = "alerts" className = "flex items-center gap-2" >
120+ < Bell className = "w-4 h-4" />
121+ My Alerts
122+ { priceAlerts . length > 0 && (
123+ < Badge variant = "secondary" className = "ml-1" >
124+ { priceAlerts . length }
125+ </ Badge >
126+ ) }
127+ </ TabsTrigger >
128+ < TabsTrigger value = "notifications" className = "flex items-center gap-2" >
129+ < BellRing className = "w-4 h-4" />
130+ Notifications
131+ { unreadNotificationsCount > 0 && (
132+ < Badge variant = "destructive" className = "ml-1" >
133+ { unreadNotificationsCount }
134+ </ Badge >
135+ ) }
136+ </ TabsTrigger >
137+ < TabsTrigger value = "settings" className = "flex items-center gap-2" >
138+ < Settings className = "w-4 h-4" />
139+ Settings
140+ </ TabsTrigger >
141+ </ TabsList >
142+
143+ { /* Alerts Tab */ }
144+ < TabsContent value = "alerts" className = "mt-6" >
145+ { priceAlerts . length === 0 ? (
146+ < Card >
147+ < CardContent className = "py-12 text-center" >
148+ < Bell className = "w-12 h-12 mx-auto mb-4 text-gray-400" />
149+ < h3 className = "text-lg font-semibold mb-2" > No alerts set</ h3 >
150+ < p className = "text-gray-600 dark:text-gray-400 mb-4" >
151+ Set up price alerts on property detail pages to get notified when prices change.
152+ </ p >
153+ < Link href = "/properties" >
154+ < Button > Browse Properties</ Button >
155+ </ Link >
156+ </ CardContent >
157+ </ Card >
158+ ) : (
159+ < div className = "space-y-4" >
160+ { priceAlerts . map ( ( alert ) => (
161+ < Card key = { alert . id } className = { ! alert . isActive ? 'opacity-60' : '' } >
162+ < CardContent className = "p-4" >
163+ < div className = "flex items-center justify-between" >
164+ < div className = "flex items-center gap-4" >
165+ { getAlertTypeIcon ( alert . alertType ) }
166+ < div >
167+ < Link
168+ href = { `/properties/${ alert . propertyId } ` }
169+ className = "font-medium hover:underline"
170+ >
171+ { alert . propertyName }
172+ </ Link >
173+ < div className = "flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400" >
174+ < span > { PRICE_ALERT_TYPE_LABELS [ alert . alertType ] } </ span >
175+ < span > •</ span >
176+ < span > Target: { formatPrice ( alert . targetPrice ) } </ span >
177+ < span > •</ span >
178+ < span > Current: { formatPrice ( alert . currentPrice ) } </ span >
179+ </ div >
180+ </ div >
181+ </ div >
182+ < div className = "flex items-center gap-3" >
183+ { alert . isTriggered && (
184+ < Badge variant = "default" className = "bg-green-600" >
185+ Triggered
186+ </ Badge >
187+ ) }
188+ < Switch
189+ checked = { alert . isActive }
190+ onCheckedChange = { ( ) => handleToggleAlert ( alert . id ) }
191+ />
192+ < Button
193+ variant = "ghost"
194+ size = "icon"
195+ onClick = { ( ) => handleRemoveAlert ( alert . id ) }
196+ >
197+ < Trash2 className = "w-4 h-4 text-red-500" />
198+ </ Button >
199+ </ div >
200+ </ div >
201+ </ CardContent >
202+ </ Card >
203+ ) ) }
204+ </ div >
205+ ) }
206+ </ TabsContent >
207+
208+ { /* Notifications Tab */ }
209+ < TabsContent value = "notifications" className = "mt-6" >
210+ { priceAlertNotifications . length === 0 ? (
211+ < Card >
212+ < CardContent className = "py-12 text-center" >
213+ < BellRing className = "w-12 h-12 mx-auto mb-4 text-gray-400" />
214+ < h3 className = "text-lg font-semibold mb-2" > No notifications</ h3 >
215+ < p className = "text-gray-600 dark:text-gray-400" >
216+ You'll see notifications here when your price alerts are triggered.
217+ </ p >
218+ </ CardContent >
219+ </ Card >
220+ ) : (
221+ < >
222+ < div className = "flex justify-end mb-4" >
223+ < Button
224+ variant = "outline"
225+ size = "sm"
226+ onClick = { ( ) => {
227+ clearAllPriceAlertNotifications ( ) ;
228+ toast . success ( 'All notifications cleared' ) ;
229+ } }
230+ >
231+ Clear All
232+ </ Button >
233+ </ div >
234+ < div className = "space-y-3" >
235+ { priceAlertNotifications . map ( ( notification ) => (
236+ < Card
237+ key = { notification . id }
238+ className = { ! notification . isRead ? 'border-l-4 border-l-blue-500' : '' }
239+ >
240+ < CardContent className = "p-4" >
241+ < div className = "flex items-start justify-between" >
242+ < div className = "flex items-start gap-3" >
243+ { getAlertTypeIcon ( notification . alertType ) }
244+ < div >
245+ < p className = "font-medium" > { notification . message } </ p >
246+ < p className = "text-sm text-gray-500 mt-1" >
247+ { new Date ( notification . createdAt ) . toLocaleString ( ) }
248+ </ p >
249+ </ div >
250+ </ div >
251+ < div className = "flex items-center gap-2" >
252+ { ! notification . isRead && (
253+ < Button
254+ variant = "ghost"
255+ size = "sm"
256+ onClick = { ( ) => handleMarkAsRead ( notification . id ) }
257+ >
258+ Mark as read
259+ </ Button >
260+ ) }
261+ < Button
262+ variant = "ghost"
263+ size = "icon"
264+ onClick = { ( ) => handleClearNotification ( notification . id ) }
265+ >
266+ < X className = "w-4 h-4" />
267+ </ Button >
268+ </ div >
269+ </ div >
270+ </ CardContent >
271+ </ Card >
272+ ) ) }
273+ </ div >
274+ </ >
275+ ) }
276+ </ TabsContent >
277+
278+ { /* Settings Tab */ }
279+ < TabsContent value = "settings" className = "mt-6" >
280+ < Card >
281+ < CardHeader >
282+ < CardTitle > Notification Settings</ CardTitle >
283+ </ CardHeader >
284+ < CardContent className = "space-y-6" >
285+ { /* Email Settings */ }
286+ < div className = "space-y-4" >
287+ < div className = "flex items-center justify-between" >
288+ < div className = "flex items-center gap-2" >
289+ { settings . emailEnabled ? (
290+ < Mail className = "w-5 h-5 text-green-600" />
291+ ) : (
292+ < MailOff className = "w-5 h-5 text-gray-400" />
293+ ) }
294+ < div >
295+ < Label > Email Notifications</ Label >
296+ < p className = "text-sm text-gray-500" >
297+ Receive alerts via email when triggered
298+ </ p >
299+ </ div >
300+ </ div >
301+ < Switch
302+ checked = { settings . emailEnabled }
303+ onCheckedChange = { ( checked ) => updateSettings ( { emailEnabled : checked } ) }
304+ />
305+ </ div >
306+
307+ { settings . emailEnabled && (
308+ < div className = "ml-7" >
309+ < Label htmlFor = "email" > Email Address</ Label >
310+ < Input
311+ id = "email"
312+ type = "email"
313+ placeholder = "your@email.com"
314+ value = { settings . email }
315+ onChange = { ( e ) => updateSettings ( { email : e . target . value } ) }
316+ className = "max-w-md"
317+ />
318+ </ div >
319+ ) }
320+ </ div >
321+
322+ < hr />
323+
324+ { /* In-App Settings */ }
325+ < div className = "flex items-center justify-between" >
326+ < div className = "flex items-center gap-2" >
327+ < Bell className = "w-5 h-5 text-blue-600" />
328+ < div >
329+ < Label > In-App Notifications</ Label >
330+ < p className = "text-sm text-gray-500" >
331+ Show notifications in the app
332+ </ p >
333+ </ div >
334+ </ div >
335+ < Switch
336+ checked = { settings . inAppEnabled }
337+ onCheckedChange = { ( checked ) => updateSettings ( { inAppEnabled : checked } ) }
338+ />
339+ </ div >
340+
341+ < hr />
342+
343+ { /* Default Frequency */ }
344+ < div className = "space-y-2" >
345+ < Label > Default Notification Frequency</ Label >
346+ < div className = "flex gap-2" >
347+ { [ 'instant' , 'daily' , 'weekly' ] . map ( ( freq ) => (
348+ < Button
349+ key = { freq }
350+ variant = { settings . defaultFrequency === freq ? 'default' : 'outline' }
351+ size = "sm"
352+ onClick = { ( ) => updateSettings ( {
353+ defaultFrequency : freq as 'instant' | 'daily' | 'weekly'
354+ } ) }
355+ >
356+ { freq . charAt ( 0 ) . toUpperCase ( ) + freq . slice ( 1 ) }
357+ </ Button >
358+ ) ) }
359+ </ div >
360+ </ div >
361+ </ CardContent >
362+ </ Card >
363+ </ TabsContent >
364+ </ Tabs >
365+ </ div >
366+ </ div >
367+ </ div >
368+ ) ;
369+ }
0 commit comments