-
Notifications
You must be signed in to change notification settings - Fork 59
Feat tip toast queue and staking invariants #375
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: main
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 |
|---|---|---|
|
|
@@ -21,3 +21,5 @@ debug-assertions = false | |
| panic = "abort" | ||
| codegen-units = 1 | ||
| lto = true | ||
|
|
||
| [workspace] | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,14 +1,14 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import React, { useState, useEffect, useRef } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { Bell, Check } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useNotifications, Notification } from '../hooks/useNotifications'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { Toast, ToastProps } from './Toast'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useNotifications } from '../hooks/useNotifications'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { dismissTipToast, useTipToastQueue } from '../contexts/tipToastQueue'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { Toast } from './Toast'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const NotificationCenter: React.FC = () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { notifications, unreadCount, markAsRead, markAllAsRead } = useNotifications(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const { active: activeTipToast } = useTipToastQueue(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [isOpen, setIsOpen] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [toasts, setToasts] = useState<ToastProps[]>([]); | ||||||||||||||||||||||||||||||||||||||||||||||
| const dropdownRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| const prevNotificationsRef = useRef<Notification[]>([]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Close dropdown when clicking outside | ||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -22,30 +22,6 @@ export const NotificationCenter: React.FC = () => { | |||||||||||||||||||||||||||||||||||||||||||||
| return () => document.removeEventListener('mousedown', handleClickOutside); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Detect new notifications and show toast | ||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (prevNotificationsRef.current.length > 0 && notifications.length > prevNotificationsRef.current.length) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const newNotification = notifications[0]; | ||||||||||||||||||||||||||||||||||||||||||||||
| // Only show toast if it's new (compare timestamps or IDs if needed, but length check is simple proxy) | ||||||||||||||||||||||||||||||||||||||||||||||
| addToast({ | ||||||||||||||||||||||||||||||||||||||||||||||
| id: newNotification.id, | ||||||||||||||||||||||||||||||||||||||||||||||
| type: 'success', // Default to success for tips | ||||||||||||||||||||||||||||||||||||||||||||||
| title: newNotification.title, | ||||||||||||||||||||||||||||||||||||||||||||||
| message: newNotification.message, | ||||||||||||||||||||||||||||||||||||||||||||||
| onClose: removeToast, | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| prevNotificationsRef.current = notifications; | ||||||||||||||||||||||||||||||||||||||||||||||
| }, [notifications]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const addToast = (toast: Omit<ToastProps, 'duration'>) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| setToasts((prev) => [...prev, { ...toast, duration: 5000 }]); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const removeToast = (id: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| setToasts((prev) => prev.filter((t) => t.id !== id)); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const handleMarkAsRead = (e: React.MouseEvent, id: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| e.stopPropagation(); | ||||||||||||||||||||||||||||||||||||||||||||||
| markAsRead(id); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -55,9 +31,17 @@ export const NotificationCenter: React.FC = () => { | |||||||||||||||||||||||||||||||||||||||||||||
| <div className="relative" ref={dropdownRef}> | ||||||||||||||||||||||||||||||||||||||||||||||
| {/* Toast Container */} | ||||||||||||||||||||||||||||||||||||||||||||||
| <div className="fixed top-4 right-4 z-50 flex flex-col gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||||
| {toasts.map((toast) => ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <Toast key={toast.id} {...toast} /> | ||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||
| {activeTipToast && ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <Toast | ||||||||||||||||||||||||||||||||||||||||||||||
| id={activeTipToast.id} | ||||||||||||||||||||||||||||||||||||||||||||||
| type="tip" | ||||||||||||||||||||||||||||||||||||||||||||||
| title={activeTipToast.title} | ||||||||||||||||||||||||||||||||||||||||||||||
| message={activeTipToast.message} | ||||||||||||||||||||||||||||||||||||||||||||||
| duration={activeTipToast.duration} | ||||||||||||||||||||||||||||||||||||||||||||||
| priority={activeTipToast.priority} | ||||||||||||||||||||||||||||||||||||||||||||||
| onClose={dismissTipToast} | ||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+43
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. 🧩 Analysis chain🏁 Script executed: # First, find the NotificationCenter.tsx file and examine lines 34-43
fd -t f NotificationCenter.tsxRepository: OlufunbiIK/tip-tune Length of output: 109 🏁 Script executed: # Also find Toast.tsx
fd -t f Toast.tsxRepository: OlufunbiIK/tip-tune Length of output: 96 🏁 Script executed: # Read NotificationCenter.tsx to examine the current code around lines 34-43
cat -n frontend/src/components/NotificationCenter.tsxRepository: OlufunbiIK/tip-tune Length of output: 5511 🏁 Script executed: # Read Toast.tsx to understand its state management
cat -n frontend/src/components/Toast.tsxRepository: OlufunbiIK/tip-tune Length of output: 5185 🏁 Script executed: # Find and examine the tipToastQueue context to understand the queue flow
fd -t f tipToastQueueRepository: OlufunbiIK/tip-tune Length of output: 145 🏁 Script executed: # Examine the tipToastQueue context implementation
cat -n frontend/src/contexts/tipToastQueue.tsRepository: OlufunbiIK/tip-tune Length of output: 4996 Add a
Suggested fix {activeTipToast && (
<Toast
+ key={activeTipToast.id}
id={activeTipToast.id}
type="tip"
title={activeTipToast.title}
message={activeTipToast.message}
duration={activeTipToast.duration}
priority={activeTipToast.priority}
onClose={dismissTipToast}
/>
)}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| {/* Bell Icon */} | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
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.
Clamp overflow before returning
pending_rewards().This
unwrap_or(i128::MAX)only protects the final addition.Self::calculate_accrued(&env, &i)still mapscalculate_accrued_checked()errors to0via Line 343, so the view can under-report the same position thatstake,unstake, andclaim_rewardsnow reject withError::Overflow.Keep the read path consistent with the checked write path
pub fn pending_rewards(env: Env, artist: Address) -> i128 { match env.storage().persistent().get::<DataKey, StakeInfo>(&DataKey::Stake(artist)) { None => 0, - Some(i) => i - .pending_rewards - .checked_add(Self::calculate_accrued(&env, &i)) - .unwrap_or(i128::MAX), + Some(i) => Self::calculate_accrued_checked(&env, &i) + .ok() + .and_then(|accrued| i.pending_rewards.checked_add(accrued)) + .unwrap_or(i128::MAX), } }📝 Committable suggestion
🤖 Prompt for AI Agents