import { Avatar, Box, BoxedNextImage as Image, BoxProps, Button, CloseIcon, ExternalLinkIcon, Flex, HamburgerIcon, HStack, Input, InputGroup, InputLeftElement, Link, Modal, ModalBody, ModalContent, ModalOverlay, SimpleGrid, Stack, Text, Tooltip, useBreakpointValue, useDisclosure, } from '@metafam/ds'; import { Maybe } from '@metafam/utils'; import { ConnectKitButton } from 'connectkit'; import { GuildFragment, Player, PlayerFragment, SearchQuestsQuery, } from 'graphql/autogen/hasura-sdk'; import { useRouter } from 'next/router'; import React, { FormEventHandler, ReactNode, useEffect, useRef, useState, } from 'react'; import { distinctUntilChanged, forkJoin, from, Subject } from 'rxjs'; import { debounceTime, filter, shareReplay, switchMap } from 'rxjs/operators'; import { getPlayerName, getPlayerURL, getPlayerUsername, } from 'utils/playerHelpers'; import { useAccount } from 'wagmi'; import LogoImage from '#assets/new_logo_svg.svg'; import SearchIcon from '#assets/search-icon.svg'; import { MetaLink } from '#components/Link'; import { DesktopNavLinks } from '#components/MegaMenu/DesktopNavLinks'; import { DesktopPlayerStats } from '#components/MegaMenu/DesktopPlayerStats'; import { searchPatrons } from '#graphql/getPatrons'; import { searchPlayers } from '#graphql/getPlayers'; import { searchQuests } from '#graphql/getQuests'; import { searchGuilds } from '#graphql/queries/guild'; import { Patron } from '#graphql/types'; import { useMounted, useUser, useWeb3 } from '#lib/hooks'; import { useProfileImageOnload } from '#lib/hooks/useProfileImageOnload'; import { menuIcons } from '#utils/menuIcons'; import { MenuSectionLinks } from '#utils/menuLinks'; import { authenticateWallet } from '#contexts/Web3Context'; import { useViemClients } from '#lib/hooks/useEthersProvider'; type LogoProps = { link: string; } & BoxProps; const Logo: React.FC = ({ link, ...props }) => { const w = useBreakpointValue({ base: 9, lg: 10 }) ?? 9; const h = useBreakpointValue({ base: 12, lg: 14 }) ?? 12; return ( ); }; interface OptionProps { text: string; name: string; player?: Player; image?: string; onClick: () => void; } const Option = ({ onClick, name, player, text }: OptionProps) => { const imageURL = useProfileImageOnload({ player }); return ( div': { fontSize: 'xs', }, }} /> {text} ); }; const ResultsTitle = ({ children }: { children: ReactNode }) => ( {children} ); const SeeAllOption = ({ type, onClick, }: { type: string; onClick: () => void; }) => ( See All {type} ); const LIMIT = 3; interface SearchResults { players: PlayerFragment[]; guilds: GuildFragment[]; patrons: Patron[]; quests: SearchQuestsQuery['quest']; } const SearchModal = ({ isOpen, onClose, }: { isOpen: boolean; onClose: () => void; }) => { const router = useRouter(); const searchInputSubjectRef = useRef(new Subject()); const searchBarRef = useRef(null); const [query, setQuery] = useState(''); const [{ players, guilds, patrons, quests }, setSearchResults] = useState({ players: [], guilds: [], patrons: [], quests: [], }); const resetResults = () => { setSearchResults({ players: [], guilds: [], patrons: [], quests: [], }); }; const dropdown = useRef>(null); const handleSubmit: FormEventHandler = (e) => { e.preventDefault(); onClose(); // Default Show Players Matching With Query router.push(`/search/players?q=${query}`); }; useEffect(() => { searchInputSubjectRef.current.next(query); }, [query]); useEffect(() => { const searchSubscription = searchInputSubjectRef.current .pipe( filter((searchValue: string) => { if (searchValue.length >= 1) return true; resetResults(); return false; }), debounceTime(300), distinctUntilChanged(), switchMap((queryString) => forkJoin([ from(searchPlayers(queryString)), from(searchGuilds({ search: queryString, limit: LIMIT })), from(searchPatrons(queryString, LIMIT)), from(searchQuests(queryString, LIMIT)), ]), ), shareReplay(1), ) .subscribe( ([ { players: p }, { guilds: g }, searchPatronsRes, searchQuestsRes, ]) => { setSearchResults({ players: p, guilds: g, patrons: searchPatronsRes, quests: searchQuestsRes, }); }, ); return () => searchSubscription?.unsubscribe(); }, []); const isBodyEmpty = players.length + guilds.length === 0; return ( 0 ? 0 : 4} overflow="hidden" > } /> setQuery(value)} size="sm" fontSize="md" ref={searchBarRef} /> {!isBodyEmpty && ( {players.length > 0 && Players} {players.length > 0 && ( {players?.map((player: PlayerFragment) => ( )} {guilds.length > 0 && Guilds} {guilds.length > 0 && ( {guilds?.map((guild: GuildFragment) => ( )} {patrons.length > 0 && Patrons} {patrons.length > 0 && ( {patrons?.map((patron: Patron) => ( )} {quests.length > 0 && Quests} {quests.length > 0 && ( {quests.map((quest: SearchQuestsQuery['quest'][number]) => ( )} )} ); }; type HeaderSearchBarProps = BoxProps & { onOpen: () => void }; const HeaderSearchBar = (props: HeaderSearchBarProps) => { const { onOpen, ...restProps } = props; return ( search ); }; export const MegaMenuHeader: React.FC = () => { const router = useRouter(); const { user, fetching } = useUser(); const { isOpen, onOpen, onClose } = useDisclosure(); const menuToggle = () => (isOpen ? onClose() : onOpen()); const { isOpen: isSearchOpen, onOpen: onSearchOpen, onClose: onSearchClose, } = useDisclosure(); // Toggle the menu when ⌘K is pressed React.useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); if (isSearchOpen) { onSearchClose(); } else { onSearchOpen(); } } }; document.addEventListener('keydown', down); return () => document.removeEventListener('keydown', down); }, [isSearchOpen, onSearchClose, onSearchOpen]); return ( <> {isOpen ? ( ) : ( // max-width set in style attribute because the unstyled version // is flashing at 100% width on load )} {({ isConnected, isConnecting, show }) => ( isConnected ? ( !!user ? ( ) : ( ) ) : ( ) )} {MenuSectionLinks.map((section) => ( {section.label} {section.type === 'external-link' && ( )} {section?.menuItems?.length && section.menuItems.map(({ title, icon, url }) => ( {title} ))} ))} ); };