From 2c80dca23c5156d5ab096d7d06bb6da681daddff Mon Sep 17 00:00:00 2001 From: Sam Kuhlmann Date: Tue, 12 Jul 2022 12:00:10 -0600 Subject: [PATCH] adds search (#401) * adds search wip * correctly types the onChange for the input * cleans up unsed imports * naming * Feature/public profile (#384) * Add some stuff * Add public profile * Add ens * Get build to pass * Update apps/hub-app/src/pages/PublicProfilePage.tsx Co-authored-by: Rowdy Co-authored-by: Sam Kuhlmann Co-authored-by: Rowdy * fixing doc display name from <[object Object]> to displayName (#402) Co-authored-by: Sam Kuhlmann * conflicts * correctly types the onChange for the input * cleans up unsed imports * naming * fix build error Co-authored-by: Alexander Keating Co-authored-by: Rowdy Co-authored-by: Brian Rossetti --- apps/hub-app/src/Routes.tsx | 9 +-- apps/hub-app/src/components/BodyNav.tsx | 5 -- apps/hub-app/src/components/HomeDashboard.tsx | 13 +++- apps/hub-app/src/components/Profile.tsx | 4 +- apps/hub-app/src/components/SearchInput.tsx | 43 +++++++++++++ apps/hub-app/src/components/SortDropdown.tsx | 4 +- apps/hub-app/src/components/TableControl.tsx | 39 +++-------- apps/hub-app/src/hooks/useDaoData.tsx | 1 + apps/hub-app/src/pages/HomePage.tsx | 64 ++++++++++++++++--- apps/hub-app/src/pages/PublicProfilePage.tsx | 2 +- apps/hub-app/src/utils/debounceHook.ts | 17 +++++ libs/ui/src/components/atoms/Input/Input.tsx | 1 + 12 files changed, 146 insertions(+), 56 deletions(-) create mode 100644 apps/hub-app/src/components/SearchInput.tsx create mode 100644 apps/hub-app/src/utils/debounceHook.ts diff --git a/apps/hub-app/src/Routes.tsx b/apps/hub-app/src/Routes.tsx index 0c5e2c510..a1a6ba5f5 100644 --- a/apps/hub-app/src/Routes.tsx +++ b/apps/hub-app/src/Routes.tsx @@ -7,13 +7,8 @@ import PublicProfilePage from './pages/PublicProfilePage'; const Routes = () => { return ( - - } /> - } /> - - } /> - - + } /> + } /> ); }; diff --git a/apps/hub-app/src/components/BodyNav.tsx b/apps/hub-app/src/components/BodyNav.tsx index 92a83b13b..159bbe6fb 100644 --- a/apps/hub-app/src/components/BodyNav.tsx +++ b/apps/hub-app/src/components/BodyNav.tsx @@ -79,17 +79,12 @@ const NavLink = ({ children, path, selected }: NavLinkProps) => { export const BodyNav = () => { const match = useMatch('/explore'); const isHome = !match; - const isExplore = !!match; return ( Home - - - Explore - ); }; diff --git a/apps/hub-app/src/components/HomeDashboard.tsx b/apps/hub-app/src/components/HomeDashboard.tsx index 9b215bef3..f88f7166c 100644 --- a/apps/hub-app/src/components/HomeDashboard.tsx +++ b/apps/hub-app/src/components/HomeDashboard.tsx @@ -1,11 +1,12 @@ +import { MouseEvent, useState, ChangeEvent } from 'react'; import { ITransformedMembership } from '@daohaus/dao-data'; import { ParMd, Spinner, useBreakpoint, widthQuery } from '@daohaus/ui'; -import { MouseEvent, useState } from 'react'; import styled from 'styled-components'; -import { ListType } from '../utils/appSpecificTypes'; + import { DaoCards } from './DaoCards'; import { DaoTable } from './DaoTable'; import TableControl from './TableControl'; +import { ListType } from '../utils/appSpecificTypes'; // Refactored this to be a component that we might be able to reuse // for explore view and other similar views. @@ -22,6 +23,8 @@ type DashProps = { toggleDelegateFilter: (event: MouseEvent) => void; sortBy: string; toggleSortBy: (event: MouseEvent) => void; + searchTerm: string; + setSearchTerm: (event: ChangeEvent) => void; loading: boolean; }; @@ -33,6 +36,8 @@ export const HomeDashboard = ({ toggleDelegateFilter, sortBy, toggleSortBy, + searchTerm, + setSearchTerm, loading, }: DashProps) => { const [listType, setListType] = useState('cards'); @@ -76,6 +81,8 @@ export const HomeDashboard = ({ toggleDelegateFilter={toggleDelegateFilter} sortBy={sortBy} toggleSortBy={toggleSortBy} + searchTerm={searchTerm} + setSearchTerm={setSearchTerm} /> @@ -93,6 +100,8 @@ export const HomeDashboard = ({ toggleDelegateFilter={toggleDelegateFilter} sortBy={sortBy} toggleSortBy={toggleSortBy} + searchTerm={searchTerm} + setSearchTerm={setSearchTerm} /> {isMobile ? ( diff --git a/apps/hub-app/src/components/Profile.tsx b/apps/hub-app/src/components/Profile.tsx index e09d9659a..5bcb73d0e 100644 --- a/apps/hub-app/src/components/Profile.tsx +++ b/apps/hub-app/src/components/Profile.tsx @@ -88,7 +88,7 @@ const StyledLink = styled(Link)` `; export const HeaderProfile = () => { - const { profile } = useHausConnect(); + const { profile, address } = useHausConnect(); return ( @@ -117,7 +117,7 @@ export const HeaderProfile = () => { { type: 'clickable', content: ( - + View Public ), diff --git a/apps/hub-app/src/components/SearchInput.tsx b/apps/hub-app/src/components/SearchInput.tsx new file mode 100644 index 000000000..3f98db201 --- /dev/null +++ b/apps/hub-app/src/components/SearchInput.tsx @@ -0,0 +1,43 @@ +import { ChangeEvent } from 'react'; +import styled from 'styled-components'; +import { indigoDark } from '@radix-ui/colors'; +import { BiSearch } from 'react-icons/bi'; +import { Input } from '@daohaus/ui'; + +const StyledInput = styled(Input)` + background: ${indigoDark.indigo3}; + color: ${indigoDark.indigo11}; + ::placeholder { + color: ${indigoDark.indigo11}; + } + :focus { + background: ${indigoDark.indigo3}; + color: ${indigoDark.indigo11}; + } +`; + +const IconSearch = styled(BiSearch)` + fill: ${indigoDark.indigo11}; + :hover { + fill: ${indigoDark.indigo11}; + } +`; + +type SearchInputProps = { + searchTerm: string; + setSearchTerm: (event: ChangeEvent) => void; +}; + +const SearchInput = ({ searchTerm, setSearchTerm }: SearchInputProps) => { + return ( + + ); +}; + +export default SearchInput; diff --git a/apps/hub-app/src/components/SortDropdown.tsx b/apps/hub-app/src/components/SortDropdown.tsx index 8e752673f..4b2e5e6ee 100644 --- a/apps/hub-app/src/components/SortDropdown.tsx +++ b/apps/hub-app/src/components/SortDropdown.tsx @@ -20,12 +20,12 @@ const DropdownButton = styled(Button)` } `; -type FilterDropdownProps = { +type SortDropdownProps = { sortBy: string; toggleSortBy: (event: MouseEvent) => void; }; -const SortDropdown = ({ sortBy, toggleSortBy }: FilterDropdownProps) => { +const SortDropdown = ({ sortBy, toggleSortBy }: SortDropdownProps) => { const theme = useTheme(); return ( diff --git a/apps/hub-app/src/components/TableControl.tsx b/apps/hub-app/src/components/TableControl.tsx index a69f2a244..df6af742f 100644 --- a/apps/hub-app/src/components/TableControl.tsx +++ b/apps/hub-app/src/components/TableControl.tsx @@ -1,13 +1,13 @@ -import { Button, Input, useBreakpoint, widthQuery } from '@daohaus/ui'; +import { MouseEvent, ChangeEvent } from 'react'; +import { Button, useBreakpoint, widthQuery } from '@daohaus/ui'; import styled from 'styled-components'; -import { BiSearch } from 'react-icons/bi'; import { BsFillGrid3X3GapFill } from 'react-icons/bs'; - import { indigoDark } from '@radix-ui/colors'; -import { ListType } from '../utils/appSpecificTypes'; -import { MouseEvent } from 'react'; + import FilterDropdown from './FilterDropdown'; import SortDropdown from './SortDropdown'; +import { ListType } from '../utils/appSpecificTypes'; +import SearchInput from './SearchInput'; const IconGrid = styled(BsFillGrid3X3GapFill)` height: 1.8rem; @@ -19,13 +19,6 @@ const IconGrid = styled(BsFillGrid3X3GapFill)` } `; -const IconSearch = styled(BiSearch)` - fill: ${indigoDark.indigo11}; - :hover { - fill: ${indigoDark.indigo11}; - } -`; - const Layout = styled.div` display: flex; align-items: center; @@ -38,18 +31,6 @@ const Layout = styled.div` } `; -const StyledInput = styled(Input)` - background: ${indigoDark.indigo3}; - color: ${indigoDark.indigo11}; - ::placeholder { - color: ${indigoDark.indigo11}; - } - :focus { - background: ${indigoDark.indigo3}; - color: ${indigoDark.indigo11}; - } -`; - type TableControlProps = { listType: ListType; toggleListType: () => void; @@ -59,6 +40,8 @@ type TableControlProps = { toggleDelegateFilter: (event: MouseEvent) => void; sortBy: string; toggleSortBy: (event: MouseEvent) => void; + searchTerm: string; + setSearchTerm: (event: ChangeEvent) => void; }; const TableControl = ({ @@ -70,16 +53,14 @@ const TableControl = ({ toggleDelegateFilter, sortBy, toggleSortBy, + searchTerm, + setSearchTerm, }: TableControlProps) => { const isMobile = useBreakpoint(widthQuery.sm); return ( - + { return Haus.create(); diff --git a/apps/hub-app/src/pages/HomePage.tsx b/apps/hub-app/src/pages/HomePage.tsx index 4091bef28..46b8e9873 100644 --- a/apps/hub-app/src/pages/HomePage.tsx +++ b/apps/hub-app/src/pages/HomePage.tsx @@ -1,20 +1,25 @@ -import { MouseEvent, useState } from 'react'; - +import { MouseEvent, ChangeEvent, useEffect, useState } from 'react'; import { useHausConnect } from '@daohaus/daohaus-connect-feature'; +import { + isValidNetwork, + networkData, + ValidNetwork, +} from '@daohaus/common-utilities'; import { Layout, SideTopLeft, SideTopRight } from '../components/Layout'; import ProfileArea from '../components/ProfileArea'; import Header from '../components/Header'; import { HomeDashboard } from '../components/HomeDashboard'; -import useDaoData from '../hooks/useDaoData'; import { HomeNotConnected } from './HomeNotConnected'; -import { isValidNetwork, networkData } from '@daohaus/common-utilities'; -import { DEFAULT_SORT_KEY } from '../utils/constants'; +import { getDelegateFilter } from '../utils/queryHelpers'; +import { DEFAULT_SORT_KEY, SORT_FIELDS } from '../utils/constants'; +import useDebounce from '../utils/debounceHook'; +import { Haus, ITransformedMembership } from '@daohaus/dao-data'; const HomePage = () => { - const { isConnected } = useHausConnect(); - const { daoData, isLoadingDaoData } = useDaoData(); + const { isConnected, address } = useHausConnect(); + const [daoData, setDaoData] = useState([]); const [filterNetworks, setFilterNetworks] = useState>( Object.keys(networkData).reduce( (acc, networkId) => ({ ...acc, [networkId]: networkId }), @@ -23,6 +28,41 @@ const HomePage = () => { ); const [filterDelegate, setFilterDelegate] = useState(''); const [sortBy, setSortBy] = useState(DEFAULT_SORT_KEY); + const [searchTerm, setSearchTerm] = useState(''); + const [loading, setLoading] = useState(true); + + const debouncedSearchTerm = useDebounce(searchTerm, 1000); + + useEffect(() => { + const getDaos = async (address: string) => { + setLoading(true); + try { + const haus = Haus.create(); + + const query = await haus.profile.listDaosByMember({ + memberAddress: address, + networkIds: Object.keys(filterNetworks) as ValidNetwork[], + includeTokens: true, + daoFilter: { name_contains_nocase: debouncedSearchTerm }, + memberFilter: getDelegateFilter(filterDelegate, address), + ordering: SORT_FIELDS[sortBy].ordering, + }); + + if (query.data?.daos) { + setDaoData(query.data.daos); + } + } catch (error) { + error instanceof Error + ? console.error(error.message) + : console.error('Well, shit...'); + } finally { + setLoading(false); + } + }; + + if (!address) return; + getDaos(address); + }, [address, filterNetworks, filterDelegate, sortBy, debouncedSearchTerm]); const toggleNetworkFilter = (event: MouseEvent) => { const network = event.currentTarget.value; @@ -53,6 +93,12 @@ const HomePage = () => { ); }; + const handleSearchTermChange = (event: ChangeEvent) => { + setSearchTerm((prevState) => + prevState === event.target.value ? '' : event.target.value + ); + }; + return ( @@ -68,7 +114,9 @@ const HomePage = () => { toggleDelegateFilter={toggleDelegateFilter} sortBy={sortBy} toggleSortBy={toggleSortBy} - loading={isLoadingDaoData} + searchTerm={searchTerm} + setSearchTerm={handleSearchTermChange} + loading={loading} /> ) : ( diff --git a/apps/hub-app/src/pages/PublicProfilePage.tsx b/apps/hub-app/src/pages/PublicProfilePage.tsx index 60fd83a9e..444cacc7b 100644 --- a/apps/hub-app/src/pages/PublicProfilePage.tsx +++ b/apps/hub-app/src/pages/PublicProfilePage.tsx @@ -137,7 +137,7 @@ const PublicProfilePage = () => { ) : ( <> - + diff --git a/apps/hub-app/src/utils/debounceHook.ts b/apps/hub-app/src/utils/debounceHook.ts new file mode 100644 index 000000000..5106a6765 --- /dev/null +++ b/apps/hub-app/src/utils/debounceHook.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from 'react'; + +function useDebounce(value: T, delay?: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay || 500); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +} + +export default useDebounce; diff --git a/libs/ui/src/components/atoms/Input/Input.tsx b/libs/ui/src/components/atoms/Input/Input.tsx index d35804a08..da44a33dc 100644 --- a/libs/ui/src/components/atoms/Input/Input.tsx +++ b/libs/ui/src/components/atoms/Input/Input.tsx @@ -6,6 +6,7 @@ import React from 'react'; export type InputProps = Field & { icon?: IconType; + onChange?: React.ChangeEventHandler | undefined; className?: string; }; type Ref =