BigBlocks Docs
Components/Profiles

ProfileSwitcher

Switch between multiple Bitcoin identities (BAP profiles) with a dropdown interface

BitcoinAuthProvider
profilesnavigationui

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

PropTypeRequiredDefaultDescription
profilesProfileInfo[]Yes-Array of available profiles
activeProfileIdstringYes-Currently active profile's BAP ID
onSwitch(profileId: string) => voidYes-Callback when profile is switched
onCreate() => voidNo-Callback for creating new profile
maxProfilesnumberNo-Maximum number of profiles allowed
classNamestringNo-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 profiles
  • Enter: Select highlighted profile
  • Escape: Close dropdown
  • Tab: 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

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,
  };
}