BigBlocks Docs
Components/Profiles

ProfileDropdownMenu

Comprehensive dropdown menu for user profiles with authentication states, wallet features, profile switching, and settings

A comprehensive dropdown menu component for user profiles that handles authentication states, wallet features, profile switching, theme selection, and settings access.

Installation

npx bigblocks add profile-dropdown-menu

Import

import { ProfileDropdownMenu } from 'bigblocks';

Props

PropTypeRequiredDefaultDescription
showWalletBalancebooleanNotrueShow wallet balance when available
walletBalanceBSVBalanceNo-Wallet balance data
exchangeRatenumberNo50BSV to USD exchange rate
currency'BSV' | 'USD'No'USD'Display currency
useMenuIconbooleanNofalseUse menu icon instead of avatar
triggerReact.ReactNodeNo-Custom trigger element
showThemeSwitchbooleanNotrueShow theme switcher in menu
onViewProfile() => voidNo-View profile callback
onSettings() => voidNo-Settings callback
onWalletUnlocked() => voidNo-Wallet unlock callback
onCreateAccount() => voidNo-Create account callback
onSignIn() => voidNo-Sign in callback
additionalItemsReact.ReactNodeNo-Extra menu items
align'start' | 'center' | 'end'No'end'Menu alignment
side'top' | 'right' | 'bottom' | 'left'No'bottom'Menu side
signOutRedirectTostringNo'/'Sign out redirect URL
showProfileDetailsbooleanNotrueShow profile info in menu

Basic Usage

import { ProfileDropdownMenu } from 'bigblocks';

export default function Header() {
  return (
    <nav className="flex justify-between items-center p-4">
      <div className="text-xl font-bold">
        My App
      </div>
      
      <ProfileDropdownMenu
        showWalletBalance={true}
        showThemeSwitch={true}
        onViewProfile={() => router.push('/profile')}
        onSettings={() => router.push('/settings')}
      />
    </nav>
  );
}

Advanced Usage

Complete Header Integration

import { ProfileDropdownMenu } from 'bigblocks';
import { useState, useEffect } from 'react';

export default function AppHeader() {
  const [walletBalance, setWalletBalance] = useState(null);
  const [exchangeRate, setExchangeRate] = useState(50);

  useEffect(() => {
    // Load wallet balance
    const loadBalance = async () => {
      try {
        const balance = await fetchWalletBalance();
        setWalletBalance(balance);
      } catch (error) {
        console.error('Failed to load balance:', error);
      }
    };

    // Load exchange rate
    const loadExchangeRate = async () => {
      try {
        const rate = await fetchBSVUSDRate();
        setExchangeRate(rate);
      } catch (error) {
        console.error('Failed to load exchange rate:', error);
      }
    };

    loadBalance();
    loadExchangeRate();
  }, []);

  const handleWalletUnlocked = () => {
    // Refresh balance when wallet is unlocked
    loadBalance();
    toast.success('Wallet unlocked successfully!');
  };

  const handleCreateAccount = () => {
    router.push('/signup');
  };

  const handleSignIn = () => {
    router.push('/signin');
  };

  return (
    <header className="bg-white shadow-sm border-b">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex justify-between items-center h-16">
          <div className="flex items-center">
            <h1 className="text-xl font-semibold">Bitcoin App</h1>
          </div>

          <ProfileDropdownMenu
            showWalletBalance={true}
            walletBalance={walletBalance}
            exchangeRate={exchangeRate}
            currency="USD"
            showThemeSwitch={true}
            onViewProfile={() => router.push('/profile')}
            onSettings={() => router.push('/settings')}
            onWalletUnlocked={handleWalletUnlocked}
            onCreateAccount={handleCreateAccount}
            onSignIn={handleSignIn}
            signOutRedirectTo="/home"
          />
        </div>
      </div>
    </header>
  );
}

Custom Trigger Element

import { ProfileDropdownMenu } from 'bigblocks';
import { Button } from '@radix-ui/themes';

export default function CustomTriggerMenu() {
  return (
    <ProfileDropdownMenu
      trigger={
        <Button variant="soft" size="2">
          <PersonIcon />
          Account
        </Button>
      }
      onViewProfile={() => router.push('/profile')}
      onSettings={() => router.push('/settings')}
    />
  );
}

Mobile Navigation Menu

