Skip to content

feat(FRO-8): implement smart price target alerts frontend components …#11

Open
Anjaldev-vk wants to merge 2 commits into
developfrom
feat/FRO-8-smart-price-target-alerts-frontend
Open

feat(FRO-8): implement smart price target alerts frontend components …#11
Anjaldev-vk wants to merge 2 commits into
developfrom
feat/FRO-8-smart-price-target-alerts-frontend

Conversation

@Anjaldev-vk

@Anjaldev-vk Anjaldev-vk commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

…and real-time audio notifications

Summary by CodeRabbit

  • New Features

    • Added price target alarms with above/below conditions and target price entry.
    • Added alarm controls from dashboards, stock holdings, charts, and market lists.
    • Added an alerts drawer showing active and triggered alarms, with refresh and removal options.
    • Added live alert counts, notifications, and an alert chime when targets are reached.
    • Added quick percentage adjustments and validation when creating alarms.
  • Style

    • Added dark-themed modal and drawer layouts with loading, empty, error, and transition states.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b1ce9d7-bbad-4b32-9394-5240516a6e0c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/FRO-8-smart-price-target-alerts-frontend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
src/styles/components/price-alerts.css (1)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keyframe names violate the project's kebab-case lint rule.

Stylelint flags fadeIn, scaleUp, and slideLeft as errors under keyframes-name-pattern.

