feat(crypto): add SurfSense 2.0 Crypto Co-Pilot UI components

Frontend - Web Dashboard:
- Add crypto dashboard page with Watchlist, Alerts, Market, Profile tabs
- Add 11 tool-ui components for inline chat display
- Add crypto components (ChainIcon, SafetyBadge, PriceDisplay, etc.)
- Add modals (AddTokenModal, CreateAlertModal)
- Add mock data for development

Frontend - Browser Extension:
- Add shared components (ChainIcon, RiskBadge, PriceDisplay, SuggestionCard)
- Add crypto components (SafetyScoreDisplay, WatchlistPanel, AlertConfigModal)
- Add chat enhancements (WelcomeScreen, ThinkingStepsDisplay)
- Add widget components for inline display
- Enhance TokenInfoCard, ChatHeader, ChatInput, ChatInterface

Documentation:
- Add conversational UX specification
- Add UX analysis report
- Update extension UX design

This implements the Conversational UX paradigm where crypto features
are AI-callable tools that render inline in the chat interface.
This commit is contained in:
API Test Bot 2026-02-04 02:19:57 +07:00
parent ad795eb830
commit e4d020799b
58 changed files with 11315 additions and 661 deletions

View file

@ -0,0 +1,94 @@
import { cn } from "~/lib/utils";
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
export interface PriceDisplayProps {
/** Current price value */
price: string | number;
/** Price change percentage (positive = up, negative = down) */
priceChange?: number;
/** Show the change indicator arrow */
showChangeIndicator?: boolean;
/** Size variant */
size?: "sm" | "md" | "lg";
/** Additional class names */
className?: string;
}
/**
* PriceDisplay - Shows price with optional change indicator
*
* Features:
* - Color-coded price changes (green for up, red for down)
* - Animated arrow indicators
* - Multiple size variants
*/
export function PriceDisplay({
price,
priceChange,
showChangeIndicator = true,
size = "md",
className,
}: PriceDisplayProps) {
const isPositive = priceChange !== undefined && priceChange > 0;
const isNegative = priceChange !== undefined && priceChange < 0;
const isNeutral = priceChange === undefined || priceChange === 0;
const sizeClasses = {
sm: "text-sm",
md: "text-base",
lg: "text-xl font-semibold",
};
const changeClasses = {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
};
const iconSizes = {
sm: "h-3 w-3",
md: "h-4 w-4",
lg: "h-5 w-5",
};
const formatPrice = (value: string | number): string => {
if (typeof value === "string") return value;
if (value < 0.00001) return `$${value.toExponential(2)}`;
if (value < 1) return `$${value.toFixed(6)}`;
if (value < 1000) return `$${value.toFixed(2)}`;
return `$${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
};
const formatChange = (change: number): string => {
const sign = change > 0 ? "+" : "";
return `${sign}${change.toFixed(2)}%`;
};
return (
<div className={cn("flex items-center gap-2", className)}>
{/* Price */}
<span className={cn("font-medium", sizeClasses[size])}>
{formatPrice(price)}
</span>
{/* Change indicator */}
{showChangeIndicator && priceChange !== undefined && (
<div
className={cn(
"flex items-center gap-0.5",
changeClasses[size],
isPositive && "text-green-500",
isNegative && "text-red-500",
isNeutral && "text-muted-foreground"
)}
>
{isPositive && <TrendingUp className={iconSizes[size]} />}
{isNegative && <TrendingDown className={iconSizes[size]} />}
{isNeutral && <Minus className={iconSizes[size]} />}
<span>{formatChange(priceChange)}</span>
</div>
)}
</div>
);
}