Skip to content

Conversation

@Michaelkingsdev
Copy link
Contributor

@Michaelkingsdev Michaelkingsdev commented Jan 30, 2026

Closes #69

Wallet Page Implementation

This PR introduces a comprehensive, dedicated wallet page at /wallet to provide users with a complete view of their abstracted wallet. This extends the functionality of the existing wallet sheet, allowing contributors to manage their earnings, view detailed asset breakdowns, access full transaction history, and off-ramp funds to fiat currency.

Core Features

Dedicated Wallet Hub (/wallet)

  • Balance Card: High-level overview of total balance (available + pending) with 24h change indicators and multi-token breakdown.
  • Assets Management: Extended list of all token holdings with search, sort, and portfolio percentage distribution.
  • Transaction History: Full paginated list of all earnings and withdrawals with status tracking and search filters.
  • Off-Ramp Section: Interface for withdrawing crypto earnings to linked bank accounts, including fee estimations and receipt totals.
  • Security Center: Management for 2FA, withdrawal whitelisting, and active session tracking.

UI & UX Improvements

  • Abstracted Wallet Integration: Automated provisioning and management indicator (explaining no manual connection needed).
  • Light Mode Optimization: Specific readability fixes for lemon-colored elements (#a7f950) on white backgrounds using dynamic color switching (primary-foreground in light mode).
  • Global Integration: Added Wallet to the main navigation and View More links in the WalletSheet for seamless transition to the full page.
  • Responsive Design: Fully optimized for mobile, tablet, and desktop viewing.

Technical Changes

New Route

  • /wallet/page.tsx

New Components

  • WalletOverview: General account status and explorer links.
  • BalanceCard: Gradient-styled balance display with tooltips.
  • AssetsList: Filterable token table.
  • TransactionHistory: Activity log with status badges.
  • WithdrawalSection: Bank transfer flow.
  • SecuritySection: Security settings and session logs.

Mock Data

  • Extended lib/mock-wallet.ts to include mockWalletWithAssets for realistic testing.

How to Verify

  1. Navigate to the new /wallet route (either via direct URL or the new Wallet link in the navbar).
  2. Open the Wallet Sheet from the navbar and click View More.
  3. Toggle between the Assets, Activity, Withdraw, and Security tabs.
  4. Test the search functionality in both Assets and Activity lists.
  5. Verify the Light/Dark mode switch to ensure the lemon green text remains readable (it should darken slightly in light mode).

Proof

Summary by CodeRabbit

  • New Features

    • Full Wallet dashboard: balance overview, assets list (search/sort/filter), transaction history (search + CSV export), withdrawal flow, security panel, and account overview with copy-to-clipboard and quick links.
    • Wallet link added to main navigation and a dedicated Wallet layout.
  • Refactor

    • Updated wallet sheet and layout styling for improved responsiveness.
  • Chores

    • Updated mock wallet address and minor navigation/theme UI tweaks.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Adds a new /wallet page and layout plus multiple wallet UI components (BalanceCard, AssetsList, TransactionHistory, WithdrawalSection, SecuritySection, WalletOverview), wires them to mockWallet data, updates the global navbar, tweaks some UI primitives, and updates mock wallet addresses.

Changes

Cohort / File(s) Summary
Wallet Page & Layout
app/wallet/page.tsx, app/wallet/layout.tsx
New /wallet page and layout exposing metadata and a WalletPage component that composes balance, tabs (Assets, Activity, Withdraw, Security), and a right sidebar overview.
Navigation
components/global-navbar.tsx
Added /wallet link with active conditional styling; minor formatting of existing nav link templates.
Balance & Assets
components/wallet/balance-card.tsx, components/wallet/assets-list.tsx
New BalanceCard (total, breakdown, 24h change, tooltips) and AssetsList (search, hide-small, sort, portfolio %) components accepting WalletInfo/WalletAsset props.
Transactions & Withdrawals
components/wallet/transaction-history.tsx, components/wallet/withdrawal-section.tsx
New TransactionHistory (search/filter, CSV export, status badges) and WithdrawalSection (withdraw form, bank accounts, validation, limits UI) components and props.
Overview & Security
components/wallet/wallet-overview.tsx, components/wallet/security-section.tsx
New WalletOverview (truncated address, copy-to-clipboard, explorer link) and SecuritySection (2FA badge, sessions, recent security events) components.
Wallet Sheet & UI primitives
components/wallet/wallet-sheet.tsx, components/ui/resizable-navbar.tsx, components/animate-ui/primitives/effects/theme-toggler.tsx
Presentation tweaks in WalletSheet; removed MobileNavMenu.onClose and switched logo to Next.js Image; ThemeToggler signature no longer captures rest props.
Mocks
lib/mock-wallet.ts
Updated exported mock wallet addresses to GC64SVY3XSYEE7MYADTEQNO3ACCWJ6NLWNNJLPO2FGS4I2PCNBPVNZOP.
Layout cleanup
app/layout.tsx
Removed import and usage of GlobalResizableNavbar (commented out).

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant Next as Next.js Router
  participant WalletPage as WalletPage
  participant Mock as mockWalletWithAssets
  participant Components as Wallet Components
  Browser->>Next: Request /wallet
  Next->>WalletPage: render WalletPage component
  WalletPage->>Mock: import mockWalletWithAssets
  WalletPage->>Components: pass walletInfo & assets props
  Components->>Browser: render BalanceCard, AssetsList, TransactionHistory, WithdrawalSection, WalletOverview
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~23 minutes

Possibly related PRs

Poem

🐰 I hopped into code with a pocket of cheer,

A wallet page sprouted, assets crystal clear,
Tabs for history, withdraw, and security too,
Copy the address — hooray! — and explore what’s new,
The warren applauds this shiny dashboard debut. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Wallet page' is vague and generic, using non-descriptive terms that don't convey meaningful information about the substantial changes introduced. Use a more specific title that captures the main feature being added, such as 'Add comprehensive wallet page with balance, assets, activity and off-ramping' or 'Implement wallet dashboard with asset management and withdrawals'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request implements all primary coding requirements from issue #69, including wallet overview, balance card, assets management, transaction history, withdrawal section, and security features with appropriate component structure.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the wallet page feature. Minor updates to global navbar, layout files, and utility components support the core wallet functionality without introducing unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In `@app/wallet/page.tsx`:
- Line 1: The wallet route is missing SEO metadata because app/wallet/page.tsx
is a client component and cannot export metadata; add a server-level metadata
provider by creating app/wallet/layout.tsx that exports a Metadata object (e.g.,
export const metadata: Metadata = { title: "...", description: "..." }) and a
default WalletLayout component that returns children, or alternatively move
static parts of page.tsx into a new server component (e.g., WalletServerPage)
which can export metadata/generateMetadata and then import the client-only
component; update imports so page.tsx is nested under the server wrapper or
include the layout to ensure SEO metadata is applied.
- Around line 55-61: The "Quick Links" block contains anchors with placeholder
href="#" that don't navigate; update the three <a> elements in the "Quick Links"
section (the Quick Links div / its anchor tags) to point to real routes or
external URLs (e.g., /how-it-works, /fee-schedule, /support) or, if the pages
aren’t ready, remove the entire Quick Links block or replace the anchors with
non-interactive placeholders and add a TODO comment indicating the intended
routes; ensure any placeholder anchors are not left with href="#" to avoid
unexpected scrolling or accessibility issues.
- Line 9: Replace the hardcoded mockWalletWithAssets usage by adding a server
API route handler at /app/api/wallet/route.ts that exposes real wallet data
(implement an exported default GET handler named GET or handler that returns
JSON via Next Response) and create a client hook hooks/use-wallet.ts that
follows existing React Query patterns: export a useWallet hook which calls an
async fetchWallet function, registers the query under a stable key like
['wallet', address?], uses react-query's useQuery to fetch from '/api/wallet'
and returns data/error/status; update page.tsx to import and use useWallet
instead of mockWalletWithAssets and remove the mock import. Ensure the
fetchWallet function handles errors and parses JSON, and mirror conventions used
in use-reputation.ts / use-bounties.ts (same queryClient behavior and staleTime
defaults).

In `@components/wallet/assets-list.tsx`:
- Around line 41-48: The Sort and Filter buttons in the assets-list component
are rendered without handlers; add minimal wiring by introducing local state
(e.g., sortKey, sortOrder, filterQuery) and functions named sortAssets and
filterAssets that update the displayed assets list, then attach these as
onClick/onChange handlers to the existing Button elements (the ones rendering
ArrowUpDown and Filter icons) so clicking Sort toggles sortOrder or opens a
simple sort menu and clicking Filter applies filterQuery; alternatively, if full
behavior isn't ready, mark the Button components as disabled with an explanatory
aria-label/title ("Coming soon") and add TODO comments referencing sortAssets
and filterAssets so future work can implement the stateful handlers.
- Around line 90-98: Precompute the portfolio total once (e.g., const totalUsd =
assets.reduce((acc, c) => acc + c.usdValue, 0)) and use that inside the JSX
instead of calling assets.reduce for every row; also guard both divisions:
compute unitValue as asset.amount ? asset.usdValue / asset.amount : 0 and
compute percent as totalUsd ? (asset.usdValue / totalUsd) * 100 : 0, then pass
unitValue to formatCurrency and percent.toFixed(1) for display, referencing
formatCurrency, asset.usdValue, asset.amount and assets.reduce to locate the
changes.

In `@components/wallet/security-section.tsx`:
- Around line 6-7: Remove the unused imports Label and Shield from the import
statement in components/wallet/security-section.tsx; update the import line that
currently imports Label and Shield (alongside other icons like Smartphone, Key,
etc.) so it only imports the symbols actually used in the file (e.g.,
Smartphone, Key, CircleAlert, CheckCircle2, ListFilter, MonitorPlay) to satisfy
the linter.
- Around line 37-41: The Switch is rendered as a controlled input with
checked={walletInfo.has2FA} but no onCheckedChange, so make its behavior
explicit: either mark the Switch disabled/readOnly when it’s meant for display
(e.g., add the disabled prop) or wire it to state by providing an
onCheckedChange handler that updates the relevant state/prop (update the parent
state or call a handler that toggles walletInfo.has2FA). Locate the Switch
component near Badge and adjust it to either include disabled (for display-only)
or add an onCheckedChange callback that updates walletInfo.has2FA.

In `@components/wallet/transaction-history.tsx`:
- Line 6: Remove the unused ExternalLink import from the import statement in
transaction-history.tsx; update the import line that currently reads importing {
Search, Download, ArrowUpRight, ArrowDownLeft, ExternalLink } (from
lucide-react) to omit ExternalLink so only used icons remain, which will resolve
the CI lint error.
- Around line 56-59: The Export CSV Button is rendered without a click handler;
either wire it to an export function or mark it disabled. Add a handler function
(e.g., exportTransactionsToCsv or handleExportCsv) in the TransactionHistory
component that gathers the transactions list (from props/state like
transactions), converts them to CSV rows/headers, creates a Blob/URL and
triggers a download, and attach it to the Button via onClick={handleExportCsv};
alternatively, if export is out of scope, set the Button disabled and add an
accessible title/aria-label to indicate unavailable functionality.

In `@components/wallet/wallet-overview.tsx`:
- Around line 5-55: The truncateStellarAddress import is unused and the full
address is rendered; update the WalletOverview component to display the
truncated address (use truncateStellarAddress(walletInfo.address)) where
{walletInfo.address} is currently rendered, keep the full address available via
a title/tooltip on that element (e.g., title={walletInfo.address}) so
copy/clipboard and accessibility still use the full value, and ensure the
truncateStellarAddress import is kept to satisfy the linter.

In `@components/wallet/withdrawal-section.tsx`:
- Line 8: Remove the unused Wallet icon from the import list to fix the lint
error: edit the import statement in withdrawal-section.tsx that currently
imports { Building, Plus, ArrowRight, Wallet, History, Info } and remove the
Wallet symbol so only used icons remain (Building, Plus, ArrowRight, History,
Info); ensure no other references to Wallet exist in the file.
- Line 102: The JSX text "You'll Receive" in the <span> within
withdrawal-section.tsx triggers react/no-unescaped-entities; fix it by escaping
the apostrophe or using a JS string expression — replace the raw text inside the
<span> (the element containing "You'll Receive") with either "You&apos;ll
Receive" or {"You'll Receive"} so the apostrophe is not an unescaped entity.
🧹 Nitpick comments (5)
components/wallet/wallet-sheet.tsx (1)

45-53: Consider extracting formatCurrency to a shared utility.

This helper duplicates logic that also exists in helpers/format.helper.ts and is repeated in withdrawal-section.tsx. Centralizing currency formatting ensures consistent formatting across the app and reduces maintenance burden.

♻️ Suggested refactor
+import { formatCurrency } from "@/helpers/format.helper";

Then remove the local formatCurrency definition and update the existing helper if needed to match this implementation's behavior.

components/wallet/withdrawal-section.tsx (4)

24-29: Duplicate formatCurrency helper.

This is the same currency formatting logic duplicated in wallet-sheet.tsx. Consider extracting to a shared utility in helpers/format.helper.ts.


103-103: Edge case: negative receive amount displays as $0.00.

The calculation Math.max(0, (parseFloat(amount) || 0) - 2.50) correctly prevents negative values, but users entering amounts between $0.01-$2.49 will see "$0.00" receive amount while the button remains disabled only below $10. Consider adding a visual warning when the fee exceeds the amount.


107-110: Validation is correct but could include upper bound check.

The button disables for amounts below $10, but there's no validation against the wallet balance or daily/monthly limits shown in the UI. Users could attempt to withdraw more than available.

♻️ Suggested enhancement
-<Button className="w-full h-12 text-md font-semibold" disabled={!amount || parseFloat(amount) < 10}>
+<Button className="w-full h-12 text-md font-semibold" disabled={!amount || parseFloat(amount) < 10 || parseFloat(amount) > walletInfo.balance}>

19-22: Mock bank accounts are hardcoded.

This is acceptable for initial implementation, but the comment on line 19 should be addressed before production. Consider adding a TODO with tracking context.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@components/wallet/transaction-history.tsx`:
- Around line 76-81: The search Input currently relies on placeholder text for
identification (component: Input with props placeholder, value={search},
onChange={(e) => setSearch(e.target.value)}); add an accessible label by either
providing an explicit aria-label prop (e.g., aria-label="Search transactions")
or by adding a visible or visually-hidden <label> tied to the Input via id and
aria-labelledby so screen readers can announce the field correctly.
- Around line 43-69: The CSV export (handleExportCsv) needs two fixes: sanitize
cells to prevent CSV injection by prefixing any cell that begins with "=", "+",
"-", "@" (or a tab) with a safe character (e.g., a leading single quote) when
building rows from filteredActivity, and ensure the created object URL is
revoked after download (use URL.revokeObjectURL(url) once the link click
completes or in a short setTimeout) so the Blob URL is not leaked; update the
rows/csvContent generation to apply the sanitization to each cell and call
URL.revokeObjectURL(url) after triggering the download and removing the link.
- Line 8: The code calls format(new Date(item.date), ...) directly (in
components/wallet/transaction-history.tsx at the two locations that construct
dates) which throws on invalid dates; add a small guard/helper (e.g., a local
formatSafeDate or use date-fns isValid) that builds a Date from item.date,
checks isValid(date) (or !isNaN(date.getTime())), and only calls format when
valid, otherwise return a safe fallback (empty string or a placeholder like
"—"); replace the two direct format(new Date(item.date), ...) usages with this
safe helper to prevent crashes when upstream date values are malformed.

In `@components/wallet/withdrawal-section.tsx`:
- Around line 16-18: The withdrawal form currently only checks amount >= 10,
allowing negative, non-numeric, or over-balance submissions; update the
validation in WithdrawalSection to (1) parse amount to a number (e.g., Number or
parseFloat), (2) ensure it is a finite positive number, (3) enforce min value
(>= 10) and max value <= walletInfo.balance, and (4) use this combined predicate
wherever the submit button is enabled/disabled and in the submit handler
(references: WithdrawalSection component, amount state, setAmount,
walletInfo.balance, and the submit/handleWithdraw functions) so both UI and
server-side call prevention are consistent.
🧹 Nitpick comments (5)
components/wallet/wallet-overview.tsx (3)

16-24: Consider cleanup on unmount to avoid React warnings.

The setTimeout callback may fire after the component unmounts, causing a no-op state update. This is harmless but can trigger React warnings in development.

♻️ Optional: Clean up timeout on unmount
+import { useState, useRef, useEffect } from "react";
...
 export function WalletOverview({ walletInfo }: WalletOverviewProps) {
     const [copied, setCopied] = useState(false);
+    const timeoutRef = useRef<NodeJS.Timeout | null>(null);
+
+    useEffect(() => {
+        return () => {
+            if (timeoutRef.current) clearTimeout(timeoutRef.current);
+        };
+    }, []);

     const handleCopyAddress = async () => {
         try {
             await navigator.clipboard.writeText(walletInfo.address);
             setCopied(true);
-            setTimeout(() => setCopied(false), 2000);
+            timeoutRef.current = setTimeout(() => setCopied(false), 2000);
         } catch (error) {
             console.error("Failed to copy address:", error);
         }
     };

32-38: Status is hardcoded rather than derived from wallet state.

The status displays "Active & Secured" statically. Consider deriving this from walletInfo.isConnected and walletInfo.has2FA for accurate representation.


70-76: Creation date is hardcoded.

The "Created" field shows a static "May 2024" value. Per PR objectives, this should display the actual wallet creation date. Consider adding a createdAt field to WalletInfo type.

components/wallet/withdrawal-section.tsx (1)

19-23: Make destination selection a real, accessible control.
The account row is styled as interactive but has no selection state or keyboard semantics. Either remove the “clickable” affordance or wire up selection (recommended).

♻️ Suggested refactor
 export function WithdrawalSection({ walletInfo }: WithdrawalSectionProps) {
-    const [amount, setAmount] = useState("");
-
     // Mock bank accounts
     const bankAccounts = [
-        { id: '1', name: 'Chase Bank', last4: '4242', isPrimary: true },
+        { id: "1", name: "Chase Bank", last4: "4242", isPrimary: true },
     ];
+    const [amount, setAmount] = useState("");
+    const [selectedBankId, setSelectedBankId] = useState(bankAccounts[0]?.id ?? "");
@@
                                 {bankAccounts.map((account) => (
-                                    <div key={account.id} className="flex items-center justify-between p-3 rounded-xl border border-primary bg-primary/5 cursor-pointer">
+                                    <button
+                                        key={account.id}
+                                        type="button"
+                                        onClick={() => setSelectedBankId(account.id)}
+                                        aria-pressed={account.id === selectedBankId}
+                                        className={`flex w-full items-center justify-between p-3 rounded-xl border ${
+                                            account.id === selectedBankId ? "border-primary bg-primary/5" : "border-border"
+                                        }`}
+                                    >
                                         <div className="flex items-center gap-3">
                                             <div className="p-2 rounded-full bg-primary/10">
                                                 <Building className="h-4 w-4 text-primary" />
                                             </div>
@@
-                                        {account.isPrimary && <Badge className="text-[10px] h-5">Primary</Badge>}
-                                    </div>
+                                        {account.isPrimary && <Badge className="text-[10px] h-5">Primary</Badge>}
+                                    </button>
                                 ))}

Also applies to: 70-82

components/wallet/transaction-history.tsx (1)

24-32: Avoid recreating Intl.NumberFormat per cell. Hoist the formatter to cut repeated allocations in large tables.

♻️ Proposed refactor
+const usdFormatter = new Intl.NumberFormat("en-US", {
+  style: "currency",
+  currency: "USD",
+});
+
 export function TransactionHistory({ activity }: TransactionHistoryProps) {
@@
-    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 formatCurrency = (amount: number, currency: string) => {
+        if (currency === "USD" || currency === "USDC") {
+            return usdFormatter.format(amount);
+        }
+        return `${amount.toLocaleString()} ${currency}`;
+    };

@0xdevcollins
Copy link
Contributor

0xdevcollins commented Jan 30, 2026

@Michaelkingsdev resolve the coderabbit's comment

@Michaelkingsdev
Copy link
Contributor Author

@0xdevcollins
Done

@0xdevcollins 0xdevcollins merged commit 44f52a7 into boundlessfi:main Jan 30, 2026
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 31, 2026
21 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Full Wallet Page with Balance, Assets, Activity History, and Fiat Off-Ramping

2 participants