From 86289043b6d4286b33825b302c02f114c116675b Mon Sep 17 00:00:00 2001 From: chidii Date: Wed, 30 Sep 2026 09:04:25 -0700 Subject: [PATCH 1/5] feat(alerts): price alert configuration UI (#1057) --- .../common/ActivePriceAlertsList.tsx | 141 +++++++++++++ src/components/common/PriceAlertButton.tsx | 71 +++++++ src/components/common/PriceAlertModal.tsx | 196 ++++++++++++++++++ src/hooks/usePriceAlerts.ts | 88 ++++++++ src/pages/CreatorDetailPage.tsx | 10 +- src/pages/ProfilePage.tsx | 1 + src/services/alert.service.ts | 114 ++++++---- 7 files changed, 577 insertions(+), 44 deletions(-) create mode 100644 src/components/common/ActivePriceAlertsList.tsx create mode 100644 src/components/common/PriceAlertButton.tsx create mode 100644 src/components/common/PriceAlertModal.tsx create mode 100644 src/hooks/usePriceAlerts.ts diff --git a/src/components/common/ActivePriceAlertsList.tsx b/src/components/common/ActivePriceAlertsList.tsx new file mode 100644 index 00000000..6ca0320e --- /dev/null +++ b/src/components/common/ActivePriceAlertsList.tsx @@ -0,0 +1,141 @@ +import { useState } from 'react'; +import { useActivePriceAlerts, useDeletePriceAlert, useUpdatePriceAlert } from '@/hooks/usePriceAlerts'; +import { PriceAlertModal } from './PriceAlertModal'; +import { Button } from '@/components/ui/button'; +import type { PriceAlert } from '@/services/alert.service'; + +export interface ActivePriceAlertsListProps { + userId: string | undefined; + /** Optional resolver so the modal can show a current price when editing. */ + resolveCurrentPrice?: (keyId: string) => number | undefined; +} + +export function ActivePriceAlertsList({ userId, resolveCurrentPrice }: ActivePriceAlertsListProps) { + const { data: alerts, isLoading, isError } = useActivePriceAlerts(userId); + const updateAlert = useUpdatePriceAlert(userId); + const deleteAlert = useDeletePriceAlert(userId); + const [editing, setEditing] = useState(null); + const [submitError, setSubmitError] = useState(null); + + const handleDelete = async (alertId: string) => { + try { + await deleteAlert.mutateAsync(alertId); + } catch (err) { + setSubmitError((err as Error).message ?? 'Could not delete alert.'); + } + }; + + const handleUpdate = async (input: { targetPrice: number; direction: 'above' | 'below' }) => { + if (!editing) return; + setSubmitError(null); + try { + await updateAlert.mutateAsync({ + alertId: editing.id, + targetPrice: input.targetPrice, + direction: input.direction, + }); + setEditing(null); + } catch (err) { + setSubmitError((err as Error).message ?? 'Could not update alert.'); + } + }; + + if (isLoading) { + return ( +
+ Loading alerts… +
+ ); + } + + if (isError) { + return ( +
+ Could not load your price alerts. Try refreshing. +
+ ); + } + + if (!alerts || alerts.length === 0) { + return ( +
+ No active price alerts. Open any creator key page and choose "Set price alert" + to add one. +
+ ); + } + + const currentPrice = editing + ? resolveCurrentPrice?.(editing.keyId) ?? editing.targetPrice + : 0; + + return ( +
+ {alerts.map(alert => ( +
+
+

+ {alert.keyTitle ?? alert.keyName ?? alert.keyId} +

+

+ Notify when price goes{' '} + {alert.direction}{' '} + {alert.targetPrice} XLM +

+
+
+ + +
+
+ ))} + + {editing && ( + { + if (!open) setEditing(null); + }} + keyId={editing.keyId} + keyName={editing.keyTitle ?? editing.keyName ?? editing.keyId} + currentPrice={currentPrice} + existingAlert={editing} + onSubmit={handleUpdate} + isSubmitting={updateAlert.isPending} + submitError={submitError} + /> + )} +
+ ); +} + +export default ActivePriceAlertsList; diff --git a/src/components/common/PriceAlertButton.tsx b/src/components/common/PriceAlertButton.tsx new file mode 100644 index 00000000..965cf384 --- /dev/null +++ b/src/components/common/PriceAlertButton.tsx @@ -0,0 +1,71 @@ +import { useState } from 'react'; +import { Bell } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { PriceAlertModal } from './PriceAlertModal'; +import { useCreatePriceAlert } from '@/hooks/usePriceAlerts'; + +export interface PriceAlertButtonProps { + userId: string | undefined; + keyId: string; + keyName: string; + currentPrice: number; + className?: string; +} + +export function PriceAlertButton({ + userId, + keyId, + keyName, + currentPrice, + className, +}: PriceAlertButtonProps) { + const [open, setOpen] = useState(false); + const [submitError, setSubmitError] = useState(null); + const createAlert = useCreatePriceAlert(userId); + + const handleSubmit = async (input: { targetPrice: number; direction: 'above' | 'below' }) => { + setSubmitError(null); + try { + await createAlert.mutateAsync({ + keyId, + keyName, + targetPrice: input.targetPrice, + direction: input.direction, + }); + setOpen(false); + } catch (err) { + setSubmitError((err as Error).message ?? 'Could not create alert.'); + } + }; + + return ( + <> + + + + ); +} + +export default PriceAlertButton; diff --git a/src/components/common/PriceAlertModal.tsx b/src/components/common/PriceAlertModal.tsx new file mode 100644 index 00000000..e70bf8ea --- /dev/null +++ b/src/components/common/PriceAlertModal.tsx @@ -0,0 +1,196 @@ +import { useEffect, useMemo, useState } from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { FormInput } from '@/components/common/FormInput'; +import { InlineValidationMessage } from '@/components/common/InlineValidationMessage'; +import type { AlertDirection, PriceAlert } from '@/services/alert.service'; + +export interface PriceAlertModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + keyId: string; + keyName: string; + currentPrice: number; + /** When provided, the modal edits this alert instead of creating a new one. */ + existingAlert?: PriceAlert | null; + onSubmit: (input: { targetPrice: number; direction: AlertDirection }) => Promise | void; + isSubmitting?: boolean; + submitError?: string | null; +} + +function validateTargetPrice( + raw: string, + direction: AlertDirection, + currentPrice: number +): string | null { + if (!raw.trim()) return 'Target price is required.'; + const parsed = Number(raw); + if (!Number.isFinite(parsed) || parsed <= 0) { + return 'Target price must be a positive number.'; + } + if (direction === 'above' && parsed <= currentPrice) { + return `An "above" alert needs a target greater than the current price (${currentPrice}).`; + } + if (direction === 'below' && parsed >= currentPrice) { + return `A "below" alert needs a target less than the current price (${currentPrice}).`; + } + return null; +} + +export function PriceAlertModal({ + open, + onOpenChange, + keyId, + keyName, + currentPrice, + existingAlert, + onSubmit, + isSubmitting = false, + submitError = null, +}: PriceAlertModalProps) { + const isEditing = Boolean(existingAlert); + const [direction, setDirection] = useState( + existingAlert?.direction ?? 'above' + ); + const [targetPrice, setTargetPrice] = useState( + existingAlert?.targetPrice != null ? String(existingAlert.targetPrice) : '' + ); + const [touched, setTouched] = useState(false); + + useEffect(() => { + if (!open) return; + setDirection(existingAlert?.direction ?? 'above'); + setTargetPrice( + existingAlert?.targetPrice != null ? String(existingAlert.targetPrice) : '' + ); + setTouched(false); + }, [open, existingAlert]); + + const validationError = useMemo( + () => validateTargetPrice(targetPrice, direction, currentPrice), + [targetPrice, direction, currentPrice] + ); + const canSubmit = validationError === null && !isSubmitting; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setTouched(true); + if (!canSubmit) return; + await onSubmit({ targetPrice: Number(targetPrice), direction }); + }; + + return ( + + +
+ + + {isEditing ? 'Edit price alert' : 'Set price alert'} + + + Get an in-app notification when {keyName}'s key price crosses + your threshold. Current price:{' '} + + {currentPrice} XLM + + . + + + +
+
+ + Direction + + + +
+ + setTargetPrice(e.target.value)} + onBlur={() => setTouched(true)} + data-testid="price-alert-target" + /> + + {touched && validationError && ( + + + {validationError} + + + )} + + {submitError && ( + + + {submitError} + + + )} +
+ + + + + +
+
+
+ ); +} + +export default PriceAlertModal; diff --git a/src/hooks/usePriceAlerts.ts b/src/hooks/usePriceAlerts.ts new file mode 100644 index 00000000..62246b07 --- /dev/null +++ b/src/hooks/usePriceAlerts.ts @@ -0,0 +1,88 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { queryKeys } from '@/lib/queryKeys'; +import { + alertService, + type AlertDirection, + type PriceAlert, +} from '@/services/alert.service'; + +export interface CreatePriceAlertInput { + keyId: string; + keyName?: string; + targetPrice: number; + direction: AlertDirection; +} + +export interface UpdatePriceAlertInput { + alertId: string; + targetPrice: number; + direction: AlertDirection; +} + +/** + * Fetch the current user's active price alerts. Polls every 60s so a + * backend-triggered alert disappears from the list promptly. + */ +export function useActivePriceAlerts(userId: string | undefined) { + return useQuery({ + queryKey: queryKeys.alerts.active(userId ?? ''), + queryFn: () => alertService.getActiveAlerts(userId as string), + enabled: Boolean(userId), + refetchInterval: 60000, + }); +} + +/** Create a price alert and refresh the active list. */ +export function useCreatePriceAlert(userId: string | undefined) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (input: CreatePriceAlertInput) => alertService.createAlert(input), + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: queryKeys.alerts.active(userId ?? ''), + }); + }, + }); +} + +/** Update an existing alert's target price and/or direction. */ +export function useUpdatePriceAlert(userId: string | undefined) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ alertId, targetPrice, direction }: UpdatePriceAlertInput) => + alertService.updateAlert(alertId, { targetPrice, direction }), + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: queryKeys.alerts.active(userId ?? ''), + }); + }, + }); +} + +/** Delete an alert. Optimistically removes it from the active list. */ +export function useDeletePriceAlert(userId: string | undefined) { + const queryClient = useQueryClient(); + const queryKey = queryKeys.alerts.active(userId ?? ''); + return useMutation({ + mutationFn: (alertId: string) => alertService.deleteAlert(alertId), + onMutate: async (alertId: string) => { + await queryClient.cancelQueries({ queryKey }); + const previous = queryClient.getQueryData(queryKey); + if (previous) { + queryClient.setQueryData( + queryKey, + previous.filter(alert => alert.id !== alertId) + ); + } + return { previous }; + }, + onError: (_err, _id, context) => { + if (context?.previous) { + queryClient.setQueryData(queryKey, context.previous); + } + }, + onSettled: () => { + void queryClient.invalidateQueries({ queryKey }); + }, + }); +} diff --git a/src/pages/CreatorDetailPage.tsx b/src/pages/CreatorDetailPage.tsx index 1d187ea4..5c32e6eb 100644 --- a/src/pages/CreatorDetailPage.tsx +++ b/src/pages/CreatorDetailPage.tsx @@ -54,6 +54,7 @@ import KeyBuybackModal from '@/components/common/KeyBuybackModal'; import type { KeyBuybackReceipt } from '@/hooks/useKeyBuyback'; import { usePerformanceBond } from '@/hooks/usePerformanceBond'; import PerformanceBondPanel from '@/components/common/PerformanceBondPanel'; +import PriceAlertButton from '@/components/common/PriceAlertButton'; function CreatorDetailPageContent() { usePurchaseConfetti(); @@ -393,7 +394,14 @@ function CreatorDetailPageContent() { /> + + {isKeyDeprecated(creator) ? ( + + ) : ( + setBuyDialogOpen(true)} + buttonProps={{ 'data-testid': 'key-detail-buy-button' }} + /> + )} + - {/* Buy Cooldown Countdown (only meaningful for authenticated users) */} {userAddress && ( )} - {/* Share to X Button (only visible for authenticated holders) */}
) : null} - {/* Staking Rewards */} - {/* Price Chart */}
- {/* Buy Simulation Tool */} - {/* Holder Concentration */}
- {/* Fee Structure */}

