From 20dbf1634645762cf25afce0f6926bdb1e6bdb9d Mon Sep 17 00:00:00 2001 From: Kevin O'Connell Date: Mon, 26 Aug 2024 17:22:19 -0400 Subject: [PATCH 1/4] add search --- .env.example | 3 +- package-lock.json | 9 +- package.json | 2 +- src/app/[identifier]/page.tsx | 478 ++--------------------------- src/components/CastSearch.tsx | 135 ++++++++ src/components/NetworkResponse.tsx | 443 ++++++++++++++++++++++++++ src/components/search.tsx | 24 +- 7 files changed, 634 insertions(+), 460 deletions(-) create mode 100644 src/components/CastSearch.tsx create mode 100644 src/components/NetworkResponse.tsx diff --git a/.env.example b/.env.example index 4a28369..d5f900f 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ NEXT_PUBLIC_NEYNAR_API_KEY="" -NEXT_PUBLIC_AMPLITUDE_API_KEY="" \ No newline at end of file +NEXT_PUBLIC_AMPLITUDE_API_KEY="" +NEXT_PUBLIC_CLIENT_ID="" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a1963b2..0ee9262 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@amplitude/analytics-browser": "^2.9.3", "@farcaster/hub-nodejs": "^0.11.11", "@neynar/nodejs-sdk": "^1.27.0", - "@neynar/react": "^0.6.1", + "@neynar/react": "^0.6.4", "@pigment-css/react": "^0.0.9", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", @@ -2574,9 +2574,10 @@ } }, "node_modules/@neynar/react": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@neynar/react/-/react-0.6.1.tgz", - "integrity": "sha512-eQxd+wIzEBSytH1/PBjR+tfycGSJfY4p1NJEUVpXqMiuKIOyBSnmqj9VEtGi/29yIhIbsmOfe8YY8/H4T4ZA1w==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@neynar/react/-/react-0.6.4.tgz", + "integrity": "sha512-pgbof0ouDa/P1dE4mw/37sn+8aim/ll2GdmyHhireSXuGtWhl/JccU5oxQ08JaM11elQJNaNICfSjKVGl8jDWA==", + "license": "MIT", "peerDependencies": { "@pigment-css/react": "^0.0.9", "hls.js": "^1.5.13", diff --git a/package.json b/package.json index e48718c..9a10b69 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@amplitude/analytics-browser": "^2.9.3", "@farcaster/hub-nodejs": "^0.11.11", "@neynar/nodejs-sdk": "^1.27.0", - "@neynar/react": "^0.6.1", + "@neynar/react": "^0.6.4", "@pigment-css/react": "^0.0.9", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", diff --git a/src/app/[identifier]/page.tsx b/src/app/[identifier]/page.tsx index 191ab92..9299209 100644 --- a/src/app/[identifier]/page.tsx +++ b/src/app/[identifier]/page.tsx @@ -1,464 +1,50 @@ 'use client'; -import Modal from '@/components/modal-component'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader } from '@/components/ui/card'; -import { hubs } from '@/constants'; -import { useClipboard } from '@/hooks/useClipboard'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { - extractIdentifierFromUrl, - extractUsernameFromUrl, - fetchCastAndFidData, - isValidWarpcastUrl, -} from '@/lib/utils'; -import { NeynarProfileCard, NeynarCastCard } from '@neynar/react'; -import { capitalizeNickname, isFollowSyntax, isNumeric } from '@/lib/helpers'; -import { CopyCheckIcon, CopyIcon, UserIcon, SearchIcon } from 'lucide-react'; -import Link from 'next/link'; -import * as amplitude from '@amplitude/analytics-browser'; -import { useEffect, useState } from 'react'; -import SkeletonHeader from '@/components/skeleton-header'; -import { Input } from '@/components/ui/input'; -import { usePathname, useRouter } from 'next/navigation'; -import ActionButtons from '@/components/ActionButtons'; +import CastSearch from '@/components/CastSearch'; +import NetworkResponse from '@/components/NetworkResponse'; import Search from '@/components/search'; interface ResponseProps { params: { identifier: string }; } -export default function Page({ params }: ResponseProps) { - const router = useRouter(); - let identifier = decodeURIComponent(params.identifier); - const fid: number | null = isNumeric(identifier) ? Number(identifier) : null; - let hash = fid ? null : identifier; - - if (identifier.includes('https://www.supercast.xyz/c/')) { - const extractedIdentifier = extractIdentifierFromUrl(identifier); - if (extractedIdentifier) { - identifier = extractedIdentifier; - hash = extractedIdentifier; - } else { - console.error('Invalid URL identifier'); - } - } - const [data, setData] = useState(null); - const [modalData, setModalData] = useState(null); - const [modalTitle, setModalTitle] = useState(''); - const [isModalOpen, setIsModalOpen] = useState(false); - const [loading, setLoading] = useState(true); - const [showOtherHubs, setShowOtherHubs] = useState(false); - const [clickedHeader, setClickedHeader] = useState(null); - - const checkWarning = (message: any) => { - if (!message) return []; - - const missingObjects = []; - let expectedUserTypes = [ - 'USER_DATA_TYPE_PFP', - 'USER_DATA_TYPE_DISPLAY', - 'USER_DATA_TYPE_BIO', - 'USER_DATA_TYPE_USERNAME', - ]; - if (message?.followedBy && message?.follow) { - if ( - !message?.followedBy.type || - message?.followedBy.type !== 'MESSAGE_TYPE_LINK_ADD' - ) { - missingObjects.push('Followed By'); - } - if ( - !message?.follow.type || - message?.follow.type !== 'MESSAGE_TYPE_LINK_ADD' - ) { - missingObjects.push('Following'); - } - return missingObjects; - } - - if (message?.messages) { - const foundTypes = new Set(); - message.messages.forEach((msg: any) => { - if (msg.data?.userDataBody?.type) { - foundTypes.add(msg.data.userDataBody.type); - } - }); - - return expectedUserTypes.filter((type) => !foundTypes.has(type)); - } - - const following = message?.following; - const followedBy = message?.followed_by; - - if (following !== undefined && following === false) - missingObjects.push('Following'); - if (followedBy !== undefined && followedBy === false) - missingObjects.push('Followed By'); - if (followedBy !== undefined && following !== undefined) { - return missingObjects; - } - - const authorFid = message?.fid; - const expectedUsername = `!${authorFid}`; - const username = message?.username; - const pfp = message?.pfp?.url || message?.pfp_url; - const displayName = message?.display_name || message?.displayName; - const bio = message?.profile?.bio?.text; - - if (!pfp) missingObjects.push('PFP'); - if (!displayName) missingObjects.push('Display Name'); - if (!bio) missingObjects.push('Bio'); - if (!username || username === expectedUsername) - missingObjects.push('Username'); - - return missingObjects; - }; - - const fetchData = async () => { - setLoading(true); - const data = (await fetchCastAndFidData(hash, fid)) as any; - const warpcastAuthorMissing = checkWarning(data.apiData.warpcast?.author); - const neynarAuthorMissing = checkWarning( - data.apiData.neynar?.author?.author - ); - const warpcastAuthorHubMissing = checkWarning(data.hubData?.[0]?.author); - const neynarAuthorHubMissing = checkWarning(data.hubData?.[1]?.author); - const warpcastCastMissing = [] as any; - const neynarCastMissing = [] as any; - - setData({ - ...data, - warpcastAuthorMissing, - neynarAuthorMissing, - warpcastAuthorHubMissing, - neynarAuthorHubMissing, - warpcastCastMissing, - neynarCastMissing, - warpcastCastHubMissing: [], - neynarCastHubMissing: [], - }); +const isNetworkResponse = (identifier: string): boolean => { + const patterns = [ + /^https:\/\/www\.supercast\.xyz\/[a-zA-Z0-9]+$/, + /^https:\/\/www\.supercast\.xyz\/c\/0x[a-fA-F0-9]{40}$/, + /^https:\/\/warpcast\.com\/[a-zA-Z0-9]+$/, + /^https:\/\/warpcast\.com\/[a-zA-Z0-9]+\/0x[a-fA-F0-9]{8}$/, + /^0x[a-fA-F0-9]{40}$/, + /^\d+$/, // This pattern matches any string consisting only of digits + ]; - setLoading(false); - }; + return patterns.some((pattern) => pattern.test(identifier)); +}; - useEffect(() => { - void fetchData(); - }, [hash, fid]); - - const openModal = ( - title: string, - response: any, - missingObjects: string[] - ) => { - setModalTitle(title); - setModalData({ ...response, missingObjects }); - setIsModalOpen(true); - setClickedHeader(title); // Set the clicked header - }; - - const closeModal = () => { - setIsModalOpen(false); - setModalData(null); - setClickedHeader(null); // Reset the clicked header - }; - - const { warpcast, neynar } = data?.apiData ?? {}; - const { - warpcastAuthorMissing, - neynarAuthorMissing, - warpcastAuthorHubMissing, - neynarAuthorHubMissing, - warpcastCastMissing, - neynarCastMissing, - warpcastCastHubMissing, - neynarCastHubMissing, - } = data ?? {}; - - const hubsData = data?.hubData ?? []; - const hoyt = hubsData[0] ?? {}; - const neynarHub = hubsData[1] ?? {}; - const { author: hoytAuthor, cast: hoytCast } = hoyt || {}; - const { author: neynarHubAuthor, cast: neynarHubCast } = neynarHub || {}; - const { author: warpcastAuthor, cast: warpcastCast } = warpcast || {}; - const { author: neynarAuthor, cast: neynarCast } = neynar || {}; - - const authorFid = - warpcastCast?.author?.fid || - neynarCast?.cast?.author?.fid || - neynarAuthor?.author?.fid || - warpcastAuthor?.author?.fid; - - const username = - warpcastAuthor?.username ?? - neynarAuthor?.author?.username ?? - neynarCast?.cast?.author?.username ?? - warpcastCast?.cast?.author?.username ?? - null; - const castHash = neynar?.cast?.cast?.hash ?? warpcast?.cast?.hash ?? null; - - const renderHeader = (label: string, data: any, missingObjects: any[]) => { - if (!data) { - return null; - } - - let icon = '✅'; - let backgroundColor = '#03A800'; - let hoverColor = '#028700'; // Adjust hover color - - if (data?.is_server_dead) { - icon = '❓'; - backgroundColor = '#FFA500'; - hoverColor = '#CC8400'; // Adjust hover color - } else if (data?.error) { - icon = '❌'; - backgroundColor = '#C67A7D'; - hoverColor = '#A66060'; // Adjust hover color - } else if (missingObjects.length > 0) { - icon = '⚠️'; - backgroundColor = '#FFD700'; - hoverColor = '#CCB300'; // Adjust hover color - } - - const isClicked = clickedHeader === label; - const activeColor = isClicked ? '#03039A' : backgroundColor; +export default function Page({ params }: ResponseProps) { + let identifier = decodeURIComponent(params.identifier); - return ( - - ); - }; + const isSearch = !isNetworkResponse(identifier); return ( - <> - -
-
-
-
-

