From 5ae1b129c045b9eadd5028922f863a952d6c35c4 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Fri, 30 Jan 2026 13:47:35 +0100 Subject: [PATCH 1/4] fix: fix dark theme in wallet-sheet --- components/wallet/wallet-sheet.tsx | 235 +++++++++++++++++------------ 1 file changed, 136 insertions(+), 99 deletions(-) diff --git a/components/wallet/wallet-sheet.tsx b/components/wallet/wallet-sheet.tsx index 35fa443..5b4f5b1 100644 --- a/components/wallet/wallet-sheet.tsx +++ b/components/wallet/wallet-sheet.tsx @@ -1,41 +1,55 @@ -"use client" +"use client"; -import { useState } from "react" -import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet" -import { Button } from "@/components/ui/button" -import { Separator } from "@/components/ui/separator" -import { Wallet, Copy, Check, Shield, ExternalLink, ArrowUpRight, ArrowDownLeft } from "lucide-react" -import { WalletInfo } from "@/types/wallet" -import { truncateStellarAddress } from "@/lib/mock-wallet" -import { formatDistanceToNow } from "date-fns" +import { useState } from "react"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { + Wallet, + Copy, + Check, + Shield, + ExternalLink, + ArrowUpRight, + ArrowDownLeft, +} from "lucide-react"; +import { WalletInfo } from "@/types/wallet"; +import { truncateStellarAddress } from "@/lib/mock-wallet"; +import { formatDistanceToNow } from "date-fns"; interface WalletSheetProps { - walletInfo: WalletInfo - trigger?: React.ReactNode + walletInfo: WalletInfo; + trigger?: React.ReactNode; } export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { - const [copied, setCopied] = useState(false) + const [copied, setCopied] = useState(false); const handleCopyAddress = async () => { try { - await navigator.clipboard.writeText(walletInfo.address) - setCopied(true) - setTimeout(() => setCopied(false), 2000) + await navigator.clipboard.writeText(walletInfo.address); + setCopied(true); + setTimeout(() => setCopied(false), 2000); } catch (error) { - console.error("Failed to copy address:", error) + console.error("Failed to copy address:", error); } - } + }; - const formatCurrency = (amount: number, currency: string = 'USD') => { - if (currency === 'USD') { - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD' - }).format(amount) + const formatCurrency = (amount: number, currency: string = "USD") => { + if (currency === "USD") { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(amount); } - return `${amount.toLocaleString()} ${currency}` - } + return `${amount.toLocaleString()} ${currency}`; + }; return ( @@ -46,31 +60,36 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { )} - + +
Wallet
-
{walletInfo.displayName}
+
+ {walletInfo.displayName} +
+ {walletInfo.isConnected && (
-
+ Connected
)}
- {/* Wallet Address */} -
-
-
Address
-
+ {/* Address */} +
+
+
Address
+
{truncateStellarAddress(walletInfo.address)}
+
{walletInfo.assets.length === 0 ? ( -
- -
Your wallet is empty
-
- Your rewards will show up here when you're paid by a sponsor for a win +
+ +
+ Your wallet is empty +
+
+ Your rewards will appear here once you get paid.
) : ( @@ -157,19 +191,26 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { {walletInfo.assets.map((asset) => (
-
- {asset.tokenSymbol[0]} +
+ {asset.tokenSymbol[0]}
-
{asset.tokenSymbol}
-
{asset.tokenName}
+
+ {asset.tokenSymbol} +
+
+ {asset.tokenName} +
+
-
{asset.amount.toLocaleString()}
+
+ {asset.amount.toLocaleString()} +
{formatCurrency(asset.usdValue)}
@@ -180,29 +221,23 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { )}
- {/* Activity Section */} + {/* Activity */}
-
+ {walletInfo.recentActivity.length === 0 ? ( -
-
+
+
-
No activity yet
-
- All earnings and withdrawals from your Earn wallet will show up here. +
+ No activity yet +
+
+ Earnings and withdrawals will appear here.
) : ( @@ -210,40 +245,42 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { {walletInfo.recentActivity.slice(0, 5).map((activity) => (
-
- {activity.type === 'earning' ? ( +
+ {activity.type === "earning" ? ( ) : ( )}
-
{activity.type}
+
+ {activity.type} +
- {formatDistanceToNow(new Date(activity.date), { addSuffix: true })} + {formatDistanceToNow(new Date(activity.date), { + addSuffix: true, + })}
+
-
- {activity.type === 'earning' ? '+' : '-'} +
+ {activity.type === "earning" ? "+" : "-"} {formatCurrency(activity.amount, activity.currency)}
-
+
{activity.status}
@@ -255,11 +292,11 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { {/* Footer */} -
- Have questions? Reach out to us at{' '} +
+ Have questions?{" "} support@boundlessfi.xyz @@ -267,5 +304,5 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) {
- ) + ); } From bd3fca4a532c8732926402a85733966443f01956 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Fri, 30 Jan 2026 14:46:46 +0100 Subject: [PATCH 2/4] feat(wallet): add full wallet page with balance, assets, activity, and fiat off-ramping --- app/wallet/page.tsx | 67 +++++++++ components/global-navbar.tsx | 24 ++-- components/wallet/assets-list.tsx | 109 +++++++++++++++ components/wallet/balance-card.tsx | 90 ++++++++++++ components/wallet/security-section.tsx | 148 ++++++++++++++++++++ components/wallet/transaction-history.tsx | 122 +++++++++++++++++ components/wallet/wallet-overview.tsx | 87 ++++++++++++ components/wallet/wallet-sheet.tsx | 29 ++-- components/wallet/withdrawal-section.tsx | 159 ++++++++++++++++++++++ lib/mock-wallet.ts | 4 +- 10 files changed, 820 insertions(+), 19 deletions(-) create mode 100644 app/wallet/page.tsx create mode 100644 components/wallet/assets-list.tsx create mode 100644 components/wallet/balance-card.tsx create mode 100644 components/wallet/security-section.tsx create mode 100644 components/wallet/transaction-history.tsx create mode 100644 components/wallet/wallet-overview.tsx create mode 100644 components/wallet/withdrawal-section.tsx diff --git a/app/wallet/page.tsx b/app/wallet/page.tsx new file mode 100644 index 0000000..e4bc646 --- /dev/null +++ b/app/wallet/page.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { WalletOverview } from "@/components/wallet/wallet-overview"; +import { BalanceCard } from "@/components/wallet/balance-card"; +import { AssetsList } from "@/components/wallet/assets-list"; +import { TransactionHistory } from "@/components/wallet/transaction-history"; +import { WithdrawalSection } from "@/components/wallet/withdrawal-section"; +import { SecuritySection } from "@/components/wallet/security-section"; +import { mockWalletWithAssets } from "@/lib/mock-wallet"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +export default function WalletPage() { + return ( +
+ ); +} diff --git a/components/global-navbar.tsx b/components/global-navbar.tsx index cf45024..a1b1a04 100644 --- a/components/global-navbar.tsx +++ b/components/global-navbar.tsx @@ -29,34 +29,40 @@ export function GlobalNavbar() {
Explore Projects Leaderboard + + Wallet +
diff --git a/components/wallet/assets-list.tsx b/components/wallet/assets-list.tsx new file mode 100644 index 0000000..fe543ca --- /dev/null +++ b/components/wallet/assets-list.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { useState } from "react"; +import { WalletAsset } from "@/types/wallet"; +import { Input } from "@/components/ui/input"; +import { Search, ArrowUpDown, Filter } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +interface AssetsListProps { + assets: WalletAsset[]; +} + +export function AssetsList({ assets }: AssetsListProps) { + const [search, setSearch] = useState(""); + + const filteredAssets = assets.filter(asset => + asset.tokenName.toLowerCase().includes(search.toLowerCase()) || + asset.tokenSymbol.toLowerCase().includes(search.toLowerCase()) + ); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(amount); + }; + + return ( +
+
+
+ + setSearch(e.target.value)} + /> +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + {filteredAssets.length === 0 ? ( + + + + ) : ( + filteredAssets.map((asset) => ( + + + + + + + + )) + )} + +
AssetBalancePriceValuePortfolio %
+ No assets found matching your search. +
+
+
+ {asset.tokenSymbol} +
+
+
{asset.tokenSymbol}
+
{asset.tokenName}
+
+
+
+
{asset.amount.toLocaleString()}
+
{asset.tokenSymbol}
+
+ {formatCurrency(asset.usdValue / asset.amount)} + +
{formatCurrency(asset.usdValue)}
+
+
+ {((asset.usdValue / assets.reduce((acc, current) => acc + current.usdValue, 0)) * 100).toFixed(1)}% +
+
+
+
+
+ ); +} diff --git a/components/wallet/balance-card.tsx b/components/wallet/balance-card.tsx new file mode 100644 index 0000000..e00e05a --- /dev/null +++ b/components/wallet/balance-card.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { WalletInfo } from "@/types/wallet"; +import { ArrowUpRight, ArrowDownRight, Info } from "lucide-react"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; + +interface BalanceCardProps { + walletInfo: WalletInfo; +} + +export function BalanceCard({ walletInfo }: BalanceCardProps) { + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(amount); + }; + + // Mock data for breakdown + const pendingEarnings = 150.00; + const availableForWithdrawal = walletInfo.balance; + const change24h = 12.5; + + return ( +
+
+
+

Total Balance

+

+ {formatCurrency(walletInfo.balance + pendingEarnings)} +

+
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {change24h >= 0 ? : } + {Math.abs(change24h)}% +
+ past 24h +
+
+ +
+
+
+ Available now + + + + + + Funds ready for withdrawal or transfer + + +
+

{formatCurrency(availableForWithdrawal)}

+
+
+
+ Pending + + + + + + Earnings awaiting network or platform confirmation + + +
+

+ {formatCurrency(pendingEarnings)} +

+
+
+
+ +
+ {walletInfo.assets.map((asset) => ( +
+
+ {asset.tokenSymbol} +
+
+

{asset.amount.toLocaleString()} {asset.tokenSymbol}

+

{formatCurrency(asset.usdValue)}

+
+
+ ))} +
+
+ ); +} diff --git a/components/wallet/security-section.tsx b/components/wallet/security-section.tsx new file mode 100644 index 0000000..3f9d060 --- /dev/null +++ b/components/wallet/security-section.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { WalletInfo } from "@/types/wallet"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; +import { Shield, Smartphone, Key, CircleAlert, CheckCircle2, ListFilter, MonitorPlay } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; + +interface SecuritySectionProps { + walletInfo: WalletInfo; +} + +export function SecuritySection({ walletInfo }: SecuritySectionProps) { + return ( +
+
+
+
+

Security Settings

+

Manage how your wallet and funds are protected.

+
+ +
+
+
+
+ +
+
+
Two-Factor Authentication
+
Use an authenticator app for withdrawals
+
+
+
+ + {walletInfo.has2FA ? "Enabled" : "Disabled"} + + +
+
+ +
+
+
+ +
+
+
Withdrawal Whitelist
+
Only allow withdrawals to verified addresses
+
+
+ +
+ +
+
+
+ +
+
+
Transaction Signing
+
Require approval for large movements
+
+
+ +
+
+
+ +
+
+

Active Sessions

+

Devices currently logged into your wallet.

+
+ +
+
+
+ +
This Computer
+
+ Current +
+
+
+
+
Browser
+
Chrome on macOS
+
+
+
IP Address
+
192.168.1.1
+
+
+
Location
+
San Francisco, CA
+
+
+
Last Active
+
Just now
+
+
+ +
+
+ +
+ +
+
Security Score: 85/100
+

Your account is well protected. Add 2FA to reach 100.

+
+
+
+
+ + + +
+
+

Recent Security Activity

+ +
+
+
+
+ Successful login from new device + 2 hours ago +
+
+
+ Password successfully changed + 3 days ago +
+
+ + Failed login attempt (mismatched password) + 5 days ago +
+
+
+
+ ); +} diff --git a/components/wallet/transaction-history.tsx b/components/wallet/transaction-history.tsx new file mode 100644 index 0000000..301fe79 --- /dev/null +++ b/components/wallet/transaction-history.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useState } from "react"; +import { WalletActivity } from "@/types/wallet"; +import { Input } from "@/components/ui/input"; +import { Search, Download, ArrowUpRight, ArrowDownLeft, ExternalLink } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { format } from "date-fns"; +import { Badge } from "@/components/ui/badge"; + +interface TransactionHistoryProps { + activity: WalletActivity[]; +} + +export function TransactionHistory({ activity }: TransactionHistoryProps) { + const [search, setSearch] = useState(""); + + const filteredActivity = activity.filter(item => + item.description?.toLowerCase().includes(search.toLowerCase()) || + item.type.toLowerCase().includes(search.toLowerCase()) || + item.currency.toLowerCase().includes(search.toLowerCase()) + ); + + const formatCurrency = (amount: number, currency: string) => { + if (currency === "USD" || currency === "USDC") { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(amount); + } + return `${amount.toLocaleString()} ${currency}`; + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'completed': return 'bg-green-500/10 text-green-500 border-green-500/20'; + case 'pending': return 'bg-amber-500/10 text-amber-500 border-amber-500/20'; + case 'failed': return 'bg-red-500/10 text-red-500 border-red-500/20'; + default: return 'bg-muted text-muted-foreground'; + } + }; + + return ( +
+
+
+ + setSearch(e.target.value)} + /> +
+
+ +
+
+ +
+
+ + + + + + + + + + + + {filteredActivity.length === 0 ? ( + + + + ) : ( + filteredActivity.map((item) => ( + + + + + + + + )) + )} + +
TypeDescriptionAmountDateStatus
+ No activity found. +
+
+
+ {item.type === 'earning' ? ( + + ) : ( + + )} +
+ {item.type} +
+
+ {item.description || 'No description'} + + + {item.type === 'earning' ? '+' : '-'} {formatCurrency(item.amount, item.currency)} + + + {format(new Date(item.date), 'MMM d, yyyy')} + + + {item.status} + +
+
+
+
+ ); +} diff --git a/components/wallet/wallet-overview.tsx b/components/wallet/wallet-overview.tsx new file mode 100644 index 0000000..6ebaa17 --- /dev/null +++ b/components/wallet/wallet-overview.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useState } from "react"; +import { WalletInfo } from "@/types/wallet"; +import { truncateStellarAddress } from "@/lib/mock-wallet"; +import { Button } from "@/components/ui/button"; +import { Copy, Check, ExternalLink, ShieldCheck, Calendar } from "lucide-react"; + +interface WalletOverviewProps { + walletInfo: WalletInfo; +} + +export function WalletOverview({ walletInfo }: WalletOverviewProps) { + const [copied, setCopied] = useState(false); + + const handleCopyAddress = async () => { + try { + await navigator.clipboard.writeText(walletInfo.address); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (error) { + console.error("Failed to copy address:", error); + } + }; + + return ( +
+
+

Account Information

+ +
+
+
Status
+
+ + Active & Secured +
+
+ +
+
Wallet Address
+
+
+ {walletInfo.address} +
+ +
+
+ +
+
+ + Abstracted Wallet +
+
No setup required
+
+ +
+
+ + Created +
+
May 2024
+
+
+
+ + +
+ ); +} diff --git a/components/wallet/wallet-sheet.tsx b/components/wallet/wallet-sheet.tsx index 5b4f5b1..6234f3c 100644 --- a/components/wallet/wallet-sheet.tsx +++ b/components/wallet/wallet-sheet.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import Link from "next/link"; import { Sheet, SheetContent, @@ -134,13 +135,13 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) {
@@ -168,10 +169,10 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { className="h-auto p-0 text-xs text-primary" asChild > - + View More - + )}
@@ -194,7 +195,7 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) { className="flex items-center justify-between rounded-lg bg-muted p-3 transition-colors hover:bg-muted/70" >
-
+
{asset.tokenSymbol[0]}
@@ -226,6 +227,18 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) {

Activity

+ {walletInfo.recentActivity.length > 0 && ( + + )}
{walletInfo.recentActivity.length === 0 ? ( @@ -250,8 +263,8 @@ export function WalletSheet({ walletInfo, trigger }: WalletSheetProps) {
{activity.type === "earning" ? ( diff --git a/components/wallet/withdrawal-section.tsx b/components/wallet/withdrawal-section.tsx new file mode 100644 index 0000000..1be0ab6 --- /dev/null +++ b/components/wallet/withdrawal-section.tsx @@ -0,0 +1,159 @@ +"use client"; + +import { useState } from "react"; +import { WalletInfo } from "@/types/wallet"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Building, Plus, ArrowRight, Wallet, History, Info } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; + +interface WithdrawalSectionProps { + walletInfo: WalletInfo; +} + +export function WithdrawalSection({ walletInfo }: WithdrawalSectionProps) { + const [amount, setAmount] = useState(""); + + // Mock bank accounts + const bankAccounts = [ + { id: '1', name: 'Chase Bank', last4: '4242', isPrimary: true }, + ]; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(amount); + }; + + return ( +
+
+
+

Off-Ramp to Fiat

+

+ Withdraw your crypto earnings directly to your bank account via our secure payment partners. +

+ +
+
+ +
+ $ + setAmount(e.target.value)} + /> + +
+
+ Balance: {formatCurrency(walletInfo.balance)} + Min: $10.00 +
+
+ +
+ +
+ {bankAccounts.map((account) => ( +
+
+
+ +
+
+
{account.name}
+
Ending in {account.last4}
+
+
+ {account.isPrimary && Primary} +
+ ))} + +
+
+ +
+
+ Exchange Rate (Est) + 1 USDC = $1.00 USD +
+
+ Processing Fee + $2.50 +
+ +
+ You'll Receive + {formatCurrency(Math.max(0, (parseFloat(amount) || 0) - 2.50))} +
+
+ + +
+
+ +
+
+
+ +

Withdrawal History

+
+ +
+ {/* Empty state or items */} +
+ +

No recent withdrawals

+

Your payout history will appear here.

+
+
+ + + +
+
+ +

Limits & Settings

+
+
+
+ Daily Limit + $5,000 / $5,000 +
+
+
+
+
+ Monthly Limit + $50,000 / $50,000 +
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/lib/mock-wallet.ts b/lib/mock-wallet.ts index 1043a12..3f077b7 100644 --- a/lib/mock-wallet.ts +++ b/lib/mock-wallet.ts @@ -2,7 +2,7 @@ import { WalletInfo } from '@/types/wallet' // Mock Stellar wallet with proper address format export const mockWalletInfo: WalletInfo = { - address: 'GDJKL4MZWXZQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQ', + address: 'GC64SVY3XSYEE7MYADTEQNO3ACCWJ6NLWNNJLPO2FGS4I2PCNBPVNZOP', displayName: 'John Doe', balance: 0, balanceCurrency: 'USD', @@ -14,7 +14,7 @@ export const mockWalletInfo: WalletInfo = { // Example with assets and activity (for testing populated states) export const mockWalletWithAssets: WalletInfo = { - address: 'GDJKL4MZWXZQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQXQ', + address: 'GC64SVY3XSYEE7MYADTEQNO3ACCWJ6NLWNNJLPO2FGS4I2PCNBPVNZOP', displayName: 'Jane Smith', balance: 1250.50, balanceCurrency: 'USD', From caf43382a342a46a09c4e07537bdac84cb84b2d0 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Fri, 30 Jan 2026 15:19:51 +0100 Subject: [PATCH 3/4] fix: fix coderabbit corrections --- app/layout.tsx | 1 - app/wallet/layout.tsx | 14 ++ .../primitives/effects/theme-toggler.tsx | 17 +- components/ui/resizable-navbar.tsx | 16 +- components/wallet/assets-list.tsx | 71 +++++++-- components/wallet/security-section.tsx | 5 +- components/wallet/transaction-history.tsx | 38 ++++- components/wallet/wallet-overview.tsx | 5 +- components/wallet/withdrawal-section.tsx | 4 +- package-lock.json | 145 +++++++++++++----- 10 files changed, 236 insertions(+), 80 deletions(-) create mode 100644 app/wallet/layout.tsx diff --git a/app/layout.tsx b/app/layout.tsx index 4fbf53b..4c54c47 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,7 +3,6 @@ import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { QueryProvider } from "@/providers/query-provider"; // replaced with resizable global navbar -import GlobalResizableNavbar from "@/components/ui/global-resizable-navbar"; import { ThemeProvider } from "@/components/theme-provider"; import { GlobalNavbar } from "@/components/global-navbar"; diff --git a/app/wallet/layout.tsx b/app/wallet/layout.tsx new file mode 100644 index 0000000..c54055c --- /dev/null +++ b/app/wallet/layout.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Wallet | Bounties", + description: "Manage your abstracted wallet, view assets, track earnings, and off-ramp funds directly to your bank account.", +}; + +export default function WalletLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/components/animate-ui/primitives/effects/theme-toggler.tsx b/components/animate-ui/primitives/effects/theme-toggler.tsx index 064f7b2..d84a6fe 100644 --- a/components/animate-ui/primitives/effects/theme-toggler.tsx +++ b/components/animate-ui/primitives/effects/theme-toggler.tsx @@ -10,10 +10,10 @@ type Direction = 'btt' | 'ttb' | 'ltr' | 'rtl'; type ChildrenRender = | React.ReactNode | ((state: { - resolved: Resolved; - effective: ThemeSelection; - toggleTheme: (theme: ThemeSelection) => void; - }) => React.ReactNode); + resolved: Resolved; + effective: ThemeSelection; + toggleTheme: (theme: ThemeSelection) => void; + }) => React.ReactNode); function getSystemEffective(): Resolved { if (typeof window === 'undefined') return 'light'; @@ -53,7 +53,6 @@ function ThemeToggler({ onImmediateChange, direction = 'ltr', children, - ...props }: ThemeTogglerProps) { const [preview, setPreview] = React.useState {typeof children === 'function' ? children({ - effective: current.effective, - resolved: current.resolved, - toggleTheme, - }) + effective: current.effective, + resolved: current.resolved, + toggleTheme, + }) : children} diff --git a/components/ui/resizable-navbar.tsx b/components/ui/resizable-navbar.tsx index 04bf52f..23deca0 100644 --- a/components/ui/resizable-navbar.tsx +++ b/components/ui/resizable-navbar.tsx @@ -9,6 +9,7 @@ import { } from "motion/react"; import React, { useRef, useState } from "react"; +import Image from "next/image"; interface NavbarProps { children: React.ReactNode; @@ -73,9 +74,9 @@ export const Navbar = ({ children, className }: NavbarProps) => { {React.Children.map(children, (child) => React.isValidElement(child) ? React.cloneElement( - child as React.ReactElement<{ visible?: boolean }>, - { visible }, - ) + child as React.ReactElement<{ visible?: boolean }>, + { visible }, + ) : child, )} @@ -194,7 +195,6 @@ export const MobileNavMenu = ({ children, className, isOpen, - onClose, }: MobileNavMenuProps) => { return ( @@ -240,7 +240,7 @@ export const NavbarLogo = () => { href="#" className="relative z-20 mr-4 flex items-center space-x-2 px-2 py-1 text-sm font-normal text-black" > - Bounties logo + Bounties logo Bounties ); @@ -260,9 +260,9 @@ export const NavbarButton = ({ className?: string; variant?: "primary" | "secondary" | "dark" | "gradient"; } & ( - | React.ComponentPropsWithoutRef<"a"> - | React.ComponentPropsWithoutRef<"button"> -)) => { + | React.ComponentPropsWithoutRef<"a"> + | React.ComponentPropsWithoutRef<"button"> + )) => { const baseStyles = "px-4 py-2 rounded-md bg-white button bg-white text-black text-sm font-bold relative cursor-pointer hover:-translate-y-0.5 transition duration-200 inline-block text-center"; diff --git a/components/wallet/assets-list.tsx b/components/wallet/assets-list.tsx index fe543ca..eff0918 100644 --- a/components/wallet/assets-list.tsx +++ b/components/wallet/assets-list.tsx @@ -12,11 +12,46 @@ interface AssetsListProps { export function AssetsList({ assets }: AssetsListProps) { const [search, setSearch] = useState(""); + const [hideSmallBalances, setHideSmallBalances] = useState(false); + const [sortConfig, setSortConfig] = useState<{ + key: 'tokenSymbol' | 'amount' | 'usdValue'; + direction: 'asc' | 'desc'; + }>({ key: 'usdValue', direction: 'desc' }); - const filteredAssets = assets.filter(asset => - asset.tokenName.toLowerCase().includes(search.toLowerCase()) || - asset.tokenSymbol.toLowerCase().includes(search.toLowerCase()) - ); + const handleSort = (key: 'tokenSymbol' | 'amount' | 'usdValue') => { + setSortConfig(prev => ({ + key, + direction: prev.key === key && prev.direction === 'desc' ? 'asc' : 'desc' + })); + }; + + const filteredAndSortedAssets = [...assets] + .filter(asset => { + const matchesSearch = asset.tokenName.toLowerCase().includes(search.toLowerCase()) || + asset.tokenSymbol.toLowerCase().includes(search.toLowerCase()); + const passesFilter = !hideSmallBalances || asset.usdValue >= 1; + return matchesSearch && passesFilter; + }) + .sort((a, b) => { + const aValue = a[sortConfig.key]; + const bValue = b[sortConfig.key]; + + if (typeof aValue === 'string' && typeof bValue === 'string') { + return sortConfig.direction === 'asc' + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + } + + if (typeof aValue === 'number' && typeof bValue === 'number') { + return sortConfig.direction === 'asc' + ? aValue - bValue + : bValue - aValue; + } + + return 0; + }); + + const totalUsd = assets.reduce((acc, c) => acc + c.usdValue, 0); const formatCurrency = (amount: number) => { return new Intl.NumberFormat("en-US", { @@ -38,13 +73,25 @@ export function AssetsList({ assets }: AssetsListProps) { />
- -
@@ -62,14 +109,14 @@ export function AssetsList({ assets }: AssetsListProps) { - {filteredAssets.length === 0 ? ( + {filteredAndSortedAssets.length === 0 ? ( No assets found matching your search. ) : ( - filteredAssets.map((asset) => ( + filteredAndSortedAssets.map((asset) => (
@@ -87,14 +134,14 @@ export function AssetsList({ assets }: AssetsListProps) {
{asset.tokenSymbol}
- {formatCurrency(asset.usdValue / asset.amount)} + {formatCurrency(asset.amount ? asset.usdValue / asset.amount : 0)}
{formatCurrency(asset.usdValue)}
- {((asset.usdValue / assets.reduce((acc, current) => acc + current.usdValue, 0)) * 100).toFixed(1)}% + {(totalUsd ? (asset.usdValue / totalUsd) * 100 : 0).toFixed(1)}%
diff --git a/components/wallet/security-section.tsx b/components/wallet/security-section.tsx index 3f9d060..6a2b2b2 100644 --- a/components/wallet/security-section.tsx +++ b/components/wallet/security-section.tsx @@ -3,8 +3,7 @@ import { WalletInfo } from "@/types/wallet"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; -import { Shield, Smartphone, Key, CircleAlert, CheckCircle2, ListFilter, MonitorPlay } from "lucide-react"; +import { Smartphone, Key, CircleAlert, CheckCircle2, ListFilter, MonitorPlay } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; @@ -37,7 +36,7 @@ export function SecuritySection({ walletInfo }: SecuritySectionProps) { {walletInfo.has2FA ? "Enabled" : "Disabled"} - +
diff --git a/components/wallet/transaction-history.tsx b/components/wallet/transaction-history.tsx index 301fe79..84ec1f5 100644 --- a/components/wallet/transaction-history.tsx +++ b/components/wallet/transaction-history.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { WalletActivity } from "@/types/wallet"; import { Input } from "@/components/ui/input"; -import { Search, Download, ArrowUpRight, ArrowDownLeft, ExternalLink } from "lucide-react"; +import { Search, Download, ArrowUpRight, ArrowDownLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { format } from "date-fns"; import { Badge } from "@/components/ui/badge"; @@ -40,6 +40,34 @@ export function TransactionHistory({ activity }: TransactionHistoryProps) { } }; + const handleExportCsv = () => { + const headers = ["ID", "Type", "Description", "Amount", "Currency", "Date", "Status"]; + const rows = filteredActivity.map(item => [ + item.id, + item.type, + item.description || "", + item.amount.toString(), + item.currency, + format(new Date(item.date), 'yyyy-MM-dd HH:mm:ss'), + item.status + ]); + + const csvContent = [ + headers.join(","), + ...rows.map(row => row.map(cell => `"${cell.replace(/"/g, '""')}"`).join(",")) + ].join("\n"); + + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", `transactions_${format(new Date(), 'yyyyMMdd_HHmm')}.csv`); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + return (
@@ -53,7 +81,13 @@ export function TransactionHistory({ activity }: TransactionHistoryProps) { />
- diff --git a/components/wallet/wallet-overview.tsx b/components/wallet/wallet-overview.tsx index 6ebaa17..2d0e454 100644 --- a/components/wallet/wallet-overview.tsx +++ b/components/wallet/wallet-overview.tsx @@ -39,15 +39,16 @@ export function WalletOverview({ walletInfo }: WalletOverviewProps) {
Wallet Address
-
+
- {walletInfo.address} + {truncateStellarAddress(walletInfo.address)}
- You'll Receive + You'll Receive {formatCurrency(Math.max(0, (parseFloat(amount) || 0) - 2.50))}
diff --git a/package-lock.json b/package-lock.json index 55d5417..28656d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -174,7 +174,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@csstools/css-calc": "^2.1.3", @@ -188,7 +188,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/@babel/code-frame": { @@ -1267,7 +1267,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -1287,7 +1287,7 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -1311,7 +1311,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -1339,7 +1339,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -1362,7 +1362,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -1468,6 +1468,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1484,6 +1485,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1500,6 +1502,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1516,6 +1519,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1532,6 +1536,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1548,6 +1553,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1564,6 +1570,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1580,6 +1587,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1596,6 +1604,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1612,6 +1621,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1628,6 +1638,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1644,6 +1655,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1660,6 +1672,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1676,6 +1689,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1692,6 +1706,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1708,6 +1723,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1724,6 +1740,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1740,6 +1757,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1756,6 +1774,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1772,6 +1791,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1788,6 +1808,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1804,6 +1825,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1820,6 +1842,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1836,6 +1859,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1852,6 +1876,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1868,6 +1893,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7123,6 +7149,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7136,6 +7163,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7149,6 +7177,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7162,6 +7191,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7175,6 +7205,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7188,6 +7219,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7201,6 +7233,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7214,6 +7247,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7227,6 +7261,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7240,6 +7275,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7253,6 +7289,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7266,6 +7303,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7279,6 +7317,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7292,6 +7331,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7305,6 +7345,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7318,6 +7359,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7331,6 +7373,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7344,6 +7387,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7357,6 +7401,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7370,6 +7415,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7383,6 +7429,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7396,6 +7443,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7409,6 +7457,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7422,6 +7471,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7435,6 +7485,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -8265,7 +8316,7 @@ "version": "20.19.29", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.29.tgz", "integrity": "sha512-YrT9ArrGaHForBaCNwFjoqJWmn8G1Pr7+BH/vwyLHciA9qT/wSiuOhxGCT50JA5xLvFBd6PIiGkE3afxcPE1nw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -9137,7 +9188,7 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -10516,7 +10567,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^3.2.0", @@ -10674,7 +10725,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "whatwg-mimetype": "^4.0.0", @@ -10805,7 +10856,7 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/decimal.js-light": { @@ -11180,7 +11231,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -12317,6 +12368,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -13157,7 +13209,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "whatwg-encoding": "^3.1.1" @@ -13187,7 +13239,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -13201,7 +13253,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -13225,7 +13277,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -13764,7 +13816,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/is-regex": { @@ -15102,7 +15154,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -15140,7 +15192,7 @@ "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "cssstyle": "^4.2.1", @@ -15327,7 +15379,7 @@ "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "devOptional": true, + "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -15360,6 +15412,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15380,6 +15433,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15400,6 +15454,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15420,6 +15475,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15440,6 +15496,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15460,6 +15517,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15480,6 +15538,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15500,6 +15559,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15520,6 +15580,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15540,6 +15601,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -15560,6 +15622,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -17003,7 +17066,7 @@ "version": "2.2.23", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/object-assign": { @@ -17349,7 +17412,7 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -17718,7 +17781,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -18301,7 +18364,7 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/run-parallel": { @@ -18397,14 +18460,14 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "xmlchars": "^2.2.0" @@ -19299,7 +19362,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sync-fetch": { @@ -19525,7 +19588,7 @@ "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "tldts-core": "^6.1.86" @@ -19538,7 +19601,7 @@ "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/tmpl": { @@ -19565,7 +19628,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "tldts": "^6.1.32" @@ -19578,7 +19641,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "punycode": "^2.3.1" @@ -19925,7 +19988,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unified": { @@ -20489,7 +20552,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "xml-name-validator": "^5.0.0" @@ -20522,7 +20585,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -20533,7 +20596,7 @@ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" @@ -20546,7 +20609,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -20556,7 +20619,7 @@ "version": "14.2.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "tr46": "^5.1.0", @@ -20836,7 +20899,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18" @@ -20846,7 +20909,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/xmlhttprequest-ssl": { @@ -20878,7 +20941,7 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "devOptional": true, + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" From 64044f98241e39741ca6d930d0deebd35e0f9b68 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Fri, 30 Jan 2026 16:41:27 +0100 Subject: [PATCH 4/4] fix: fix coderabbit correction --- app/wallet/page.tsx | 7 ++++--- components/wallet/transaction-history.tsx | 21 +++++++++++++++++---- components/wallet/withdrawal-section.tsx | 18 ++++++++++++++++-- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/app/wallet/page.tsx b/app/wallet/page.tsx index e4bc646..128d4b7 100644 --- a/app/wallet/page.tsx +++ b/app/wallet/page.tsx @@ -55,9 +55,10 @@ export default function WalletPage() {

Quick Links

- How it works? - Fee Schedule - Support Center + {/* TODO: Implement these routes */} + How it works? + Fee Schedule + Support Center
diff --git a/components/wallet/transaction-history.tsx b/components/wallet/transaction-history.tsx index 84ec1f5..aabbdd5 100644 --- a/components/wallet/transaction-history.tsx +++ b/components/wallet/transaction-history.tsx @@ -5,7 +5,7 @@ import { WalletActivity } from "@/types/wallet"; import { Input } from "@/components/ui/input"; import { Search, Download, ArrowUpRight, ArrowDownLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { format } from "date-fns"; +import { format, isValid } from "date-fns"; import { Badge } from "@/components/ui/badge"; interface TransactionHistoryProps { @@ -15,6 +15,11 @@ interface TransactionHistoryProps { export function TransactionHistory({ activity }: TransactionHistoryProps) { const [search, setSearch] = useState(""); + const formatSafeDate = (dateString: string, formatString: string) => { + const date = new Date(dateString); + return isValid(date) ? format(date, formatString) : "—"; + }; + const filteredActivity = activity.filter(item => item.description?.toLowerCase().includes(search.toLowerCase()) || item.type.toLowerCase().includes(search.toLowerCase()) || @@ -54,18 +59,25 @@ export function TransactionHistory({ activity }: TransactionHistoryProps) { const csvContent = [ headers.join(","), - ...rows.map(row => row.map(cell => `"${cell.replace(/"/g, '""')}"`).join(",")) + ...rows.map(row => row.map(cell => { + // Sanitize to prevent CSV injection + const sanitized = cell.replace(/"/g, '""'); + return /^[=+\-@]/.test(sanitized) ? `"'${sanitized}"` : `"${sanitized}"`; + }).join(",")) ].join("\n"); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); - link.setAttribute("download", `transactions_${format(new Date(), 'yyyyMMdd_HHmm')}.csv`); + link.setAttribute("download", `transactions_${formatSafeDate(new Date().toISOString(), 'yyyyMMdd_HHmm')}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); + + // Cleanup document.body.removeChild(link); + setTimeout(() => URL.revokeObjectURL(url), 100); }; return ( @@ -78,6 +90,7 @@ export function TransactionHistory({ activity }: TransactionHistoryProps) { className="pl-9 bg-muted/50" value={search} onChange={(e) => setSearch(e.target.value)} + aria-label="Search transactions" />
@@ -137,7 +150,7 @@ export function TransactionHistory({ activity }: TransactionHistoryProps) { - {format(new Date(item.date), 'MMM d, yyyy')} + {formatSafeDate(item.date, 'MMM d, yyyy')} diff --git a/components/wallet/withdrawal-section.tsx b/components/wallet/withdrawal-section.tsx index f1843e2..96eb720 100644 --- a/components/wallet/withdrawal-section.tsx +++ b/components/wallet/withdrawal-section.tsx @@ -21,6 +21,12 @@ export function WithdrawalSection({ walletInfo }: WithdrawalSectionProps) { { id: '1', name: 'Chase Bank', last4: '4242', isPrimary: true }, ]; + const parsedAmount = parseFloat(amount); + const isValidAmount = !isNaN(parsedAmount) && + isFinite(parsedAmount) && + parsedAmount >= 10 && + parsedAmount <= walletInfo.balance; + const formatCurrency = (amount: number) => { return new Intl.NumberFormat("en-US", { style: "currency", @@ -100,11 +106,19 @@ export function WithdrawalSection({ walletInfo }: WithdrawalSectionProps) {
You'll Receive - {formatCurrency(Math.max(0, (parseFloat(amount) || 0) - 2.50))} + {formatCurrency(Math.max(0, (parsedAmount || 0) - 2.50))}
-