Skip to content

Commit 028d57f

Browse files
committed
refactor: enhance UI components with improved styling and accessibility
- Add comprehensive JSDoc comments and type definitions for all components - Implement consistent styling with Tailwind CSS and improved visual hierarchy - Enhance accessibility with ARIA attributes and keyboard navigation support - Add new features: tooltip positioning, recent tokens management, and network selection - Improve error handling and user feedback across all interactive components
1 parent f42b5cd commit 028d57f

6 files changed

Lines changed: 378 additions & 136 deletions

File tree

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,68 @@
1+
/**
2+
* Add Trustline Button Component.
3+
* Encapsulates the logic for establishing a Stellar trustline for a specific asset.
4+
* A trustline is mandatory before an account can hold non-native assets.
5+
*/
6+
17
import React, { useState } from "react";
28
import { toast } from "react-hot-toast";
3-
import { Plus, Check, Loader2 } from "lucide-react";
9+
import { Plus, Check, Loader2, ShieldCheck, AlertCircle } from "lucide-react";
410
import { addTrustline } from "../lib/stellar";
511
import Button from "./ui/Button";
612

13+
/**
14+
* Props for the AddTrustlineButton component.
15+
*/
716
interface AddTrustlineButtonProps {
17+
/** The 1-12 character asset code (e.g., "USDC") */
818
assetCode: string;
19+
/** The public Stellar address of the asset issuer */
920
assetIssuer: string;
1021
}
1122

