Components/Profiles
ProfileManager
Complete profile management interface for viewing, editing, publishing, and switching between BAP profiles
A comprehensive profile management interface that provides a tabbed interface for viewing, editing, publishing, and switching between multiple BAP (Bitcoin Attestation Protocol) profiles.
Installation
npx bigblocks add profile-manager
Import
import { ProfileManager } from 'bigblocks';
Props
Prop | Type | Required | Default | Description |
---|---|---|---|---|
user | AuthUser | No | - | Current user with profiles |
onProfileUpdate | (profile: ProfileInfo) => void | No | - | Profile update handler |
onProfilePublish | (profileId: string) => Promise<{ txid: string }> | No | - | Publish profile to blockchain |
onProfileCreate | () => Promise<ProfileInfo> | No | - | Create new profile handler |
onProfileSwitch | (profileId: string) => void | No | - | Switch active profile handler |
showPublisher | boolean | No | true | Show publish tab |
maxProfiles | number | No | 5 | Maximum number of profiles |
className | string | No | - | Additional CSS classes |
enableProfileSync | boolean | No | false | Enable profile synchronization |
maxDiscoveryAttempts | number | No | 3 | Max profile discovery attempts |
Basic Usage
import { ProfileManager } from 'bigblocks';
import { useAuth } from 'bigblocks';
export default function ProfileSettings() {
const { user } = useAuth();
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Profile Management</h1>
<ProfileManager
user={user}
onProfileUpdate={(profile) => {
console.log('Profile updated:', profile);
}}
onProfilePublish={async (profileId) => {
console.log('Publishing profile:', profileId);
// Return transaction ID after publishing
return { txid: 'abc123...' };
}}
onProfileSwitch={(profileId) => {
console.log('Switched to profile:', profileId);
}}
/>
</div>
);
}
Advanced Usage
Complete Profile Management System
import { ProfileManager } from 'bigblocks';
import { useState, useEffect } from 'react';
export default function AdvancedProfileManager() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadUser = async () => {
try {
const userData = await fetchCurrentUser();
setUser(userData);
} catch (error) {
console.error('Failed to load user:', error);
} finally {
setLoading(false);
}
};
loadUser();
}, []);
const handleProfileUpdate = async (profile) => {
try {
// Update profile in backend
await updateProfile(profile.id, profile);
// Update local user state
setUser(prev => ({
...prev,
profiles: prev.profiles.map(p =>
p.id === profile.id ? profile : p
)
}));
toast.success('Profile updated successfully!');
} catch (error) {
toast.error('Failed to update profile');
}
};
const handleProfilePublish = async (profileId) => {
try {
// Publish to blockchain
const result = await publishProfile(profileId);
// Update published status
setUser(prev => ({
...prev,
profiles: prev.profiles.map(p =>
p.id === profileId
? { ...p, isPublished: true }
: p
)
}));
toast.success(`Profile published! TX: ${result.txid}`);
return result;
} catch (error) {
toast.error('Failed to publish profile');
throw error;
}
};
const handleProfileCreate = async () => {
try {
const newProfile = await createNewProfile();
setUser(prev => ({
...prev,
profiles: [...prev.profiles, newProfile]
}));
toast.success('New profile created!');
return newProfile;
} catch (error) {
toast.error('Failed to create profile');
throw error;
}
};
const handleProfileSwitch = async (profileId) => {
try {
await switchActiveProfile(profileId);
setUser(prev => ({
...prev,
activeProfileId: profileId
}));
toast.success('Profile switched!');
} catch (error) {
toast.error('Failed to switch profile');
}
};
if (loading) {
return <div>Loading profile manager...</div>;
}
return (
<ProfileManager
user={user}
onProfileUpdate={handleProfileUpdate}
onProfilePublish={handleProfilePublish}
onProfileCreate={handleProfileCreate}
onProfileSwitch={handleProfileSwitch}
showPublisher={true}
maxProfiles={10}
enableProfileSync={true}
maxDiscoveryAttempts={5}
/>
);
}
Organization Profile Management
import { ProfileManager } from 'bigblocks';
export default function OrganizationProfileManager({ organization }) {
const handleOrgProfileUpdate = async (profile) => {
// Validate organization-specific fields
if (!profile.legalName || !profile.address) {
toast.error('Organization profiles require legal name and address');
return;
}
try {
await updateOrganizationProfile(profile.id, profile);
toast.success('Organization profile updated!');
} catch (error) {
toast.error('Failed to update organization profile');
}
};
const handleOrgProfilePublish = async (profileId) => {
try {
// Organization profiles require additional verification
const verification = await verifyOrganization(profileId);
if (!verification.verified) {
toast.error('Organization verification required before publishing');
throw new Error('Verification required');
}
const result = await publishOrganizationProfile(profileId);
toast.success('Organization profile published and verified!');
return result;
} catch (error) {
toast.error('Failed to publish organization profile');
throw error;
}
};
return (
<div>
<h2 className="text-xl font-semibold mb-4">Organization Profiles</h2>
<ProfileManager
user={organization}
onProfileUpdate={handleOrgProfileUpdate}
onProfilePublish={handleOrgProfilePublish}
showPublisher={true}
maxProfiles={3} // Limit for organizations
className="border-2 border-blue-200 rounded-lg"
/>
</div>
);
}
Multi-tenant Profile Management
import { ProfileManager } from 'bigblocks';
export default function MultiTenantProfileManager({ tenantId }) {
const [users, setUsers] = useState([]);
const [selectedUser, setSelectedUser] = useState(null);
useEffect(() => {
const loadTenantUsers = async () => {
const tenantUsers = await fetchTenantUsers(tenantId);
setUsers(tenantUsers);
setSelectedUser(tenantUsers[0]);
};
loadTenantUsers();
}, [tenantId]);
const handleBulkProfileUpdate = async (profile) => {
try {
await updateTenantProfile(tenantId, profile.id, profile);
// Update local state
setUsers(prev => prev.map(user => ({
...user,
profiles: user.profiles.map(p =>
p.id === profile.id ? profile : p
)
})));
toast.success('Profile updated across tenant!');
} catch (error) {
toast.error('Failed to update tenant profile');
}
};
return (
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* User selector */}
<div className="lg:col-span-1">
<h3 className="font-semibold mb-3">Select User</h3>
<div className="space-y-2">
{users.map(user => (
<button
key={user.id}
onClick={() => setSelectedUser(user)}
className={`w-full text-left p-3 rounded ${
selectedUser?.id === user.id
? 'bg-blue-100 text-blue-700'
: 'bg-gray-50 hover:bg-gray-100'
}`}
>
{user.profiles[0]?.name || user.address}
</button>
))}
</div>
</div>
{/* Profile manager */}
<div className="lg:col-span-3">
{selectedUser && (
<ProfileManager
key={selectedUser.id}
user={selectedUser}
onProfileUpdate={handleBulkProfileUpdate}
showPublisher={false} // Disable for tenant management
maxProfiles={3}
/>
)}
</div>
</div>
);
}
Profile Manager with Sync
import { ProfileManager } from 'bigblocks';
export default function SyncedProfileManager() {
const [user, setUser] = useState(null);
const [syncStatus, setSyncStatus] = useState('idle');
useEffect(() => {
// Subscribe to profile sync events
const unsubscribe = subscribeToProfileSync((event) => {
switch (event.type) {
case 'sync_started':
setSyncStatus('syncing');
break;
case 'sync_completed':
setSyncStatus('completed');
// Reload user profiles
loadUserProfiles();
break;
case 'sync_failed':
setSyncStatus('failed');
toast.error('Profile sync failed');
break;
}
});
return unsubscribe;
}, []);
const loadUserProfiles = async () => {
try {
const userData = await fetchUserWithProfiles();
setUser(userData);
} catch (error) {
console.error('Failed to load user profiles:', error);
}
};
return (
<div>
{syncStatus === 'syncing' && (
<div className="mb-4 p-3 bg-blue-100 text-blue-700 rounded">
Syncing profiles across devices...
</div>
)}
{syncStatus === 'failed' && (
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
Profile sync failed. Some changes may not be reflected.
</div>
)}
<ProfileManager
user={user}
enableProfileSync={true}
maxDiscoveryAttempts={5}
onProfileUpdate={async (profile) => {
await updateProfile(profile.id, profile);
// Trigger sync after update
await triggerProfileSync();
}}
/>
</div>
);
}
Tab Interface
The ProfileManager provides a tabbed interface with the following tabs:
View Tab
- Displays current profile information
- Shows publication status
- Profile address and details
- Avatar and basic information
Edit Tab
- Profile editing form
- Real-time validation
- Image URL input with preview
- Save/cancel actions
Publish Tab (if showPublisher
is true)
- Blockchain publishing interface
- Transaction status
- Publication confirmation
- TXID display after successful publish
Switch Tab
- List of all user profiles
- Active profile indicator
- Switch profile actions
- Profile creation option
Profile Creation
When creating new profiles:
const handleProfileCreate = async () => {
try {
// Generate new profile
const newProfile = {
id: generateProfileId(),
address: generateNewAddress(),
isPublished: false,
name: '',
description: '',
image: ''
};
// Save to backend
const savedProfile = await createProfile(newProfile);
return savedProfile;
} catch (error) {
console.error('Profile creation failed:', error);
throw error;
}
};
Profile Discovery
When enableProfileSync
is true, the component will attempt to discover profiles across devices:
const discoverProfiles = async (maxAttempts = 3) => {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const discoveredProfiles = await searchBlockchainProfiles(user.address);
if (discoveredProfiles.length > 0) {
// Merge with existing profiles
await mergeDiscoveredProfiles(discoveredProfiles);
break;
}
} catch (error) {
if (attempt === maxAttempts) {
console.error('Profile discovery failed after', maxAttempts, 'attempts');
}
}
}
};
Common Patterns
Profile Management Dashboard
import { ProfileManager, WalletOverview } from 'bigblocks';
export default function UserDashboard() {
const { user } = useAuth();
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-6xl mx-auto py-8 px-4">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Profile management */}
<div className="lg:col-span-2">
<ProfileManager
user={user}
onProfileUpdate={handleProfileUpdate}
onProfilePublish={handleProfilePublish}
/>
</div>
{/* Wallet sidebar */}
<div className="lg:col-span-1">
<WalletOverview address={user?.address} />
</div>
</div>
</div>
</div>
);
}
Mobile Profile Manager
import { ProfileManager } from 'bigblocks';
export default function MobileProfileManager({ user }) {
return (
<div className="min-h-screen bg-white">
<div className="px-4 py-6">
<h1 className="text-xl font-bold mb-4">Profile Settings</h1>
<ProfileManager
user={user}
className="w-full"
showPublisher={true}
maxProfiles={3} // Limit for mobile
onProfileUpdate={handleMobileProfileUpdate}
/>
</div>
</div>
);
}
Admin Profile Manager
import { ProfileManager } from 'bigblocks';
export default function AdminProfileManager({ userToManage }) {
const { user: adminUser } = useAuth();
if (!adminUser?.isAdmin) {
return <div>Access denied</div>;
}
return (
<div>
<div className="mb-6 p-4 bg-yellow-100 text-yellow-800 rounded">
<strong>Admin Mode:</strong> Managing profiles for {userToManage.address}
</div>
<ProfileManager
user={userToManage}
onProfileUpdate={async (profile) => {
// Admin update with audit log
await adminUpdateProfile(profile.id, profile, adminUser.id);
}}
onProfilePublish={async (profileId) => {
// Admin publish with approval
const result = await adminPublishProfile(profileId, adminUser.id);
return result;
}}
showPublisher={true}
maxProfiles={10}
/>
</div>
);
}
User Data Structure
interface AuthUser {
id: string;
address: string;
idKey: string;
profiles: ProfileInfo[];
activeProfileId: string;
ordinalsAddress?: string;
identityAddress?: string;
}
interface ProfileInfo {
id: string;
address: string;
isPublished: boolean;
name?: string;
description?: string;
image?: string;
// ... other profile fields
}
Features
- Tabbed Interface: Organized tabs for view, edit, publish, and switch operations
- Profile Creation: Create new profiles with validation
- Profile Switching: Switch between multiple profiles seamlessly
- Blockchain Publishing: Publish profiles to Bitcoin blockchain
- Profile Synchronization: Sync profiles across devices (optional)
- Profile Discovery: Discover existing profiles on blockchain
- Responsive Design: Works on desktop and mobile devices
- Real-time Updates: Live updates when profiles change
Publishing Process
- Validation: Ensure profile has required fields
- Transaction Creation: Create Bitcoin transaction with profile data
- Signing: Sign transaction with profile's private key
- Broadcasting: Broadcast transaction to Bitcoin network
- Confirmation: Wait for transaction confirmation
- Status Update: Update profile publication status
Synchronization
When enableProfileSync
is enabled:
- Profiles are synced across devices
- Changes are propagated to all connected devices
- Conflict resolution for simultaneous edits
- Backup and restore functionality
Error Handling
The component handles various error scenarios:
- Network connectivity issues
- Invalid profile data
- Publishing failures
- Sync conflicts
- Validation errors
Best Practices
- Profile Limits: Set reasonable
maxProfiles
limits - Validation: Validate profile data before saving
- Error Feedback: Provide clear error messages
- Loading States: Show loading indicators for async operations
- Confirmation: Confirm destructive actions like publishing
Troubleshooting
Profiles Not Loading
- Check user authentication status
- Verify backend API connectivity
- Check browser console for errors
Publishing Failures
- Verify Bitcoin network connectivity
- Check private key access
- Ensure sufficient network fees
Sync Issues
- Check network connectivity
- Verify sync service status
- Clear local cache if needed
Related Components
- ProfileViewer - Display profiles
- ProfileEditor - Edit profiles
- ProfileSwitcher - Quick profile switching
- ProfileDropdownMenu - Profile selection menu
API Integration
The component integrates with profile management APIs:
// Get user with profiles
GET /api/users/{id}/profiles
// Update profile
PUT /api/profiles/{id}
// Publish profile
POST /api/profiles/{id}/publish
// Create profile
POST /api/profiles
// Switch active profile
POST /api/users/{id}/switch-profile
Notes for Improvement
Enhanced Implementation: The actual component provides more sophisticated features than the basic prompt described:
- Complete tabbed interface for all profile operations
- Profile synchronization and discovery capabilities
- Better error handling and user feedback
- Support for multiple profile types (Person/Organization)
- Advanced publishing workflow with transaction tracking
- Real-time profile updates and conflict resolution