diff --git a/apps/web/components/Organization/SearchBox/SearchBox.tsx b/apps/web/components/Organization/SearchBox/SearchBox.tsx index 5daaddc68d8a..6b2813da2ae1 100644 --- a/apps/web/components/Organization/SearchBox/SearchBox.tsx +++ b/apps/web/components/Organization/SearchBox/SearchBox.tsx @@ -1,12 +1,12 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { useEffect, useState } from 'react' import { - AsyncSearchInput, Box, - Button, + AsyncSearch, Text, - Link, + Button, + AsyncSearchOption, } from '@island.is/island-ui/core' -import { useQuery } from '@apollo/client' +import { useLazyQuery } from '@apollo/client' import { Query, QueryGetArticlesArgs, @@ -15,6 +15,11 @@ import { import { GET_ORGANIZATION_SERVICES_QUERY } from '@island.is/web/screens/queries' import { LinkType, useLinkResolver } from '@island.is/web/hooks/useLinkResolver' import { useRouter } from 'next/router' +import { useDebounce } from 'react-use' + +interface AsyncSearchOptionWithIsArticleField extends AsyncSearchOption { + isArticle: boolean +} interface SearchBoxProps { organizationPage: Query['getOrganizationPage'] @@ -30,135 +35,206 @@ export const SearchBox = ({ searchAllText, }: SearchBoxProps) => { const { linkResolver } = useLinkResolver() - const Router = useRouter() + const router = useRouter() + + const [value, setValue] = useState('') + const [options, setOptions] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [waitingForNextPageToLoad, setWaitingForNextPageToLoad] = useState( + false, + ) - const { data, loading } = useQuery( + const [fetch, { data, loading }] = useLazyQuery( GET_ORGANIZATION_SERVICES_QUERY, - { - variables: { - input: { - lang: 'is', - organization: organizationPage.slug, - size: 500, - sort: SortField.Popular, - }, - }, - }, ) - const items = useMemo( - () => - data?.getArticles - .map((o) => ({ - type: 'article', - label: o.title, - value: o.slug, - })) - .concat( - ...organizationPage.menuLinks.map((x) => [ - { - type: 'url', - label: x.primaryLink.text, - value: x.primaryLink.url, + useDebounce( + () => { + if (value) { + fetch({ + variables: { + input: { + lang: router.asPath.includes('/en/') ? 'en' : 'is', + organization: organizationPage.slug, + size: 500, + sort: SortField.Popular, }, - ...x.childrenLinks.map((y) => ({ - type: 'url', - label: y.text, - value: y.url, - })), - ]), - ) ?? [], - [data], + }, + }) + } + setIsLoading(false) + }, + 300, + [value], ) - const [value, setValue] = useState('') - const [options, setOptions] = useState([]) + const items = data?.getArticles?.map((item, index) => ({ + label: item.title, + value: item.slug, + isArticle: true, + component: ({ active }) => { + return ( + { + setOptions([]) + }} + > + {item.title} + + ) + }, + })) + + const clearAll = () => { + setIsLoading(false) + setOptions([]) + } + + const handleOptionSelect = ( + selectedItem: AsyncSearchOptionWithIsArticleField, + value: string, + ) => { + setOptions([]) + if (!value) return + setWaitingForNextPageToLoad(true) - const [hasFocus, setHasFocus] = useState(false) + let pathname: string + let searchAll = false - useEffect(() => { + if (selectedItem && selectedItem?.isArticle) { + const newValue = selectedItem.value + pathname = linkResolver('Article' as LinkType, [newValue]).href + setValue(selectedItem.label) + } else { + pathname = linkResolver('search').href + searchAll = true + } + + router + .push({ + pathname, + query: searchAll ? { q: value } : {}, + }) + .then(() => window.scrollTo(0, 0)) + } + + const updateOptions = () => { const newOpts = items - .filter( - (item) => - value && item.label.toLowerCase().includes(value.toLowerCase()), - ) - .slice(0, 5) + ? items + .filter( + (item) => + value && item.label.toLowerCase().includes(value.toLowerCase()), + ) + .slice(0, 5) + : [] if (!value) { - setOptions([]) + clearAll() } - setOptions(newOpts) - }, [value]) - - const onBlur = () => { - setTimeout(() => { - setHasFocus(false) - }, 100) - } - return ( - - setHasFocus(true), - onBlur, - placeholder, - value, - onChange: (e) => setValue(e.target.value), - }} - > - {!!value && ( - <> - e.stopPropagation()}> - {options?.map((x) => ( - - - - {x.label} - - - - ))} - {!options?.length && ( - - {noResultsText} - - )} - + setOptions( + newOpts.length + ? newOpts.concat({ + label: value, + value: '', + isArticle: false, + component: ({ active }) => ( + - - - )} - + ), + }) + : [ + { + label: value, + value: '', + isArticle: false, + component: () => ( + + {noResultsText} + + + + + ), + }, + ], + ) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(updateOptions, [value, loading, data]) + + const busy = loading || isLoading || waitingForNextPageToLoad + + return ( + + { + setIsLoading(newValue !== value) + setValue(newValue ?? '') + }} + closeMenuOnSubmit + onSubmit={(value, selectedOption) => { + handleOptionSelect( + selectedOption as AsyncSearchOptionWithIsArticleField, + value, + ) + }} + onChange={(i, option) => { + handleOptionSelect( + option.selectedItem as AsyncSearchOptionWithIsArticleField, + value, + ) + }} + /> ) }