1223
/**
13-
* A reusable button that establishes a Trustline for a specific Stellar asset
14-
* using the Freighter wallet extension.
24+
* A specialized button that handles the 'change_trust' operation flow.
1525
*/
1626
export default function AddTrustlineButton({ assetCode, assetIssuer }: AddTrustlineButtonProps) {
27+
// --- Component State ---
28+
/** Current status of the asynchronous trustline operation */
1729
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
1830

31+
/**
32+
* Initiates the trustline transaction flow.
33+
*/
1934
const handleAddTrustline = async () => {
35+
// Prevent redundant clicks
2036
if (status === "loading" || status === "success") return;
2137

2238
setStatus("loading");
23-
const toastId = toast.loading(`Requesting ${assetCode} trustline...`);
39+
const toastId = toast.loading(`Establishing ${assetCode} trustline...`);
2440

2541
try {
42+
// 1. Trigger the Stellar SDK / Wallet transaction
2643
await addTrustline(assetCode, assetIssuer);
2744

2845
setStatus("success");
29-
toast.success(`${assetCode} Trustline Established!`, {
46+
toast.success(`${assetCode} Trustline Active`, {
3047
id: toastId,
31-
icon: ''
48+
icon: '🛡️'
3249
});
3350

34-
// Revert to idle after 5 seconds
51+
// 2. Revert to idle after 5 seconds to reset UI
3552
setTimeout(() => setStatus("idle"), 5000);
53+
console.log(`[AddTrustline] Successfully added ${assetCode} from ${assetIssuer}`);
3654
} catch (error: any) {
37-
console.error(`[AddTrustline] Error:`, error);
55+
console.error(`[AddTrustline] Failed to add ${assetCode}:`, error);
3856
setStatus("error");
3957

40-
// Handle rejection vs generic error
41-
const errorMsg = error.message?.includes("denied")
42-
? "Access Denied by User"
43-
: `Failed to add ${assetCode}`;
58+
// User-friendly error mapping
59+
const errorMsg = error.message?.toLowerCase().includes("denied")
60+
? "Transaction rejected by user"
61+
: `Network error adding ${assetCode}`;
4462

4563
toast.error(errorMsg, { id: toastId });
4664

47-
// Revert to idle after 3 seconds to allow retry
65+
// 3. Revert to idle after a short delay to allow retry
4866
setTimeout(() => setStatus("idle"), 3000);
4967
}
5068
};
@@ -54,21 +72,36 @@ export default function AddTrustlineButton({ assetCode, assetIssuer }: AddTrustl
5472
variant="secondary"
5573
onClick={handleAddTrustline}
5674
disabled={status === "loading" || status === "success"}
57-
className={`flex items-center gap-2 text-xs py-1.5 px-3 h-auto transition-all duration-200 ${
58-
status === "success" ? "bg-green-600/20 text-green-400 border-green-600/50" : ""
75+
className={`flex items-center gap-2 text-[10px] font-black uppercase tracking-widest py-2 px-4 h-auto transition-all duration-300 border ${
76+
status === "success"
77+
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/30"
78+
: "bg-slate-800/50 text-slate-400 border-slate-700 hover:border-slate-500 hover:text-white"
5979
}`}
80+
aria-label={`Add trustline for ${assetCode}`}
6081
>
82+
{/* Dynamic Icon State */}
6183
{status === "loading" ? (
62-
<Loader2 size={14} className="animate-spin" />
84+
<Loader2 size={12} className="animate-spin text-blue-400" />
6385
) : status === "success" ? (
64-
<Check size={14} />
86+
<ShieldCheck size={12} className="text-emerald-400" />
87+
) : status === "error" ? (
88+
<AlertCircle size={12} className="text-rose-400" />
6589
) : (
66-
<Plus size={14} />
90+
<Plus size={12} className="group-hover:rotate-90 transition-transform" />
6791
)}
6892

93+
{/* Dynamic Label State */}
6994
<span>
70-
{status === "loading" ? "Signing..." : status === "success" ? "Trustline Active" : `Add ${assetCode}`}
95+
{status === "loading"
96+
? "Signing..."
97+
: status === "success"
98+
? "Established"
99+
: status === "error"
100+
? "Retry"
101+
: `Add ${assetCode}`
102+
}
71103
</span>
72104
</Button>
73105
);
74106
}
107+

src/components/ConnectWallet.tsx

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
1+
/**
2+
* Connect Wallet Component.
3+
* A standalone button/dropdown component for managing Stellar wallet sessions.
4+
* Displays the connected address and provides a disconnect option.
5+
*/
6+
17
"use client";
8+
29
import { useState, useRef, useEffect } from "react";
3-
import { connectWallet, WalletType, FREIGHTER_ID } from "../lib/stellar";
10+
import { connectWallet, WalletType, FREIGHTER_ID, shortenAddress } from "../lib/stellar";
411
import Button from "./ui/Button";
512
import { useTokenStore } from "../stores/tokenStore";
6-
import { LogOut, ChevronDown } from "lucide-react";
13+
import { LogOut, ChevronDown, User, ShieldCheck } from "lucide-react";
714

15+
/** Key for purging recent token history on logout */
816
const RECENT_TOKENS_KEY = "tradeflow_recent_tokens";
917

18+
/**
19+
* A component that handles the wallet connection flow and account display.
20+
*/
1021
export default function ConnectWallet() {
22+
// --- Component State ---
1123
const [pubKey, setPubKey] = useState<string | null>(null);
1224
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
1325
const { setConnected } = useTokenStore();
1426
const dropdownRef = useRef<HTMLDivElement>(null);
1527

16-
// Close dropdown when clicking outside
28+
/**
29+
* Effect: Closes the account dropdown when clicking outside.
30+
*/
1731
useEffect(() => {
1832
const handleClickOutside = (event: MouseEvent) => {
1933
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
@@ -24,65 +38,92 @@ export default function ConnectWallet() {
2438
return () => document.removeEventListener("mousedown", handleClickOutside);
2539
}, []);
2640

41+
/**
42+
* Initiates the wallet connection process.
43+
*
44+
* @param {WalletType} walletType - The ID of the wallet provider.
45+
*/
2746
const handleConnect = async (walletType: WalletType = FREIGHTER_ID) => {
2847
try {
2948
const userInfo = await connectWallet(walletType);
3049
if (userInfo.publicKey) {
3150
setPubKey(userInfo.publicKey);
3251
setConnected(true, userInfo.publicKey);
52+
console.log(`[ConnectWallet] Session started for: ${userInfo.publicKey}`);
3353
}
3454
} catch (e: any) {
35-
console.error("Connection error:", e);
55+
console.error("[ConnectWallet] Connection failed:", e);
56+
// TODO: Use a toast instead of native alert
3657
alert(e.message || "Failed to connect to wallet!");
3758
}
3859
};
3960

61+
/**
62+
* Terminates the session and clears related data.
63+
*/
4064
const handleDisconnect = () => {
4165
setPubKey(null);
4266
setConnected(false, undefined);
67+
// Clear local history for privacy/security
4368
localStorage.removeItem(RECENT_TOKENS_KEY);
4469
setIsDropdownOpen(false);
70+
console.log("[ConnectWallet] Session terminated.");
4571
};
4672

73+
// 1. Authenticated UI (Dropdown)
4774
if (pubKey) {
4875
return (
4976
<div className="relative" ref={dropdownRef}>
5077
<button
5178
onClick={() => setIsDropdownOpen((prev) => !prev)}
52-
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 text-sm font-medium transition-colors"
79+
className="flex items-center gap-2.5 px-4 py-2.5 rounded-xl bg-slate-800/80 hover:bg-slate-700/80 border border-slate-700 text-slate-200 text-sm font-bold transition-all shadow-sm active:scale-[0.98]"
80+
aria-haspopup="menu"
81+
aria-expanded={isDropdownOpen}
5382
>
54-
<span className="w-2 h-2 rounded-full bg-green-400 shrink-0" />
55-
{`${pubKey.slice(0, 4)}...${pubKey.slice(-4)}`}
56-
<ChevronDown size={14} className={`transition-transform ${isDropdownOpen ? "rotate-180" : ""}`} />
83+
<div className="w-2 h-2 rounded-full bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.5)] shrink-0" />
84+
<span className="font-mono tracking-tight">{shortenAddress(pubKey, 4)}</span>
85+
<ChevronDown size={14} className={`text-slate-500 transition-transform duration-300 ${isDropdownOpen ? "rotate-180" : ""}`} />
5786
</button>
5887

5988
{isDropdownOpen && (
60-
<div className="absolute right-0 mt-2 w-48 rounded-lg bg-slate-800 border border-slate-700 shadow-xl z-50 overflow-hidden">
61-
<div className="px-3 py-2 border-b border-slate-700">
62-
<p className="text-xs text-slate-400 font-medium">Connected Wallet</p>
63-
<p className="text-xs text-slate-300 font-mono truncate mt-0.5">{pubKey}</p>
89+
<div
90+
className="absolute right-0 mt-2 w-64 rounded-2xl bg-slate-800 border border-slate-700 shadow-2xl z-50 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200"
91+
role="menu"
92+
>
93+
<div className="px-4 py-4 border-b border-slate-700 bg-slate-900/50">
94+
<div className="flex items-center gap-2 mb-1.5">
95+
<ShieldCheck size={14} className="text-blue-400" />
96+
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">Verified Account</p>
97+
</div>
98+
<p className="text-xs text-slate-200 font-mono break-all leading-relaxed">{pubKey}</p>
99+
</div>
100+
<div className="p-1.5">
101+
<button
102+
onClick={handleDisconnect}
103+
className="w-full flex items-center gap-3 px-3 py-2.5 text-sm font-bold text-rose-400 hover:bg-rose-500/10 rounded-xl transition-all group"
104+
role="menuitem"
105+
>
106+
<LogOut size={16} className="group-hover:translate-x-0.5 transition-transform" />
107+
Disconnect Wallet
108+
</button>
64109
</div>
65-
<button
66-
onClick={handleDisconnect}
67-
className="w-full flex items-center gap-2 px-3 py-2.5 text-sm text-slate-300 hover:bg-red-500/10 hover:text-red-400 transition-colors"
68-
>
69-
<LogOut size={14} />
70-
Disconnect Wallet
71-
</button>
72110
</div>
73111
)}
74112
</div>
75113
);
76114
}
77115

116+
// 2. Unauthenticated UI (CTA Button)
78117
return (
79118
<div>
80119
<Button
81120
onClick={() => handleConnect(FREIGHTER_ID)}
82-
className="bg-purple-600 hover:bg-purple-700 shadow-lg flex items-center gap-2 px-6 py-3"
121+
className="bg-indigo-600 hover:bg-indigo-500 shadow-xl shadow-indigo-500/20 flex items-center gap-2 px-7 py-3 rounded-xl font-bold uppercase tracking-widest text-xs transition-all active:scale-95"
83122
>
123+
<User size={16} />
84124
Connect Wallet
85125
</Button>
86126
</div>
87127
);
88128
}
129+

0 commit comments

Comments
 (0)