diff --git a/.env.example b/.env.example index 32d71fb0..8afeccb6 100644 --- a/.env.example +++ b/.env.example @@ -146,12 +146,15 @@ RATE_LIMIT_MAX_REQUESTS=100 RATE_LIMIT_MAX_REQUESTS_PER_WALLET=50 # ----------------------------------------------------------------------------- -# Webhook Configuration # ----------------------------------------------------------------------------- +# Chainalysis Security API Configuration +# ----------------------------------------------------------------------------- +# API key for Chainalysis address/transaction risk checks. +# IMPORTANT: This is read server-side only. Never expose it to the browser. +# CHAINALYSIS_API_KEY=your_chainalysis_api_key -# Secret used to sign and verify revalidation webhook requests -# This must be set in production to secure the /api/revalidate endpoint -REVALIDATE_WEBHOOK_SECRET=your-webhook-secret +# Chainalysis API base URL (optional, uses default if not set) +# CHAINALYSIS_API_URL=https://api.chainalysis.com/api/v2 # ----------------------------------------------------------------------------- # Secret Management (Production) diff --git a/src/components/PropertyCard.tsx b/src/components/PropertyCard.tsx index d70c4534..80bfcd46 100644 --- a/src/components/PropertyCard.tsx +++ b/src/components/PropertyCard.tsx @@ -35,28 +35,24 @@ const PropertyCardInner: React.FC = ({ const compareLimitReached = selectedIds.length >= 3 && !isCompared; const { addFavorite, removeFavorite, isFavorite } = useFavoritesStore(); - const handleAddToCart = useCallback((e: React.MouseEvent) => { - e.preventDefault(); + const handleAddToCart = (e: React.MouseEvent) => { e.stopPropagation(); addItem(property, 1); }, [addItem, property]); - const handleComparisonToggle = useCallback((e: React.MouseEvent) => { - e.preventDefault(); + const handleComparisonToggle = (e: React.MouseEvent) => { e.stopPropagation(); toggleProperty(property); }, [toggleProperty, property]); - const handleCompareToggle = useCallback((e: React.MouseEvent) => { - e.preventDefault(); + const handleCompareToggle = (e: React.MouseEvent) => { e.stopPropagation(); if (!compareLimitReached) { togglePropertyId(property.id); } }, [compareLimitReached, togglePropertyId, property.id]); - const handleToggleFavorite = useCallback((e: React.MouseEvent) => { - e.preventDefault(); + const handleToggleFavorite = (e: React.MouseEvent) => { e.stopPropagation(); if (isFavorite(property.id)) { removeFavorite(property.id); @@ -196,12 +192,12 @@ const PropertyCardInner: React.FC = ({ {/* Title */} -

{property.name} -

+ {/* Location */}
@@ -293,7 +289,7 @@ const PropertyCardInner: React.FC = ({ View diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx index 39b2215f..6cacb6f2 100644 --- a/src/components/ui/chart.tsx +++ b/src/components/ui/chart.tsx @@ -77,6 +77,20 @@ function buildChartCSS(id: string, config: ChartConfig): string { if (!colorConfig.length) return '' + return Object.entries(THEMES) + .map(([theme, prefix]) => ` +${prefix} [data-chart=${id}] { +${colorConfig + .map(([key, itemConfig]) => { + const color = + itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || + itemConfig.color + return color ? ` --color-${encodeURIComponent(key)}: ${color};` : null + }) + .filter(Boolean) + .join("\n")} +} +`) const cssContent = Object.entries(THEMES) .map(([theme, prefix]) => { const rules = colorConfig @@ -92,7 +106,16 @@ function buildChartCSS(id: string, config: ChartConfig): string { }) .filter(Boolean) .join("\n") +} + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const css = React.useMemo(() => buildChartCSS(id, config), [id, config]) + + if (!css) return null + + const sanitizedCss = DOMPurify.sanitize(css) + return