-
Notifications
You must be signed in to change notification settings - Fork 0
feat(FRO-8): implement smart price target alerts frontend components … #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| import React, { useEffect } from 'react'; | ||
| import { X, Bell, Trash2, Plus, TrendingUp, TrendingDown, RefreshCw } from 'lucide-react'; | ||
| import { usePriceAlertStore } from '@/stores/priceAlertStore'; | ||
| import '@/styles/components/price-alerts.css'; | ||
|
|
||
| export const ActiveAlertsDrawer: React.FC = () => { | ||
| const { | ||
| alerts, | ||
| isDrawerOpen, | ||
| setDrawerOpen, | ||
| fetchAlerts, | ||
| removeAlert, | ||
| openModal, | ||
| isLoading, | ||
| } = usePriceAlertStore(); | ||
|
|
||
| useEffect(() => { | ||
| if (isDrawerOpen) { | ||
| fetchAlerts(); | ||
| } | ||
| }, [isDrawerOpen, fetchAlerts]); | ||
|
|
||
| if (!isDrawerOpen) return null; | ||
|
|
||
| const activeAlerts = alerts.filter((a) => !a.is_triggered); | ||
| const triggeredAlerts = alerts.filter((a) => a.is_triggered); | ||
|
|
||
| const formatDate = (dateStr?: string | null) => { | ||
| if (!dateStr) return ''; | ||
| const d = new Date(dateStr); | ||
| return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="vercel-drawer-backdrop" onClick={() => setDrawerOpen(false)}> | ||
| <div className="vercel-drawer-content" onClick={(e) => e.stopPropagation()}> | ||
| {/* Drawer Header */} | ||
| <div className="vercel-drawer-header"> | ||
| <div className="vercel-drawer-title-group"> | ||
| <Bell size={18} strokeWidth={2.5} style={{ color: '#00e599' }} /> | ||
| <h3 className="vercel-drawer-title">Price Target Alarms</h3> | ||
| <span className="vercel-drawer-count-badge"> | ||
| {activeAlerts.length} / 10 Active | ||
| </span> | ||
| </div> | ||
|
|
||
| <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> | ||
| <button | ||
| className="vercel-icon-btn" | ||
| onClick={() => fetchAlerts()} | ||
| title="Refresh Alerts" | ||
| disabled={isLoading} | ||
| > | ||
| <RefreshCw size={14} className={isLoading ? 'spin' : ''} /> | ||
| </button> | ||
| <button | ||
| className="vercel-drawer-close-btn" | ||
| onClick={() => setDrawerOpen(false)} | ||
| title="Close Drawer" | ||
| > | ||
| <X size={16} strokeWidth={2} /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Drawer Subheader Action */} | ||
| <div className="vercel-drawer-subbar"> | ||
| <button | ||
| className="vercel-btn-create-alert" | ||
| onClick={() => { | ||
| openModal(); | ||
| }} | ||
| > | ||
| <Plus size={14} strokeWidth={3} /> | ||
| <span>Create New Price Alarm</span> | ||
| </button> | ||
| </div> | ||
|
|
||
| {/* Alerts List */} | ||
| <div className="vercel-drawer-body"> | ||
| {isLoading && alerts.length === 0 ? ( | ||
| <div className="vercel-drawer-empty">Loading active price alarms...</div> | ||
| ) : alerts.length === 0 ? ( | ||
| <div className="vercel-drawer-empty"> | ||
| <Bell size={32} strokeWidth={1.5} style={{ color: '#444444', marginBottom: '12px' }} /> | ||
| <p style={{ margin: 0, fontWeight: 600, color: '#ededed' }}>No Price Alarms Set</p> | ||
| <p style={{ margin: '4px 0 0 0', fontSize: '12px', color: '#888888' }}> | ||
| Set target price alarms for your stocks to receive instant Web and Email notifications. | ||
| </p> | ||
| </div> | ||
| ) : ( | ||
| <div className="vercel-alerts-list"> | ||
| {/* Active Alarms Section */} | ||
| {activeAlerts.length > 0 && ( | ||
| <div className="vercel-alert-group"> | ||
| <h4 className="vercel-group-label">Active Alarms ({activeAlerts.length})</h4> | ||
| {activeAlerts.map((alert) => ( | ||
| <div key={alert.id} className="vercel-alert-item"> | ||
| <div className="vercel-item-left"> | ||
| <div className="vercel-symbol-avatar"> | ||
| {alert.symbol.substring(0, 2)} | ||
| </div> | ||
| <div> | ||
| <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> | ||
| <span className="vercel-symbol-name">{alert.symbol}</span> | ||
| <span className={`vercel-condition-badge ${alert.condition.toLowerCase()}`}> | ||
| {alert.condition === 'ABOVE' ? ( | ||
| <TrendingUp size={11} strokeWidth={3} /> | ||
| ) : ( | ||
| <TrendingDown size={11} strokeWidth={3} /> | ||
| )} | ||
| {alert.condition} ${alert.target_price.toFixed(2)} | ||
| </span> | ||
| </div> | ||
| <span className="vercel-alert-time">Set {formatDate(alert.created_at)}</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <button | ||
| className="vercel-alert-delete-btn" | ||
| onClick={() => removeAlert(alert.id)} | ||
| title="Delete Alert" | ||
| > | ||
| <Trash2 size={14} strokeWidth={2} /> | ||
| </button> | ||
|
Comment on lines
+119
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Unhandled rejection on delete/dismiss failure; no error surfaced.
🐛 Proposed fix- const {
- alerts,
- isDrawerOpen,
- setDrawerOpen,
- fetchAlerts,
- removeAlert,
- openModal,
- isLoading,
- } = usePriceAlertStore();
+ const {
+ alerts,
+ isDrawerOpen,
+ setDrawerOpen,
+ fetchAlerts,
+ removeAlert,
+ openModal,
+ isLoading,
+ error,
+ } = usePriceAlertStore(); <button
className="vercel-alert-delete-btn"
- onClick={() => removeAlert(alert.id)}
+ onClick={() => removeAlert(alert.id).catch(() => {})}
title="Delete Alert"(apply the same Also applies to: 152-158 🤖 Prompt for AI Agents |
||
| </div> | ||
| ))} | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Triggered Alarms Section */} | ||
| {triggeredAlerts.length > 0 && ( | ||
| <div className="vercel-alert-group" style={{ marginTop: '20px' }}> | ||
| <h4 className="vercel-group-label">Triggered History ({triggeredAlerts.length})</h4> | ||
| {triggeredAlerts.map((alert) => ( | ||
| <div key={alert.id} className="vercel-alert-item triggered"> | ||
| <div className="vercel-item-left"> | ||
| <div className="vercel-symbol-avatar triggered">🔔</div> | ||
| <div> | ||
| <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> | ||
| <span className="vercel-symbol-name">{alert.symbol}</span> | ||
| <span className="vercel-triggered-tag"> | ||
| Triggered @ ${alert.target_price.toFixed(2)} | ||
| </span> | ||
| </div> | ||
| <span className="vercel-alert-time"> | ||
| Triggered {formatDate(alert.triggered_at)} | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <button | ||
| className="vercel-alert-delete-btn" | ||
| onClick={() => removeAlert(alert.id)} | ||
| title="Dismiss Alert" | ||
| > | ||
| <Trash2 size={14} strokeWidth={2} /> | ||
| </button> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Opening the create-alert modal doesn't close the drawer.
openModal()here doesn't setisDrawerOpento false, and the store'sopenModalaction doesn't touch it either. Both the drawer and modal backdrops (position: fixed; inset: 0;, samez-index: 1100) can be open simultaneously, causing overlapping full-screen overlays.🐛 Proposed fix
<button className="vercel-btn-create-alert" onClick={() => { + setDrawerOpen(false); openModal(); }} >📝 Committable suggestion
🤖 Prompt for AI Agents