mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 17:26:23 +02:00
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:
parent
ad795eb830
commit
e4d020799b
58 changed files with 11315 additions and 661 deletions
150
surfsense_web/components/crypto/AddTokenModal.tsx
Normal file
150
surfsense_web/components/crypto/AddTokenModal.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Plus, Search, Loader2 } from "lucide-react";
|
||||
|
||||
interface AddTokenModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onAddToken: (token: { symbol: string; name: string; chain: string; contractAddress?: string }) => void;
|
||||
}
|
||||
|
||||
const SUPPORTED_CHAINS = [
|
||||
{ value: "solana", label: "Solana" },
|
||||
{ value: "ethereum", label: "Ethereum" },
|
||||
{ value: "base", label: "Base" },
|
||||
{ value: "arbitrum", label: "Arbitrum" },
|
||||
{ value: "polygon", label: "Polygon" },
|
||||
];
|
||||
|
||||
export function AddTokenModal({ open, onOpenChange, onAddToken }: AddTokenModalProps) {
|
||||
const [symbol, setSymbol] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [chain, setChain] = useState("solana");
|
||||
const [contractAddress, setContractAddress] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
if (!symbol.trim()) {
|
||||
setError("Token symbol is required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chain) {
|
||||
setError("Please select a chain");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate API call delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
onAddToken({
|
||||
symbol: symbol.toUpperCase().trim(),
|
||||
name: name.trim() || symbol.toUpperCase().trim(),
|
||||
chain,
|
||||
contractAddress: contractAddress.trim() || undefined,
|
||||
});
|
||||
|
||||
// Reset form
|
||||
setSymbol("");
|
||||
setName("");
|
||||
setChain("solana");
|
||||
setContractAddress("");
|
||||
setIsLoading(false);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Plus className="h-5 w-5" />
|
||||
Add Token to Watchlist
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="symbol">Token Symbol *</Label>
|
||||
<Input
|
||||
id="symbol"
|
||||
placeholder="e.g., BULLA, SOL, ETH"
|
||||
value={symbol}
|
||||
onChange={(e) => setSymbol(e.target.value)}
|
||||
className="uppercase"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Token Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="e.g., Bulla Token"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="chain">Chain *</Label>
|
||||
<Select value={chain} onValueChange={setChain}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select chain" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SUPPORTED_CHAINS.map((c) => (
|
||||
<SelectItem key={c.value} value={c.value}>
|
||||
{c.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="contract">Contract Address (optional)</Label>
|
||||
<Input
|
||||
id="contract"
|
||||
placeholder="0x... or token mint address"
|
||||
value={contractAddress}
|
||||
onChange={(e) => setContractAddress(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Provide contract address for accurate token identification
|
||||
</p>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
Adding...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add to Watchlist
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue