{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "account-hover-card",
  "type": "registry:block",
  "title": "Lens Account Hover Card",
  "description": "A hover card that displays Lens account details when hovering over an account name or handle.",
  "dependencies": [
    "@lens-protocol/react@0.0.0-canary-20250820102520",
    "@lens-protocol/client@0.0.0-canary-20250820102520",
    "lucide-react",
    "viem@2.33.0"
  ],
  "registryDependencies": [
    "avatar",
    "hover-card",
    "skeleton",
    "https://lensblocks.com/r/follow-button.json",
    "https://lensblocks.com/r/lens-markdown.json",
    "https://lensblocks.com/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/account/lens-account-hover-card.tsx",
      "content": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/registry/new-york/ui/avatar\";\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@/registry/new-york/ui/hover-card\";\nimport { ReactNode, useEffect, useState } from \"react\";\nimport { Account, AccountStats, mainnet, PublicClient, SessionClient, testnet, TxHash } from \"@lens-protocol/react\";\nimport { fetchAccount, fetchAccountStats } from \"@lens-protocol/client/actions\";\nimport { formatFollowerCount, parseUri } from \"@/registry/new-york/lib/lens-utils\";\nimport { CircleUserRoundIcon } from \"lucide-react\";\nimport { WalletClient } from \"viem\";\nimport { LensMarkdown } from \"@/registry/new-york/components/common/lens-markdown\";\nimport { LensFollowButton } from \"@/registry/new-york/components/account/lens-follow-button\";\nimport { Skeleton } from \"@/registry/new-york/ui/skeleton\";\nimport { isResult, Result } from \"@/registry/new-york/lib/result\";\n\ninterface Props {\n  /**\n   * The component that will trigger the hover card when hovered over.\n   *\n   */\n  children?: ReactNode;\n\n  /**\n   * The Lens Account to display information about\n   */\n  account: Account | Result<Account>;\n\n  /**\n   * The Lens Client used for making public and authenticated calls\n   */\n  session: Result<SessionClient>;\n\n  /**\n   * The wallet client from viem used to sign messages for authentication.\n   * If not provided, follow/unfollow button will not be rendered.\n   */\n  wallet?: { data: WalletClient | undefined | null; isLoading?: boolean; error?: unknown };\n\n  publicClient?: PublicClient;\n\n  /**\n   * Callback function that is called when the follow operation is successful\n   */\n  onFollowSuccess?: (account: Account, txHash: TxHash) => void;\n\n  /**\n   * Callback function that is called when the unfollow operation is successful\n   */\n  onUnfollowSuccess?: (account: Account, txHash: TxHash) => void;\n\n  /**\n   * Callback function that is called when an error occurs during follow/unfollow operations\n   */\n  onError?: (account: Account, error: Error) => void;\n\n  useTestnet?: boolean;\n}\n\nexport const LensAccountHoverCard = ({\n  children,\n  account: accountRes,\n  session,\n  wallet,\n  publicClient,\n  onFollowSuccess,\n  onUnfollowSuccess,\n  onError,\n  useTestnet,\n}: Props) => {\n  const sessionClient = session.data;\n\n  const [isOpen, setIsOpen] = useState(false);\n  const [accountStats, setAccountStats] = useState<AccountStats>();\n  const [showFollowButton, setShowFollowButton] = useState(false);\n  const [account, setAccount] = useState<Account | null | undefined>(\n    isResult(accountRes) ? accountRes.data : accountRes,\n  );\n\n  const isLoading = \"loading\" in accountRes ? accountRes.loading : false;\n\n  const lensClient =\n    publicClient ??\n    PublicClient.create({\n      environment: useTestnet ? testnet : mainnet,\n    });\n\n  useEffect(() => {\n    setAccount(\"data\" in accountRes ? accountRes.data : accountRes);\n  }, [accountRes]);\n\n  useEffect(() => {\n    const fetchStats = async () => {\n      console.log(\"fetching stats for\", account?.address);\n      if (!isOpen || !account) return;\n      const res = await fetchAccountStats(lensClient, {\n        account: account.address,\n      });\n      console.log(\"fetched stats\", res);\n      if (res.isOk() && res.value) {\n        setAccountStats(res.value);\n      }\n    };\n    fetchStats();\n  }, [account, isOpen]);\n\n  useEffect(() => {\n    if (!isOpen || !sessionClient) return;\n\n    const user = sessionClient.getAuthenticatedUser();\n    if (user.isOk()) {\n      setShowFollowButton(\n        account?.operations?.canFollow.__typename === \"AccountFollowOperationValidationPassed\" ||\n          account?.operations?.canUnfollow.__typename === \"AccountFollowOperationValidationPassed\",\n      );\n    }\n  }, [sessionClient, account?.operations]);\n\n  const updateAccount = async () => {\n    if (!account) return;\n    const client = sessionClient ?? lensClient;\n    const res = await fetchAccount(client, {\n      address: account.address,\n    });\n    if (res.isOk() && res.value) {\n      setAccount(res.value);\n    }\n  };\n\n  const handleFollowSuccess = async (txHash: TxHash) => {\n    await updateAccount();\n    if (account) {\n      onFollowSuccess?.(account, txHash);\n    }\n  };\n\n  const handleUnfollowSuccess = async (txHash: TxHash) => {\n    await updateAccount();\n    if (account) {\n      onUnfollowSuccess?.(account, txHash);\n    }\n  };\n\n  return (\n    <HoverCard open={isOpen} onOpenChange={setIsOpen}>\n      <HoverCardTrigger>{children}</HoverCardTrigger>\n      <HoverCardContent className=\"w-80\">\n        {!account || isLoading ? (\n          <HoverCardSkeleton />\n        ) : (\n          <div className=\"flex flex-col w-full min-w-0\">\n            <div className=\"flex justify-between\">\n              <Avatar className=\"w-10 h-10\">\n                <AvatarImage src={parseUri(account.metadata?.picture)} alt={\"@\" + account.username?.localName} />\n                <AvatarFallback className=\"bg-primary\">\n                  <CircleUserRoundIcon className=\"text-primary-foreground\" />\n                </AvatarFallback>\n              </Avatar>\n              {showFollowButton && (\n                <LensFollowButton\n                  account={accountRes}\n                  session={session}\n                  wallet={wallet}\n                  onFollowSuccess={(_account, txHash) => handleFollowSuccess(txHash)}\n                  onUnfollowSuccess={(_account, txHash) => handleUnfollowSuccess(txHash)}\n                  onFollowError={onError}\n                />\n              )}\n            </div>\n            <div className=\"flex flex-col pt-2 w-full min-w-0\">\n              <div className=\"font-bold text-lg truncate\">\n                {account.metadata?.name ??\n                  (account.username ? \"@\" + account.username.value.replace(\"lens/\", \"\") : undefined) ??\n                  account.address}\n              </div>\n              <div className=\"flex flex-wrap gap-2 items-center -mt-0.5\">\n                {account.metadata?.name && account.username && (\n                  <span className=\"opacity-60 truncate\">{`@${account.username.value.replace(\"lens/\", \"\")}`}</span>\n                )}\n                {account.operations?.isFollowingMe && (\n                  <span className=\"h-5 bg-muted text-muted-foreground rounded-full px-2 text-xs font-semibold flex items-center justify-center\">\n                    Follows you\n                  </span>\n                )}\n              </div>\n              {account.metadata?.bio && (\n                <LensMarkdown content={account.metadata.bio} className=\"opacity-70 pt-3 pb-1 line-clamp-5 text-sm\" />\n              )}\n              {accountStats && (\n                <div className=\"flex gap-3 pt-2 text-sm\">\n                  <div className=\"flex gap-1\">\n                    <span className=\"font-bold\">{formatFollowerCount(accountStats.graphFollowStats.following)}</span>\n                    <span className=\"opacity-60\">following</span>\n                  </div>\n                  <div className=\"flex gap-1\">\n                    <span className=\"font-bold\">{formatFollowerCount(accountStats.graphFollowStats.followers)}</span>\n                    <span className=\"opacity-60\">followers</span>\n                  </div>\n                </div>\n              )}\n            </div>\n          </div>\n        )}\n      </HoverCardContent>\n    </HoverCard>\n  );\n};\n\nconst HoverCardSkeleton = () => {\n  return (\n    <div className=\"w-full flex flex-col w-full min-w-0 gap-1\">\n      <div className=\"w-full flex justify-between\">\n        <Skeleton className=\"w-10 h-10 rounded-full\" />\n        <Skeleton className=\"w-28 h-8 rounded-full\" />\n      </div>\n      <div className=\"w-full flex flex-col pt-2 w-full min-w-0 gap-2\">\n        <Skeleton className=\"h-5 w-32 rounded-full\" />\n        <Skeleton className=\"h-4 w-20 rounded-full\" />\n      </div>\n      <div className=\"flex flex-col gap-1.5 mt-3\">\n        <Skeleton className=\"w-11/12 h-4 rounded-full\" />\n        <Skeleton className=\"w-full h-4 rounded-full\" />\n        <Skeleton className=\"w-2/3 h-4 rounded-full\" />\n      </div>\n      <div className=\"w-full flex gap-2 text-sm mt-3\">\n        <div className=\"w-full flex gap-1\">\n          <Skeleton className=\"h-4 w-8 rounded-full\" />\n          <Skeleton className=\"h-4 w-16 rounded-full\" />\n        </div>\n        <div className=\"w-full flex gap-1\">\n          <Skeleton className=\"h-4 w-8 rounded-full\" />\n          <Skeleton className=\"h-4 w-16 rounded-full\" />\n        </div>\n      </div>\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/lib/result.ts",
      "content": "export type Result<T, E = never> = {\n  data: T | null | undefined;\n  error: E | null | undefined;\n  loading: boolean;\n};\n\nexport const isResult = <T, E = never>(result: any): result is Result<T, E> => {\n  return \"data\" in result && \"error\" in result && \"loading\" in result;\n};\n\nexport const isLoading = (result: any): boolean => {\n  if (isResult(result)) {\n    return result.loading;\n  }\n  return false;\n};\n",
      "type": "registry:lib"
    }
  ]
}