import { ProfileDropdownMenu } from 'bigblocks';

export default function MobileHeader() {
  return (
    <header className="md:hidden bg-white shadow-sm">
      <div className="flex justify-between items-center p-4">
        <h1 className="text-lg font-semibold">App</h1>
        
        <ProfileDropdownMenu
          useMenuIcon={true}
          additionalItems={
            <>
              <DropdownMenu.Item onClick={() => router.push('/about')}>
                <InfoCircledIcon className="mr-2 h-4 w-4" />
                About
              </DropdownMenu.Item>
              <DropdownMenu.Item onClick={() => router.push('/help')}>
                <QuestionMarkCircledIcon className="mr-2 h-4 w-4" />
                Help
              </DropdownMenu.Item>
              <DropdownMenu.Item onClick={() => router.push('/contact')}>
                <ChatBubbleIcon className="mr-2 h-4 w-4" />
                Contact
              </DropdownMenu.Item>
            </>
          }
        />
      </div>
    </header>
  );
}

Dashboard Layout

import { ProfileDropdownMenu } from 'bigblocks';

export default function Dashboard() {
  const [currency, setCurrency] = useState('USD');

  return (
    <div className="min-h-screen bg-gray-50">
      <nav className="bg-white shadow">
        <div className="max-w-7xl mx-auto px-4">
          <div className="flex justify-between h-16">
            <div className="flex items-center space-x-8">
              <h1 className="text-xl font-bold">Dashboard</h1>
              
              <div className="hidden md:flex space-x-4">
                <a href="/dashboard" className="text-gray-900">Overview</a>
                <a href="/transactions" className="text-gray-500">Transactions</a>
                <a href="/ordinals" className="text-gray-500">Ordinals</a>
              </div>
            </div>

            <div className="flex items-center space-x-4">
              <select
                value={currency}
                onChange={(e) => setCurrency(e.target.value)}
                className="text-sm border rounded px-2 py-1"
              >
                <option value="USD">USD</option>
                <option value="BSV">BSV</option>
              </select>

              <ProfileDropdownMenu
                currency={currency}
                showWalletBalance={true}
                onViewProfile={() => router.push('/profile')}
                onSettings={() => router.push('/settings')}
              />
            </div>
          </div>
        </div>
      </nav>

      <main className="max-w-7xl mx-auto py-6 px-4">
        {/* Dashboard content */}
      </main>
    </div>
  );
}

Admin Interface

import { ProfileDropdownMenu } from 'bigblocks';

export default function AdminHeader({ user }) {
  const isAdmin = user?.role === 'admin';

  return (
    <header className="bg-gray-900 text-white">
      <div className="max-w-7xl mx-auto px-4">
        <div className="flex justify-between items-center h-16">
          <div className="flex items-center space-x-4">
            <h1 className="text-xl font-bold">Admin Panel</h1>
            {isAdmin && (
              <span className="bg-red-600 text-xs px-2 py-1 rounded">
                ADMIN
              </span>
            )}
          </div>

          <ProfileDropdownMenu
            showWalletBalance={false} // Hide wallet for admin
            additionalItems={
              isAdmin ? (
                <>
                  <DropdownMenu.Separator />
                  <DropdownMenu.Item onClick={() => router.push('/admin/users')}>
                    <PersonIcon className="mr-2 h-4 w-4" />
                    Manage Users
                  </DropdownMenu.Item>
                  <DropdownMenu.Item onClick={() => router.push('/admin/settings')}>
                    <GearIcon className="mr-2 h-4 w-4" />
                    System Settings
                  </DropdownMenu.Item>
                  <DropdownMenu.Item onClick={() => router.push('/admin/logs')}>
                    <FileTextIcon className="mr-2 h-4 w-4" />
                    System Logs
                  </DropdownMenu.Item>
                </>
              ) : null
            }
            onSettings={() => router.push('/admin/settings')}
            signOutRedirectTo="/admin/login"
          />
        </div>
      </div>
    </header>
  );
}

E-commerce Integration

import { ProfileDropdownMenu } from 'bigblocks';

