UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

15 lines 12.3 kB
{ "name": "profile-management-profilemanager", "type": "registry:component", "dependencies": [], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/profile-management/ProfileManager.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport {\n\tMagnifyingGlassIcon,\n\tPencil1Icon,\n\tPersonIcon,\n\tPlusIcon,\n\tRocketIcon,\n} from \"@radix-ui/react-icons\";\nimport { useState } from \"react\";\nimport { useBitcoinAuth } from \"../../hooks/useBitcoinAuth.js\";\nimport type { AuthUser, ProfileInfo } from \"../../lib/types.js\";\nimport { useBapProfileSync } from \"../bap-identity/hooks/useBapProfileSync.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs.js\";\nimport { ProfileEditor } from \"./ProfileEditor.js\";\nimport { ProfilePublisher } from \"./ProfilePublisher.js\";\nimport { ProfileSwitcher } from \"./ProfileSwitcher.js\";\nimport { ProfileViewer } from \"./ProfileViewer.js\";\n\nexport interface ProfileManagerProps {\n\tuser?: AuthUser;\n\tonProfileUpdate?: (profile: ProfileInfo) => void;\n\tonProfilePublish?: (profileId: string) => Promise<{ txid: string }>;\n\tonProfileCreate?: () => Promise<ProfileInfo>;\n\tonProfileSwitch?: (profileId: string) => void;\n\tshowPublisher?: boolean;\n\tmaxProfiles?: number;\n\tclassName?: string;\n\t// Profile sync options\n\tenableProfileSync?: boolean;\n\tmaxDiscoveryAttempts?: number;\n}\n\n/**\n * ProfileManager - Complete profile management interface\n *\n * Features:\n * - View current profile details\n * - Edit profile information\n * - Publish profiles to blockchain\n * - Switch between multiple profiles\n * - Create new profiles\n * - Tabbed interface for different actions\n */\nexport function ProfileManager({\n\tuser: userProp,\n\tonProfileUpdate,\n\tonProfilePublish,\n\tonProfileCreate,\n\tonProfileSwitch,\n\tshowPublisher = true,\n\tmaxProfiles = 10,\n\tclassName = \"\",\n\tenableProfileSync = true,\n\tmaxDiscoveryAttempts = 50,\n}: ProfileManagerProps) {\n\tconst { user: authUser, createProfile, switchProfile } = useBitcoinAuth();\n\tconst user = userProp || authUser;\n\n\tconst [activeTab, setActiveTab] = useState<\n\t\t\"view\" | \"edit\" | \"publish\" | \"sync\"\n\t>(\"view\");\n\tconst [isCreating, setIsCreating] = useState(false);\n\n\t// Profile synchronization\n\tconst profileSync = useBapProfileSync({\n\t\tautoSync: false, // Manual sync only in ProfileManager\n\t\tmaxDiscoveryAttempts,\n\t});\n\n\tif (!user) {\n\t\treturn (\n\t\t\t<div className={className}>\n\t\t\t\t<p className=\"text-base text-muted-foreground\">\n\t\t\t\t\tPlease sign in to manage your profiles.\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst activeProfile =\n\t\tuser.profiles.find((p) => p.id === user.activeProfileId) ||\n\t\tuser.profiles[0];\n\n\tif (!activeProfile) {\n\t\treturn (\n\t\t\t<div className={className}>\n\t\t\t\t<div className=\"flex flex-col gap-4 items-center py-8\">\n\t\t\t\t\t<PersonIcon\n\t\t\t\t\t\twidth=\"48\"\n\t\t\t\t\t\theight=\"48\"\n\t\t\t\t\t\tclassName=\"text-muted-foreground\"\n\t\t\t\t\t/>\n\t\t\t\t\t<h3 className=\"text-base font-medium\">No Profiles Yet</h3>\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\tCreate your first profile to get started\n\t\t\t\t\t</p>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\tonClick={async () => {\n\t\t\t\t\t\t\tsetIsCreating(true);\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst newProfile = onProfileCreate\n\t\t\t\t\t\t\t\t\t? await onProfileCreate()\n\t\t\t\t\t\t\t\t\t: await createProfile();\n\t\t\t\t\t\t\t\tif (onProfileSwitch) {\n\t\t\t\t\t\t\t\t\tonProfileSwitch(newProfile.id);\n\t\t\t\t\t\t\t\t} else if (switchProfile) {\n\t\t\t\t\t\t\t\t\tswitchProfile(newProfile.id);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconsole.error(\"Failed to create profile:\", error);\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tsetIsCreating(false);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tdisabled={isCreating}\n\t\t\t\t\t>\n\t\t\t\t\t\t<PlusIcon className=\"h-4 w-4 mr-2\" />\n\t\t\t\t\t\t{isCreating ? \"Creating...\" : \"Create First Profile\"}\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst handleProfileUpdate = async (updates: Partial<ProfileInfo>) => {\n\t\tif (onProfileUpdate) {\n\t\t\tawait onProfileUpdate({ ...activeProfile, ...updates });\n\t\t}\n\t\tsetActiveTab(\"view\");\n\t};\n\n\tconst handleProfilePublish = async (profileId: string) => {\n\t\tif (onProfilePublish) {\n\t\t\treturn await onProfilePublish(profileId);\n\t\t}\n\t\tthrow new Error(\"No publish handler available\");\n\t};\n\n\tconst handleProfileSwitch = (profileId: string) => {\n\t\tif (onProfileSwitch) {\n\t\t\tonProfileSwitch(profileId);\n\t\t} else if (switchProfile) {\n\t\t\tswitchProfile(profileId);\n\t\t}\n\t};\n\n\tconst handleProfileCreate = async () => {\n\t\tsetIsCreating(true);\n\t\ttry {\n\t\t\tconst newProfile = onProfileCreate\n\t\t\t\t? await onProfileCreate()\n\t\t\t\t: await createProfile();\n\t\t\thandleProfileSwitch(newProfile.id);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to create profile:\", error);\n\t\t} finally {\n\t\t\tsetIsCreating(false);\n\t\t}\n\t};\n\n\treturn (\n\t\t<div className={className}>\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t{/* Header with Profile Switcher */}\n\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t<h2 className=\"text-2xl font-bold\">Profile Management</h2>\n\t\t\t\t\t{user.profiles.length > 1 && (\n\t\t\t\t\t\t<ProfileSwitcher\n\t\t\t\t\t\t\tprofiles={user.profiles}\n\t\t\t\t\t\t\tactiveProfileId={user.activeProfileId}\n\t\t\t\t\t\t\tonSwitch={handleProfileSwitch}\n\t\t\t\t\t\t\tonCreate={\n\t\t\t\t\t\t\t\tuser.profiles.length < maxProfiles\n\t\t\t\t\t\t\t\t\t? handleProfileCreate\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmaxProfiles={maxProfiles}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Tabs */}\n\t\t\t\t<Tabs\n\t\t\t\t\tvalue={activeTab}\n\t\t\t\t\tonValueChange={(value) => setActiveTab(value as typeof activeTab)}\n\t\t\t\t>\n\t\t\t\t\t<TabsList>\n\t\t\t\t\t\t<TabsTrigger value=\"view\" className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<PersonIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\tView Profile\n\t\t\t\t\t\t</TabsTrigger>\n\t\t\t\t\t\t<TabsTrigger value=\"edit\" className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<Pencil1Icon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\tEdit Profile\n\t\t\t\t\t\t</TabsTrigger>\n\t\t\t\t\t\t{showPublisher && !activeProfile.isPublished && (\n\t\t\t\t\t\t\t<TabsTrigger value=\"publish\" className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<RocketIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\tPublish\n\t\t\t\t\t\t\t</TabsTrigger>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{enableProfileSync && (\n\t\t\t\t\t\t\t<TabsTrigger value=\"sync\" className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<MagnifyingGlassIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\tDiscover Profiles\n\t\t\t\t\t\t\t</TabsTrigger>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</TabsList>\n\n\t\t\t\t\t<div className=\"pt-4\">\n\t\t\t\t\t\t<TabsContent value=\"view\">\n\t\t\t\t\t\t\t<ProfileViewer\n\t\t\t\t\t\t\t\tprofile={activeProfile}\n\t\t\t\t\t\t\t\tonEdit={() => setActiveTab(\"edit\")}\n\t\t\t\t\t\t\t\tonPublish={\n\t\t\t\t\t\t\t\t\t!activeProfile.isPublished\n\t\t\t\t\t\t\t\t\t\t? () => setActiveTab(\"publish\")\n\t\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</TabsContent>\n\n\t\t\t\t\t\t<TabsContent value=\"edit\">\n\t\t\t\t\t\t\t<ProfileEditor\n\t\t\t\t\t\t\t\tprofile={activeProfile}\n\t\t\t\t\t\t\t\tonSave={handleProfileUpdate}\n\t\t\t\t\t\t\t\tonCancel={() => setActiveTab(\"view\")}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</TabsContent>\n\n\t\t\t\t\t\t{showPublisher && (\n\t\t\t\t\t\t\t<TabsContent value=\"publish\">\n\t\t\t\t\t\t\t\t<ProfilePublisher\n\t\t\t\t\t\t\t\t\tprofile={activeProfile}\n\t\t\t\t\t\t\t\t\tonPublish={handleProfilePublish}\n\t\t\t\t\t\t\t\t\tonSuccess={(txid) => {\n\t\t\t\t\t\t\t\t\t\tconsole.log(\"Profile published:\", txid);\n\t\t\t\t\t\t\t\t\t\tsetActiveTab(\"view\");\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonError={(error) => {\n\t\t\t\t\t\t\t\t\t\tconsole.error(\"Publishing failed:\", error);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</TabsContent>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{enableProfileSync && (\n\t\t\t\t\t\t\t<TabsContent value=\"sync\">\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<h3 className=\"text-lg font-bold mb-3\">\n\t\t\t\t\t\t\t\t\t\tDiscover Additional Profiles\n\t\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground mb-4\">\n\t\t\t\t\t\t\t\t\t\tScan the blockchain for additional BAP identities associated\n\t\t\t\t\t\t\t\t\t\twith your master backup.\n\t\t\t\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t\t\t\t{/* Sync Status */}\n\t\t\t\t\t\t\t\t\t{profileSync.isScanning && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"mb-4\">\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm mb-2\">\n\t\t\t\t\t\t\t\t\t\t\t\tScanning for profiles... {profileSync.progress}%\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tCurrent ID: {profileSync.currentId} | Found:{\" \"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{profileSync.foundCount}\n\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t\t\t{/* Results */}\n\t\t\t\t\t\t\t\t\t{profileSync.isSuccess && profileSync.result && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"mb-4 p-3 bg-green-50 border border-green-200 rounded-lg\">\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium text-green-800\">\n\t\t\t\t\t\t\t\t\t\t\t\tDiscovery Complete!\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm text-green-700\">\n\t\t\t\t\t\t\t\t\t\t\t\tFound {profileSync.result.totalFound} profiles total.\n\t\t\t\t\t\t\t\t\t\t\t\t{profileSync.result.updatedBackup\n\t\t\t\t\t\t\t\t\t\t\t\t\t? \" Your backup has been updated with new identities.\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t: \" No new identities discovered.\"}\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t\t\t{/* Error */}\n\t\t\t\t\t\t\t\t\t{profileSync.error && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"mb-4 p-3 bg-red-50 border border-red-200 rounded-lg\">\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm text-red-700\">\n\t\t\t\t\t\t\t\t\t\t\t\tError: {profileSync.error.message}\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t\t\t{/* Action Buttons */}\n\t\t\t\t\t\t\t\t\t<div className=\"flex gap-3\">\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={profileSync.syncProfiles}\n\t\t\t\t\t\t\t\t\t\t\tdisabled={profileSync.isScanning}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<MagnifyingGlassIcon className=\"h-4 w-4 mr-2\" />\n\t\t\t\t\t\t\t\t\t\t\t{profileSync.isScanning\n\t\t\t\t\t\t\t\t\t\t\t\t? \"Scanning...\"\n\t\t\t\t\t\t\t\t\t\t\t\t: \"Start Discovery\"}\n\t\t\t\t\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t\t\t\t\t{(profileSync.error || profileSync.isSuccess) && (\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" onClick={profileSync.resetSync}>\n\t\t\t\t\t\t\t\t\t\t\t\tReset\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</TabsContent>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</Tabs>\n\n\t\t\t\t{/* Create New Profile Button (if not at max) */}\n\t\t\t\t{user.profiles.length < maxProfiles && user.profiles.length === 1 && (\n\t\t\t\t\t<div className=\"flex justify-center pt-4\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tonClick={handleProfileCreate}\n\t\t\t\t\t\t\tdisabled={isCreating}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<PlusIcon className=\"h-4 w-4 mr-2\" />\n\t\t\t\t\t\t\t{isCreating ? \"Creating...\" : \"Create Additional Profile\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n", "target": "<%- config.aliases.components %>/profilemanager.tsx" } ] }