Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
4.2 kB · 146 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147import { Button, Icon, Text, useTheme, zero } from "@streamplace/components";import { Plus } from "lucide-react-native";import React, { useEffect, useState } from "react";import { View } from "react-native";import { useStore } from "store";import { useStreamplaceUrl } from "store/hooks";
/** * FollowButton component for following/unfollowing a streamer. * * Props: * - streamerDID: string — The DID of the streamer to follow/unfollow * - currentUserDID?: string — The DID of the current user (optional) * - onFollowChange?: (isFollowing: boolean) => void — Optional callback when follow state changes */interface FollowButtonProps { streamerDID: string; currentUserDID?: string; onFollowChange?: (isFollowing: boolean) => void;}
const FollowButton: React.FC<FollowButtonProps> = ({ streamerDID, currentUserDID, onFollowChange,}) => { const [isFollowing, setIsFollowing] = useState<boolean | null>(null); const [error, setError] = useState<string | null>(null); const [followUri, setFollowUri] = useState<string | null>(null); const { theme } = useTheme(); const streamplaceUrl = useStreamplaceUrl(); const followUser = useStore((state) => state.followUser); const unfollowUser = useStore((state) => state.unfollowUser);
// Hide button if not logged in or viewing own stream if (!currentUserDID || currentUserDID === streamerDID) return null;
// Fetch initial follow state using xrpc useEffect(() => { let cancelled = false;
const fetchFollowStatus = async () => { if (!currentUserDID || !streamerDID) return;
setError(null); try { const res = await fetch( `${streamplaceUrl}/xrpc/place.stream.graph.getFollowingUser?subjectDID=${encodeURIComponent(streamerDID)}&userDID=${encodeURIComponent(currentUserDID)}`, { credentials: "include", }, );
if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed to fetch follow status: ${errorText}`); }
const data = await res.json(); if (cancelled) return;
if (data.follow) { setIsFollowing(true); setFollowUri(data.follow.uri); } else { setIsFollowing(false); setFollowUri(null); } } catch (err) { if (!cancelled) setError("Could not determine follow state"); } };
fetchFollowStatus(); return () => { cancelled = true; }; }, [currentUserDID, streamerDID]);
const handleFollow = async () => { setError(null); setIsFollowing(true); // Optimistic try { await followUser(streamerDID); setIsFollowing(true); onFollowChange?.(true); } catch (err) { setIsFollowing(false); setError( `Failed to follow: ${err instanceof Error ? err.message : "Unknown error"}`, ); } };
const handleUnfollow = async () => { setError(null); setIsFollowing(false); // Optimistic try { await unfollowUser(streamerDID, followUri ?? undefined); setIsFollowing(false); setFollowUri(null); onFollowChange?.(false); } catch (err) { setIsFollowing(true); setError( `Failed to unfollow: ${err instanceof Error ? err.message : "Unknown error"}`, ); } };
return ( <View style={[ { flexDirection: "row" }, { alignItems: "center" }, zero.gap.all[2], ]} > <Button onPress={isFollowing ? handleUnfollow : handleFollow} variant={isFollowing ? "secondary" : "primary"} size="pill" width="min" disabled={isFollowing === null} loading={isFollowing === null} leftIcon={!isFollowing && <Icon icon={Plus} size="sm" />} hoverStyle={ isFollowing ? { backgroundColor: theme.colors.danger } : undefined } > {isFollowing === null ? "Loading..." : isFollowing ? "Following" : "Follow"} </Button> {error && ( <Text style={[{ color: theme.colors.danger }, zero.ml[2]]}> {error} </Text> )} </View> );};
export default FollowButton;