{
  "name": "social-components-postcard",
  "type": "registry:component",
  "dependencies": [
    "bmap-api-types"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/social/components/PostCard.tsx",
      "type": "registry:component",
      "content": "\"use client\";\nimport type { BapIdentity, PostMeta, PostTransaction } from \"bmap-api-types\";\nimport { responsive } from \"../../../lib/layout-constants.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { BitcoinAvatar } from \"../../ui-components/index.js\";\nimport { Card, CardContent } from \"../../ui/card.js\";\nimport { useFetchLikes } from \"../hooks/useFetchLikes.js\";\nimport { FollowButton } from \"./FollowButton.js\";\nimport { LikeButton } from \"./LikeButton.js\";\n\nexport interface PostCardProps {\n\tpost: PostTransaction;\n\tmeta?: PostMeta;\n\tsigner?: BapIdentity;\n\tshowActions?: boolean;\n\tshowAuthor?: boolean;\n\tshowTimestamp?: boolean;\n\tshowReplies?: boolean;\n\texpandedReplies?: boolean;\n\treplies?: PostTransaction[];\n\trepliesSigners?: BapIdentity[];\n\trepliesMeta?: PostMeta[];\n\tcompact?: boolean;\n\tonReply?: (post: PostTransaction) => void;\n\tonShare?: (post: PostTransaction) => void;\n\tonUserClick?: (identity: BapIdentity) => void;\n\tonShowReplies?: (txid: string) => void;\n\tclassName?: string;\n}\n\n/**\n * PostCard displays individual social posts from the Bitcoin blockchain\n *\n * Features:\n * - Post content with markdown support\n * - Author information and avatar\n * - Like/reaction buttons\n * - Follow/unfollow buttons\n * - Reply and share actions\n * - Rich media display\n * - Timestamp and metadata\n * - Nested replies/comments display\n * - Expandable reply threads\n */\nexport function PostCard({\n\tpost,\n\tmeta,\n\tsigner,\n\tshowActions = true,\n\tshowAuthor = true,\n\tshowTimestamp = true,\n\tshowReplies = false,\n\texpandedReplies = false,\n\treplies,\n\trepliesSigners,\n\trepliesMeta,\n\tcompact = false,\n\tonReply,\n\tonShare,\n\tonUserClick,\n\tonShowReplies,\n\tclassName = \"\",\n}: PostCardProps) {\n\t// Auto-fetch like data if not provided in meta\n\tconst { likeInfo } = useFetchLikes({\n\t\ttxid: post.tx.h,\n\t\tenabled: !meta, // Only fetch if meta not provided\n\t});\n\tconst formatTimestamp = (timestamp: number) => {\n\t\tconst now = Date.now();\n\t\tconst diff = now - timestamp;\n\n\t\tconst minutes = Math.floor(diff / 60000);\n\t\tconst hours = Math.floor(diff / 3600000);\n\t\tconst days = Math.floor(diff / 86400000);\n\n\t\tif (minutes < 1) return \"now\";\n\t\tif (minutes < 60) return `${minutes}m`;\n\t\tif (hours < 24) return `${hours}h`;\n\t\tif (days < 7) return `${days}d`;\n\n\t\treturn new Date(timestamp).toLocaleDateString();\n\t};\n\n\t// Extract post data\n\tconst content = post.B[0]?.content || \"\";\n\tconst _contentType = post.B[0]?.[\"content-type\"] || \"text/plain\";\n\tconst txId = post.tx.h;\n\tconst timestamp = post.timestamp;\n\n\tconst handleUserClick = () => {\n\t\tif (signer && onUserClick) {\n\t\t\tonUserClick(signer);\n\t\t}\n\t};\n\n\tconst handleReplyClick = () => {\n\t\tif (onReply) {\n\t\t\tonReply(post);\n\t\t}\n\t};\n\n\tconst handleShareClick = () => {\n\t\tif (onShare) {\n\t\t\tonShare(post);\n\t\t}\n\t};\n\n\tconst avatarSize = compact ? \"default\" : \"lg\";\n\n\t// Extract username/paymail from MAP data if no signer\n\tconst mapData = post.MAP[0];\n\tconst mapUsername = mapData?.username || mapData?.paymail;\n\tconst aipAddress = post.AIP[0]?.address;\n\n\t// Use signer info if available, otherwise fall back to MAP data\n\tconst signerName =\n\t\tsigner?.identity?.alternateName ||\n\t\tsigner?.identity?.givenName ||\n\t\tsigner?.displayName ||\n\t\tmapUsername;\n\tconst signerImage = signer?.identity?.image || signer?.icon;\n\tconst signerAddress = signer?.currentAddress || aipAddress;\n\tconst fallbackText =\n\t\tsignerName?.charAt(0) || signerAddress?.slice(0, 2) || \"?\";\n\n\treturn (\n\t\t<Card className={cn(\"w-full\", className)}>\n\t\t\t<CardContent\n\t\t\t\tclassName={cn(\"flex flex-col\", compact ? \"gap-2 p-3\" : \"gap-4 p-6\")}\n\t\t\t>\n\t\t\t\t{/* Author Header */}\n\t\t\t\t{showAuthor && (signerName || signerAddress) && (\n\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<BitcoinAvatar\n\t\t\t\t\t\t\t\tsize={avatarSize}\n\t\t\t\t\t\t\t\tsrc={signerImage}\n\t\t\t\t\t\t\t\talt={signerName || \"User\"}\n\t\t\t\t\t\t\t\tfallback={fallbackText}\n\t\t\t\t\t\t\t\tclassName={cn(onUserClick && \"cursor-pointer\")}\n\t\t\t\t\t\t\t\tonClick={onUserClick ? handleUserClick : undefined}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t<p\n\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\tcompact ? \"text-sm\" : \"text-base\",\n\t\t\t\t\t\t\t\t\t\t\"font-medium\",\n\t\t\t\t\t\t\t\t\t\tonUserClick && \"cursor-pointer\",\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\tonClick={handleUserClick}\n\t\t\t\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\t\t\t\t\t\t\t\t\t\thandleUserClick();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\ttabIndex={onUserClick ? 0 : undefined}\n\t\t\t\t\t\t\t\t\trole={onUserClick ? \"button\" : undefined}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{signerName ||\n\t\t\t\t\t\t\t\t\t\t(signerAddress && signerAddress.length > 10\n\t\t\t\t\t\t\t\t\t\t\t? `${signerAddress.slice(0, 6)}...${signerAddress.slice(-4)}`\n\t\t\t\t\t\t\t\t\t\t\t: signerAddress || \"Anonymous\")}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t{signerName && signerAddress && signerAddress.length > 10\n\t\t\t\t\t\t\t\t\t\t? `${signerAddress.slice(0, 6)}...${signerAddress.slice(-4)}`\n\t\t\t\t\t\t\t\t\t\t: \"\"}\n\t\t\t\t\t\t\t\t\t{showTimestamp && (\n\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{formatTimestamp(timestamp)}\n\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{signer && (\n\t\t\t\t\t\t\t<FollowButton\n\t\t\t\t\t\t\t\tidKey={signer.idKey || signer.currentAddress || \"\"}\n\t\t\t\t\t\t\t\tisFollowing={false}\n\t\t\t\t\t\t\t\tusername={signerName}\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t{/* Post Content */}\n\t\t\t\t<div>\n\t\t\t\t\t<p className={compact ? \"text-sm\" : \"text-base\"}>{content}</p>\n\n\t\t\t\t\t{/* Media would be extracted from B protocol data if present */}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Post Actions */}\n\t\t\t\t{showActions && (\n\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t\t\t\t<LikeButton\n\t\t\t\t\t\t\t\ttxid={txId}\n\t\t\t\t\t\t\t\tshowCount={true}\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tlikeInfo={likeInfo || undefined}\n\t\t\t\t\t\t\t\tautoFetch={!likeInfo}\n\t\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t\t{onReply && (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground cursor-pointer hover:text-foreground\"\n\t\t\t\t\t\t\t\t\tonClick={handleReplyClick}\n\t\t\t\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\t\t\t\t\t\t\t\t\t\thandleReplyClick();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\ttabIndex={0}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tReply\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{onShare && (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-muted-foreground cursor-pointer hover:text-foreground\"\n\t\t\t\t\t\t\t\t\tonClick={handleShareClick}\n\t\t\t\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\t\t\t\t\t\t\t\t\t\thandleShareClick();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\ttabIndex={0}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tShare\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{meta?.replies && meta.replies > 0 && (\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"text-xs text-muted-foreground\",\n\t\t\t\t\t\t\t\t\tshowReplies &&\n\t\t\t\t\t\t\t\t\t\tonShowReplies &&\n\t\t\t\t\t\t\t\t\t\t\"cursor-pointer hover:text-foreground\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\tshowReplies && onShowReplies && onShowReplies(txId)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t(e.key === \"Enter\" || e.key === \" \") &&\n\t\t\t\t\t\t\t\t\t\tshowReplies &&\n\t\t\t\t\t\t\t\t\t\tonShowReplies\n\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\tonShowReplies(txId);\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\ttabIndex={showReplies && onShowReplies ? 0 : undefined}\n\t\t\t\t\t\t\t\trole={showReplies && onShowReplies ? \"button\" : undefined}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{meta.replies} {meta.replies === 1 ? \"reply\" : \"replies\"}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t{/* Replies Preview */}\n\t\t\t\t{showReplies && expandedReplies && replies && replies.length > 0 && (\n\t\t\t\t\t<div className=\"pl-4 pt-2 border-l-2 border-border\">\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t{replies.map((reply, index) => {\n\t\t\t\t\t\t\t\tconst replySigner = repliesSigners?.[index];\n\t\t\t\t\t\t\t\tconst replyMeta = repliesMeta?.[index];\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<PostCard\n\t\t\t\t\t\t\t\t\t\tkey={reply.tx.h}\n\t\t\t\t\t\t\t\t\t\tpost={reply}\n\t\t\t\t\t\t\t\t\t\tmeta={replyMeta}\n\t\t\t\t\t\t\t\t\t\tsigner={replySigner}\n\t\t\t\t\t\t\t\t\t\tcompact={true}\n\t\t\t\t\t\t\t\t\t\tshowActions={showActions}\n\t\t\t\t\t\t\t\t\t\tonReply={onReply}\n\t\t\t\t\t\t\t\t\t\tonShare={onShare}\n\t\t\t\t\t\t\t\t\t\tonUserClick={onUserClick}\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\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n",
      "target": "<%- config.aliases.components %>/postcard.tsx"
    }
  ]
}