- showing results for: -

-
-
- -
-
-
- {loading ? ( - <> - {} - {} - {} - {} - - ) : ( - <> - {renderHeader( - 'Warpcast API', - warpcastAuthor, - warpcastAuthorMissing - )} - {renderHeader( - 'Warpcast API', - warpcastCast, - warpcastCastMissing - )} - {renderHeader( - capitalizeNickname(hoyt.name), - hoytAuthor, - warpcastAuthorHubMissing - )} - {renderHeader( - capitalizeNickname(hoyt.name), - hoytCast, - warpcastCastHubMissing - )} - {renderHeader( - 'Neynar hub', - neynarHubAuthor, - neynarAuthorHubMissing - )} - {renderHeader( - 'Neynar hub', - neynarHubCast, - neynarCastHubMissing - )} - {renderHeader( - 'Neynar API', - neynarAuthor, - neynarAuthorMissing - )} - {renderHeader('Neynar API', neynarCast, neynarCastMissing)} - {!showOtherHubs ? ( - - ) : ( - - )} - {showOtherHubs && ( -
- {hubs.slice(2).map((hub, index) => { - const hubData = data?.hubData?.[index + 2]; - const hubAuthor = hubData?.author; - const hubCast = hubData?.cast; - const missingObjectsAuthor = checkWarning(hubAuthor); - const missingObjectsCast: any[] = []; - return ( -
- {renderHeader( - `${capitalizeNickname(hub.shortname)}`, - hubAuthor, - missingObjectsAuthor - )} - {renderHeader( - `${capitalizeNickname(hub.shortname)}`, - hubCast, - missingObjectsCast - )} -
- ); - })} -
- )} - - )} -
-
- {hash && isFollowSyntax(hash) ? null : hash && - !extractUsernameFromUrl(hash) ? ( - - ) : authorFid ? ( - - ) : null} -
-
-
- {loading ? null : castHash || username ? ( -
- {loading ? null : castHash || username ? ( - - - {' '} - - - - { - router.push( - castHash - ? `https://warpcast.com/${username}/${castHash.slice(0, 10)}` - : `https://warpcast.com/${username}` - ); - }} - > - in Warpcast - - { - router.push( - castHash - ? `https://www.supercast.xyz/c/${castHash}` - : `https://www.supercast.xyz/${username}` - ); - }} - > - in Supercast - - - - ) : null} -
- ) : null} -
+
+
+
+
+