🧹 Proposed fix
-@keyframes fadeIn {
+@keyframes fade-in {
   from { opacity: 0; }
   to { opacity: 1; }
 }

-@keyframes scaleUp {
+@keyframes scale-up {
   from { transform: scale(0.95); opacity: 0; }
   to { transform: scale(1); opacity: 1; }
 }

-@keyframes slideLeft {
+@keyframes slide-left {
   from { transform: translateX(100%); }
   to { transform: translateX(0); }
 }

Update the corresponding animation: references at lines 14, 30, and 43 to match the renamed keyframes.

Also applies to: 30-30, 43-43, 498-536

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/styles/components/price-alerts.css` at line 14, Rename the keyframes
fadeIn, scaleUp, and slideLeft to kebab-case names, and update the corresponding
animation references in the price-alert styles so each animation uses its
renamed keyframe consistently.

Source: Linters/SAST tools

src/pages/LiveMarketDashboard.tsx (1)

82-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing fetchAlerts dependency in the WebSocket effect.

Static analysis flags the useEffect (empty dep array at line 141) for using fetchAlerts without listing it as a dependency. Zustand action references are typically stable so this is low-risk in practice, but it's a one-line fix to satisfy the rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/LiveMarketDashboard.tsx` around lines 82 - 141, Update the
WebSocket useEffect dependency array to include fetchAlerts, while preserving
the existing connection and cleanup behavior. Use the fetchAlerts symbol already
referenced in the ws.onmessage handler.

Source: Linters/SAST tools

src/components/PriceAlertModal.tsx (1)

62-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Modal has no Escape-key close or focus trap.

Only the backdrop click and close button dismiss the modal; keyboard-only users can't close it with Escape, and focus isn't moved into/trapped within the dialog when it opens.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/PriceAlertModal.tsx` around lines 62 - 79, Update the
PriceAlertModal component to close when Escape is pressed and to move focus into
the modal when it opens. Add a focus trap so Tab and Shift+Tab remain within the
dialog’s interactive elements, while preserving backdrop and close-button
dismissal behavior; use the existing modal content and close button symbols as
the focus-management targets.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ActiveAlertsDrawer.tsx`:
- Around line 119-125: Update ActiveAlertsDrawer’s delete and dismiss button
handlers to catch rejected promises from the store’s removeAlert action,
preventing unhandled rejections and surfacing the failure. Destructure the store
error state and render it in the drawer body using the modal’s existing
error-banner pattern.
- Around line 68-76: Update the Create New Price Alarm button handler in
ActiveAlertsDrawer to close the drawer by setting isDrawerOpen to false before
or when invoking openModal(). Preserve the existing modal-opening behavior and
ensure only the modal overlay remains open after the click.

In `@src/components/PriceAlertModal.tsx`:
- Around line 48-52: Remove the unused err binding from the catch clause in the
addAlert flow of PriceAlertModal, leaving the existing store-managed error
handling and try/catch behavior unchanged.

In `@src/components/TradingViewChart.tsx`:
- Around line 92-95: Move the usePriceAlertStore hook call before the symbol
guard in TradingViewChart so it executes on every render. Keep the existing
early return for a missing symbol after all hooks have been invoked.

In `@src/pages/LiveMarketDashboard.tsx`:
- Around line 25-42: The playAlertChime function currently creates a new
AudioContext for every alert and never closes it. Add a lazily initialized
shared AudioContext for reuse across calls, resume it when its state is
suspended, and update playAlertChime to use that instance while preserving the
existing oscillator and error handling behavior.
- Around line 487-490: Remove the local PriceAlertModal and ActiveAlertsDrawer
mounts from LiveMarketDashboard, leaving their global mounts in App.tsx as the
sole instances. Do not alter the modal or drawer components or their shared
state behavior.

In `@src/stores/priceAlertStore.ts`:
- Around line 63-70: Update the fetchAlerts flow and the alert-mutating
operations around create/delete to track a monotonically increasing request
revision or token. Invalidate the active fetch revision whenever those
operations mutate alerts, and apply fetched data or loading/error updates only
when the response belongs to the current revision, preventing stale successes or
failures from overwriting newer state.

---

Nitpick comments:
In `@src/components/PriceAlertModal.tsx`:
- Around line 62-79: Update the PriceAlertModal component to close when Escape
is pressed and to move focus into the modal when it opens. Add a focus trap so
Tab and Shift+Tab remain within the dialog’s interactive elements, while
preserving backdrop and close-button dismissal behavior; use the existing modal
content and close button symbols as the focus-management targets.

In `@src/pages/LiveMarketDashboard.tsx`:
- Around line 82-141: Update the WebSocket useEffect dependency array to include
fetchAlerts, while preserving the existing connection and cleanup behavior. Use
the fetchAlerts symbol already referenced in the ws.onmessage handler.

In `@src/styles/components/price-alerts.css`:
- Line 14: Rename the keyframes fadeIn, scaleUp, and slideLeft to kebab-case
names, and update the corresponding animation references in the price-alert
styles so each animation uses its renamed keyframe consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e1312eda-a7b7-4a44-ad0f-2650653aca6f

📥 Commits

Reviewing files that changed from the base of the PR and between d4340d5 and ec19b49.

📒 Files selected for processing (10)
  • src/App.tsx
  • src/components/ActiveAlertsDrawer.tsx
  • src/components/PortfolioView.tsx
  • src/components/PriceAlertModal.tsx
  • src/components/TradingViewChart.tsx
  • src/features/dashboard/DashboardPage.tsx
  • src/pages/LiveMarketDashboard.tsx
  • src/services/priceAlert.service.ts
  • src/stores/priceAlertStore.ts
  • src/styles/components/price-alerts.css

Comment on lines +68 to +76
<button
className="vercel-btn-create-alert"
onClick={() => {
openModal();
}}
>
<Plus size={14} strokeWidth={3} />
<span>Create New Price Alarm</span>
</button>

Copy link
Copy Markdown

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 set isDrawerOpen to false, and the store's openModal action doesn't touch it either. Both the drawer and modal backdrops (position: fixed; inset: 0;, same z-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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
className="vercel-btn-create-alert"
onClick={() => {
openModal();
}}
>
<Plus size={14} strokeWidth={3} />
<span>Create New Price Alarm</span>
</button>
<button
className="vercel-btn-create-alert"
onClick={() => {
setDrawerOpen(false);
openModal();
}}
>
<Plus size={14} strokeWidth={3} />
<span>Create New Price Alarm</span>
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ActiveAlertsDrawer.tsx` around lines 68 - 76, Update the
Create New Price Alarm button handler in ActiveAlertsDrawer to close the drawer
by setting isDrawerOpen to false before or when invoking openModal(). Preserve
the existing modal-opening behavior and ensure only the modal overlay remains
open after the click.

Comment on lines +119 to +125
<button
className="vercel-alert-delete-btn"
onClick={() => removeAlert(alert.id)}
title="Delete Alert"
>
<Trash2 size={14} strokeWidth={2} />
</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

removeAlert in the store rethrows the error after setting error in state, but neither call site here catches the promise, and this component never destructures/render's the store's error. A failed delete produces an unhandled promise rejection and silent failure from the user's perspective.

🐛 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 .catch to the triggered-list delete button, and render error in the drawer body similar to the modal's error banner.)

Also applies to: 152-158

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ActiveAlertsDrawer.tsx` around lines 119 - 125, Update
ActiveAlertsDrawer’s delete and dismiss button handlers to catch rejected
promises from the store’s removeAlert action, preventing unhandled rejections
and surfacing the failure. Destructure the store error state and render it in
the drawer body using the modal’s existing error-banner pattern.

Comment on lines +48 to +52
try {
await addAlert(cleanSymbol, priceNum, condition);
} catch (err: any) {
// Error handles in Zustand store state
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Unused catch parameter.

Static analysis flags err as unused. Drop the binding since the store already handles the error state.

🧹 Proposed fix
     try {
       await addAlert(cleanSymbol, priceNum, condition);
-    } catch (err: any) {
+    } catch {
       // Error handles in Zustand store state
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
await addAlert(cleanSymbol, priceNum, condition);
} catch (err: any) {
// Error handles in Zustand store state
}
try {
await addAlert(cleanSymbol, priceNum, condition);
} catch {
// Error handles in Zustand store state
}
🧰 Tools
🪛 GitHub Check: Type-check · Lint · Format · Test · Build

[warning] 50-50: eslint(no-unused-vars)
Catch parameter 'err' is caught but never used.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/PriceAlertModal.tsx` around lines 48 - 52, Remove the unused
err binding from the catch clause in the addAlert flow of PriceAlertModal,
leaving the existing store-managed error handling and try/catch behavior
unchanged.

Source: Linters/SAST tools

Comment thread src/components/TradingViewChart.tsx Outdated
Comment on lines +25 to +42
const playAlertChime = () => {
try {
const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1760, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.35);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.35);
} catch (e) {
console.error('Audio chime error:', e);
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)LiveMarketDashboard\.tsx$|src/pages|package.json|tsconfig' || true

echo "== target outline =="
ast-grep outline src/pages/LiveMarketDashboard.tsx --view expanded || true

echo "== target relevant lines =="
cat -n src/pages/LiveMarketDashboard.tsx | sed -n '1,130p;475,500p'

echo "== usages of playAlertChime / useAudioContext/WebSocket handler =="
rg -n "playAlertChime|AudioContext|webkitAudioContext|onmessage|fetchAlerts|useCallback" src/pages/LiveMarketDashboard.tsx .

Repository: TradeDisciplineAI/frontned

Length of output: 8228


🌐 Web query:

MDN AudioContext close releases system audio resources browser autoplay policy AudioContext created not user gesture suspended resume

💡 Result:

The AudioContext.close method is designed to release system audio resources, such as threads, processes, and audio streams, that are used by the context [1][2]. By doing so, it stops the processing of audio data and suspends the progression of audio time [1]. Importantly, calling close does not automatically release all JavaScript objects (nodes, buffers, etc.) created within that context; these remain in memory until their references are dropped and they are garbage collected [1][3]. Once an AudioContext is closed, it cannot be resumed [4][2]. Regarding browser autoplay policies, an AudioContext created without a user gesture is typically initialized in a suspended state [5][6]. To begin audio playback, the context must be resumed [5]. This can be achieved by calling the resume method or by triggering a start call on an attached source node, usually within a handler for a user-initiated event (such as a click or keypress) [5][7]. While browser implementations may automatically suspend contexts due to autoplay restrictions, developers should use the AudioContext.state property to monitor whether the context is running or suspended and utilize the statechange event to respond to these changes asynchronously [5]. If a developer intentionally calls suspend, it is generally expected that the system will honor that state until an explicit resume is called by the application, though interaction between automatic and user-invoked suspension remains a complex area of browser-specific implementation [6].

Citations:


Avoid creating a new closed-less AudioContext for every alert chime.

Each call creates an AudioContext that is never closed, and the chime can trigger repeatedly through the WebSocket message handler. Reuse a single lazy AudioContext (resume it when state === 'suspended') instead of constructing a fresh one per alert.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/LiveMarketDashboard.tsx` around lines 25 - 42, The playAlertChime
function currently creates a new AudioContext for every alert and never closes
it. Add a lazily initialized shared AudioContext for reuse across calls, resume
it when its state is suspended, and update playAlertChime to use that instance
while preserving the existing oscillator and error handling behavior.

Comment on lines +487 to +490

{/* Price Alert Modal & Active Alerts Drawer */}
<PriceAlertModal />
<ActiveAlertsDrawer />

Copy link
Copy Markdown

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

Duplicate PriceAlertModal/ActiveAlertsDrawer mount — conflicts with the global mount in App.tsx.

App.tsx already mounts <PriceAlertModal /> and <ActiveAlertsDrawer /> globally (outside <Routes>), and this component is rendered at ROUTES.EXPLORE. Rendering both here again means two live instances of each component exist simultaneously on this route: two modal/drawer backdrops sharing the same store state, and — since each ActiveAlertsDrawer has its own useEffect(() => { if (isDrawerOpen) fetchAlerts(); }, [isDrawerOpen]) — opening the drawer fires fetchAlerts() twice.

🐛 Proposed fix
-      {/* Price Alert Modal & Active Alerts Drawer */}
-      <PriceAlertModal />
-      <ActiveAlertsDrawer />
     </div>
   );
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* Price Alert Modal & Active Alerts Drawer */}
<PriceAlertModal />
<ActiveAlertsDrawer />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/LiveMarketDashboard.tsx` around lines 487 - 490, Remove the local
PriceAlertModal and ActiveAlertsDrawer mounts from LiveMarketDashboard, leaving
their global mounts in App.tsx as the sole instances. Do not alter the modal or
drawer components or their shared state behavior.

Comment on lines +63 to +70
fetchAlerts: async () => {
set({ isLoading: true, error: null });
try {
const data = await priceAlertService.getUserAlerts();
set({ alerts: data.items, isLoading: false });
} catch (err: any) {
const msg = err.response?.data?.detail || 'Failed to fetch price alerts.';
set({ error: msg, isLoading: false });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent stale requests from reverting alert state.

A GET started at Line 66 can resolve after a later fetch, create, or delete. Line 67 then replaces newer local state—hiding a new/triggered alert or resurrecting a deleted one—and the stale failure path can also overwrite current loading/error state. Track a list request revision/token, ignore obsolete responses, and invalidate pending fetches when Lines 82-86 or Lines 101-103 mutate alerts.

Also applies to: 82-86, 98-107

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/priceAlertStore.ts` around lines 63 - 70, Update the fetchAlerts
flow and the alert-mutating operations around create/delete to track a
monotonically increasing request revision or token. Invalidate the active fetch
revision whenever those operations mutate alerts, and apply fetched data or
loading/error updates only when the response belongs to the current revision,
preventing stale successes or failures from overwriting newer state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant