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
Prop | Type | Required | Default | Description |
---|---|---|---|---|
showWalletBalance | boolean | No | true | Show wallet balance when available |
walletBalance | BSVBalance | No | - | Wallet balance data |
exchangeRate | number | No | 50 | BSV to USD exchange rate |
currency | 'BSV' | 'USD' | No | 'USD' | Display currency |
useMenuIcon | boolean | No | false | Use menu icon instead of avatar |
trigger | React.ReactNode | No | - | Custom trigger element |
showThemeSwitch | boolean | No | true | Show theme switcher in menu |
onViewProfile | () => void | No | - | View profile callback |
onSettings | () => void | No | - | Settings callback |
onWalletUnlocked | () => void | No | - | Wallet unlock callback |
onCreateAccount | () => void | No | - | Create account callback |
onSignIn | () => void | No | - | Sign in callback |
additionalItems | React.ReactNode | No | - | Extra menu items |
align | 'start' | 'center' | 'end' | No | 'end' | Menu alignment |
side | 'top' | 'right' | 'bottom' | 'left' | No | 'bottom' | Menu side |
signOutRedirectTo | string | No | '/' | Sign out redirect URL |
showProfileDetails | boolean | No | true | Show 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
Menu Sections
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
Menu Position
<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
- Consistent Placement: Usually top-right corner of header
- Mobile Optimization: Use menu icon on smaller screens
- Clear Visual States: Distinguish between auth states clearly
- Quick Access: Most common actions easily reachable
- 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
Menu Not Opening
- 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
Related Components
- ProfileViewer - Display full profile details
- ProfileSwitcher - Dedicated profile switching
- WalletOverview - Detailed wallet information
- ThemeSelector - Standalone theme selection
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