diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index b7d4ebb..bd71ff2 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -25,7 +25,7 @@ import { useTheme } from "@/components/theme/theme-provider"; import { NotificationBell } from "@/components/shared/notification-bell"; export function Header() { - const { isAuthenticated, walletAddress } = useAuthStore(); + const { isAuthenticated, walletAddress, disconnect } = useAuthStore(); const pathname = usePathname(); const [mobileOpen, setMobileOpen] = useState(false); const { resolvedTheme, toggleTheme } = useTheme(); @@ -80,16 +80,23 @@ export function Header() { My Account - - - Profile + + + + Profile + - - - Settings + + + + Settings + - + disconnect()} + className="text-red-600 focus:text-red-700 cursor-pointer" + > Log out diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx index 0142cdf..b630232 100644 --- a/src/components/layout/sidebar.tsx +++ b/src/components/layout/sidebar.tsx @@ -5,37 +5,140 @@ import { usePathname } from "next/navigation"; import { useAuthStore } from "@/store/auth-store"; import { cn } from "@/lib/utils/cn"; import { isNavLinkActive, navLinks } from "./nav-links"; +import { useState, useEffect } from "react"; +import { ChevronLeft, ChevronRight, Menu, X } from "lucide-react"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; export function Sidebar() { const pathname = usePathname(); const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const hasHydrated = useAuthStore((s) => s.hasHydrated); + const [isCollapsed, setIsCollapsed] = useState(false); + const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); + + // Close mobile drawer on route change + useEffect(() => { + setMobileDrawerOpen(false); + }, [pathname]); if (!hasHydrated || !isAuthenticated) return null; return ( - + <> + {/* Mobile Drawer Trigger Button */} +
+ +
+ + {/* Mobile Drawer Backdrop Overlay */} + {mobileDrawerOpen && ( +
setMobileDrawerOpen(false)} + /> + )} + + {/* Mobile Drawer Content */} + + + {/* Desktop Sidebar */} + + ); } diff --git a/src/components/wallet/balance-display.tsx b/src/components/wallet/balance-display.tsx index e8bdce4..4400672 100644 --- a/src/components/wallet/balance-display.tsx +++ b/src/components/wallet/balance-display.tsx @@ -2,9 +2,11 @@ import { useRewards } from "@/lib/hooks/use-rewards"; import { formatTokenBalance } from "@/lib/utils/format"; -import { Coins, TrendingUp } from "lucide-react"; +import { Coins, ExternalLink, Copy, Check } from "lucide-react"; import { cn } from "@/lib/utils/cn"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { useState, useEffect } from "react"; +import Link from "next/link"; interface BalanceDisplayProps { className?: string; @@ -13,16 +15,39 @@ interface BalanceDisplayProps { export function BalanceDisplay({ className, compact = false }: BalanceDisplayProps) { const { balances, loading } = useRewards(); + const [usdRate, setUsdRate] = useState(null); + const [priceLoading, setPriceLoading] = useState(true); + const [copied, setCopied] = useState(false); const primaryBalance = balances[0]; - const balance = primaryBalance - ? formatTokenBalance(primaryBalance.balance, primaryBalance.decimals) - : "0"; + const tokenCode = primaryBalance?.tokenCode || "LEARN"; + const contractAddress = primaryBalance?.contractAddress || "CD5...LEARNCONTRACT"; + const balanceVal = primaryBalance ? primaryBalance.balance : 0; + const balanceStr = formatTokenBalance(balanceVal, primaryBalance?.decimals || 7); - if (loading) { + useEffect(() => { + // Simulate/fetch USD price conversion rate + setPriceLoading(true); + const timer = setTimeout(() => { + setUsdRate(0.15); // 1 LEARN = $0.15 USD + setPriceLoading(false); + }, 500); + return () => clearTimeout(timer); + }, [primaryBalance]); + + const handleCopyContract = (e: React.MouseEvent) => { + e.stopPropagation(); + navigator.clipboard.writeText(contractAddress); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const usdValue = usdRate !== null ? (balanceVal * usdRate).toFixed(2) : "0.00"; + + if (loading || priceLoading) { return (
-
+
); } @@ -33,41 +58,69 @@ export function BalanceDisplay({ className, compact = false }: BalanceDisplayPro
- {balance} - {primaryBalance?.tokenCode || "LEARN"} + {balanceStr} + {tokenCode} + ($ {usdValue})
-

Available {primaryBalance?.tokenCode || "LEARN"} balance

+

Available {tokenCode} balance (${usdValue} USD)

); } return ( - - -
+
+
+ + Token Balance +
+ -
- - Token Balance -
+ View Transactions + +
+ +
+
- {balance} - - {primaryBalance?.tokenCode || "LEARN"} - + {balanceStr} + {tokenCode} +
+
+ ≈ ${usdValue} USD
- - -

Your total {primaryBalance?.tokenCode || "LEARN"} balance

-
- + +
+ +
+
+
); } diff --git a/src/components/wallet/connect-button.tsx b/src/components/wallet/connect-button.tsx index 4d9ddfc..024ad2d 100644 --- a/src/components/wallet/connect-button.tsx +++ b/src/components/wallet/connect-button.tsx @@ -1,28 +1,28 @@ "use client"; import { useAuth } from "@/lib/hooks/use-auth"; +import { useAuthStore } from "@/store/auth-store"; import { Button } from "@/components/ui/button"; -import { Wallet, LogOut, Loader2 } from "lucide-react"; +import { Wallet, LogOut, Loader2, Copy, Check, Globe, RefreshCw } from "lucide-react"; import { truncateAddress } from "@/lib/utils/format"; import { useState, useRef, useEffect, useCallback } from "react"; export function ConnectButton() { const { isAuthenticated, walletAddress, isConnecting, connectWallet, disconnect } = useAuth(); - const [showDisconnect, setShowDisconnect] = useState(false); + const { network, setNetwork } = useAuthStore(); + const [showDropdown, setShowDropdown] = useState(false); + const [copied, setCopied] = useState(false); const dropdownRef = useRef(null); - const disconnectButtonRef = useRef(null); const triggerRef = useRef(null); const closeDropdown = useCallback(() => { - setShowDisconnect(false); + setShowDropdown(false); triggerRef.current?.focus(); }, []); useEffect(() => { - if (!showDisconnect) return; - - disconnectButtonRef.current?.focus(); + if (!showDropdown) return; const handleClickOutside = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { @@ -33,25 +33,6 @@ export function ConnectButton() { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { closeDropdown(); - return; - } - - if (e.key === "Tab") { - const focusableElements = dropdownRef.current?.querySelectorAll( - "button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])" - ); - if (!focusableElements || focusableElements.length === 0) return; - - const firstElement = focusableElements[0]; - const lastElement = focusableElements[focusableElements.length - 1]; - - if (e.shiftKey && document.activeElement === firstElement) { - e.preventDefault(); - lastElement.focus(); - } else if (!e.shiftKey && document.activeElement === lastElement) { - e.preventDefault(); - firstElement.focus(); - } } }; @@ -61,7 +42,20 @@ export function ConnectButton() { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", handleKeyDown); }; - }, [showDisconnect, closeDropdown]); + }, [showDropdown, closeDropdown]); + + const handleCopyAddress = () => { + if (walletAddress) { + navigator.clipboard.writeText(walletAddress); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + const handleToggleNetwork = () => { + const nextNetwork = network === "testnet" ? "public" : "testnet"; + setNetwork(nextNetwork); + }; if (isAuthenticated && walletAddress) { return ( @@ -70,29 +64,89 @@ export function ConnectButton() { ref={triggerRef} variant="outline" size="sm" - onClick={() => setShowDisconnect(!showDisconnect)} - aria-expanded={showDisconnect} + onClick={() => setShowDropdown(!showDropdown)} + aria-expanded={showDropdown} aria-haspopup="true" - className="gap-2" + className="gap-2 font-mono" > -
- {truncateAddress(walletAddress)} + + Freighter + + + {network} + + + + {truncateAddress(walletAddress)} + - {showDisconnect && ( -
- + {showDropdown && ( +
+
+
Connected Wallet
+
+ Freighter Wallet + + ACTIVE + +
+
+ {walletAddress} +
+
+ +
+ + + + +
+ + +
)}
@@ -104,7 +158,7 @@ export function ConnectButton() { onClick={connectWallet} disabled={isConnecting} size="sm" - className="gap-2" + className="gap-2 font-medium" > {isConnecting ? ( <>