+ showing results for: +

+
-
- -
+
+
+ {isSearch ? ( + + ) : ( + + )}
- +
); } diff --git a/src/components/CastSearch.tsx b/src/components/CastSearch.tsx new file mode 100644 index 0000000..27158b1 --- /dev/null +++ b/src/components/CastSearch.tsx @@ -0,0 +1,135 @@ +'use client'; +import React, { useState, useEffect, useCallback } from 'react'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { NeynarCastCard } from '@neynar/react'; +import { useRouter, useSearchParams } from 'next/navigation'; + +const CastSearch = ({ query }: any) => { + const params = useSearchParams(); + const initialAuthorFid = params.get('authorFid'); + const initialChannelId = params.get('channelId'); + const router = useRouter(); + + const [authorFid, setAuthorFid] = useState( + (initialAuthorFid as string) || '' + ); + const [channelId, setChannelId] = useState( + (initialChannelId as string) || '' + ); + const [casts, setCasts] = useState([]); + const [cursor, setCursor] = useState(''); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setAuthorFid((initialAuthorFid as string) || ''); + setChannelId((initialChannelId as string) || ''); + }, [initialAuthorFid, initialChannelId]); + + const fetchCasts = useCallback(async () => { + setLoading(true); + try { + console.log('fetching casts'); + console.log('query', query); + console.log('cursor', cursor); + console.log('authorFid', authorFid); + console.log('channelId', channelId); + const url = new URL('https://api.neynar.com/v2/farcaster/cast/search'); + if (query) url.searchParams.append('q', query); + url.searchParams.append('limit', '100'); + if (cursor) url.searchParams.append('cursor', cursor); + if (authorFid) url.searchParams.append('author_fid', authorFid); + if (channelId) url.searchParams.append('channel_id', channelId); + + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('api_key', process.env.NEXT_PUBLIC_NEYNAR_API_KEY || ''); + + const response = await fetch(url, { + method: 'GET', + headers: headers, + }); + + if (!response.ok) throw new Error('Network response was not ok'); + + const data = await response.json(); + console.log('data', data); + setCasts((prevCasts) => [...prevCasts, ...data.result.casts]); + if (data.result.next) { + setCursor(data.result.next.cursor); + } else { + setCursor(''); + } + } catch (error) { + console.error('Error fetching casts:', error); + } finally { + setLoading(false); + } + }, [query, cursor, authorFid, channelId]); + + useEffect(() => { + if (query || authorFid || channelId) fetchCasts(); + }, [query, authorFid, channelId, fetchCasts]); + + const handleScroll = useCallback(() => { + if ( + window.innerHeight + document.documentElement.scrollTop !== + document.documentElement.offsetHeight || + loading + ) { + return; + } + fetchCasts(); + }, [fetchCasts, loading]); + + useEffect(() => { + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, [handleScroll]); + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setCasts([]); + setCursor(''); + fetchCasts(); + const params = new URLSearchParams(); + if (authorFid) params.append('authorFid', authorFid); + if (channelId) params.append('channelId', channelId); + router.push(`/${query}?${params.toString()}`); + }; + + return ( +
+
+
+ setAuthorFid(e.target.value)} + placeholder="Author FID (optional)" + className="w-full" + /> + setChannelId(e.target.value)} + placeholder="Channel ID (optional)" + className="w-full" + /> +
+ +
+ +
+ {loading && !casts ?

Loading....

: null} + {casts.map((cast, index) => ( + + ))} +
+
+ ); +}; + +export default CastSearch; diff --git a/src/components/NetworkResponse.tsx b/src/components/NetworkResponse.tsx new file mode 100644 index 0000000..9fab42e --- /dev/null +++ b/src/components/NetworkResponse.tsx @@ -0,0 +1,443 @@ +import { hubs } from '@/constants'; +import { isNumeric, capitalizeNickname, isFollowSyntax } from '@/lib/helpers'; +import { + extractIdentifierFromUrl, + fetchCastAndFidData, + extractUsernameFromUrl, + isValidWarpcastUrl, +} from '@/lib/utils'; +import { NeynarCastCard, NeynarProfileCard } from '@neynar/react'; +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, +} from '@radix-ui/react-dropdown-menu'; +import { Button } from '@/components/ui/button'; +import { useRouter } from 'next13-progressbar'; +import { useState, useEffect } from 'react'; +import ActionButtons from './ActionButtons'; +import Modal from './modal-component'; +import SkeletonHeader from './skeleton-header'; +import * as amplitude from '@amplitude/analytics-browser'; +import Search from './search'; + +const NetworkResponse = ({ identifier }: any) => { + const router = useRouter(); + const fid: number | null = isNumeric(identifier) ? Number(identifier) : null; + let hash = fid ? null : identifier; + + if (identifier.includes('https://www.supercast.xyz/c/')) { + const extractedIdentifier = extractIdentifierFromUrl(identifier); + if (extractedIdentifier) { + identifier = extractedIdentifier; + hash = extractedIdentifier; + } else { + console.error('Invalid URL identifier'); + } + } + const [data, setData] = useState(null); + const [modalData, setModalData] = useState(null); + const [modalTitle, setModalTitle] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [loading, setLoading] = useState(true); + const [showOtherHubs, setShowOtherHubs] = useState(false); + const [clickedHeader, setClickedHeader] = useState(null); + + const checkWarning = (message: any) => { + if (!message) return []; + + const missingObjects = []; + let expectedUserTypes = [ + 'USER_DATA_TYPE_PFP', + 'USER_DATA_TYPE_DISPLAY', + 'USER_DATA_TYPE_BIO', + 'USER_DATA_TYPE_USERNAME', + ]; + if (message?.followedBy && message?.follow) { + if ( + !message?.followedBy.type || + message?.followedBy.type !== 'MESSAGE_TYPE_LINK_ADD' + ) { + missingObjects.push('Followed By'); + } + if ( + !message?.follow.type || + message?.follow.type !== 'MESSAGE_TYPE_LINK_ADD' + ) { + missingObjects.push('Following'); + } + return missingObjects; + } + + if (message?.messages) { + const foundTypes = new Set(); + message.messages.forEach((msg: any) => { + if (msg.data?.userDataBody?.type) { + foundTypes.add(msg.data.userDataBody.type); + } + }); + + return expectedUserTypes.filter((type) => !foundTypes.has(type)); + } + + const following = message?.following; + const followedBy = message?.followed_by; + + if (following !== undefined && following === false) + missingObjects.push('Following'); + if (followedBy !== undefined && followedBy === false) + missingObjects.push('Followed By'); + if (followedBy !== undefined && following !== undefined) { + return missingObjects; + } + + const authorFid = message?.fid; + const expectedUsername = `!${authorFid}`; + const username = message?.username; + const pfp = message?.pfp?.url || message?.pfp_url; + const displayName = message?.display_name || message?.displayName; + const bio = message?.profile?.bio?.text; + + if (!pfp) missingObjects.push('PFP'); + if (!displayName) missingObjects.push('Display Name'); + if (!bio) missingObjects.push('Bio'); + if (!username || username === expectedUsername) + missingObjects.push('Username'); + + return missingObjects; + }; + + const fetchData = async () => { + setLoading(true); + const data = (await fetchCastAndFidData(hash, fid)) as any; + const warpcastAuthorMissing = checkWarning(data.apiData.warpcast?.author); + const neynarAuthorMissing = checkWarning( + data.apiData.neynar?.author?.author + ); + const warpcastAuthorHubMissing = checkWarning(data.hubData?.[0]?.author); + const neynarAuthorHubMissing = checkWarning(data.hubData?.[1]?.author); + const warpcastCastMissing = [] as any; + const neynarCastMissing = [] as any; + + setData({ + ...data, + warpcastAuthorMissing, + neynarAuthorMissing, + warpcastAuthorHubMissing, + neynarAuthorHubMissing, + warpcastCastMissing, + neynarCastMissing, + warpcastCastHubMissing: [], + neynarCastHubMissing: [], + }); + + setLoading(false); + }; + + useEffect(() => { + void fetchData(); + }, [hash, fid]); + + const openModal = ( + title: string, + response: any, + missingObjects: string[] + ) => { + setModalTitle(title); + setModalData({ ...response, missingObjects }); + setIsModalOpen(true); + setClickedHeader(title); // Set the clicked header + }; + + const closeModal = () => { + setIsModalOpen(false); + setModalData(null); + setClickedHeader(null); // Reset the clicked header + }; + + const { warpcast, neynar } = data?.apiData ?? {}; + const { + warpcastAuthorMissing, + neynarAuthorMissing, + warpcastAuthorHubMissing, + neynarAuthorHubMissing, + warpcastCastMissing, + neynarCastMissing, + warpcastCastHubMissing, + neynarCastHubMissing, + } = data ?? {}; + + const hubsData = data?.hubData ?? []; + const hoyt = hubsData[0] ?? {}; + const neynarHub = hubsData[1] ?? {}; + const { author: hoytAuthor, cast: hoytCast } = hoyt || {}; + const { author: neynarHubAuthor, cast: neynarHubCast } = neynarHub || {}; + const { author: warpcastAuthor, cast: warpcastCast } = warpcast || {}; + const { author: neynarAuthor, cast: neynarCast } = neynar || {}; + + const authorFid = + warpcastCast?.author?.fid || + neynarCast?.cast?.author?.fid || + neynarAuthor?.author?.fid || + warpcastAuthor?.author?.fid; + + const username = + warpcastAuthor?.username ?? + neynarAuthor?.author?.username ?? + neynarCast?.cast?.author?.username ?? + warpcastCast?.cast?.author?.username ?? + null; + const castHash = neynar?.cast?.cast?.hash ?? warpcast?.cast?.hash ?? null; + + const renderHeader = (label: string, data: any, missingObjects: any[]) => { + if (!data) { + return null; + } + + let icon = '✅'; + let backgroundColor = '#03A800'; + let hoverColor = '#028700'; // Adjust hover color + + if (data?.is_server_dead) { + icon = '❓'; + backgroundColor = '#FFA500'; + hoverColor = '#CC8400'; // Adjust hover color + } else if (data?.error) { + icon = '❌'; + backgroundColor = '#C67A7D'; + hoverColor = '#A66060'; // Adjust hover color + } else if (missingObjects.length > 0) { + icon = '⚠️'; + backgroundColor = '#FFD700'; + hoverColor = '#CCB300'; // Adjust hover color + } + + const isClicked = clickedHeader === label; + const activeColor = isClicked ? '#03039A' : backgroundColor; + + return ( + + ); + }; + + return ( + <> + +
+
+
+
+
+ {loading ? ( + <> + {} + {} + {} + {} + + ) : ( + <> + {renderHeader( + 'Warpcast API', + warpcastAuthor, + warpcastAuthorMissing + )} + {renderHeader( + 'Warpcast API', + warpcastCast, + warpcastCastMissing + )} + {renderHeader( + capitalizeNickname(hoyt.name), + hoytAuthor, + warpcastAuthorHubMissing + )} + {renderHeader( + capitalizeNickname(hoyt.name), + hoytCast, + warpcastCastHubMissing + )} + {renderHeader( + 'Neynar hub', + neynarHubAuthor, + neynarAuthorHubMissing + )} + {renderHeader( + 'Neynar hub', + neynarHubCast, + neynarCastHubMissing + )} + {renderHeader( + 'Neynar API', + neynarAuthor, + neynarAuthorMissing + )} + {renderHeader('Neynar API', neynarCast, neynarCastMissing)} + {!showOtherHubs ? ( + + ) : ( + + )} + {showOtherHubs && ( +
+ {hubs.slice(2).map((hub, index) => { + const hubData = data?.hubData?.[index + 2]; + const hubAuthor = hubData?.author; + const hubCast = hubData?.cast; + const missingObjectsAuthor = checkWarning(hubAuthor); + const missingObjectsCast: any[] = []; + return ( +
+ {renderHeader( + `${capitalizeNickname(hub.shortname)}`, + hubAuthor, + missingObjectsAuthor + )} + {renderHeader( + `${capitalizeNickname(hub.shortname)}`, + hubCast, + missingObjectsCast + )} +
+ ); + })} +
+ )} + + )} +
+
+ {hash && isFollowSyntax(hash) ? null : hash && + !extractUsernameFromUrl(hash) ? ( + + ) : authorFid ? ( + + ) : null} +
+
+
+ {loading ? null : castHash || username ? ( +
+ {loading ? null : castHash || username ? ( + + + {' '} + + + + { + router.push( + castHash + ? `https://warpcast.com/${username}/${castHash.slice(0, 10)}` + : `https://warpcast.com/${username}` + ); + }} + > + in Warpcast + + { + router.push( + castHash + ? `https://www.supercast.xyz/c/${castHash}` + : `https://www.supercast.xyz/${username}` + ); + }} + > + in Supercast + + + + ) : null} +
+ ) : null} +
+
+ +
+ +
+
+
+ + ); +}; + +export default NetworkResponse; diff --git a/src/components/search.tsx b/src/components/search.tsx index 3984e84..51ea42c 100644 --- a/src/components/search.tsx +++ b/src/components/search.tsx @@ -1,13 +1,14 @@ 'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next13-progressbar'; -import { usePathname } from 'next/navigation'; +import { usePathname, useSearchParams } from 'next/navigation'; import { SearchIcon, X } from 'lucide-react'; import * as amplitude from '@amplitude/analytics-browser'; export default function Search() { const router = useRouter(); const path = usePathname(); + const searchParams = useSearchParams(); const [identifier, setIdentifier] = useState( decodeURIComponent(path.slice(1)) ); @@ -21,16 +22,23 @@ export default function Search() { setIdentifier(''); }; + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + amplitude.track('Search made', { + identifier, + ...Object.fromEntries(searchParams.entries()), + }); + + const currentParams = new URLSearchParams(searchParams.toString()); + const url = `/${encodeURIComponent(identifier)}${currentParams.toString() ? `?${currentParams.toString()}` : ''}`; + + router.push(url); + }; + return (
{ - e.preventDefault(); - amplitude.track('Search made', { - identifier, - }); - router.push(`/${encodeURIComponent(identifier)}`); - }} + onSubmit={handleSubmit} >
Date: Mon, 26 Aug 2024 18:08:39 -0400 Subject: [PATCH 2/4] make hook for search --- src/components/CastSearch.tsx | 27 +++++++++------------ src/components/search.tsx | 23 +++++++++--------- src/hooks/useSearchParamsWithoutSuspense.ts | 13 ++++++++++ 3 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 src/hooks/useSearchParamsWithoutSuspense.ts diff --git a/src/components/CastSearch.tsx b/src/components/CastSearch.tsx index 27158b1..e9a35db 100644 --- a/src/components/CastSearch.tsx +++ b/src/components/CastSearch.tsx @@ -4,28 +4,24 @@ import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { NeynarCastCard } from '@neynar/react'; import { useRouter, useSearchParams } from 'next/navigation'; +import useSearchParamsWithoutSuspense from '@/hooks/useSearchParamsWithoutSuspense'; const CastSearch = ({ query }: any) => { - const params = useSearchParams(); - const initialAuthorFid = params.get('authorFid'); - const initialChannelId = params.get('channelId'); + const params = useSearchParamsWithoutSuspense(); const router = useRouter(); - const [authorFid, setAuthorFid] = useState( - (initialAuthorFid as string) || '' - ); - const [channelId, setChannelId] = useState( - (initialChannelId as string) || '' - ); + const [authorFid, setAuthorFid] = useState(''); + const [channelId, setChannelId] = useState(''); const [casts, setCasts] = useState([]); const [cursor, setCursor] = useState(''); const [loading, setLoading] = useState(false); useEffect(() => { - setAuthorFid((initialAuthorFid as string) || ''); - setChannelId((initialChannelId as string) || ''); - }, [initialAuthorFid, initialChannelId]); - + if (params) { + setAuthorFid(params.get('authorFid') || ''); + setChannelId(params.get('channelId') || ''); + } + }, [params]); const fetchCasts = useCallback(async () => { setLoading(true); try { @@ -53,7 +49,6 @@ const CastSearch = ({ query }: any) => { if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); - console.log('data', data); setCasts((prevCasts) => [...prevCasts, ...data.result.casts]); if (data.result.next) { setCursor(data.result.next.cursor); @@ -65,11 +60,11 @@ const CastSearch = ({ query }: any) => { } finally { setLoading(false); } - }, [query, cursor, authorFid, channelId]); + }, [query, cursor]); useEffect(() => { if (query || authorFid || channelId) fetchCasts(); - }, [query, authorFid, channelId, fetchCasts]); + }, [query, fetchCasts]); const handleScroll = useCallback(() => { if ( diff --git a/src/components/search.tsx b/src/components/search.tsx index 51ea42c..09a59bf 100644 --- a/src/components/search.tsx +++ b/src/components/search.tsx @@ -4,14 +4,13 @@ import { useRouter } from 'next13-progressbar'; import { usePathname, useSearchParams } from 'next/navigation'; import { SearchIcon, X } from 'lucide-react'; import * as amplitude from '@amplitude/analytics-browser'; +import useSearchParamsWithoutSuspense from '@/hooks/useSearchParamsWithoutSuspense'; export default function Search() { const router = useRouter(); const path = usePathname(); - const searchParams = useSearchParams(); - const [identifier, setIdentifier] = useState( - decodeURIComponent(path.slice(1)) - ); + const searchParams = useSearchParamsWithoutSuspense(); + const [identifier, setIdentifier] = useState(''); useEffect(() => { const identifier = decodeURIComponent(path.slice(1)); @@ -24,15 +23,15 @@ export default function Search() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - amplitude.track('Search made', { - identifier, - ...Object.fromEntries(searchParams.entries()), - }); - - const currentParams = new URLSearchParams(searchParams.toString()); - const url = `/${encodeURIComponent(identifier)}${currentParams.toString() ? `?${currentParams.toString()}` : ''}`; + if (searchParams) { + amplitude.track('Search made', { + identifier, + ...Object.fromEntries(searchParams.entries()), + }); - router.push(url); + const url = `/${encodeURIComponent(identifier)}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; + router.push(url); + } }; return ( diff --git a/src/hooks/useSearchParamsWithoutSuspense.ts b/src/hooks/useSearchParamsWithoutSuspense.ts new file mode 100644 index 0000000..ae07aa8 --- /dev/null +++ b/src/hooks/useSearchParamsWithoutSuspense.ts @@ -0,0 +1,13 @@ +import { useEffect, useState } from 'react'; + +const useSearchParamsWithoutSuspense = () => { + const [params, setParams] = useState(null); + + useEffect(() => { + setParams(new URLSearchParams(window.location.search)); + }, []); + + return params; +}; + +export default useSearchParamsWithoutSuspense; From 36ea54a8bd74841f7e055c5c40723a1b8ff7f3f4 Mon Sep 17 00:00:00 2001 From: Kevin O'Connell Date: Mon, 26 Aug 2024 18:59:28 -0400 Subject: [PATCH 3/4] clean up --- src/components/CastSearch.tsx | 112 +++++++++++++++++++--------------- src/components/search.tsx | 13 ++-- src/lib/utils.ts | 1 - 3 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/components/CastSearch.tsx b/src/components/CastSearch.tsx index e9a35db..531cb84 100644 --- a/src/components/CastSearch.tsx +++ b/src/components/CastSearch.tsx @@ -3,10 +3,10 @@ import React, { useState, useEffect, useCallback } from 'react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { NeynarCastCard } from '@neynar/react'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { useRouter } from 'next/navigation'; import useSearchParamsWithoutSuspense from '@/hooks/useSearchParamsWithoutSuspense'; -const CastSearch = ({ query }: any) => { +const CastSearch = ({ query }: { query: string }) => { const params = useSearchParamsWithoutSuspense(); const router = useRouter(); @@ -16,55 +16,52 @@ const CastSearch = ({ query }: any) => { const [cursor, setCursor] = useState(''); const [loading, setLoading] = useState(false); + // Initialize filters from URL params useEffect(() => { if (params) { setAuthorFid(params.get('authorFid') || ''); setChannelId(params.get('channelId') || ''); } }, [params]); - const fetchCasts = useCallback(async () => { - setLoading(true); - try { - console.log('fetching casts'); - console.log('query', query); - console.log('cursor', cursor); - console.log('authorFid', authorFid); - console.log('channelId', channelId); - const url = new URL('https://api.neynar.com/v2/farcaster/cast/search'); - if (query) url.searchParams.append('q', query); - url.searchParams.append('limit', '100'); - if (cursor) url.searchParams.append('cursor', cursor); - if (authorFid) url.searchParams.append('author_fid', authorFid); - if (channelId) url.searchParams.append('channel_id', channelId); - const headers = new Headers(); - headers.append('Accept', 'application/json'); - headers.append('api_key', process.env.NEXT_PUBLIC_NEYNAR_API_KEY || ''); + const fetchCasts = useCallback( + async (newSearch: boolean = false) => { + setLoading(true); + try { + const url = new URL('https://api.neynar.com/v2/farcaster/cast/search'); + if (query) url.searchParams.append('q', query); + url.searchParams.append('limit', '100'); + if (cursor && !newSearch) url.searchParams.append('cursor', cursor); + if (authorFid) url.searchParams.append('author_fid', authorFid); + if (channelId) url.searchParams.append('channel_id', channelId); - const response = await fetch(url, { - method: 'GET', - headers: headers, - }); + const headers = new Headers(); + headers.append('Accept', 'application/json'); + headers.append('api_key', process.env.NEXT_PUBLIC_NEYNAR_API_KEY || ''); + const response = await fetch(url, { + method: 'GET', + headers: headers, + }); - if (!response.ok) throw new Error('Network response was not ok'); + if (!response.ok) throw new Error('Network response was not ok'); - const data = await response.json(); - setCasts((prevCasts) => [...prevCasts, ...data.result.casts]); - if (data.result.next) { - setCursor(data.result.next.cursor); - } else { - setCursor(''); + const data = await response.json(); + setCasts((prevCasts) => + newSearch ? data.result.casts : [...prevCasts, ...data.result.casts] + ); + if (data.result.next) { + setCursor(data.result.next.cursor); + } else { + setCursor(''); + } + } catch (error) { + console.error('Error fetching casts:', error); + } finally { + setLoading(false); } - } catch (error) { - console.error('Error fetching casts:', error); - } finally { - setLoading(false); - } - }, [query, cursor]); - - useEffect(() => { - if (query || authorFid || channelId) fetchCasts(); - }, [query, fetchCasts]); + }, + [query, cursor, authorFid, channelId] + ); const handleScroll = useCallback(() => { if ( @@ -84,15 +81,22 @@ const CastSearch = ({ query }: any) => { const handleSearch = (e: React.FormEvent) => { e.preventDefault(); + const newParams = new URLSearchParams(); + if (authorFid) newParams.append('authorFid', authorFid); + if (channelId) newParams.append('channelId', channelId); + router.push(`/${query}?${newParams.toString()}`); setCasts([]); setCursor(''); - fetchCasts(); - const params = new URLSearchParams(); - if (authorFid) params.append('authorFid', authorFid); - if (channelId) params.append('channelId', channelId); - router.push(`/${query}?${params.toString()}`); + fetchCasts(true); }; + // Initial fetch when component mounts + useEffect(() => { + if (query || authorFid || channelId) { + fetchCasts(true); + } + }, []); // Empty dependency array ensures this only runs once on mount + return (
@@ -118,10 +122,20 @@ const CastSearch = ({ query }: any) => {
- {loading && !casts ?

Loading....

: null} - {casts.map((cast, index) => ( - - ))} + {loading && casts.length === 0 ?

Loading....

: null} + {casts && casts.length ? ( + casts.map((cast, index) => ( + + )) + ) : ( +

+ No casts found +

+ )}
); diff --git a/src/components/search.tsx b/src/components/search.tsx index 09a59bf..eebf822 100644 --- a/src/components/search.tsx +++ b/src/components/search.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next13-progressbar'; -import { usePathname, useSearchParams } from 'next/navigation'; +import { usePathname } from 'next/navigation'; import { SearchIcon, X } from 'lucide-react'; import * as amplitude from '@amplitude/analytics-browser'; import useSearchParamsWithoutSuspense from '@/hooks/useSearchParamsWithoutSuspense'; @@ -24,13 +24,18 @@ export default function Search() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (searchParams) { + const newParams = new URLSearchParams(); + const authorFid = searchParams.get('authorFid') || ''; + const channelId = searchParams.get('channelId') || ''; + if (authorFid) newParams.append('authorFid', authorFid); + if (channelId) newParams.append('channelId', channelId); + amplitude.track('Search made', { identifier, - ...Object.fromEntries(searchParams.entries()), + ...Object.fromEntries(newParams.entries()), }); - const url = `/${encodeURIComponent(identifier)}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; - router.push(url); + router.push(`/${encodeURIComponent(identifier)}?${newParams.toString()}`); } }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 65e86a9..bb964b3 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -476,7 +476,6 @@ export async function fetchFidFromHub( `${hub.url}/v1/verificationsByFid?fid=${fid}`, { headers, timeout: 7000 } ); - console.log('response', response.data); return response?.data && response?.data?.messages?.length ? { ...response.data, From d48b495d77fde93b78eff45ad4e5e74e85378077 Mon Sep 17 00:00:00 2001 From: Kevin O'Connell Date: Tue, 27 Aug 2024 10:40:48 -0400 Subject: [PATCH 4/4] update package --- package-lock.json | 410 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 392 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ee9262..fa3b317 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@amplitude/analytics-browser": "^2.9.3", "@farcaster/hub-nodejs": "^0.11.11", "@neynar/nodejs-sdk": "^1.27.0", - "@neynar/react": "^0.6.4", + "@neynar/react": "^0.9.2", "@pigment-css/react": "^0.0.9", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", @@ -2574,10 +2574,13 @@ } }, "node_modules/@neynar/react": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@neynar/react/-/react-0.6.4.tgz", - "integrity": "sha512-pgbof0ouDa/P1dE4mw/37sn+8aim/ll2GdmyHhireSXuGtWhl/JccU5oxQ08JaM11elQJNaNICfSjKVGl8jDWA==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@neynar/react/-/react-0.9.2.tgz", + "integrity": "sha512-fsL5qlcN2zw/r6lYyssOcxeqVFfM9dHiu4p8QneGs66lXQw8aX+vXfCAZNSBJRPNxYYA+m+8m9z9VC87tic+DQ==", "license": "MIT", + "dependencies": { + "storybook-source-link": "^4.0.1" + }, "peerDependencies": { "@pigment-css/react": "^0.0.9", "hls.js": "^1.5.13", @@ -3774,6 +3777,199 @@ "@stablelib/wipe": "^1.0.1" } }, + "node_modules/@storybook/addons": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-7.6.17.tgz", + "integrity": "sha512-Ok18Y698Ccyg++MoUNJNHY0cXUvo8ETFIRLJk1g9ElJ70j6kPgNnzW2pAtZkBNmswHtofZ7pT156cj96k/LgfA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@storybook/manager-api": "7.6.17", + "@storybook/preview-api": "7.6.17", + "@storybook/types": "7.6.17" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/channels": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.17.tgz", + "integrity": "sha512-GFG40pzaSxk1hUr/J/TMqW5AFDDPUSu+HkeE/oqSWJbOodBOLJzHN6CReJS6y1DjYSZLNFt1jftPWZZInG/XUA==", + "license": "MIT", + "dependencies": { + "@storybook/client-logger": "7.6.17", + "@storybook/core-events": "7.6.17", + "@storybook/global": "^5.0.0", + "qs": "^6.10.0", + "telejson": "^7.2.0", + "tiny-invariant": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/client-logger": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.17.tgz", + "integrity": "sha512-6WBYqixAXNAXlSaBWwgljWpAu10tPRBJrcFvx2gPUne58EeMM20Gi/iHYBz2kMCY+JLAgeIH7ZxInqwO8vDwiQ==", + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/core-events": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.17.tgz", + "integrity": "sha512-AriWMCm/k1cxlv10f+jZ1wavThTRpLaN3kY019kHWbYT9XgaSuLU67G7GPr3cGnJ6HuA6uhbzu8qtqVCd6OfXA==", + "license": "MIT", + "dependencies": { + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/csf": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.11.tgz", + "integrity": "sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==", + "license": "MIT", + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@storybook/csf/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "license": "MIT" + }, + "node_modules/@storybook/manager-api": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.17.tgz", + "integrity": "sha512-IJIV1Yc6yw1dhCY4tReHCfBnUKDqEBnMyHp3mbXpsaHxnxJZrXO45WjRAZIKlQKhl/Ge1CrnznmHRCmYgqmrWg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@storybook/channels": "7.6.17", + "@storybook/client-logger": "7.6.17", + "@storybook/core-events": "7.6.17", + "@storybook/csf": "^0.1.2", + "@storybook/global": "^5.0.0", + "@storybook/router": "7.6.17", + "@storybook/theming": "7.6.17", + "@storybook/types": "7.6.17", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "store2": "^2.14.2", + "telejson": "^7.2.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/preview-api": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.17.tgz", + "integrity": "sha512-wLfDdI9RWo1f2zzFe54yRhg+2YWyxLZvqdZnSQ45mTs4/7xXV5Wfbv3QNTtcdw8tT3U5KRTrN1mTfTCiRJc0Kw==", + "license": "MIT", + "dependencies": { + "@storybook/channels": "7.6.17", + "@storybook/client-logger": "7.6.17", + "@storybook/core-events": "7.6.17", + "@storybook/csf": "^0.1.2", + "@storybook/global": "^5.0.0", + "@storybook/types": "7.6.17", + "@types/qs": "^6.9.5", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "synchronous-promise": "^2.0.15", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/router": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.17.tgz", + "integrity": "sha512-GnyC0j6Wi5hT4qRhSyT8NPtJfGmf82uZw97LQRWeyYu5gWEshUdM7aj40XlNiScd5cZDp0owO1idduVF2k2l2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@storybook/client-logger": "7.6.17", + "memoizerific": "^1.11.3", + "qs": "^6.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/theming": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.17.tgz", + "integrity": "sha512-ZbaBt3KAbmBtfjNqgMY7wPMBshhSJlhodyMNQypv+95xLD/R+Az6aBYbpVAOygLaUQaQk4ar7H/Ww6lFIoiFbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@storybook/client-logger": "7.6.17", + "@storybook/global": "^5.0.0", + "memoizerific": "^1.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/types": { + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.17.tgz", + "integrity": "sha512-GRY0xEJQ0PrL7DY2qCNUdIfUOE0Gsue6N+GBJw9ku1IUDFLJRDOF+4Dx2BvYcVCPI5XPqdWKlEyZdMdKjiQN7Q==", + "license": "MIT", + "dependencies": { + "@storybook/channels": "7.6.17", + "@types/babel__core": "^7.0.0", + "@types/express": "^4.7.0", + "file-system-cache": "2.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "license": "Apache-2.0" @@ -3786,11 +3982,51 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "peer": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -3800,7 +4036,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "peer": true, "dependencies": { "@types/node": "*" } @@ -3809,7 +4044,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -3821,7 +4055,6 @@ "version": "4.19.5", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", - "peer": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -3839,8 +4072,7 @@ "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "peer": true + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -3850,8 +4082,7 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "peer": true + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/node": { "version": "20.14.9", @@ -3875,18 +4106,15 @@ "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", - "peer": true + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "peer": true + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { "version": "18.3.3", - "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3905,7 +4133,6 @@ "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "peer": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -3915,7 +4142,6 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "peer": true, "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -6098,6 +6324,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", @@ -7126,6 +7361,30 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-system-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-2.3.0.tgz", + "integrity": "sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==", + "license": "MIT", + "dependencies": { + "fs-extra": "11.1.1", + "ramda": "0.29.0" + } + }, + "node_modules/file-system-cache/node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fill-range": { "version": "7.1.1", "license": "MIT", @@ -9329,6 +9588,21 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/map-or-similar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", + "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "license": "MIT" + }, + "node_modules/memoizerific": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", + "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "license": "MIT", + "dependencies": { + "map-or-similar": "^1.5.0" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "license": "MIT" @@ -10575,6 +10849,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -10623,6 +10912,16 @@ "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", "peer": true }, + "node_modules/ramda": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", + "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -10662,6 +10961,18 @@ "react": "^18.3.1" } }, + "node_modules/react-error-boundary": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz", + "integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-farcaster-embed": { "version": "1.7.2", "license": "The Unlicense", @@ -11319,6 +11630,37 @@ "node": ">= 0.4" } }, + "node_modules/store2": { + "version": "2.14.3", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.3.tgz", + "integrity": "sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==", + "license": "MIT", + "peer": true + }, + "node_modules/storybook-source-link": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/storybook-source-link/-/storybook-source-link-4.0.1.tgz", + "integrity": "sha512-eafQgCxZ5/0ZIumT/3W/LVhFgl3Z/OeBxRkmOKkLJx9F1gpZmwNhT04IvgcSScc810ovKK1IdZ1pogkefi1s/A==", + "license": "MIT", + "dependencies": { + "@storybook/preview-api": "^7.5.3", + "@types/react": "^18.2.37", + "react-error-boundary": "^4.0.11" + }, + "peerDependencies": { + "@storybook/addons": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", @@ -11719,6 +12061,12 @@ "react": "^16.11.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/synchronous-promise": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.17.tgz", + "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", + "license": "BSD-3-Clause" + }, "node_modules/system-architecture": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", @@ -11809,6 +12157,15 @@ "node": ">=6" } }, + "node_modules/telejson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz", + "integrity": "sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==", + "license": "MIT", + "dependencies": { + "memoizerific": "^1.11.3" + } + }, "node_modules/text-table": { "version": "0.2.0", "dev": true, @@ -11848,6 +12205,12 @@ "version": "1.0.3", "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.0.33", "license": "MIT", @@ -11914,6 +12277,15 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "license": "Apache-2.0" diff --git a/package.json b/package.json index 9a10b69..8eca8bd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@amplitude/analytics-browser": "^2.9.3", "@farcaster/hub-nodejs": "^0.11.11", "@neynar/nodejs-sdk": "^1.27.0", - "@neynar/react": "^0.6.4", + "@neynar/react": "^0.9.2", "@pigment-css/react": "^0.0.9", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3",