{
  "name": "metalens-components-metalenscomments",
  "type": "registry:component",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/metalens/components/MetaLensComments.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { useCallback, useState } from \"react\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\n// Removed: import { responsive } from \"../../../lib/layout-constants.js\";\nimport { BitcoinAvatar } from \"../../ui-components/BitcoinAvatar.js\";\nimport { Button } from \"../../ui/button.js\";\nimport { Card, CardContent } from \"../../ui/card.js\";\nimport { ScrollArea } from \"../../ui/scroll-area.js\";\nimport { Textarea } from \"../../ui/textarea.js\";\nimport { useMetaLens } from \"../hooks/useMetaLens.js\";\nimport { useMetaLensStream } from \"../hooks/useMetaLensStream.js\";\nimport type { MetaLensComment, MetaLensCommentsProps } from \"../types/index.js\";\nimport { MetaLensReactions } from \"./MetaLensReactions.js\";\n\n/**\n * Main MetaLens commenting component\n *\n * Based on patterns from:\n * - /Users/satchmo/code/metalens-upc/src/components/comments.tsx\n * - /Users/satchmo/code/metalens-web/src_old/views/model_comments.html\n * - /Users/satchmo/code/tonicpow-website-static/src/components/layout/MetaLens/index.tsx\n *\n * @example\n * ```tsx\n * <MetaLensComments\n *   context=\"url\"\n *   value=\"https://example.com\"\n *   showForm={true}\n *   onCommentPosted={(txid) => console.log('Posted:', txid)}\n * />\n * ```\n */\nexport function MetaLensComments({\n\tcontext,\n\tvalue,\n\tsubcontext,\n\tsubcontextValue,\n\tshowForm = true,\n\tshowCount = true,\n\tmaxHeight = \"600px\",\n\tonCommentPosted,\n\tclassName,\n\ttheme,\n}: MetaLensCommentsProps) {\n\tconst { comments, loading, error, refetch } = useMetaLensStream(\n\t\tcontext,\n\t\tvalue,\n\t);\n\tconst { postComment } = useMetaLens();\n\tconst { user } = useBitcoinAuth();\n\tconst [isPosting, setIsPosting] = useState(false);\n\tconst [commentText, setCommentText] = useState(\"\");\n\tconst [replyingTo, setReplyingTo] = useState<string | null>(null);\n\n\tconst handleSubmit = useCallback(async () => {\n\t\tif (!commentText.trim() || isPosting) return;\n\n\t\tsetIsPosting(true);\n\t\ttry {\n\t\t\tconst txid = await postComment({\n\t\t\t\tcontext,\n\t\t\t\tvalue,\n\t\t\t\tsubcontext,\n\t\t\t\tsubcontextValue,\n\t\t\t\tcontent: commentText,\n\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\tparentId: replyingTo || undefined,\n\t\t\t});\n\n\t\t\t// Clear form\n\t\t\tsetCommentText(\"\");\n\t\t\tsetReplyingTo(null);\n\n\t\t\t// Notify parent\n\t\t\tonCommentPosted?.(txid);\n\n\t\t\t// Refresh comments\n\t\t\trefetch();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to post comment:\", error);\n\t\t\t// TODO: Show error toast\n\t\t} finally {\n\t\t\tsetIsPosting(false);\n\t\t}\n\t}, [\n\t\tcommentText,\n\t\tisPosting,\n\t\tcontext,\n\t\tvalue,\n\t\tsubcontext,\n\t\tsubcontextValue,\n\t\treplyingTo,\n\t\tpostComment,\n\t\tonCommentPosted,\n\t\trefetch,\n\t]);\n\n\tconst handleReact = useCallback(async (commentId: string, emoji: string) => {\n\t\t// TODO: Implement reaction posting\n\t\tconsole.log(\"React to comment:\", commentId, emoji);\n\t}, []);\n\n\treturn (\n\t\t<Card\n\t\t\tclassName={`flex flex-col overflow-hidden ${className}`}\n\t\t\tstyle={{\n\t\t\t\tmaxHeight: typeof maxHeight === \"number\" ? `${maxHeight}px` : maxHeight,\n\t\t\t\t...(theme && {\n\t\t\t\t\tbackgroundColor: theme.background,\n\t\t\t\t\tcolor: theme.color,\n\t\t\t\t}),\n\t\t\t}}\n\t\t>\n\t\t\t{/* Header */}\n\t\t\t<div className=\"flex justify-between items-center border-b p-3 sm:p-4\">\n\t\t\t\t<h3 className=\"text-lg font-semibold\">Comments</h3>\n\t\t\t\t{showCount && (\n\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t{comments.length} comments\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Comment Form */}\n\t\t\t{showForm && user && (\n\t\t\t\t<div className=\"flex flex-col gap-4 border-b p-3 sm:p-4\">\n\t\t\t\t\t{replyingTo && (\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\tReplying to comment\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tonClick={() => setReplyingTo(null)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t<Textarea\n\t\t\t\t\t\tplaceholder={\n\t\t\t\t\t\t\treplyingTo ? \"Write a reply...\" : \"Share your thoughts...\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvalue={commentText}\n\t\t\t\t\t\tonChange={(e) => setCommentText(e.target.value)}\n\t\t\t\t\t\trows={3}\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tbackgroundColor: theme?.inputBackground,\n\t\t\t\t\t\t\tcolor: theme?.inputColor,\n\t\t\t\t\t\t\tborder: theme?.inputBorder,\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t{commentText.length} / 10240 characters\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={handleSubmit}\n\t\t\t\t\t\t\tdisabled={\n\t\t\t\t\t\t\t\t!commentText.trim() || isPosting || commentText.length > 10240\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tbackgroundColor: theme?.buttonBackground,\n\t\t\t\t\t\t\t\tcolor: theme?.buttonColor,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{isPosting && (\n\t\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\t\tclassName=\"animate-spin -ml-1 mr-3 h-4 w-4\"\n\t\t\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t\taria-label=\"Loading\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<title>Loading</title>\n\t\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t\tclassName=\"opacity-25\"\n\t\t\t\t\t\t\t\t\t\tcx=\"12\"\n\t\t\t\t\t\t\t\t\t\tcy=\"12\"\n\t\t\t\t\t\t\t\t\t\tr=\"10\"\n\t\t\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\t\t\tstrokeWidth=\"4\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\tclassName=\"opacity-75\"\n\t\t\t\t\t\t\t\t\t\tfill=\"currentColor\"\n\t\t\t\t\t\t\t\t\t\td=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{replyingTo ? \"Reply\" : \"Comment\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Login prompt */}\n\t\t\t{showForm && !user && (\n\t\t\t\t<div className=\"flex justify-center border-b p-3 sm:p-4\">\n\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\tPlease sign in to comment\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Comments List */}\n\t\t\t<ScrollArea className=\"flex-1\">\n\t\t\t\t<div className=\"flex flex-col gap-4 p-3 sm:p-4\">\n\t\t\t\t\t{loading ? (\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">Loading comments...</p>\n\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t<p className=\"text-sm text-destructive\">Error loading comments</p>\n\t\t\t\t\t) : comments.length === 0 ? (\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground text-center\">\n\t\t\t\t\t\t\tNo comments yet. Be the first to comment!\n\t\t\t\t\t\t</p>\n\t\t\t\t\t) : (\n\t\t\t\t\t\tcomments.map((comment) => (\n\t\t\t\t\t\t\t<MetaLensCommentItem\n\t\t\t\t\t\t\t\tkey={comment.txid}\n\t\t\t\t\t\t\t\tcomment={comment}\n\t\t\t\t\t\t\t\tonReply={() => setReplyingTo(comment.txid)}\n\t\t\t\t\t\t\t\tonReact={handleReact}\n\t\t\t\t\t\t\t\tdepth={0}\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</div>\n\t\t\t</ScrollArea>\n\t\t</Card>\n\t);\n}\n\n// Internal component for individual comments\nfunction MetaLensCommentItem({\n\tcomment,\n\tonReply,\n\tonReact,\n\tdepth = 0,\n}: {\n\tcomment: MetaLensComment;\n\tonReply: () => void;\n\tonReact: (commentId: string, emoji: string) => void;\n\tdepth?: number;\n}) {\n\tconst [showReplies, setShowReplies] = useState(true);\n\tconst maxDepth = 5;\n\tconst indentSize = Math.min(depth * 24, maxDepth * 24);\n\n\t// Format relative time\n\tconst formatRelativeTime = (date: Date) => {\n\t\tconst now = Date.now();\n\t\tconst diff = now - date.getTime();\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 \"just now\";\n\t\tif (minutes < 60) return `${minutes}m ago`;\n\t\tif (hours < 24) return `${hours}h ago`;\n\t\tif (days < 7) return `${days}d ago`;\n\t\treturn date.toLocaleDateString();\n\t};\n\n\treturn (\n\t\t<div style={{ marginLeft: indentSize }}>\n\t\t\t<Card className=\"bg-transparent border-0 shadow-none\">\n\t\t\t\t<CardContent className=\"p-3\">\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t{/* Author header */}\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\tsrc={\n\t\t\t\t\t\t\t\t\tcomment.authorBapId\n\t\t\t\t\t\t\t\t\t\t? `https://api.sigmaidentity.com/avatar/${comment.authorBapId}`\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\talt={comment.author}\n\t\t\t\t\t\t\t\tsize=\"default\"\n\t\t\t\t\t\t\t\tfallback={comment.author.slice(0, 2)}\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<span className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\t\t{comment.authorPaymail ||\n\t\t\t\t\t\t\t\t\t\t(comment.author.length > 10\n\t\t\t\t\t\t\t\t\t\t\t? `${comment.author.slice(0, 6)}...${comment.author.slice(-4)}`\n\t\t\t\t\t\t\t\t\t\t\t: comment.author)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t{formatRelativeTime(comment.timestamp)}\n\t\t\t\t\t\t\t\t</span>\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{/* Content */}\n\t\t\t\t\t\t<p className=\"text-sm whitespace-pre-wrap\">{comment.content}</p>\n\n\t\t\t\t\t\t{/* Actions */}\n\t\t\t\t\t\t<div className=\"flex items-center gap-4\">\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" onClick={onReply}>\n\t\t\t\t\t\t\t\tReply\n\t\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t\t<MetaLensReactions\n\t\t\t\t\t\t\t\tcommentId={comment.txid}\n\t\t\t\t\t\t\t\treactions={comment.reactions || {}}\n\t\t\t\t\t\t\t\tonReact={(emoji) => onReact(comment.txid, emoji)}\n\t\t\t\t\t\t\t\tsize=\"1\"\n\t\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\tnavigator.clipboard?.writeText(\n\t\t\t\t\t\t\t\t\t\t`https://whatsonchain.com/tx/${comment.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>\n\t\t\t\t\t\t\t\tShare\n\t\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t\t{comment.replies && comment.replies.length > 0 && (\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\tonClick={() => setShowReplies(!showReplies)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{showReplies ? \"−\" : \"+\"} {comment.replies.length}{\" \"}\n\t\t\t\t\t\t\t\t\t{comment.replies.length === 1 ? \"reply\" : \"replies\"}\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\t\t\t\t\t</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\n\t\t\t{/* Nested replies */}\n\t\t\t{showReplies && comment.replies && depth < maxDepth && (\n\t\t\t\t<div className=\"flex flex-col gap-2 mt-2\">\n\t\t\t\t\t{comment.replies.map((reply) => (\n\t\t\t\t\t\t<MetaLensCommentItem\n\t\t\t\t\t\t\tkey={reply.txid}\n\t\t\t\t\t\t\tcomment={reply}\n\t\t\t\t\t\t\tonReply={onReply}\n\t\t\t\t\t\t\tonReact={onReact}\n\t\t\t\t\t\t\tdepth={depth + 1}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n",
      "target": "<%- config.aliases.components %>/metalenscomments.tsx"
    }
  ]
}