From 20dbf1634645762cf25afce0f6926bdb1e6bdb9d Mon Sep 17 00:00:00 2001 From: Kevin O'Connell Date: Mon, 26 Aug 2024 17:22:19 -0400 Subject: [PATCH] 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} >