ProfileSwitcher
Switch between multiple Bitcoin identities (BAP profiles) with a dropdown interface
ProfileSwitcher combines the Radix DropdownMenu primitive with Bitcoin identity management, enabling users to switch between multiple BAP profiles and create new ones with complete key, data, and context isolation.
Profile Switcher
Compact Variant
Active Profile: Personal
Installation
npx bigblocks add profile-switcher
Import
import { ProfileSwitcher, ProfileSwitcherCompact } from 'bigblocks';
This component extends the DropdownMenu primitive and inherits all its props.
Props
Prop | Type | Required | Default | Description |
---|---|---|---|---|
profiles | ProfileInfo[] | Yes | - | Array of available profiles |
activeProfileId | string | Yes | - | Currently active profile's BAP ID |
onSwitch | (profileId: string) => void | Yes | - | Callback when profile is switched |
onCreate | () => void | No | - | Callback for creating new profile |
maxProfiles | number | No | - | Maximum number of profiles allowed |
className | string | No | - | Additional CSS classes |
size | '1' | '2' | '3' | '4' | No | '2' | Radix Themes size scale |
variant | 'solid' | 'soft' | 'ghost' | 'surface' | No | 'surface' | Visual variant style |
ProfileInfo Interface
interface ProfileInfo {
id: string; // BAP identity key
address: string; // Bitcoin address
name?: string; // Profile display name
image?: string; // Avatar URL or ORDFS reference
description?: string; // Profile description
isPublished: boolean; // BAP profile publication status
}
Basic Usage
import { ProfileSwitcher } from 'bigblocks';
import { useProfiles } from '@/hooks/useProfiles';
function Header() {
const { profiles, activeProfile, switchProfile } = useProfiles();
return (
<ProfileSwitcher
profiles={profiles}
activeProfileId={activeProfile.id}
onSwitch={switchProfile}
/>
);
}
Advanced Usage
import { ProfileSwitcher, type ProfileInfo } from 'bigblocks';
import { useRouter } from 'next/navigation';
function NavigationBar() {
const router = useRouter();
const [profiles, setProfiles] = useState<ProfileInfo[]>([]);
const [activeId, setActiveId] = useState('');
const handleProfileSwitch = async (profileId: string) => {
// Switch to new profile context
await switchToProfile(profileId);
// Navigate to profile-specific dashboard
router.push(`/dashboard/${profileId}`);
// Update local state
setActiveId(profileId);
};
const handleCreateProfile = () => {
// Navigate to profile creation flow
router.push('/profiles/create');
};
return (
<ProfileSwitcher
profiles={profiles}
activeProfileId={activeId}
onSwitch={handleProfileSwitch}
onCreate={handleCreateProfile}
maxProfiles={5}
size="3"
variant="soft"
/>
);
}
Common Patterns
Multi-Profile Management
Each Bitcoin identity (BAP profile) is completely isolated with its own:
- Private/Public Keys: Separate key derivation paths
- Bitcoin Address: Unique payment and identity address
- Settings & Data: Profile-specific preferences
- Social Connections: Independent follow/follower lists
- Transaction History: Separate wallet activity
import { ProfileSwitcher, BitcoinAuthProvider } from 'bigblocks';
function MultiProfileApp() {
const [currentProfile, setCurrentProfile] = useState<ProfileInfo>();
// Switch entire app context when profile changes
const handleSwitch = async (profileId: string) => {
const profile = profiles.find(p => p.id === profileId);
if (!profile) return;
// Update auth context with new profile
await updateAuthContext(profile);
// Clear cached data from previous profile
clearProfileCache();
// Load new profile data
await loadProfileData(profileId);
setCurrentProfile(profile);
};
return (
<BitcoinAuthProvider profile={currentProfile}>
<ProfileSwitcher
profiles={profiles}
activeProfileId={currentProfile?.id}
onSwitch={handleSwitch}
/>
</BitcoinAuthProvider>
);
}
Profile Creation Flow
function ProfileManagement() {
const { createProfile } = useProfiles();
const [isCreating, setIsCreating] = useState(false);
const handleCreateNew = async () => {
setIsCreating(true);
try {
// Generate new profile with hierarchical deterministic derivation
const newProfile = await createProfile({
name: 'Business Profile',
description: 'Professional identity',
derivationPath: "m/44'/236'/0'/1'/0" // Next profile index
});
// Publish BAP attestation on-chain (optional)
if (window.confirm('Publish profile on blockchain?')) {
await publishProfile(newProfile.id);
}
// Switch to new profile
await switchProfile(newProfile.id);
} catch (error) {
console.error('Profile creation failed:', error);
} finally {
setIsCreating(false);
}
};
return (
<ProfileSwitcher
profiles={profiles}
activeProfileId={activeId}
onSwitch={switchProfile}
onCreate={handleCreateNew}
maxProfiles={10} // Limit total profiles
/>
);
}
Compact Version
For space-constrained areas like mobile headers:
import { ProfileSwitcherCompact } from 'bigblocks';
function MobileHeader() {
return (
<ProfileSwitcherCompact
profiles={profiles}
activeProfileId={activeId}
onSwitch={handleSwitch}
className="w-full"
/>
);
}
With Profile Status Indicators
function ProfileSwitcherWithStatus() {
// Enhance profiles with additional status info
const enhancedProfiles = profiles.map(profile => ({
...profile,
name: profile.isPublished
? `${profile.name} ✓`
: `${profile.name} (Unpublished)`,
description: profile.isPublished
? 'Published to blockchain'
: 'Local profile only'
}));
return (
<ProfileSwitcher
profiles={enhancedProfiles}
activeProfileId={activeId}
onSwitch={onSwitch}
/>
);
}
Authentication Requirements
ProfileSwitcher requires the Bitcoin authentication context to manage profile switching:
import {
BitcoinAuthProvider,
BitcoinQueryProvider,
ProfileSwitcher
} from 'bigblocks';
function App() {
return (
<BitcoinQueryProvider>
<BitcoinAuthProvider config={{ apiUrl: '/api' }}>
<ProfileSwitcher {...props} />
</BitcoinAuthProvider>
</BitcoinQueryProvider>
);
}
API Integration
Profile Loading
The component expects profiles to be loaded from your backend:
// GET /api/users/profiles
{
"profiles": [
{
"id": "bap-id-1",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"name": "Personal",
"image": "ordfs://tx-id/0",
"description": "My personal profile",
"isPublished": true
},
{
"id": "bap-id-2",
"address": "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
"name": "Business",
"image": "https://example.com/avatar.jpg",
"description": "Professional identity",
"isPublished": false
}
]
}
Profile Switching
When switching profiles, the component triggers authentication updates:
// POST /api/auth/switch-profile
{
"profileId": "bap-id-2",
"signature": "bitcoin-signature-proof"
}
// Response updates session with new profile context
{
"success": true,
"profile": { /* ProfileInfo */ },
"sessionToken": "new-jwt-token"
}
Bitcoin Identity Concepts
BAP (Bitcoin Attestation Protocol)
Each profile is a BAP identity with:
- Hierarchical Deterministic Keys: Derived from master seed
- Identity Attestations: On-chain identity claims
- Cryptographic Proofs: All actions are signed
Profile Isolation
Different profiles provide:
- Privacy Separation: No linking between identities
- Use Case Segregation: Personal vs Business vs Anonymous
- Risk Management: Separate keys for different activities
Key Derivation
Profiles use BIP32 HD derivation:
Master Seed → m/44'/236'/0'/0'/0 (Profile 1)
→ m/44'/236'/0'/1'/0 (Profile 2)
→ m/44'/236'/0'/2'/0 (Profile 3)
Troubleshooting
Profile Not Switching
// Ensure profile data is properly loaded
const handleSwitch = async (profileId: string) => {
try {
// Clear existing session data
sessionStorage.removeItem('activeProfile');
// Load new profile's decrypted backup
const backup = await loadProfileBackup(profileId);
sessionStorage.setItem('decryptedBackup', JSON.stringify(backup));
// Update auth context
await updateAuthContext(profileId);
// Reload app data
window.location.reload();
} catch (error) {
console.error('Profile switch failed:', error);
}
};
Missing Profile Data
Ensure profiles include all required fields:
// Validate profile data
profiles.forEach(profile => {
if (!profile.id || !profile.address) {
console.error('Invalid profile:', profile);
}
});
Maximum Profiles Reached
<ProfileSwitcher
profiles={profiles}
activeProfileId={activeId}
onSwitch={onSwitch}
onCreate={profiles.length < 5 ? handleCreate : undefined}
maxProfiles={5}
/>
Keyboard Navigation
Space/Enter
: Open dropdown menu↑/↓
: Navigate between profilesEnter
: Select highlighted profileEscape
: Close dropdownTab
: Move to next focusable element
Accessibility
- ARIA Labels: Proper labeling for screen readers
- Focus Management: Keyboard navigation support
- Status Announcements: Profile changes announced
- High Contrast: Works with system preferences
Related Components
- ProfileEditor - Edit profile details
- ProfileManager - Manage all profiles
- ProfileViewer - Display profile information
- ProfilePublisher - Publish profiles to blockchain
- ProfilePopover - Hover profile preview
API Reference
ProfileSwitcher Exports
// Main component
export function ProfileSwitcher(props: ProfileSwitcherProps): JSX.Element
// Compact variant
export function ProfileSwitcherCompact(
props: Pick<ProfileSwitcherProps,
'profiles' | 'activeProfileId' | 'onSwitch' | 'className'
>
): JSX.Element
// Type exports
export type { ProfileInfo, ProfileSwitcherProps }
Custom Styling
Apply custom styles using className:
<ProfileSwitcher
className="custom-switcher"
profiles={profiles}
activeProfileId={activeId}
onSwitch={onSwitch}
/>
/* Custom CSS */
.custom-switcher {
--switcher-bg: rgba(255, 255, 255, 0.1);
--switcher-border: rgba(255, 255, 255, 0.2);
--switcher-hover: rgba(255, 255, 255, 0.15);
}
Integration with React Query
import { useQuery } from '@tanstack/react-query';
function useProfileSwitcher() {
const { data: profiles = [] } = useQuery({
queryKey: ['profiles'],
queryFn: fetchProfiles,
staleTime: 5 * 60 * 1000, // 5 minutes
});
const { data: activeProfile } = useQuery({
queryKey: ['activeProfile'],
queryFn: fetchActiveProfile,
});
return {
profiles,
activeProfileId: activeProfile?.id || profiles[0]?.id,
onSwitch: switchProfile,
};
}