diff --git a/packages/app-extension/src/app/Router.tsx b/packages/app-extension/src/app/Router.tsx index 43c10c40c..00831d581 100644 --- a/packages/app-extension/src/app/Router.tsx +++ b/packages/app-extension/src/app/Router.tsx @@ -41,6 +41,7 @@ import { import { AuthenticatedSync } from "../components/Unlocked/AuthenticatedSync"; import { WithAuth } from "../components/Unlocked/WithAuth"; import { refreshFeatureGates } from "../gates/FEATURES"; +import { Spotlight } from "../spotlight/Spotlight"; import { sanitizeTransactionWithFeeConfig } from "../utils/solana"; import "./App.css"; @@ -58,6 +59,7 @@ export default function Router() { theme={isDarkMode ? "dark" : "light"} /> <_Router /> + ); diff --git a/packages/app-extension/src/spotlight/ActionRow.tsx b/packages/app-extension/src/spotlight/ActionRow.tsx new file mode 100644 index 000000000..c68b51ace --- /dev/null +++ b/packages/app-extension/src/spotlight/ActionRow.tsx @@ -0,0 +1,38 @@ +import { PushDetail } from "@coral-xyz/react-common"; +import { useCustomTheme } from "@coral-xyz/themes"; + +import { SELECTED_BLUE } from "./colors"; +import { Line } from "./Line"; + +export const ActionRow = ({ + title, + onClick, + selected = false, +}: { + title: string; + onClick: any; + selected?: boolean; +}) => { + const theme = useCustomTheme(); + + return ( +
+
+
{title}
+
+ +
+
+ +
+ ); +}; diff --git a/packages/app-extension/src/spotlight/FriendCard.tsx b/packages/app-extension/src/spotlight/FriendCard.tsx new file mode 100644 index 000000000..c21b40186 --- /dev/null +++ b/packages/app-extension/src/spotlight/FriendCard.tsx @@ -0,0 +1,120 @@ +import { useEffect,useState } from "react"; +import { + NAV_COMPONENT_MESSAGE_CHAT, + NAV_COMPONENT_MESSAGE_PROFILE, + TAB_MESSAGES, + UI_RPC_METHOD_NAVIGATION_ACTIVE_TAB_UPDATE, +} from "@coral-xyz/common"; +import { useBackgroundClient, useNavigation } from "@coral-xyz/recoil"; + +import { ActionRow } from "./ActionRow"; +import { GroupIdentifier } from "./GroupIdentifier"; +import { SpotlightContact } from "./SpotlightContacts"; + +export const FriendCard = ({ + friend, + setOpen, +}: { + friend: { username: string; image: string; uuid: string }; + setOpen: any; +}) => { + const [arrowIndex, setArrowIndex] = useState(null); + const background = useBackgroundClient(); + const { push, toRoot } = useNavigation(); + + useEffect(() => { + async function keyDownTextField(e: any) { + if (e.which === 38) { + setArrowIndex((x) => (x === null ? 0 : x - 1)); + } else if (e.which === 40) { + setArrowIndex((x) => (x === null ? 0 : x + 1)); + } + if ((e.key === "Enter" || e.key === "Return") && arrowIndex !== null) { + await toRoot(); + await background.request({ + method: UI_RPC_METHOD_NAVIGATION_ACTIVE_TAB_UPDATE, + params: [TAB_MESSAGES], + }); + if (arrowIndex !== null && arrowIndex !== -1 && arrowIndex % 2 === 0) { + push({ + title: `@${friend.username}`, + componentId: NAV_COMPONENT_MESSAGE_CHAT, + componentProps: { + userId: friend?.uuid, + id: friend?.uuid, + username: friend?.username, + }, + }); + } else { + push({ + title: `@${friend?.username}`, + componentId: NAV_COMPONENT_MESSAGE_PROFILE, + componentProps: { + userId: friend?.uuid, + }, + }); + } + setOpen(false); + } + } + document.addEventListener("keydown", keyDownTextField); + + return () => { + document.removeEventListener("keydown", keyDownTextField); + }; + }, [arrowIndex, friend]); + + return ( +
+ + {}} + contact={friend} + selected={false} + /> + + { + await toRoot(); + await background.request({ + method: UI_RPC_METHOD_NAVIGATION_ACTIVE_TAB_UPDATE, + params: [TAB_MESSAGES], + }); + + push({ + title: `@${friend.username}`, + componentId: NAV_COMPONENT_MESSAGE_CHAT, + componentProps: { + userId: friend?.uuid, + id: friend?.uuid, + username: friend?.username, + }, + }); + setOpen(false); + }} + /> + { + await toRoot(); + await background.request({ + method: UI_RPC_METHOD_NAVIGATION_ACTIVE_TAB_UPDATE, + params: [TAB_MESSAGES], + }); + + push({ + title: `@${friend?.username}`, + componentId: NAV_COMPONENT_MESSAGE_PROFILE, + componentProps: { + userId: friend?.uuid, + }, + }); + setOpen(false); + }} + /> +
+ ); +}; diff --git a/packages/app-extension/src/spotlight/GroupIdentifier.tsx b/packages/app-extension/src/spotlight/GroupIdentifier.tsx new file mode 100644 index 000000000..ec38d7c18 --- /dev/null +++ b/packages/app-extension/src/spotlight/GroupIdentifier.tsx @@ -0,0 +1,23 @@ +import { PushDetail } from "@coral-xyz/react-common"; +import { useCustomTheme } from "@coral-xyz/themes"; + +import { ActionRow } from "./ActionRow"; + +export const GroupIdentifier = ({ name }: { name: string }) => { + const theme = useCustomTheme(); + + return ( +
+
{name}
+
+ +
+
+ ); +}; diff --git a/packages/app-extension/src/spotlight/Line.tsx b/packages/app-extension/src/spotlight/Line.tsx new file mode 100644 index 000000000..a1b6d6222 --- /dev/null +++ b/packages/app-extension/src/spotlight/Line.tsx @@ -0,0 +1,9 @@ +import { useCustomTheme } from "@coral-xyz/themes"; + +export const Line = () => { + const theme = useCustomTheme(); + + return ( +
+ ); +}; diff --git a/packages/app-extension/src/spotlight/SearchBar.tsx b/packages/app-extension/src/spotlight/SearchBar.tsx new file mode 100644 index 000000000..411f09a3f --- /dev/null +++ b/packages/app-extension/src/spotlight/SearchBar.tsx @@ -0,0 +1,38 @@ +import * as React from "react"; +import { TextInput } from "@coral-xyz/react-common"; +import { useCustomTheme } from "@coral-xyz/themes"; +import SearchIcon from "@mui/icons-material/Search"; + +import { useStyles } from "./styles"; + +export const SpotlightSearchBar = ({ + searchFilter, + setSearchFilter, +}: { + searchFilter: string; + setSearchFilter: any; +}) => { + const classes = useStyles(); + const theme = useCustomTheme(); + + return ( + + } + value={searchFilter} + setValue={async (e) => { + const prefix = e.target.value; + setSearchFilter(prefix); + }} + inputProps={{ + style: { + height: "48px", + }, + }} + /> + ); +}; diff --git a/packages/app-extension/src/spotlight/SearchBody.tsx b/packages/app-extension/src/spotlight/SearchBody.tsx new file mode 100644 index 000000000..c3f0b8311 --- /dev/null +++ b/packages/app-extension/src/spotlight/SearchBody.tsx @@ -0,0 +1,82 @@ +import { SpotlightContacts } from "./SpotlightContacts"; +import { SpotlightGroups } from "./SpotlightGroups"; +import { SpotlightNfts } from "./SpotlightNfts"; +import { SpotlightTokens } from "./SpotlightTokens"; +import { useSearchedContacts } from "./useSearchedContacts"; +import { useSearchedGroupsCollections } from "./useSearchedGroups"; +import { useSearchedNfts } from "./useSearchedNfts"; +import { useSearchedTokens } from "./useSearchedTokens"; +import { getCurrentCounter } from "./utils"; + +export const SearchBody = ({ + searchFilter, + arrowIndex, + setOpen, + setSelectedContact, +}: { + searchFilter: string; + arrowIndex: number; + setOpen: any; + setSelectedContact: any; +}) => { + const contacts = useSearchedContacts(searchFilter); + const groups = useSearchedGroupsCollections(searchFilter); + const nfts = useSearchedNfts(searchFilter); + const tokens = useSearchedTokens(searchFilter); + const allResultsLength = + contacts.length + groups.length + nfts.length + tokens.length; + const currentCounter = getCurrentCounter(arrowIndex, allResultsLength); + + if (!searchFilter) return
; + + return ( +
+
+ +
+
+ = contacts.length && + currentCounter < contacts.length + groups.length + ? currentCounter - contacts.length + : null + } + groups={groups} + setOpen={setOpen} + /> +
+
+ = contacts.length + groups.length && + currentCounter < contacts.length + groups.length + nfts.length + ? currentCounter - contacts.length - groups.length + : null + } + nfts={nfts} + setOpen={setOpen} + /> +
+
+ = contacts.length + groups.length + nfts.length && + currentCounter < + contacts.length + groups.length + nfts.length + tokens.length + ? currentCounter - contacts.length - groups.length - nfts.length + : null + } + tokens={tokens} + setOpen={setOpen} + /> +
+
+ ); +}; diff --git a/packages/app-extension/src/spotlight/Spotlight.tsx b/packages/app-extension/src/spotlight/Spotlight.tsx new file mode 100644 index 000000000..f2b87ce81 --- /dev/null +++ b/packages/app-extension/src/spotlight/Spotlight.tsx @@ -0,0 +1,218 @@ +import * as React from "react"; +import { useEffect, useRef, useState } from "react"; +import { + Blockchain, + NAV_COMPONENT_MESSAGE_GROUP_CHAT, + NAV_COMPONENT_NFT_DETAIL, + NAV_COMPONENT_TOKEN, + toTitleCase, +} from "@coral-xyz/common"; +import { + useActiveWallet, + useBlockchainConnectionUrl, + useBreakpoints, + useNavigation, +} from "@coral-xyz/recoil"; +import { useCustomTheme } from "@coral-xyz/themes"; +import Box from "@mui/material/Box"; +import Modal from "@mui/material/Modal"; + +import { FriendCard } from "./FriendCard"; +import { SpotlightSearchBar } from "./SearchBar"; +import { SearchBody } from "./SearchBody"; +import { useSearchedContacts } from "./useSearchedContacts"; +import { useSearchedGroupsCollections } from "./useSearchedGroups"; +import { useSearchedNfts } from "./useSearchedNfts"; +import { useSearchedTokens } from "./useSearchedTokens"; +import { getCurrentCounter } from "./utils"; + +const style = { + position: "fixed", + top: 50, + left: "50%", + transform: "translate(-50%, 0)", + boxShadow: 24, +}; + +export const Spotlight = () => { + const [open, setOpen] = useState(false); + const theme = useCustomTheme(); + const { isXs } = useBreakpoints(); + const [arrowIndex, setArrowIndex] = useState(0); + const [selectedContact, setSelectedContact] = useState<{ + username: string; + image: string; + uuid: string; + } | null>(null); + + useEffect(() => { + function keyDownTextField(e: any) { + if (e.key === "k" && e.metaKey) { + setOpen(true); + e.preventDefault(); + } + if (e.which === 27 || e.keyCode === 27) { + setOpen(false); + } + if (e.which === 38) { + setArrowIndex((x) => x - 1); + } else if (e.which === 40) { + setArrowIndex((x) => x + 1); + } + } + document.addEventListener("keydown", keyDownTextField); + + return () => { + document.removeEventListener("keydown", keyDownTextField); + }; + }, []); + + return ( + { + if (selectedContact) { + setSelectedContact(null); + } else { + setOpen(false); + } + }} + > + + + + + ); +}; + +function SpotlightInner({ + arrowIndex, + selectedContact, + setSelectedContact, + setOpen, +}: { + arrowIndex: number; + selectedContact: { username: string; image: string; uuid: string } | null; + setSelectedContact: any; + setOpen: any; +}) { + const [searchFilter, setSearchFilter] = useState(""); + const contacts = useSearchedContacts(searchFilter); + const groups = useSearchedGroupsCollections(searchFilter); + const nfts = useSearchedNfts(searchFilter); + const tokens = useSearchedTokens(searchFilter); + const allResultsLength = + contacts.length + groups.length + nfts.length + tokens.length; + const { push, toRoot } = useNavigation(); + const activeWallet = useActiveWallet(); + const connectionUrl = useBlockchainConnectionUrl(activeWallet.blockchain); + + if (selectedContact) { + return ( +
+ +
+ ); + } + + return ( +
{ + if (e.keyCode === 13) { + const currentCounter = getCurrentCounter( + arrowIndex, + allResultsLength + ); + const selectedContactIndex = + currentCounter < contacts.length ? currentCounter : null; + const selectedGroupChatIndex = + currentCounter >= contacts.length && + currentCounter - contacts.length < groups.length + ? currentCounter - contacts.length + : null; + const selectedNFtChatIndex = + currentCounter >= contacts.length + groups.length && + currentCounter - contacts.length - groups.length < nfts.length + ? currentCounter - contacts.length - groups.length + : null; + const selectedTokenIndex = + currentCounter >= contacts.length + groups.length + nfts.length && + currentCounter - contacts.length - groups.length - nfts.length < + tokens.length + ? currentCounter - contacts.length - groups.length - nfts.length + : null; + + if (selectedContactIndex || selectedContactIndex === 0) { + setSelectedContact(contacts[selectedContactIndex]); + return; + } + if (selectedGroupChatIndex || selectedGroupChatIndex === 0) { + const group = groups[selectedGroupChatIndex]; + push({ + title: group?.name, + componentId: NAV_COMPONENT_MESSAGE_GROUP_CHAT, + componentProps: { + fromInbox: true, + id: group.collectionId, + title: group?.name, + }, + }); + setOpen(false); + return; + } + if (selectedNFtChatIndex || selectedNFtChatIndex === 0) { + const nft = nfts[selectedNFtChatIndex]; + push({ + title: nft?.name, + componentId: NAV_COMPONENT_NFT_DETAIL, + componentProps: { + nftId: nft.id, + publicKey: activeWallet.publicKey, + connectionUrl, + }, + }); + setOpen(false); + return; + } + if (selectedTokenIndex || selectedTokenIndex === 0) { + const token = tokens[selectedTokenIndex]; + push({ + title: `${toTitleCase(Blockchain.SOLANA)} / ${token.name}`, + componentId: NAV_COMPONENT_TOKEN, + componentProps: { + blockchain: "solana", + tokenAddress: token.address, + publicKey: activeWallet.publicKey, + }, + }); + setOpen(false); + return; + } + } + }} + > + + +
+ ); +} diff --git a/packages/app-extension/src/spotlight/SpotlightContacts.tsx b/packages/app-extension/src/spotlight/SpotlightContacts.tsx new file mode 100644 index 000000000..15362f235 --- /dev/null +++ b/packages/app-extension/src/spotlight/SpotlightContacts.tsx @@ -0,0 +1,75 @@ +import { UserIcon } from "@coral-xyz/react-common"; +import { useCustomTheme } from "@coral-xyz/themes"; + +import { SELECTED_BLUE } from "./colors"; +import { GroupIdentifier } from "./GroupIdentifier"; + +export const SpotlightContacts = ({ + contacts, + selectedIndex, + setSelectedContact, +}: { + selectedIndex: number | null; + contacts: { + username: string; + image: string; + uuid: string; + }[]; + setSelectedContact: any; +}) => { + if (!contacts.length) return null; + return ( +
+ + {contacts.map((contact, index) => ( + + ))} +
+ ); +}; + +export function SpotlightContact({ + contact, + selected, + setSelectedContact, +}: { + contact: { + username: string; + image: string; + uuid: string; + }; + selected: boolean; + setSelectedContact: any; +}) { + const theme = useCustomTheme(); + return ( +
{ + setSelectedContact(contact); + }} + > + +
+ {contact.username} +
+
+ ); +} diff --git a/packages/app-extension/src/spotlight/SpotlightGroups.tsx b/packages/app-extension/src/spotlight/SpotlightGroups.tsx new file mode 100644 index 000000000..39021128f --- /dev/null +++ b/packages/app-extension/src/spotlight/SpotlightGroups.tsx @@ -0,0 +1,84 @@ +import { NAV_COMPONENT_MESSAGE_GROUP_CHAT } from "@coral-xyz/common"; +import { UserIcon } from "@coral-xyz/react-common"; +import { useNavigation } from "@coral-xyz/recoil"; +import { useCustomTheme } from "@coral-xyz/themes"; + +import { SELECTED_BLUE } from "./colors"; +import { GroupIdentifier } from "./GroupIdentifier"; + +export const SpotlightGroups = ({ + groups, + selectedIndex, + setOpen, +}: { + groups: { + name: string; + image: string; + collectionId: string; + }[]; + selectedIndex: number | null; + setOpen: any; +}) => { + if (!groups.length) return null; + return ( +
+ + {groups.map((group, index) => ( + + ))} +
+ ); +}; + +function SpotlightGroup({ + group, + selected, + setOpen, +}: { + group: { name: string; image: string; collectionId: string }; + selected: boolean; + setOpen: any; +}) { + const theme = useCustomTheme(); + const { push, toRoot } = useNavigation(); + + return ( +
{ + push({ + title: group?.name, + componentId: NAV_COMPONENT_MESSAGE_GROUP_CHAT, + componentProps: { + fromInbox: true, + id: group.collectionId, + title: group?.name, + }, + }); + setOpen(false); + }} + > + +
+ {group.name} +
+
+ ); +} diff --git a/packages/app-extension/src/spotlight/SpotlightNfts.tsx b/packages/app-extension/src/spotlight/SpotlightNfts.tsx new file mode 100644 index 000000000..6fc9a64f8 --- /dev/null +++ b/packages/app-extension/src/spotlight/SpotlightNfts.tsx @@ -0,0 +1,86 @@ +import { NAV_COMPONENT_NFT_DETAIL } from "@coral-xyz/common"; +import { UserIcon } from "@coral-xyz/react-common"; +import { + useActiveWallet, + useBlockchainConnectionUrl, + useNavigation, +} from "@coral-xyz/recoil"; +import { useCustomTheme } from "@coral-xyz/themes"; + +import { SELECTED_BLUE } from "./colors"; +import { GroupIdentifier } from "./GroupIdentifier"; + +export const SpotlightNfts = ({ + nfts, + selectedIndex, + setOpen, +}: { + nfts: { name: string; image: string; id: string }[]; + selectedIndex: number | null; + setOpen: any; +}) => { + if (!nfts.length) return null; + return ( +
+ + {nfts.map((nft, index) => ( + + ))} +
+ ); +}; + +function SpotlightNft({ + nft, + selected, + setOpen, +}: { + nft: { name: string; image: string; id: string }; + selected: boolean; + setOpen: any; +}) { + const activeWallet = useActiveWallet(); + const connectionUrl = useBlockchainConnectionUrl(activeWallet.blockchain); + const { push } = useNavigation(); + + const theme = useCustomTheme(); + return ( +
{ + push({ + title: nft?.name, + componentId: NAV_COMPONENT_NFT_DETAIL, + componentProps: { + nftId: nft.id, + publicKey: activeWallet.publicKey, + connectionUrl, + }, + }); + setOpen(false); + }} + > + +
+ {nft.name} +
+
+ ); +} diff --git a/packages/app-extension/src/spotlight/SpotlightTokens.tsx b/packages/app-extension/src/spotlight/SpotlightTokens.tsx new file mode 100644 index 000000000..be7e84e92 --- /dev/null +++ b/packages/app-extension/src/spotlight/SpotlightTokens.tsx @@ -0,0 +1,85 @@ +import { + Blockchain, + NAV_COMPONENT_TOKEN, + toTitleCase, +} from "@coral-xyz/common"; +import { UserIcon } from "@coral-xyz/react-common"; +import { useActiveWallet, useNavigation } from "@coral-xyz/recoil"; +import { useCustomTheme } from "@coral-xyz/themes"; + +import { SELECTED_BLUE } from "./colors"; +import { GroupIdentifier } from "./GroupIdentifier"; + +export const SpotlightTokens = ({ + selectedIndex, + tokens, + setOpen, +}: { + selectedIndex: number | null; + tokens: { image: string; id: string; name: string; address: string }[]; + setOpen: any; +}) => { + if (!tokens.length) return null; + return ( +
+ + {tokens.map((token, index) => ( + + ))} +
+ ); +}; + +function SpotlightToken({ + selected, + token, + setOpen, +}: { + selected: boolean; + token: { image: string; id: string; name: string; address: string }; + setOpen: any; +}) { + const theme = useCustomTheme(); + const { push } = useNavigation(); + const activeWallet = useActiveWallet(); + + return ( +
{ + push({ + title: `${toTitleCase(Blockchain.SOLANA)} / ${token.name}`, + componentId: NAV_COMPONENT_TOKEN, + componentProps: { + blockchain: "solana", + tokenAddress: token.address, + publicKey: activeWallet.publicKey, + }, + }); + setOpen(false); + }} + > + +
+ {token.name} +
+
+ ); +} diff --git a/packages/app-extension/src/spotlight/colors.ts b/packages/app-extension/src/spotlight/colors.ts new file mode 100644 index 000000000..2cb03239b --- /dev/null +++ b/packages/app-extension/src/spotlight/colors.ts @@ -0,0 +1 @@ +export const SELECTED_BLUE = "rgba(76, 148, 255, 0.65)"; diff --git a/packages/app-extension/src/spotlight/styles.ts b/packages/app-extension/src/spotlight/styles.ts new file mode 100644 index 000000000..4df59436c --- /dev/null +++ b/packages/app-extension/src/spotlight/styles.ts @@ -0,0 +1,117 @@ +import { styles } from "@coral-xyz/themes"; + +export const useStyles = styles((theme) => ({ + searchField: { + marginTop: "16px", + marginBottom: "16px", + width: "inherit", + display: "flex", + "& .MuiOutlinedInput-root": { + "& input": { + paddingTop: 0, + paddingBottom: 0, + }, + }, + }, + icon: { + background: theme.custom.colors.textBackground, + }, + iconInner: { + background: theme.custom.colors.fontColor, + }, + topImage: { + maxWidth: "25vw", + }, + horizontalCenter: { + display: "flex", + justifyContent: "center", + }, + container: { + marginLeft: "16px", + marginRight: "16px", + }, + roundBtn: { + marginLeft: 8, + borderRadius: 20, + width: 20, + height: 20, + cursor: "pointer", + background: "#FFFFFF", + border: "2px solid #F0F0F2", + fontSize: 10, + }, + add: { + width: 17, + paddingBottom: 6, + paddingRight: 1, + }, + iconCircular: { + width: "32px", + height: "32px", + borderRadius: "16px", + marginRight: "8px", + color: theme.custom.colors.positive, + }, + iconCircularBig: { + width: "40px", + height: "40px", + borderRadius: "16px", + marginRight: "8px", + color: theme.custom.colors.positive, + }, + hoverParent: { + "&:hover $hoverChild, & .Mui-focused $hoverChild": { + visibility: "visible", + }, + }, + hoverChild: { + visibility: "hidden", + }, + text: { + color: theme.custom.colors.fontColor2, + }, + smallText: { + fontSize: 12, + color: theme.custom.colors.fontColor2, + }, + userText: { + fontSize: 16, + marginTop: 4, + color: theme.custom.colors.fontColor2, + }, + userTextSmall: { + fontSize: 14, + color: theme.custom.colors.fontColor2, + }, + timestamp: { + fontSize: 14, + minWidth: 60, + color: theme.custom.colors.fontColor2, + }, + smallTitle: { + color: theme.custom.colors.smallTextColor, + fontWeight: 600, + }, + smallSubTitle: { + color: theme.custom.colors.smallTextColor, + fontWeight: 500, + }, + contactIconOuter: { + background: theme.custom.colors.textBorder, + }, + menuItem: { + fontWeight: 400, + fontSize: 14, + color: theme.custom.colors.fontColor, + padding: "12px 16px", + }, + menu: { + "& .MuiList-root": { + padding: 0, + }, + paddingTop: 0, + paddingBottom: 0, + minWidth: 184, + color: theme.custom.colors.fontColor, + }, +})); diff --git a/packages/app-extension/src/spotlight/useSearchedContacts.tsx b/packages/app-extension/src/spotlight/useSearchedContacts.tsx new file mode 100644 index 000000000..d566cef4c --- /dev/null +++ b/packages/app-extension/src/spotlight/useSearchedContacts.tsx @@ -0,0 +1,17 @@ +import { useContacts } from "@coral-xyz/db"; +import { useUser } from "@coral-xyz/recoil"; + +export const useSearchedContacts = (searchFilter: string) => { + const { uuid } = useUser(); + const contacts = useContacts(uuid); + + return contacts + .filter((x) => + x.remoteUsername?.toLowerCase().includes(searchFilter?.toLowerCase()) + ) + .map((x) => ({ + username: x.remoteUsername, + image: x.remoteUserImage, + uuid: x.remoteUserId, + })); +}; diff --git a/packages/app-extension/src/spotlight/useSearchedGroups.tsx b/packages/app-extension/src/spotlight/useSearchedGroups.tsx new file mode 100644 index 000000000..3981b7ff5 --- /dev/null +++ b/packages/app-extension/src/spotlight/useSearchedGroups.tsx @@ -0,0 +1,14 @@ +import { useGroupCollections, useUser } from "@coral-xyz/recoil"; + +export const useSearchedGroupsCollections = (searchFilter: string) => { + const { uuid } = useUser(); + const collections = useGroupCollections({ uuid }); + + return collections + .filter((x) => x.name?.toLowerCase()?.includes(searchFilter.toLowerCase())) + .map((x) => ({ + name: x.name || "", + image: x.image || "", + collectionId: x.collectionId || "", + })); +}; diff --git a/packages/app-extension/src/spotlight/useSearchedNfts.tsx b/packages/app-extension/src/spotlight/useSearchedNfts.tsx new file mode 100644 index 000000000..a9ea74654 --- /dev/null +++ b/packages/app-extension/src/spotlight/useSearchedNfts.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import type { Nft } from "@coral-xyz/common"; +import { Blockchain } from "@coral-xyz/common"; +import { nftsByOwner, useActiveSolanaWallet } from "@coral-xyz/recoil"; +import { useRecoilValueLoadable } from "recoil"; + +export const useSearchedNfts = (searchFilter: string) => { + const activeSolWallet = useActiveSolanaWallet(); + const { contents, state }: any = useRecoilValueLoadable( + nftsByOwner({ + publicKey: activeSolWallet.publicKey, + blockchain: Blockchain.SOLANA, + }) + ); + + if (state === "loading" || state === "hasError") { + return []; + } + + return contents + .filter((x: Nft) => + x.name?.toLowerCase()?.includes(searchFilter.toLowerCase()) + ) + .map((x: Nft) => ({ + name: x.name || "", + image: x.imageUrl || "", + id: x.id || "", + })); +}; diff --git a/packages/app-extension/src/spotlight/useSearchedTokens.tsx b/packages/app-extension/src/spotlight/useSearchedTokens.tsx new file mode 100644 index 000000000..cb3178129 --- /dev/null +++ b/packages/app-extension/src/spotlight/useSearchedTokens.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { Blockchain } from "@coral-xyz/common"; +import { + blockchainBalancesSorted, + useActiveSolanaWallet, + useLoader, +} from "@coral-xyz/recoil"; + +export const useSearchedTokens = (searchFilter: string) => { + const activeSolWallet = useActiveSolanaWallet(); + + const [tokenAccounts, , isLoading] = useLoader( + blockchainBalancesSorted({ + publicKey: activeSolWallet.publicKey, + blockchain: Blockchain.SOLANA, + }), + [], + [activeSolWallet] + ); + + if (isLoading) { + //TODO: adda skeletons here + return []; + } + + return tokenAccounts + .filter((x) => x.name.toLowerCase().includes(searchFilter.toLowerCase())) + .map((x) => ({ + name: x.name || "", + id: x.mint || "", + image: x.logo || "", + address: x.address || "", + })); +}; diff --git a/packages/app-extension/src/spotlight/utils.ts b/packages/app-extension/src/spotlight/utils.ts new file mode 100644 index 000000000..999e9afd8 --- /dev/null +++ b/packages/app-extension/src/spotlight/utils.ts @@ -0,0 +1,10 @@ +export const getCurrentCounter = ( + arrowIndex: number, + allResultsLength: number +) => { + return arrowIndex >= 0 + ? arrowIndex % allResultsLength + : (arrowIndex + + -1 * Math.ceil(arrowIndex / allResultsLength) * allResultsLength) % + allResultsLength; +};