Lens Follow Button

PreviousNext

A button component to follow or unfollow a Lens Account, displaying the current follow state.

Docs
lens-blockscomponent

Preview

Loading preview…
registry/new-york/components/account/lens-follow-button.tsx
"use client";

import { useEffect, useState } from "react";
import { Button } from "@/registry/new-york/ui/button";
import { Account, SessionClient, TxHash, UnauthenticatedError } from "@lens-protocol/react";
import { follow, unfollow } from "@lens-protocol/client/actions";
import { handleOperationWith } from "@lens-protocol/client/viem";
import { WalletClient } from "viem";
import { Spinner } from "@/registry/new-york/ui/spinner";
import { isResult, Result } from "@/registry/new-york/lib/result";

type Props = {
  account: Account | Result<Account>;
  session: Result<SessionClient>;
  wallet?: { data: WalletClient | undefined | null; isLoading?: boolean; error?: unknown };
  onFollowSuccess?: (account: Account, txHash: TxHash) => void;
  onUnfollowSuccess?: (account: Account, txHash: TxHash) => void;
  onFollowError?: (account: Account, error: Error) => void;
};

export const LensFollowButton = ({ ...props }: Props) => {
  const { account: accountRes, session, wallet, onFollowSuccess, onUnfollowSuccess, onFollowError } = props;
  const [isSubmitting, setIsSubmitting] = useState(false);

  const account = isResult(accountRes) ? accountRes?.data : accountRes;
  const sessionClient = session?.data;
  const walletClient = wallet?.data;

  const accountLoading = accountRes && "loading" in accountRes ? accountRes.loading : false;

  const [optimisticIsFollowed, setOptimisticIsFollowed] = useState<boolean>(
    account?.operations?.isFollowedByMe ?? false,
  );

  useEffect(() => {
    setOptimisticIsFollowed(account?.operations?.isFollowedByMe ?? false);
  }, [account?.operations]);

  const handleUnfollow = async () => {
    if (!account || !sessionClient) return;

    if (!walletClient) {
      onFollowError?.(account, new Error("Wallet client is not available"));
      return;
    }

    const res = await unfollow(sessionClient, {
      account: account.address,
    })
      .andThen(handleOperationWith(walletClient))
      .andThen(sessionClient.waitForTransaction);

    if (res.isErr()) {
      onFollowError?.(account, res.error);
      return;
    }

    setOptimisticIsFollowed(false);
    onUnfollowSuccess?.(account, res.value);
  };

  const handleFollow = async () => {
    if (!account || !sessionClient) return;

    if (!walletClient) {
      onFollowError?.(account, new Error("Wallet client is not available"));
      return;
    }

    const res = await follow(sessionClient, {
      account: account.address,
    })
      .andThen(handleOperationWith(walletClient))
      .andThen(sessionClient.waitForTransaction);

    if (res.isErr()) {
      onFollowError?.(account, res.error);
      return;
    }

    setOptimisticIsFollowed(true);
    onFollowSuccess?.(account, res.value);
  };

  const onFollowButtonClick = async () => {
    if (!account) return;

    if (!session) {
      onFollowError?.(account, new UnauthenticatedError());
      return;
    }

    setIsSubmitting(true);

    try {
      if (optimisticIsFollowed) {
        await handleUnfollow();
      } else {
        await handleFollow();
      }
    } catch (e) {
      if (e instanceof Error) {
        onFollowError?.(account, e);
      }
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <Button
      variant={optimisticIsFollowed ? "outline" : "default"}
      className="group hover:border-destructive"
      onClick={onFollowButtonClick}
      disabled={isSubmitting || accountLoading || session?.loading || wallet?.isLoading}
    >
      {isSubmitting ? (
        <>
          <Spinner /> {optimisticIsFollowed ? "Unfollowing..." : "Following..."}
        </>
      ) : (
        <>
          <span className="hidden group-hover:inline">{optimisticIsFollowed ? "Unfollow" : "Follow"}</span>
          <span className="inline group-hover:hidden">{optimisticIsFollowed ? "Following" : "Follow"}</span>
        </>
      )}
    </Button>
  );
};

Installation

npx shadcn@latest add @lens-blocks/follow-button

Usage

import { FollowButton } from "@/components/follow-button"
<FollowButton />