{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "collect-dialog",
  "type": "registry:block",
  "title": "Lens Collect Post Dialog",
  "description": "A dialog component for collecting Lens Posts with support for paid collects.",
  "dependencies": ["@lens-protocol/react@0.0.0-canary-20250820102520", "lucide-react", "moment@2.30.1"],
  "registryDependencies": [
    "button",
    "dialog",
    "https://lensblocks.com/r/post-provider.json",
    "https://lensblocks.com/r/use-post-context.json",
    "https://lensblocks.com/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/feed/lens-collect-dialog.tsx",
      "content": "\"use client\";\n\nimport { PaymentSource, TxHash, useAuthenticatedUser } from \"@lens-protocol/react\";\nimport { forwardRef, useEffect, useImperativeHandle, useState } from \"react\";\nimport { BoxIcon, CircleDollarSign, Clock, Snowflake, Users } from \"lucide-react\";\nimport { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from \"@/registry/new-york/ui/dialog\";\nimport moment from \"moment/moment\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport { Referral } from \"@/registry/new-york/lib/lens-post-provider\";\nimport { useLensPostContext } from \"@/registry/new-york/hooks/use-lens-post-context\";\nimport { getUsernamePath, truncateAddress } from \"@/registry/new-york/lib/lens-utils\";\n\nexport type CollectDialogRef = {\n  open: () => void;\n  close: () => void;\n  isOpen: boolean;\n};\n\ntype CollectDialogProps = {\n  referrals?: Referral[];\n  onSuccess?: (txHash: TxHash) => void;\n  onError?: (error: Error) => void;\n  nftUrlPattern?: string;\n};\n\nexport const LensCollectDialog = forwardRef<CollectDialogRef, CollectDialogProps>((props, ref) => {\n  const { referrals, onSuccess, onError, nftUrlPattern = \"https://lenscan.io/nfts/{collectNftAddress}\" } = props;\n  const { post, collect } = useLensPostContext();\n\n  const [collectDialogOpen, setCollectDialogOpen] = useState(false);\n  const [collecting, setCollecting] = useState(false);\n  const [collectEnded, setCollectEnded] = useState(false);\n  const [collectAvailable, setCollectAvailable] = useState(false);\n\n  const { data: user } = useAuthenticatedUser();\n\n  const action = post && \"actions\" in post && post.actions?.find(action => action.__typename === \"SimpleCollectAction\");\n  const operations = post && \"operations\" in post ? post.operations : null;\n  const stats = post && \"stats\" in post ? post.stats : null;\n\n  useImperativeHandle(ref, () => ({\n    open: () => setCollectDialogOpen(true),\n    close: () => setCollectDialogOpen(false),\n    isOpen: collectDialogOpen,\n  }));\n\n  useEffect(() => {\n    if (!action) {\n      setCollectAvailable(false);\n      onError?.(new Error(\"This post is not collectable\"));\n      return;\n    }\n\n    if (action.endsAt) {\n      setCollectEnded(moment().isAfter(action.endsAt));\n    }\n\n    if (!operations?.canSimpleCollect || operations?.hasSimpleCollected) {\n      setCollectAvailable(false);\n      return;\n    }\n\n    if (action.collectLimit) {\n      setCollectAvailable((stats?.collects ?? 0) < action.collectLimit);\n    }\n  }, [action]);\n\n  const onCollectClick = async () => {\n    setCollecting(true);\n    try {\n      const txHash = await collect(PaymentSource.Signer, referrals);\n      if (txHash) {\n        setCollectDialogOpen(false);\n        onSuccess?.(txHash);\n      }\n    } catch (e: any) {\n      if (e instanceof Error) {\n        onError?.(e);\n      }\n    } finally {\n      setCollecting(false);\n    }\n  };\n\n  if (!action) {\n    return null;\n  }\n\n  return (\n    <Dialog open={collectDialogOpen} onOpenChange={setCollectDialogOpen}>\n      <DialogContent className=\"sm:max-w-sm\">\n        <DialogHeader className=\"border-b\">\n          <DialogTitle>Collect</DialogTitle>\n        </DialogHeader>\n        <div className=\"flex flex-col gap-1 px-4\">\n          <div className=\"text-lg font-bold pb-3\">\n            {\"collectibleMetadata\" in post && post.collectibleMetadata.name ? (\n              post.collectibleMetadata.name\n            ) : (\n              <span>\n                Post by{\" \"}\n                {post.author.username ? (\n                  <a\n                    href={getUsernamePath(post.author.username.value)}\n                    className=\"!font-bold\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    @{post.author.username.localName}\n                  </a>\n                ) : (\n                  <span className=\"!font-bold\">{truncateAddress(post.author.address)}</span>\n                )}\n              </span>\n            )}\n          </div>\n          {action.payToCollect && (\n            <div className=\"flex gap-2.5 items-center\">\n              <CircleDollarSign className=\"w-7 h-7\" />\n              <span className=\"font-bold\">\n                <span className=\"text-xl\">{action.payToCollect.amount.value}</span>{\" \"}\n                {action.payToCollect.amount.asset.symbol}\n              </span>\n            </div>\n          )}\n          <div className=\"flex gap-4 items-center pl-1.5\">\n            <BoxIcon className=\"w-4 h-4 opacity-60\" />\n            <a\n              href={nftUrlPattern.replace(\"{collectNftAddress}\", action.collectNftAddress)}\n              className=\"font-semibold hover:underline\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              {truncateAddress(action.collectNftAddress, 10)}\n            </a>\n          </div>\n          <div className=\"flex gap-4 items-center pl-1.5\">\n            <Users className=\"w-4 h-4 opacity-60\" />\n            <span>\n              <span className=\"font-semibold\">{new Intl.NumberFormat().format(stats?.collects ?? 0)}</span>{\" \"}\n              {stats?.collects === 1 ? \"collector\" : \"collectors\"}\n            </span>\n          </div>\n          {action.collectLimit && (\n            <div className=\"flex gap-4 items-center pl-1.5\">\n              <Snowflake className=\"w-4 h-4 opacity-60\" />\n              <span>\n                <span className=\"font-semibold\">\n                  {new Intl.NumberFormat().format(action.collectLimit - (stats?.collects ?? 0))} of{\" \"}\n                  {action.collectLimit}\n                </span>{\" \"}\n                remaining\n              </span>\n            </div>\n          )}\n          {action.endsAt && (\n            <div className=\"flex gap-4 items-center pl-1.5\">\n              <Clock className=\"w-4 h-4 opacity-60\" />\n              <abbr className=\"!border-b-0 no-underline\" title={moment(action.endsAt).format(\"LLL\")}>\n                {collectEnded ? \"Ended \" : \"Ends \"}{\" \"}\n                <span className=\"font-semibold\">{moment(action.endsAt).fromNow()}</span>\n              </abbr>\n            </div>\n          )}\n        </div>\n        <DialogFooter className=\"flex justify-end pt-2\">\n          <Button\n            size=\"lg\"\n            className=\"font-bold\"\n            onClick={onCollectClick}\n            disabled={collecting || collectEnded || !collectAvailable}\n          >\n            {collecting ? (\n              <span className=\"loader\"></span>\n            ) : !user?.address ? (\n              \"Log in to collect\"\n            ) : operations?.hasSimpleCollected ? (\n              \"Collected\"\n            ) : collectEnded ? (\n              \"No longer available\"\n            ) : collectAvailable ? (\n              action.payToCollect ? (\n                `Collect for ${action.payToCollect.amount.value} ${action.payToCollect.amount.asset.symbol}`\n              ) : (\n                \"Collect for free\"\n              )\n            ) : (\n              \"Unable to collect\"\n            )}\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n});\nexport default LensCollectDialog;\n",
      "type": "registry:block"
    }
  ]
}