export default function StoreHeader({ cartItems }) {
  return (
    <header className="bg-white shadow-sm">
      <div className="max-w-7xl mx-auto px-4">
        <div className="flex justify-between items-center h-16">
          <div className="flex items-center space-x-8">
            <h1 className="text-xl font-bold">Bitcoin Store</h1>
            <nav className="hidden md:flex space-x-6">
              <a href="/products">Products</a>
              <a href="/categories">Categories</a>
              <a href="/deals">Deals</a>
            </nav>
          </div>

          <div className="flex items-center space-x-4">
            <button className="relative p-2">
              <ShoppingCartIcon className="h-6 w-6" />
              {cartItems.length > 0 && (
                <span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
                  {cartItems.length}
                </span>
              )}
            </button>

            <ProfileDropdownMenu
              showWalletBalance={true}
              additionalItems={
                <>
                  <DropdownMenu.Item onClick={() => router.push('/orders')}>
                    <BoxIcon className="mr-2 h-4 w-4" />
                    My Orders
                  </DropdownMenu.Item>
                  <DropdownMenu.Item onClick={() => router.push('/wishlist')}>
                    <HeartIcon className="mr-2 h-4 w-4" />
                    Wishlist
                  </DropdownMenu.Item>
                </>
              }
              onViewProfile={() => router.push('/account')}
              onSettings={() => router.push('/account/settings')}
            />
          </div>
        </div>
      </div>
    </header>
  );
}

Authentication States

The component automatically handles different authentication states:

1. Authenticated User

  • Shows avatar with initials or profile image
  • Displays wallet balance (if enabled)
  • Profile details and switcher
  • Theme selection
  • Settings and sign out options

2. Has Local Backup

  • Shows "Unlock" button
  • Password dialog to decrypt backup
  • Seamless sign-in flow after unlock

3. New User

  • Shows "Sign Up" button
  • Create account option
  • Sign in option

Wallet Overview

interface BSVBalance {
  confirmed: number;    // Confirmed satoshis
  unconfirmed: number;  // Unconfirmed satoshis
  total: number;        // Total satoshis
}

Profile Information

  • Avatar display with fallback initials
  • Profile name and alternate name
  • Bitcoin address preview
  • Publication status indicator

Address Copying

Quick copy buttons for:

  • Payment Address: Default receiving address
  • Ordinals Address: NFT/inscription address
  • Identity Address: BAP identity address

Profile Switcher

  • List of all user profiles
  • Active profile indicator
  • Switch profile functionality
  • Create new profile option

Theme Selection

Built-in theme options:

  • Light/Dark modes
  • Bitcoin themes: Orange, Purple, Blue, Mint, Ruby, Cyan, Gold, Bronze

Wallet States

Locked Wallet

// Shows lock icon in menu
<ProfileDropdownMenu
  onWalletUnlocked={() => {
    // Called when user successfully unlocks wallet
    refreshWalletData();
    toast.success('Wallet unlocked!');
  }}
/>

Unlocked Wallet

// Shows balance and "Active" badge
<ProfileDropdownMenu
  showWalletBalance={true}
  walletBalance={{
    confirmed: 100000000, // 1 BSV in satoshis
    unconfirmed: 0,
    total: 100000000
  }}
  exchangeRate={50} // $50 per BSV
  currency="USD"
/>

Common Patterns

Responsive Header

import { ProfileDropdownMenu } from 'bigblocks';

export default function ResponsiveHeader() {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const checkMobile = () => {
      setIsMobile(window.innerWidth < 768);
    };
    
    checkMobile();
    window.addEventListener('resize', checkMobile);
    return () => window.removeEventListener('resize', checkMobile);
  }, []);

  return (
    <header className="bg-white shadow">
      <div className="max-w-7xl mx-auto px-4">
        <div className="flex justify-between items-center h-16">
          <h1 className="text-xl font-bold">My App</h1>
          
          <ProfileDropdownMenu
            useMenuIcon={isMobile}
            showWalletBalance={!isMobile} // Hide balance on mobile
            additionalItems={
              isMobile ? (
                <>
                  <DropdownMenu.Item onClick={() => router.push('/about')}>
                    About
                  </DropdownMenu.Item>
                  <DropdownMenu.Item onClick={() => router.push('/contact')}>
                    Contact
                  </DropdownMenu.Item>
                </>
              ) : null
            }
          />
        </div>
      </div>
    </header>
  );
}

Multi-language Support

import { ProfileDropdownMenu } from 'bigblocks';

