mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 09:16:22 +02:00
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.
150 lines
6.1 KiB
TypeScript
150 lines
6.1 KiB
TypeScript
"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>
|
|
);
|
|
}
|
|
|