@@ -535,7 +525,6 @@ function CreatorDetailPageContent() {

- {/* Co-Creator Section */}{' '} - {/* Key Holders */}
- {/* Key Buyback Modal (#923) */} {isKeyDeprecated(creator) && ( ); -} +} \ No newline at end of file From f51b50a868cfd9e8185d9f9bf63fc6a6f24977b1 Mon Sep 17 00:00:00 2001 From: chidii Date: Wed, 30 Sep 2026 20:00:47 -0700 Subject: [PATCH 3/5] fix: align PriceAlertModal with FormInput and InlineValidationMessage prop shapes --- src/components/common/PriceAlertModal.tsx | 321 ++++++++++------------ 1 file changed, 152 insertions(+), 169 deletions(-) diff --git a/src/components/common/PriceAlertModal.tsx b/src/components/common/PriceAlertModal.tsx index e70bf8ea..e2bb3a0a 100644 --- a/src/components/common/PriceAlertModal.tsx +++ b/src/components/common/PriceAlertModal.tsx @@ -1,196 +1,179 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { FormInput } from '@/components/common/FormInput'; -import { InlineValidationMessage } from '@/components/common/InlineValidationMessage'; +import InlineValidationMessage from '@/components/common/InlineValidationMessage'; import type { AlertDirection, PriceAlert } from '@/services/alert.service'; export interface PriceAlertModalProps { - open: boolean; - onOpenChange: (open: boolean) => void; - keyId: string; - keyName: string; - currentPrice: number; - /** When provided, the modal edits this alert instead of creating a new one. */ - existingAlert?: PriceAlert | null; - onSubmit: (input: { targetPrice: number; direction: AlertDirection }) => Promise | void; - isSubmitting?: boolean; - submitError?: string | null; + open: boolean; + onOpenChange: (open: boolean) => void; + keyId: string; + keyName: string; + currentPrice: number; + existingAlert?: PriceAlert | null; + onSubmit: (input: { targetPrice: number; direction: AlertDirection }) => Promise | void; + isSubmitting?: boolean; + submitError?: string | null; } function validateTargetPrice( - raw: string, - direction: AlertDirection, - currentPrice: number + raw: string, + direction: AlertDirection, + currentPrice: number ): string | null { - if (!raw.trim()) return 'Target price is required.'; - const parsed = Number(raw); - if (!Number.isFinite(parsed) || parsed <= 0) { - return 'Target price must be a positive number.'; - } - if (direction === 'above' && parsed <= currentPrice) { - return `An "above" alert needs a target greater than the current price (${currentPrice}).`; - } - if (direction === 'below' && parsed >= currentPrice) { - return `A "below" alert needs a target less than the current price (${currentPrice}).`; - } - return null; + if (!raw.trim()) return 'Target price is required.'; + const parsed = Number(raw); + if (!Number.isFinite(parsed) || parsed <= 0) { + return 'Target price must be a positive number.'; + } + if (direction === 'above' && parsed <= currentPrice) { + return `An "above" alert needs a target greater than the current price (${currentPrice}).`; + } + if (direction === 'below' && parsed >= currentPrice) { + return `A "below" alert needs a target less than the current price (${currentPrice}).`; + } + return null; } export function PriceAlertModal({ - open, - onOpenChange, - keyId, - keyName, - currentPrice, - existingAlert, - onSubmit, - isSubmitting = false, - submitError = null, + open, + onOpenChange, + keyId, + keyName, + currentPrice, + existingAlert, + onSubmit, + isSubmitting = false, + submitError = null, }: PriceAlertModalProps) { - const isEditing = Boolean(existingAlert); - const [direction, setDirection] = useState( - existingAlert?.direction ?? 'above' - ); - const [targetPrice, setTargetPrice] = useState( - existingAlert?.targetPrice != null ? String(existingAlert.targetPrice) : '' - ); - const [touched, setTouched] = useState(false); + void keyId; - useEffect(() => { - if (!open) return; - setDirection(existingAlert?.direction ?? 'above'); - setTargetPrice( - existingAlert?.targetPrice != null ? String(existingAlert.targetPrice) : '' - ); - setTouched(false); - }, [open, existingAlert]); + const isEditing = Boolean(existingAlert); + const [direction, setDirection] = useState( + existingAlert?.direction ?? 'above' + ); + const [targetPrice, setTargetPrice] = useState( + existingAlert?.targetPrice != null ? String(existingAlert.targetPrice) : '' + ); + const [touched, setTouched] = useState(false); - const validationError = useMemo( - () => validateTargetPrice(targetPrice, direction, currentPrice), - [targetPrice, direction, currentPrice] - ); - const canSubmit = validationError === null && !isSubmitting; + useEffect(() => { + if (!open) return; + setDirection(existingAlert?.direction ?? 'above'); + setTargetPrice( + existingAlert?.targetPrice != null ? String(existingAlert.targetPrice) : '' + ); + setTouched(false); + }, [open, existingAlert]); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setTouched(true); - if (!canSubmit) return; - await onSubmit({ targetPrice: Number(targetPrice), direction }); - }; + const validationError = useMemo( + () => validateTargetPrice(targetPrice, direction, currentPrice), + [targetPrice, direction, currentPrice] + ); + const canSubmit = validationError === null && !isSubmitting; - return ( - - -
- - - {isEditing ? 'Edit price alert' : 'Set price alert'} - - - Get an in-app notification when {keyName}'s key price crosses - your threshold. Current price:{' '} - - {currentPrice} XLM - - . - - + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setTouched(true); + if (!canSubmit) return; + await onSubmit({ targetPrice: Number(targetPrice), direction }); + }; -
-
- - Direction - - - -
+ return ( + + + + + + {isEditing ? 'Edit price alert' : 'Set price alert'} + + + Get an in-app notification when {keyName}'s key price crosses + your threshold. Current price:{' '} + {currentPrice} XLM. + + - setTargetPrice(e.target.value)} - onBlur={() => setTouched(true)} - data-testid="price-alert-target" - /> +
+
+ Direction + + +
- {touched && validationError && ( - - - {validationError} - - - )} + { + setTargetPrice(value); + setTouched(true); + }} + error={touched && validationError ? validationError : ''} + touched={touched} + required + /> - {submitError && ( - - - {submitError} - - - )} -
+ {submitError && ( + + )} +
- - - - -
-
-
- ); + + + + + + + + ); } export default PriceAlertModal; From 491dc1ee165813400c568422388f7687598af8a0 Mon Sep 17 00:00:00 2001 From: chidii Date: Wed, 30 Sep 2026 20:02:26 -0700 Subject: [PATCH 4/5] chore: commit pending Page and Alert component changes --- .../common/ActivePriceAlertsList.tsx | 6 +-- src/components/common/PriceAlertButton.tsx | 4 +- src/pages/CreatorDetailPage.tsx | 38 +++++-------------- src/pages/ProfilePage.tsx | 15 ++++++++ 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/components/common/ActivePriceAlertsList.tsx b/src/components/common/ActivePriceAlertsList.tsx index 6ca0320e..54f4f22c 100644 --- a/src/components/common/ActivePriceAlertsList.tsx +++ b/src/components/common/ActivePriceAlertsList.tsx @@ -1,6 +1,6 @@ -import { useState } from 'react'; +import { useState } from 'react'; import { useActivePriceAlerts, useDeletePriceAlert, useUpdatePriceAlert } from '@/hooks/usePriceAlerts'; -import { PriceAlertModal } from './PriceAlertModal'; +import PriceAlertModal from './PriceAlertModal'; import { Button } from '@/components/ui/button'; import type { PriceAlert } from '@/services/alert.service'; @@ -122,7 +122,7 @@ export function ActivePriceAlertsList({ userId, resolveCurrentPrice }: ActivePri {editing && ( { + onOpenChange={(open: boolean) => { if (!open) setEditing(null); }} keyId={editing.keyId} diff --git a/src/components/common/PriceAlertButton.tsx b/src/components/common/PriceAlertButton.tsx index 965cf384..3ac93620 100644 --- a/src/components/common/PriceAlertButton.tsx +++ b/src/components/common/PriceAlertButton.tsx @@ -1,7 +1,7 @@ -import { useState } from 'react'; +import { useState } from 'react'; import { Bell } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { PriceAlertModal } from './PriceAlertModal'; +import PriceAlertModal from './PriceAlertModal'; import { useCreatePriceAlert } from '@/hooks/usePriceAlerts'; export interface PriceAlertButtonProps { diff --git a/src/pages/CreatorDetailPage.tsx b/src/pages/CreatorDetailPage.tsx index 372ad571..7c22a20c 100644 --- a/src/pages/CreatorDetailPage.tsx +++ b/src/pages/CreatorDetailPage.tsx @@ -55,9 +55,6 @@ import type { KeyBuybackReceipt } from '@/hooks/useKeyBuyback'; import { usePerformanceBond } from '@/hooks/usePerformanceBond'; import PerformanceBondPanel from '@/components/common/PerformanceBondPanel'; import PriceAlertButton from '@/components/common/PriceAlertButton'; -import TradeCooldownButton from '@/components/common/TradeCooldownButton'; -import { useTradeCooldownStatus, resolveActiveTradeCooldown } from '@/hooks/useTradeCooldownStatus'; -import { type ActiveTradeCooldown } from '@/utils/tradeCooldown.utils'; function CreatorDetailPageContent() { usePurchaseConfetti(); @@ -148,12 +145,6 @@ function CreatorDetailPageContent() { const [tradeSubmitting, setTradeSubmitting] = useState(false); const tradeMutation = useTradeMutation(userAddress ?? 'demo-wallet'); - const { data: tradeCooldownStatus } = useTradeCooldownStatus(id || ''); - const tradeCooldown: ActiveTradeCooldown | null = resolveActiveTradeCooldown( - tradeCooldownStatus, - nextBuyAllowedAt - ); - const handleConfirmBuy = async ( amount: number, _pricePreview?: unknown, @@ -389,24 +380,15 @@ function CreatorDetailPageContent() { keyName={creator.title || creator.name || 'Creator Key'} currentPrice={resolveCreatorKeyPriceStroops(creator) ?? 0} /> - {isKeyDeprecated(creator) ? ( - - ) : ( - setBuyDialogOpen(true)} - buttonProps={{ 'data-testid': 'key-detail-buy-button' }} - /> - )} + {userAddress && ( @@ -599,4 +581,4 @@ export default function CreatorDetailPage() { ); -} \ No newline at end of file +} diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index f0041d47..94c9152f 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -477,6 +477,21 @@ export default function ProfilePage() { )} + {/* Price alerts panel (#1057) */} + {activeTab === 'price-alerts' && ( +
+
+

Price Alerts

+ +
+
+ )} + {/* Activity feed panel */} {activeTab === 'activity' && (
Date: Wed, 30 Sep 2026 22:09:13 -0700 Subject: [PATCH 5/5] fix(lint): remove unused showStaleIndicator --- src/pages/CreatorDetailPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/CreatorDetailPage.tsx b/src/pages/CreatorDetailPage.tsx index cbb19868..2d6aa9d6 100644 --- a/src/pages/CreatorDetailPage.tsx +++ b/src/pages/CreatorDetailPage.tsx @@ -283,8 +283,6 @@ function CreatorDetailPageContent() { } ); - const showStaleIndicator = shouldShowBadge || isFallbackActive; - const tradeMutation = useTradeMutation(connectedWalletAddress ?? ''); const { data: tradeCooldownStatus } = useTradeCooldownStatus(id || '');