export default function InternationalHeader() {
  const { t, locale } = useTranslation();

  return (
    <ProfileDropdownMenu
      additionalItems={
        <>
          <DropdownMenu.Sub>
            <DropdownMenu.SubTrigger>
              <GlobeIcon className="mr-2 h-4 w-4" />
              {t('language')}
            </DropdownMenu.SubTrigger>
            <DropdownMenu.SubContent>
              <DropdownMenu.Item onClick={() => setLocale('en')}>
                English
              </DropdownMenu.Item>
              <DropdownMenu.Item onClick={() => setLocale('es')}>
                Español
              </DropdownMenu.Item>
              <DropdownMenu.Item onClick={() => setLocale('fr')}>
                Français
              </DropdownMenu.Item>
            </DropdownMenu.SubContent>
          </DropdownMenu.Sub>
        </>
      }
    />
  );
}

Notification Integration

import { ProfileDropdownMenu } from 'bigblocks';

export default function NotificationHeader({ notifications }) {
  const unreadCount = notifications.filter(n => !n.read).length;

  return (
    <div className="flex items-center space-x-4">
      <button className="relative p-2">
        <BellIcon className="h-6 w-6" />
        {unreadCount > 0 && (
          <span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
            {unreadCount}
          </span>
        )}
      </button>

      <ProfileDropdownMenu
        additionalItems={
          <>
            <DropdownMenu.Item onClick={() => router.push('/notifications')}>
              <BellIcon className="mr-2 h-4 w-4" />
              Notifications
              {unreadCount > 0 && (
                <span className="ml-auto bg-red-500 text-white text-xs rounded-full px-2 py-1">
                  {unreadCount}
                </span>
              )}
            </DropdownMenu.Item>
          </>
        }
      />
    </div>
  );
}

Customization Options

<ProfileDropdownMenu
  align="start"    // left, center, right
  side="bottom"    // top, right, bottom, left
/>

Custom Styling

<ProfileDropdownMenu
  trigger={
    <Button 
      variant="ghost" 
      className="rounded-full"
      size="1"
    >
      <Avatar 
        src={user?.image} 
        fallback={user?.name?.[0] || '?'}
        size="1"
      />
    </Button>
  }
/>

Features

  • Authentication State Management: Automatically adapts to user auth status
  • Wallet Integration: Display balance, lock/unlock functionality
  • Profile Management: Switch between profiles, view/edit profile
  • Theme Selection: Built-in Bitcoin-inspired themes
  • Address Utilities: Quick copy for different address types
  • Responsive Design: Mobile-friendly with menu icon option
  • Extensible: Add custom menu items easily
  • Accessibility: Full keyboard navigation and screen reader support

Security Considerations

  • Password fields are properly secured
  • Sensitive data cleared on sign out
  • Session security maintained
  • No private keys exposed in UI

Best Practices

  1. Consistent Placement: Usually top-right corner of header
  2. Mobile Optimization: Use menu icon on smaller screens
  3. Clear Visual States: Distinguish between auth states clearly
  4. Quick Access: Most common actions easily reachable
  5. Extension Points: Use additionalItems for app-specific features

Accessibility

  • Full keyboard navigation support
  • Screen reader compatible
  • Focus management in dialogs
  • High contrast mode support
  • Clear visual state indicators

Troubleshooting

  • Check that component is properly wrapped in providers
  • Verify no conflicting z-index styles
  • Ensure proper event handling setup

Balance Not Showing

  • Verify showWalletBalance is true
  • Check wallet balance data format
  • Ensure wallet is unlocked if required

Theme Switch Not Working

  • Verify theme provider is properly configured
  • Check that showThemeSwitch is true
  • Ensure theme persistence is set up

API Integration

Works with authentication and profile APIs:

// Get current user
GET /api/auth/session

// Switch profile
POST /api/profiles/switch

// Sign out
POST /api/auth/signout

// Get wallet balance
GET /api/wallet/balance

Notes for Improvement

Enhanced Implementation: The actual component provides more sophisticated features than the basic prompt described:

  • Advanced authentication state handling with multiple backup types
  • Comprehensive wallet integration with BSV balance display
  • Better theme system with 8+ Bitcoin-inspired color schemes
  • More robust profile switching with validation
  • Enhanced accessibility and keyboard navigation
  • Better mobile responsiveness with adaptive layouts