UNPKG

bigblocks

Version:

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

15 lines 19.4 kB
{ "name": "profile-management-profiledropdownmenu", "type": "registry:component", "dependencies": [], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/profile-management/ProfileDropdownMenu.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport {\n\tCheckIcon,\n\tChevronDownIcon,\n\tCopyIcon,\n\tGearIcon,\n\tHamburgerMenuIcon,\n\tLockClosedIcon,\n\tLockOpen1Icon,\n\tMoonIcon,\n\tPersonIcon,\n\tPlusIcon,\n\tSunIcon,\n} from \"@radix-ui/react-icons\";\nimport { useEffect, useState } from \"react\";\nimport { useBitcoinAuth } from \"../../hooks/useBitcoinAuth.js\";\nimport { responsive } from \"../../lib/layout-constants.js\";\nimport { AuthButton } from \"../authentication/AuthButton.js\";\nimport { BitcoinAvatar } from \"../ui-components/BitcoinAvatar.js\";\nimport { LoadingButton } from \"../ui-components/LoadingButton.js\";\nimport { PasswordInput } from \"../ui-components/PasswordInput.js\";\nimport { Badge } from \"../ui/badge.js\";\nimport { Button } from \"../ui/button.js\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogHeader,\n\tDialogTitle,\n} from \"../ui/dialog.js\";\nimport {\n\tDropdownMenu,\n\tDropdownMenuContent,\n\tDropdownMenuItem,\n\tDropdownMenuLabel,\n\tDropdownMenuRadioGroup,\n\tDropdownMenuRadioItem,\n\tDropdownMenuSeparator,\n\tDropdownMenuSub,\n\tDropdownMenuSubContent,\n\tDropdownMenuSubTrigger,\n\tDropdownMenuTrigger,\n} from \"../ui/dropdown-menu.js\";\nimport { CompactWalletOverview } from \"../wallet/components/WalletOverview.js\";\nimport type { BSVBalance } from \"../wallet/types/wallet.js\";\n\nexport interface ProfileDropdownMenuProps {\n\t/** Show wallet balance when available */\n\tshowWalletBalance?: boolean;\n\t/** Wallet balance data */\n\twalletBalance?: BSVBalance;\n\t/** Exchange rate for fiat conversion */\n\texchangeRate?: number;\n\t/** Currency to display */\n\tcurrency?: \"BSV\" | \"USD\";\n\t/** Use menu icon instead of avatar when not authenticated */\n\tuseMenuIcon?: boolean;\n\t/** Custom trigger element */\n\ttrigger?: React.ReactNode;\n\t/** Show theme switcher in menu (only shown when authenticated) */\n\tshowThemeSwitch?: boolean;\n\t/** Callback when profile is viewed */\n\tonViewProfile?: () => void;\n\t/** Callback when settings clicked */\n\tonSettings?: () => void;\n\t/** Callback when wallet unlocked */\n\tonWalletUnlocked?: () => void;\n\t/** Callback when create account clicked */\n\tonCreateAccount?: () => void;\n\t/** Callback when sign in clicked */\n\tonSignIn?: () => void;\n\t/** Additional menu items */\n\tadditionalItems?: React.ReactNode;\n\t/** Align dropdown menu */\n\talign?: \"start\" | \"center\" | \"end\";\n\t/** Side of dropdown */\n\tside?: \"top\" | \"right\" | \"bottom\" | \"left\";\n\t/** Sign out redirect URL */\n\tsignOutRedirectTo?: string;\n\t/** Show profile details in menu */\n\tshowProfileDetails?: boolean;\n}\n\nexport function ProfileDropdownMenu({\n\tshowWalletBalance = true,\n\twalletBalance,\n\texchangeRate = 50,\n\tcurrency = \"USD\",\n\tuseMenuIcon = false,\n\ttrigger,\n\tshowThemeSwitch = true,\n\tonViewProfile,\n\tonSettings,\n\tonWalletUnlocked,\n\tonCreateAccount,\n\tonSignIn,\n\tadditionalItems,\n\talign = \"end\",\n\tside = \"bottom\",\n\tsignOutRedirectTo = \"/\",\n\tshowProfileDetails = true,\n}: ProfileDropdownMenuProps) {\n\tconst {\n\t\tuser,\n\t\tisAuthenticated,\n\t\tprofiles,\n\t\tactiveProfile,\n\t\thasWalletCapabilities,\n\t\twalletExtension,\n\t\tswitchProfile,\n\t\tvalidatePassword,\n\t\thasLocalBackup,\n\t\tsignIn,\n\t} = useBitcoinAuth();\n\n\tconst [isUnlockDialogOpen, setIsUnlockDialogOpen] = useState(false);\n\tconst [unlockPassword, setUnlockPassword] = useState(\"\");\n\tconst [isUnlocking, setIsUnlocking] = useState(false);\n\tconst [unlockError, setUnlockError] = useState<string | null>(null);\n\tconst [copiedAddress, setCopiedAddress] = useState<string | null>(null);\n\n\t// Reset copied state after 2 seconds\n\tuseEffect(() => {\n\t\tif (copiedAddress) {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tsetCopiedAddress(null);\n\t\t\t}, 2000);\n\t\t\treturn () => clearTimeout(timer);\n\t\t}\n\t\treturn undefined;\n\t}, [copiedAddress]);\n\n\t// Handle copy with toast notification\n\tconst handleCopy = async (address: string) => {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(address);\n\t\t\tsetCopiedAddress(address);\n\t\t\t// Note: Toast notifications should be handled by the consuming app\n\t\t\t// We'll use the copiedAddress state to show feedback in the UI\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tconsole.error(\"Failed to copy:\", err);\n\t\t\treturn false;\n\t\t}\n\t};\n\n\t// Check if wallet is locked\n\tconst isWalletLocked = hasWalletCapabilities && !walletExtension;\n\n\t// Get user avatar and name\n\tconst getAvatarData = () => {\n\t\tif (!isAuthenticated || !user) {\n\t\t\treturn { src: \"\", fallback: \"?\" };\n\t\t}\n\n\t\tconst profile = activeProfile || profiles[0];\n\t\tconst name = profile?.name || \"User\";\n\t\tconst avatar = profile?.image || \"\";\n\n\t\tconst initials = name\n\t\t\t.split(\" \")\n\t\t\t.map((word) => word[0])\n\t\t\t.join(\"\")\n\t\t\t.toUpperCase()\n\t\t\t.slice(0, 2);\n\n\t\treturn { src: avatar, fallback: initials, name };\n\t};\n\n\tconst avatarData = getAvatarData();\n\n\t// Handle unlock - works for both initial unlock and wallet re-unlock\n\tconst handleUnlock = async () => {\n\t\tif (!unlockPassword) {\n\t\t\tsetUnlockError(\"Password is required\");\n\t\t\treturn;\n\t\t}\n\n\t\tsetIsUnlocking(true);\n\t\tsetUnlockError(null);\n\n\t\ttry {\n\t\t\tif (!isAuthenticated && hasLocalBackup) {\n\t\t\t\t// Initial unlock - sign in with password\n\t\t\t\tawait signIn(unlockPassword);\n\t\t\t\tsetIsUnlockDialogOpen(false);\n\t\t\t\tsetUnlockPassword(\"\");\n\t\t\t} else if (isAuthenticated) {\n\t\t\t\t// Re-unlock wallet features\n\t\t\t\tconst isValid = await validatePassword(unlockPassword);\n\t\t\t\tif (isValid) {\n\t\t\t\t\tsetIsUnlockDialogOpen(false);\n\t\t\t\t\tsetUnlockPassword(\"\");\n\t\t\t\t\tonWalletUnlocked?.();\n\t\t\t\t} else {\n\t\t\t\t\tsetUnlockError(\"Invalid password\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : \"Failed to unlock\";\n\t\t\tsetUnlockError(errorMessage);\n\t\t} finally {\n\t\t\tsetIsUnlocking(false);\n\t\t}\n\t};\n\n\t// Create trigger element based on auth state\n\tconst getTriggerElement = () => {\n\t\tif (trigger) return trigger;\n\n\t\tif (isAuthenticated) {\n\t\t\t// Authenticated - show avatar\n\t\t\treturn (\n\t\t\t\t<Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n\t\t\t\t\t<BitcoinAvatar\n\t\t\t\t\t\tsrc={avatarData.src}\n\t\t\t\t\t\talt={avatarData.name || \"User\"}\n\t\t\t\t\t\tfallback={avatarData.fallback}\n\t\t\t\t\t\tsize=\"default\"\n\t\t\t\t\t/>\n\t\t\t\t</Button>\n\t\t\t);\n\t\t}\n\n\t\t// Not authenticated\n\t\tif (hasLocalBackup) {\n\t\t\t// Has backup - show unlock button\n\t\t\treturn (\n\t\t\t\t<Button variant=\"secondary\" size=\"lg\">\n\t\t\t\t\t<LockClosedIcon className=\"h-4 w-4 mr-2\" />\n\t\t\t\t\tUnlock\n\t\t\t\t</Button>\n\t\t\t);\n\t\t}\n\n\t\t// No backup - show sign up button\n\t\treturn (\n\t\t\t<Button variant=\"secondary\" size=\"lg\">\n\t\t\t\t{useMenuIcon ? (\n\t\t\t\t\t<HamburgerMenuIcon className=\"h-4 w-4\" />\n\t\t\t\t) : (\n\t\t\t\t\t<PersonIcon className=\"h-4 w-4 mr-2\" />\n\t\t\t\t)}\n\t\t\t\t{!useMenuIcon && \"Sign Up\"}\n\t\t\t</Button>\n\t\t);\n\t};\n\n\tconst triggerElement = getTriggerElement();\n\n\treturn (\n\t\t<>\n\t\t\t<DropdownMenu>\n\t\t\t\t<DropdownMenuTrigger asChild>{triggerElement}</DropdownMenuTrigger>\n\n\t\t\t\t<DropdownMenuContent\n\t\t\t\t\talign={align}\n\t\t\t\t\tside={side}\n\t\t\t\t\tclassName=\"min-w-[240px]\"\n\t\t\t\t>\n\t\t\t\t\t{isAuthenticated && user ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{/* Wallet Balance Section */}\n\t\t\t\t\t\t\t{showWalletBalance && hasWalletCapabilities && walletBalance && (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<DropdownMenuItem disabled>\n\t\t\t\t\t\t\t\t\t\t<CompactWalletOverview\n\t\t\t\t\t\t\t\t\t\t\tbalance={walletBalance}\n\t\t\t\t\t\t\t\t\t\t\tshowValues={!isWalletLocked}\n\t\t\t\t\t\t\t\t\t\t\tcurrency={currency}\n\t\t\t\t\t\t\t\t\t\t\texchangeRate={exchangeRate}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Profile Section */}\n\t\t\t\t\t\t\t{showProfileDetails && activeProfile && (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<DropdownMenuItem disabled>\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2 w-full\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t<BitcoinAvatar\n\t\t\t\t\t\t\t\t\t\t\t\t\tsrc={activeProfile.image}\n\t\t\t\t\t\t\t\t\t\t\t\t\talt={activeProfile.name || \"Profile\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallback={avatarData.fallback}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{activeProfile.name}\n\t\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\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{user.address.slice(0, 8)}...\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{user.address.slice(-6)}\n\t\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\t</div>\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</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* View Profile */}\n\t\t\t\t\t\t\t{onViewProfile && (\n\t\t\t\t\t\t\t\t<DropdownMenuItem onClick={onViewProfile}>\n\t\t\t\t\t\t\t\t\t<PersonIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\tView Profile\n\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Copy Addresses */}\n\t\t\t\t\t\t\t{user && (\n\t\t\t\t\t\t\t\t<DropdownMenuSub>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSubTrigger>\n\t\t\t\t\t\t\t\t\t\t<CopyIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\tCopy Address\n\t\t\t\t\t\t\t\t\t</DropdownMenuSubTrigger>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSubContent>\n\t\t\t\t\t\t\t\t\t\t{/* Payment Address */}\n\t\t\t\t\t\t\t\t\t\t<DropdownMenuItem onClick={() => handleCopy(user.address)}>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-1 w-full\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">Payment Address</span>\n\t\t\t\t\t\t\t\t\t\t\t\t{copiedAddress === user.address && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<CheckIcon className=\"ml-auto h-4 w-4 text-green-600\" />\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\n\t\t\t\t\t\t\t\t\t\t{/* Ordinals Address - if different from payment */}\n\t\t\t\t\t\t\t\t\t\t{user.ordinalsAddress &&\n\t\t\t\t\t\t\t\t\t\t\tuser.ordinalsAddress !== user.address && (\n\t\t\t\t\t\t\t\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tuser.ordinalsAddress &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thandleCopy(user.ordinalsAddress)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-1 w-full\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">Ordinals Address</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{copiedAddress === user.ordinalsAddress && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CheckIcon className=\"ml-auto h-4 w-4 text-green-600\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t\t\t\t{/* Identity Address - if available */}\n\t\t\t\t\t\t\t\t\t\t{user.identityAddress && (\n\t\t\t\t\t\t\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tuser.identityAddress &&\n\t\t\t\t\t\t\t\t\t\t\t\t\thandleCopy(user.identityAddress)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-1 w-full\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">Identity Address</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{copiedAddress === user.identityAddress && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CheckIcon className=\"ml-auto h-4 w-4 text-green-600\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</DropdownMenuSubContent>\n\t\t\t\t\t\t\t\t</DropdownMenuSub>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Profile Switcher */}\n\t\t\t\t\t\t\t{profiles.length > 1 && (\n\t\t\t\t\t\t\t\t<DropdownMenuSub>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSubTrigger>\n\t\t\t\t\t\t\t\t\t\t<ChevronDownIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\tSwitch Profile\n\t\t\t\t\t\t\t\t\t</DropdownMenuSubTrigger>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSubContent>\n\t\t\t\t\t\t\t\t\t\t{profiles.map((profile) => (\n\t\t\t\t\t\t\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\t\t\t\t\t\t\tkey={profile.id}\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => switchProfile(profile.id)}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-1 w-full\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<BitcoinAvatar\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsrc={profile.image}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\talt={profile.name || \"Profile\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfallback={profile.name?.[0] || \"?\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">{profile.name}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{profile.id === activeProfile?.id && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CheckIcon className=\"ml-auto h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t\t\t\t<DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t<PlusIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\tCreate New Profile\n\t\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t</DropdownMenuSubContent>\n\t\t\t\t\t\t\t\t</DropdownMenuSub>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Wallet Lock/Unlock */}\n\t\t\t\t\t\t\t{hasWalletCapabilities && (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t\t\t{isWalletLocked ? (\n\t\t\t\t\t\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => setIsUnlockDialogOpen(true)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<LockClosedIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\tUnlock Wallet\n\t\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t<DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t<LockOpen1Icon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-1 w-full\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span>Wallet Unlocked</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<Badge variant=\"secondary\" className=\"ml-auto\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tActive\n\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\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)}\n\n\t\t\t\t\t\t\t{/* Settings */}\n\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t<DropdownMenuItem onClick={onSettings}>\n\t\t\t\t\t\t\t\t<GearIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\tSettings\n\t\t\t\t\t\t\t</DropdownMenuItem>\n\n\t\t\t\t\t\t\t{/* Additional Items */}\n\t\t\t\t\t\t\t{additionalItems && (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t\t\t{additionalItems}\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Sign Out */}\n\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t<DropdownMenuItem className=\"text-destructive\">\n\t\t\t\t\t\t\t\t<AuthButton\n\t\t\t\t\t\t\t\t\tmode=\"signout\"\n\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\tsize=\"default\"\n\t\t\t\t\t\t\t\t\tredirectTo={signOutRedirectTo}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{/* Not Authenticated Menu */}\n\t\t\t\t\t\t\t{hasLocalBackup ? (\n\t\t\t\t\t\t\t\t// Has backup - show unlock option\n\t\t\t\t\t\t\t\t<DropdownMenuItem onClick={() => setIsUnlockDialogOpen(true)}>\n\t\t\t\t\t\t\t\t\t<LockClosedIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\tUnlock\n\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t// No backup - show sign up / sign in options\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<DropdownMenuItem onClick={onCreateAccount}>\n\t\t\t\t\t\t\t\t\t\t<PersonIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\tCreate Account\n\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t\t<DropdownMenuItem onClick={onSignIn}>\n\t\t\t\t\t\t\t\t\t\t<LockOpen1Icon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\tSign In\n\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Settings - optional */}\n\t\t\t\t\t\t\t{onSettings && (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t\t\t<DropdownMenuItem onClick={onSettings}>\n\t\t\t\t\t\t\t\t\t\t<GearIcon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\tSettings\n\t\t\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Additional Items */}\n\t\t\t\t\t\t\t{additionalItems && (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t\t\t\t\t{additionalItems}\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</>\n\t\t\t\t\t)}\n\t\t\t\t</DropdownMenuContent>\n\t\t\t</DropdownMenu>\n\n\t\t\t{/* Unlock Dialog */}\n\t\t\t<Dialog open={isUnlockDialogOpen} onOpenChange={setIsUnlockDialogOpen}>\n\t\t\t\t<DialogContent className=\"max-w-[400px]\">\n\t\t\t\t\t<DialogHeader>\n\t\t\t\t\t\t<DialogTitle>Unlock</DialogTitle>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground mb-4\">\n\t\t\t\t\t\t\tEnter your password to unlock\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</DialogHeader>\n\n\t\t\t\t\t<form\n\t\t\t\t\t\tonSubmit={(e) => {\n\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\thandleUnlock();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t\t<PasswordInput\n\t\t\t\t\t\t\t\tvalue={unlockPassword}\n\t\t\t\t\t\t\t\tonChange={(value) => setUnlockPassword(value)}\n\t\t\t\t\t\t\t\tplaceholder=\"Enter password\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{unlockError && (\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-destructive\">{unlockError}</p>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t<div className=\"flex gap-3 justify-end\">\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => setIsUnlockDialogOpen(false)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\t\tloading={isUnlocking}\n\t\t\t\t\t\t\t\t\tdisabled={!unlockPassword}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<LockOpen1Icon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t\t\t\t\tUnlock\n\t\t\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</form>\n\t\t\t\t</DialogContent>\n\t\t\t</Dialog>\n\t\t</>\n\t);\n}\n", "target": "<%- config.aliases.components %>/profiledropdownmenu.tsx" } ] }