Skip to content

Commit

Permalink
fix: make the farcaster-related hooks work with the new API (#1518)
Browse files Browse the repository at this point in the history
* fix: make the farcaster-related hooks work with the new API

* cleanup: remove the unused `mutate` functions
  • Loading branch information
BrickheadJohnny authored Oct 8, 2024
1 parent 9985937 commit 5fa402d
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 151 deletions.
10 changes: 6 additions & 4 deletions src/app/(marketing)/profile/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {
LayoutMain,
} from "@/components/Layout"
import { SWRProvider } from "@/components/SWRProvider"
import {
NEYNAR_BASE_URL,
NEYNAR_DEFAULT_HEADERS,
} from "@/hooks/useFarcasterAPI/constants"
import { FarcasterProfile, Guild, Role, Schemas } from "@guildxyz/types"
import { env } from "env"
import { Metadata } from "next"
Expand Down Expand Up @@ -77,13 +81,11 @@ const fetchPublicProfileData = async ({
)
const fcProfile = farcasterProfiles.at(0)
const neynarRequest =
fcProfile &&
new URL(
`https://api.neynar.com/v2/farcaster/user/bulk?api_key=NEYNAR_API_DOCS&fids=${fcProfile.fid}`
)
fcProfile && new URL(`${NEYNAR_BASE_URL}/user/bulk?fids=${fcProfile.fid}`)
const fcFollowers =
neynarRequest &&
(await ssrFetcher(neynarRequest, {
headers: NEYNAR_DEFAULT_HEADERS,
next: {
revalidate: 12 * 3600,
},
Expand Down
18 changes: 11 additions & 7 deletions src/app/(marketing)/profile/_hooks/useFarcasterProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useFarcasterAPI } from "@/hooks/useFarcasterAPI"
import { FarcasterProfile } from "@guildxyz/types"
import type { NeynarAPIClient } from "@neynar/nodejs-sdk"
import useUser from "components/[guild]/hooks/useUser"
Expand All @@ -18,28 +19,31 @@ export const useFarcasterProfile = (guildUserId?: number) => {
).data?.at(0)

// API reference: https://docs.neynar.com/reference/user-bulk
const { data, ...rest } = useSWRImmutable<BulkUsersResponse>(
linkedFcProfile
? `https://api.neynar.com/v2/farcaster/user/bulk?api_key=NEYNAR_API_DOCS&fids=${linkedFcProfile.fid}`
: null
const { data, ...rest } = useFarcasterAPI<BulkUsersResponse>(
linkedFcProfile ? `/user/bulk?fids=${linkedFcProfile.fid}` : null
)
return { farcasterProfile: data?.users.at(0), ...rest }
return {
farcasterProfile: data?.users.at(0),
...rest,
mutate: undefined as never,
}
}

export const useRelevantFarcasterFollowers = (farcasterId?: number) => {
const currentUser = useUser()
const currentUserFcProfile = currentUser.farcasterProfiles?.at(0)

// API reference: https://docs.neynar.com/reference/relevant-followers
const { data, ...rest } = useSWRImmutable<RelevantFollowersResponse>(
const { data, ...rest } = useFarcasterAPI<RelevantFollowersResponse>(
farcasterId && currentUserFcProfile
? `https://api.neynar.com/v2/farcaster/followers/relevant?api_key=NEYNAR_API_DOCS&target_fid=${farcasterId}&viewer_fid=${currentUserFcProfile.fid}`
? `/followers/relevant?api_key=NEYNAR_API_DOCS&target_fid=${farcasterId}&viewer_fid=${currentUserFcProfile.fid}`
: null
)
return {
relevantFollowers: data?.top_relevant_followers_hydrated?.map(
({ user }) => user
),
...rest,
mutate: undefined as never,
}
}
13 changes: 4 additions & 9 deletions src/requirements/Farcaster/FarcasterRequirement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useRoleMembership } from "components/explorer/hooks/useMembership"
import REQUIREMENTS from "requirements"
import FarcasterAction from "./components/FarcasterAction"
import FarcasterCast from "./components/FarcasterCast"
import useFarcasterCast from "./hooks/useFarcasterCast"
import { useFarcasterCast } from "./hooks/useFarcasterCast"
import { useFarcasterChannel } from "./hooks/useFarcasterChannels"
import { useFarcasterUser } from "./hooks/useFarcasterUsers"

Expand All @@ -34,12 +34,7 @@ const FarcasterFollowUser = (props: RequirementProps) => {
"FARCASTER_FOLLOW" | "FARCASTER_FOLLOWED_BY"
>()

const { data: farcasterUser } = useFarcasterUser(
// TODO: Why is this check needed? Can't we just pass data.id?
["FARCASTER_FOLLOW", "FARCASTER_FOLLOWED_BY"].includes(type)
? data?.id
: undefined
)
const { data: farcasterUser } = useFarcasterUser(data.id)

const { reqAccesses } = useRoleMembership(roleId)

Expand Down Expand Up @@ -67,7 +62,7 @@ const FarcasterFollowUser = (props: RequirementProps) => {
colorScheme="blue"
fontWeight="medium"
>
{farcasterUser?.display_name ?? "Loading..."}
{farcasterUser?.display_name ?? farcasterUser?.username ?? "Loading..."}
</Link>
</Skeleton>
{" on Farcaster"}
Expand Down Expand Up @@ -124,7 +119,7 @@ const FarcasterLikeRecast = (props: RequirementProps) => {
<Skeleton isLoaded={!!cast} display="inline">
<FarcasterCast
size="sm"
cast={cast!}
cast={cast}
loading={isCastLoading}
error={castError}
/>
Expand Down
16 changes: 8 additions & 8 deletions src/requirements/Farcaster/components/FarcasterCast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
WarningCircle,
} from "@phosphor-icons/react"
import { PropsWithChildren } from "react"
import { FarcasterCastData } from "../types"
import { useFarcasterCast } from "../hooks/useFarcasterCast"
import FarcasterCastSmall from "./FarcasterCastSmall"

const FarcasterCast = ({
Expand All @@ -27,14 +27,14 @@ const FarcasterCast = ({
error,
size = "md",
}: {
cast: FarcasterCastData
cast: ReturnType<typeof useFarcasterCast>["data"]
loading: boolean
error: boolean
size?: string
}) => {
const bgHover = useColorModeValue("gray.100", "blackAlpha.300")

const url = `https://warpcast.com/${cast?.username}/${cast?.hash}`
const url = `https://warpcast.com/${cast?.author.username}/${cast?.hash}`
const prettyDate =
cast?.timestamp &&
new Intl.DateTimeFormat("en-US", {
Expand Down Expand Up @@ -90,21 +90,21 @@ const FarcasterCast = ({
height={7}
objectFit="cover"
rounded="full"
src={cast.profile_pic}
src={cast.author.pfp_url}
alt="Profile picture"
/>
<Stack spacing={0}>
<Text fontWeight="bold" fontSize="sm" noOfLines={1}>
{cast.display_name}
{cast.author.display_name ?? cast.author.username}
</Text>
<Text fontSize="xs" opacity={0.6}>
{prettyDate}
</Text>
</Stack>
<HStack spacing={3} ml="auto">
<Stat icon={Heart} value={cast.likes} />
<Stat icon={ShareNetwork} value={cast.recasts} />
<Stat icon={Chat} value={cast.replies} />
<Stat icon={Heart} value={cast.reactions.likes_count} />
<Stat icon={ShareNetwork} value={cast.reactions.recasts_count} />
<Stat icon={Chat} value={cast.replies.count} />
</HStack>
<Icon as={ArrowSquareOut} />
</CastWrapper>
Expand Down
14 changes: 7 additions & 7 deletions src/requirements/Farcaster/components/FarcasterCastHash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import useDebouncedState from "hooks/useDebouncedState"
import { useEffect } from "react"
import { useController, useForm } from "react-hook-form"
import { ADDRESS_REGEX } from "utils/guildCheckout/constants"
import useFarcasterCast from "../hooks/useFarcasterCast"
import { useFarcasterCast } from "../hooks/useFarcasterCast"
import FarcasterCast from "./FarcasterCast"

type Props = {
Expand Down Expand Up @@ -45,12 +45,12 @@ const FarcasterCastHash = ({ baseFieldPath }: Props) => {

const debouncedUrlOrHash = useDebouncedState(urlOrHashField.field.value)

const { data, isLoading, error } = useFarcasterCast(debouncedUrlOrHash)
const { data: cast, isLoading, error } = useFarcasterCast(debouncedUrlOrHash)

useEffect(() => {
if (hash === data?.hash) return
onHashChange(data?.hash)
}, [hash, onHashChange, data?.hash])
if (hash === cast?.hash) return
onHashChange(cast?.hash)
}, [hash, onHashChange, cast?.hash])

return (
<>
Expand All @@ -64,8 +64,8 @@ const FarcasterCastHash = ({ baseFieldPath }: Props) => {
</FormErrorMessage>
</FormControl>

<Collapse in={data || isLoading || error} style={{ width: "100%" }}>
<FarcasterCast cast={data} loading={isLoading} error={!!error} />
<Collapse in={!!cast || isLoading || error} style={{ width: "100%" }}>
<FarcasterCast cast={cast} loading={isLoading} error={!!error} />
</Collapse>
</>
)
Expand Down
18 changes: 9 additions & 9 deletions src/requirements/Farcaster/components/FarcasterCastSmall.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Flex, HStack, Image, Link, Skeleton, Text } from "@chakra-ui/react"
import { Chat, Heart, ShareNetwork, WarningCircle } from "@phosphor-icons/react"
import { DataBlock } from "components/common/DataBlock"
import { FarcasterCastData } from "../types"
import { useFarcasterCast } from "../hooks/useFarcasterCast"

const FarcasterCastSmall = ({
cast,
loading,
error,
}: {
cast: FarcasterCastData
cast: ReturnType<typeof useFarcasterCast>["data"]
loading: boolean
error: boolean
}) => {
const url = `https://warpcast.com/${cast?.username}/${cast?.hash}`
const url = `https://warpcast.com/${cast?.author.username}/${cast?.hash}`

if (loading) return <Skeleton w={32} h={5} />
if (error)
Expand All @@ -24,7 +24,7 @@ const FarcasterCastSmall = ({
</DataBlock>
)

if (cast && !loading && !error)
if (!!cast && !loading && !error)
return (
<>
<Link
Expand All @@ -42,11 +42,11 @@ const FarcasterCastSmall = ({
height={3}
objectFit={"cover"}
rounded={"full"}
src={cast.profile_pic}
src={cast.author.pfp_url}
alt={"Profile picture"}
/>
<Text fontWeight={"bold"} fontSize={"xs"} m={0}>
{cast.display_name}
{cast.author.display_name ?? cast.author.username}
</Text>
</HStack>

Expand All @@ -55,21 +55,21 @@ const FarcasterCastSmall = ({
{" "}
<Heart weight="fill" size={10} />
<Text fontSize={"xs"} fontWeight={"bold"}>
{cast.likes}
{cast.reactions.likes_count}
</Text>{" "}
</HStack>
<HStack gap={0.5}>
{" "}
<ShareNetwork weight="fill" size={10} />
<Text fontSize={"xs"} fontWeight={"bold"}>
{cast.recasts}
{cast.reactions.recasts_count}
</Text>{" "}
</HStack>
<HStack gap={0.5}>
{" "}
<Chat weight="fill" size={10} />
<Text fontSize={"xs"} fontWeight={"bold"}>
{cast.replies}
{cast.replies.count}
</Text>{" "}
</HStack>
</HStack>
Expand Down
3 changes: 2 additions & 1 deletion src/requirements/Farcaster/components/FarcasterChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import useDebouncedState from "hooks/useDebouncedState"
import { useState } from "react"
import { useFormState, useWatch } from "react-hook-form"
import parseFromObject from "utils/parseFromObject"
import useFarcasterChannels, {
import {
useFarcasterChannel,
useFarcasterChannels,
} from "../hooks/useFarcasterChannels"

type Props = {
Expand Down
19 changes: 10 additions & 9 deletions src/requirements/Farcaster/components/FarcasterUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useState } from "react"
import { useFormContext, useWatch } from "react-hook-form"
import { SelectOption } from "types"
import parseFromObject from "utils/parseFromObject"
import useFarcasterUsers, { useFarcasterUser } from "../hooks/useFarcasterUsers"
import { useFarcasterUser, useFarcasterUsers } from "../hooks/useFarcasterUsers"

type Props = {
baseFieldPath: string
Expand All @@ -27,12 +27,13 @@ const FarcasterUser = ({ baseFieldPath }: Props) => {
const debounceSearch = useDebouncedState(search)

const { data: farcasterUsers, isValidating } = useFarcasterUsers(debounceSearch)
const options: SelectOption<number>[] = farcasterUsers?.map((user) => ({
label: user.display_name,
details: user.username,
value: user.fid,
img: user.pfp_url,
}))
const options: SelectOption<number>[] =
farcasterUsers?.map((user) => ({
label: user.display_name ?? user.username,
details: user.username,
value: user.fid,
img: user.pfp_url,
})) ?? []

const fid = useWatch({ name: `${baseFieldPath}.data.id` })
const { data: farcasterUser } = useFarcasterUser(fid)
Expand All @@ -49,7 +50,7 @@ const FarcasterUser = ({ baseFieldPath }: Props) => {
<InputLeftElement>
<OptionImage
img={farcasterUser.pfp_url}
alt={farcasterUser.display_name}
alt={farcasterUser.display_name ?? farcasterUser.username}
/>
</InputLeftElement>
)}
Expand All @@ -71,7 +72,7 @@ const FarcasterUser = ({ baseFieldPath }: Props) => {
}}
fallbackValue={
farcasterUser && {
label: farcasterUser.display_name,
label: farcasterUser.display_name ?? farcasterUser.username,
value: farcasterUser.fid,
img: farcasterUser.pfp_url,
}
Expand Down
52 changes: 20 additions & 32 deletions src/requirements/Farcaster/hooks/useFarcasterCast.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,36 @@
import { useEffect } from "react"
import { useFarcasterAPI } from "@/hooks/useFarcasterAPI"
import { NEYNAR_BASE_URL } from "@/hooks/useFarcasterAPI/constants"
import type { NeynarAPIClient } from "@neynar/nodejs-sdk"
import { useSWRConfig } from "swr"
import useSWRImmutable from "swr/immutable"
import { ADDRESS_REGEX } from "utils/guildCheckout/constants"

const BASE_URL =
"https://api.neynar.com/v2/farcaster/cast?api_key=NEYNAR_API_DOCS&identifier="

const useFarcasterCast = (hashOrUrl: string) => {
const isHash = ADDRESS_REGEX.test(hashOrUrl)
const isUrl = hashOrUrl?.startsWith("https://warpcast.com/")

const fetchUrl = `${BASE_URL}${hashOrUrl}&type=${isHash ? "hash" : "url"}`
const { data, isLoading, error } = useSWRImmutable(
isHash || isUrl ? fetchUrl : null
)

const { cache } = useSWRConfig()

// If we fetched data by url, we populate the hash cache too
useEffect(() => {
if (!isUrl || !data?.cast?.hash) return
cache.set(`${BASE_URL}${data.cast.hash}&type=hash`, {
data,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hashOrUrl, isUrl, data?.cast?.hash])
const { data, isLoading, error } = useFarcasterAPI<
Awaited<ReturnType<NeynarAPIClient["lookUpCastByHashOrWarpcastUrl"]>>
>(
isHash || isUrl
? `/cast?identifier=${hashOrUrl}&type=${isHash ? "hash" : "url"}`
: null,
{
onSuccess: (data) => {
if (!isUrl) return
cache.set(`${NEYNAR_BASE_URL}/cast?identifier=${data.cast.hash}&type=hash`, {
data,
})
},
}
)

return {
isLoading: isLoading,
error: error,
data: data
? {
hash: data?.cast?.hash,
username: data?.cast?.author?.username,
display_name: data?.cast?.author?.display_name,
profile_pic: data?.cast?.author?.pfp_url,
text: data?.cast?.text,
timestamp: data?.cast?.timestamp,
likes: data?.cast?.reactions?.likes?.length,
recasts: data?.cast?.reactions?.recasts?.length,
replies: data?.cast?.replies?.count,
}
: null,
data: data?.cast,
}
}

export default useFarcasterCast
export { useFarcasterCast }
Loading

0 comments on commit 5fa402d

Please sign in to comment.