BigBlocks

Friend Button

A friend request button with four states built on the BSocial follow protocol

Installation

bunx shadcn@latest add https://registry.bigblocks.dev/r/friend-button.json

Usage

Pass the identity key of the other user and the current FriendshipStatus. The button automatically renders the correct UI for each state and transitions between states as actions complete.

When both users have followed each other (status: "friends"), you can derive a shared encryption key via getFriendPublicKey() from @1sat/actions. The FriendResult returned by onAccept includes the friendPublicKey for this purpose.

import { FriendButton } from "@/components/blocks/friend-button"
 
export default function UserProfile({ user }) {
  return (
    <FriendButton
      identityKey={user.identityKey}
      status={user.friendshipStatus}
      onAddFriend={async (id) => {
        // Send a BSocial follow transaction
        const txid = await broadcastFollow(id)
        return { txid }
      }}
      onAccept={async (id) => {
        // Create a mutual follow and derive the shared encryption key
        const result = await acceptFriendRequest(id)
        return { txid: result.txid, friendPublicKey: result.sharedKey }
      }}
      onDecline={async (id) => {
        const txid = await declineFriendRequest(id)
        return { txid }
      }}
      onRemove={async (id) => {
        const txid = await removeFriend(id)
        return { txid }
      }}
      onStatusChange={(newStatus, result) => {
        if (newStatus === "friends" && result.friendPublicKey) {
          console.log("Shared key:", result.friendPublicKey)
        }
      }}
    />
  )
}

Friendship States

The status prop drives which UI the button renders:

StatusUI shownActions available
none"Add Friend" buttononAddFriend
pending-sent"Pending" (disabled)None
pending-received"Accept" and "Decline" buttonsonAccept, onDecline
friends"Friends" button; hover reveals "Remove"onRemove

States

Pending (Sent)

After sending a friend request, the button shows a disabled "Pending" state.

Pending (Received)

When another user has sent a request, Accept and Decline buttons are shown.

Friends

Once mutual, the button shows "Friends". Hover to reveal the remove option.

Variants

VariantDescription
defaultStandard rounded button (px-4 py-2)
compactSmaller button (px-3 py-1.5 text-xs)

Hook: useFriend

Use useFriend when you need to drive a fully custom friendship UI — for example, a dropdown menu or an icon row.

import { useFriend } from "@/components/blocks/friend-button"
 
export function FriendActions({ identityKey, status }) {
  const {
    currentStatus,
    isLoading,
    loadingAction,
    handleAdd,
    handleAccept,
    handleDecline,
    handleRemove,
    hasAccept,
    hasDecline,
    hasRemove,
  } = useFriend({
    identityKey,
    status,
    onAddFriend: async (id) => ({ txid: await broadcastFollow(id) }),
    onAccept: async (id) => acceptRequest(id),
    onDecline: async (id) => declineRequest(id),
    onRemove: async (id) => removeConnection(id),
  })
 
  if (currentStatus === "pending-received") {
    return (
      <div>
        <button onClick={handleAccept} disabled={isLoading}>
          {loadingAction === "accept" ? "Accepting..." : "Accept"}
        </button>
        {hasDecline && (
          <button onClick={handleDecline} disabled={isLoading}>
            Decline
          </button>
        )}
      </div>
    )
  }
 
  if (currentStatus === "friends" && hasRemove) {
    return (
      <button onClick={handleRemove} disabled={isLoading}>
        Remove Friend
      </button>
    )
  }
 
  return (
    <button onClick={handleAdd} disabled={isLoading || currentStatus === "pending-sent"}>
      Add Friend
    </button>
  )
}

API Reference

FriendButton

PropTypeDefaultDescription
identityKeystring—Required. Identity key of the other user
statusFriendshipStatus—Required. Current friendship status
onAddFriend(identityKey: string) => Promise<FriendResult>—Required. Called to send a friend request
onAccept(identityKey: string) => Promise<FriendResult>—Called to accept an incoming request
onDecline(identityKey: string) => Promise<FriendResult>—Called to decline an incoming request
onRemove(identityKey: string) => Promise<FriendResult>—Called to remove an existing friend
onStatusChange(newStatus: FriendshipStatus, result: FriendResult) => void—Called after any successful action
onError(error: Error) => void—Called when an action fails
variant"default" | "compact""default"Visual variant
disabledbooleanfalseDisable the button
classNamestring—Additional CSS classes

FriendshipStatus

type FriendshipStatus =
  | "none"            // No relationship
  | "pending-sent"    // Current user sent a request (waiting for acceptance)
  | "pending-received" // Another user sent a request (can accept or decline)
  | "friends"         // Mutual follow established

FriendResult

interface FriendResult {
  /** Transaction ID of the action */
  txid?: string
  /** Derived public key for encrypted messaging (returned on accept) */
  friendPublicKey?: string
  /** Raw transaction hex */
  rawtx?: string
  /** Error message if the action failed */
  error?: string
}

useFriend Options

OptionTypeDefaultDescription
identityKeystring—Required. Identity key of the other user
statusFriendshipStatus—Required. Initial friendship status
onAddFriend(identityKey: string) => Promise<FriendResult>—Required. Add friend callback
onAccept(identityKey: string) => Promise<FriendResult>—Accept request callback
onDecline(identityKey: string) => Promise<FriendResult>—Decline request callback
onRemove(identityKey: string) => Promise<FriendResult>—Remove friend callback
onStatusChange(newStatus: FriendshipStatus, result: FriendResult) => void—Status change callback
onError(error: Error) => void—Error callback

useFriend Return

PropertyTypeDescription
currentStatusFriendshipStatusCurrent friendship status
isLoadingbooleanWhether an action is in progress
loadingActionstring | nullWhich action is currently loading ("add", "accept", "decline", "remove")
isHoveringbooleanWhether the user is hovering (used for friends state)
setIsHovering(hovering: boolean) => voidSet the hovering state
hasAcceptbooleanWhether onAccept was provided
hasDeclinebooleanWhether onDecline was provided
hasRemovebooleanWhether onRemove was provided
handleAdd() => voidSend a friend request
handleAccept() => voidAccept the incoming request
handleDecline() => voidDecline the incoming request
handleRemove() => voidRemove the friend