diff --git a/src/components/search/Modal/SearchModalContext.jsx b/src/components/search/Modal/SearchModalContext.jsx
new file mode 100644
index 000000000..abdbec55d
--- /dev/null
+++ b/src/components/search/Modal/SearchModalContext.jsx
@@ -0,0 +1,71 @@
+import React, { createContext, useState, useRef } from 'react';
+import scrollTo from 'gatsby-plugin-smoothscroll';
+import useEscape from '../../../hooks/useEscape';
+import useDisplay from '../../../hooks/useDisplay';
+
+/**
+ * A context to manage the state of the search modal.
+ */
+const SearchModalContext = createContext({});
+export const SearchModalContextProvider = SearchModalContext.Provider;
+export default SearchModalContext;
+
+/**
+ * A provider to manage the state of the search modal.
+ * @param {Object} props
+ * @param {React.ReactNode} props.children
+ */
+export function SearchModalProvider({ children }) {
+ const { showDesktop, showMobile } = useDisplay();
+ const [isOpen, setIsOpen] = useState(false);
+ const desktopButtonRef = useRef();
+ const mobileButtonRef = useRef();
+ const modalSearchInputRef = useRef();
+ const searchInputRef = useRef();
+
+ // Close handler.
+ const close = () => {
+ setIsOpen(false);
+ if (showDesktop) desktopButtonRef.current.focus();
+ if (showMobile) mobileButtonRef.current.focus();
+ };
+
+ // Open handler.
+ const open = () => {
+ // Don't open the modal if the user is already on the search page.
+ // Instead focus on the search input.
+ if (
+ window &&
+ window.location &&
+ window.location.pathname.startsWith('/search')
+ ) {
+ searchInputRef.current.focus();
+ scrollTo(`#${searchInputRef.current.id}`);
+ return;
+ }
+
+ setIsOpen(true);
+ };
+
+ // Close the modal when the escape key is pressed.
+ useEscape(() => {
+ if (isOpen) close();
+ });
+
+ // Provider wrapper.
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/search/SearchFacet.jsx b/src/components/search/SearchFacet.jsx
new file mode 100644
index 000000000..ac033553e
--- /dev/null
+++ b/src/components/search/SearchFacet.jsx
@@ -0,0 +1,87 @@
+import React, { useEffect } from 'react';
+import { useInstantSearch, useRefinementList } from 'react-instantsearch';
+import { Skeleton } from '@mui/material';
+import { dcnb } from 'cnbuilder';
+import { Heading } from '../simple/Heading';
+import { slugify } from '../../utilities/slugify';
+
+const SearchFacet = ({ className, attribute, label, excludes = [] }) => {
+ const { items, refine } = useRefinementList({ attribute, limit: 100 });
+ const { status } = useInstantSearch();
+ const isLoading = status === 'loading';
+ const isStalled = status === 'stalled';
+
+ // Filter out any items we don't want to show.
+ const filteredItems = items.filter((item) => !excludes.includes(item.value));
+ // Sort the items alphabetically.
+ filteredItems.sort((a, b) => a.value.localeCompare(b.value));
+
+ // Show loading.
+ if (isStalled) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ // Nothing to show.
+ if (!filteredItems.length) {
+ return null;
+ }
+
+ // Render stuff.
+ return (
+
+
+ {label}
+
+
+ {filteredItems.map((option, index) => (
+
+ ))}
+
+ );
+};
+
+export default SearchFacet;
diff --git a/src/components/search/SearchField.jsx b/src/components/search/SearchField.jsx
new file mode 100644
index 000000000..b1953094e
--- /dev/null
+++ b/src/components/search/SearchField.jsx
@@ -0,0 +1,216 @@
+import React, { useCallback, useContext, useEffect, useState } from 'react';
+import algoliasearch from 'algoliasearch/lite';
+import { Autocomplete, TextField } from '@mui/material';
+import { useInstantSearch, useSearchBox } from 'react-instantsearch';
+import { X, Search } from 'react-hero-icon/solid';
+import { useDebouncedValue } from '../../hooks/useDebouncedValue';
+import SearchModalContext from './Modal/SearchModalContext';
+
+/**
+ * @type {React.FC
}
+ * @returns {React.ReactElement}
+ */
+const SearchField = ({ emptySearchMessage }) => {
+ // Algolia Client.
+ const algoliaClient = algoliasearch(
+ process.env.GATSBY_ALGOLIA_APP_ID,
+ process.env.GATSBY_ALGOLIA_API_KEY
+ );
+ const indexName = 'federated-search-with-events'; // TODO: CHANGE THIS BACK BEFORE MERGING
+ const index = algoliaClient.initIndex(indexName);
+
+ // Hooks and state.
+ const { query, refine, clear } = useSearchBox();
+ const [value, setValue] = useState(query);
+ const [inputValue, setInputValue] = useState(query);
+ const [emptySearch, setEmptySearch] = useState(false);
+ const debouncedInputValue = useDebouncedValue(inputValue);
+ const [options, setOptions] = useState([]);
+ const { searchInputRef } = useContext(SearchModalContext);
+ const { indexUiState } = useInstantSearch();
+
+ // Debounce the input value and fetch options.
+ // -------------------------------------------
+ useEffect(() => {
+ const fetchOptions = async () => {
+ if (!debouncedInputValue) {
+ setOptions([]);
+ return;
+ }
+
+ // Add the filters to the autocomplete as well.
+ const siteRefinements = indexUiState?.refinementList?.siteName || [];
+ const typeRefinements = indexUiState?.refinementList?.fileType || [];
+ const namedSiteRefinements = siteRefinements.map(
+ (site) => `siteName:${site}`
+ );
+ const namedTypeRefinements = typeRefinements.map(
+ (type) => `fileType:${type}`
+ );
+ const facetFilters = [namedSiteRefinements, namedTypeRefinements];
+
+ try {
+ const res = await index.search(debouncedInputValue, {
+ attributesToRetrieve: ['title'],
+ hitsPerPage: 10,
+ facetFilters,
+ });
+ const newOptions = res.hits.map((hit) => hit.title);
+
+ if (!newOptions?.length && !options?.length) {
+ setOptions([]);
+ return;
+ }
+
+ setOptions(newOptions);
+ } catch (err) {
+ setOptions([]);
+ }
+ };
+
+ fetchOptions();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [debouncedInputValue, indexUiState]);
+
+ // Handle input change as the user types
+ // --------------------------------------
+ const handleInputChange = useCallback(
+ (v) => {
+ setInputValue(v);
+ },
+ [setInputValue]
+ );
+
+ // Handle value change when the user selects an option.
+ // ----------------------------------------------------
+ const handleValueChange = useCallback(
+ (v) => {
+ setValue(v);
+ refine(v);
+ if (v.length > 0) {
+ setEmptySearch(false);
+ }
+ },
+ [setValue, refine, setEmptySearch]
+ );
+
+ // Handle form submission.
+ // -----------------------
+ const handleSubmit = useCallback(
+ (e) => {
+ e.preventDefault();
+ handleValueChange(inputValue, refine, setEmptySearch);
+ },
+ [setEmptySearch, handleValueChange, refine, inputValue]
+ );
+
+ // Handle clearing the search field.
+ // ---------------------------------
+ const handleClear = useCallback(() => {
+ setInputValue('');
+ setValue('');
+ clear();
+ searchInputRef.current.focus();
+ }, [setInputValue, setValue, clear, searchInputRef]);
+
+ // The search field component.
+ // ---------------------------
+ return (
+ <>
+
+ {emptySearch && emptySearchMessage && (
+
+ {emptySearchMessage}
+
+ )}
+ >
+ );
+};
+
+export default SearchField;
diff --git a/src/components/search/searchKeywordBanner.js b/src/components/search/SearchKeywordBanner.jsx
similarity index 87%
rename from src/components/search/searchKeywordBanner.js
rename to src/components/search/SearchKeywordBanner.jsx
index e90494134..f50a61313 100644
--- a/src/components/search/searchKeywordBanner.js
+++ b/src/components/search/SearchKeywordBanner.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import { useStaticQuery, graphql } from 'gatsby';
+import { useSearchBox } from 'react-instantsearch';
import CreateBloks from '../../utilities/createBloks';
// Get most recently created Banner.
@@ -21,7 +22,9 @@ const getBanner = (data, q) => {
return max && created[max] ? created[max].content : '';
};
-const SearchKeywordBanner = function ({ queryText }) {
+const SearchKeywordBanner = function () {
+ const { query } = useSearchBox();
+
// Get Search Keyword Banners.
const data = useStaticQuery(graphql`
query searchKeywordBanners {
@@ -42,7 +45,7 @@ const SearchKeywordBanner = function ({ queryText }) {
return null;
}
- const banner = getBanner(data.allStoryblokEntry, queryText);
+ const banner = getBanner(data.allStoryblokEntry, query);
if (banner) {
return (
diff --git a/src/components/search/SearchNoResults.jsx b/src/components/search/SearchNoResults.jsx
new file mode 100644
index 000000000..e1b3b71bb
--- /dev/null
+++ b/src/components/search/SearchNoResults.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { useSearchBox } from 'react-instantsearch';
+import { Heading } from '../simple/Heading';
+import CreateBloks from '../../utilities/createBloks';
+
+const SearchNoResults = ({ heading, body, additionalContent }) => {
+ const { query } = useSearchBox();
+ const parsedHeading = heading.replace('[query]', query);
+ return (
+
+
+ {parsedHeading}
+
+
{body}
+
+
+
+
+ );
+};
+
+export default SearchNoResults;
diff --git a/src/components/search/SearchPageContent.jsx b/src/components/search/SearchPageContent.jsx
new file mode 100644
index 000000000..67728acb8
--- /dev/null
+++ b/src/components/search/SearchPageContent.jsx
@@ -0,0 +1,266 @@
+import React, { useMemo, useState } from 'react';
+import {
+ useStats,
+ useInstantSearch,
+ useClearRefinements,
+ ClearRefinements,
+} from 'react-instantsearch';
+import SbEditable from 'storyblok-react';
+import { cnb, dcnb } from 'cnbuilder';
+import Icon from 'react-hero-icon';
+import scrollTo from 'gatsby-plugin-smoothscroll';
+import { Container } from '../layout/Container';
+import Layout from '../partials/Layout';
+import { Heading } from '../simple/Heading';
+import { Grid } from '../layout/Grid';
+import { GridCell } from '../layout/GridCell';
+import SearchField from './SearchField';
+import getNumBloks from '../../utilities/getNumBloks';
+import CreateBloks from '../../utilities/createBloks';
+import { Skiplink } from '../accessibility/Skiplink';
+import SearchFacet from './SearchFacet';
+import SearchResults from './SearchResults';
+import SearchPager from './SearchPager';
+import SearchNoResults from './SearchNoResults';
+import SearchKeywordBanner from './SearchKeywordBanner';
+import { SAAButton } from '../simple/SAAButton';
+
+/**
+ * Content Block.
+ *
+ * @param {*} props
+ * @returns
+ */
+const SearchPageContent = (props) => {
+ const { blok } = props;
+ const { nbHits, areHitsSorted, nbSortedHits } = useStats();
+ const {
+ status,
+ results: { __isArtificial: isArtificial },
+ } = useInstantSearch();
+ const [opened, setOpened] = useState(false);
+ const { refine: clearFilters } = useClearRefinements();
+ const { maxPagerLinks, maxPagerLinksMobile } = blok;
+ const maxPagerLinksInt = parseInt(maxPagerLinks, 10);
+ const maxPagerLinksMobileInt = parseInt(maxPagerLinksMobile, 10);
+
+ const resultCount = useMemo(() => {
+ if (areHitsSorted) {
+ return nbSortedHits;
+ }
+ return nbHits;
+ }, [areHitsSorted, nbHits, nbSortedHits]);
+
+ const isLoading = status === 'loading' && !isArtificial;
+ const hasNoResults = resultCount === 0 && !isArtificial;
+
+ return (
+
+
+
+
+ {blok.pageTitle}
+
+
+
+
+ 0 ? 6 : 8}
+ className={
+ resultCount > 0 ? 'lg:su-col-start-4' : 'lg:su-col-start-3'
+ }
+ >
+
+
+
+
+ {/* MOBILE FILTERS */}
+
+
+
+
+
+
+
+
+
+
+
+ {
+ setOpened(false);
+ }}
+ >
+ View results
+
+
+
+
+
+ {/* END MOBILE FILTERS */}
+
+ {getNumBloks(blok.aboveResultsContent) > 0 && (
+
+
+
+ )}
+
+
+ {/* DESKTOP FILTERS */}
+
+
+ Skip past filters to search results
+
+
+
+ {/* END DESKTOP FILTERS */}
+
+
+
+
+
+
+
+
+ {hasNoResults && (
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default SearchPageContent;
diff --git a/src/components/search/SearchPager.jsx b/src/components/search/SearchPager.jsx
new file mode 100644
index 000000000..2eec2ca22
--- /dev/null
+++ b/src/components/search/SearchPager.jsx
@@ -0,0 +1,147 @@
+import React, { useMemo } from 'react';
+import { dcnb } from 'cnbuilder';
+import { usePagination } from 'react-instantsearch';
+import scrollTo from 'gatsby-plugin-smoothscroll';
+import useDisplay from '../../hooks/useDisplay';
+
+/**
+ * @type {React.FC
}
+ * @returns {React.ReactElement}
+ */
+const SearchPager = ({ maxDesktop = 6, maxMobile = 2 }) => {
+ const { showMobile } = useDisplay();
+
+ const {
+ pages,
+ currentRefinement,
+ isFirstPage,
+ isLastPage,
+ canRefine,
+ refine,
+ createURL,
+ } = usePagination({ padding: maxDesktop }); // Assuming maxDesktop will be the larger of the two.
+
+ /**
+ * We need to calculate the pages to show.
+ * This is needed because changing the padding is causing the UI to 'search' and lose the current page.
+ */
+ const renderPages = useMemo(() => {
+ const padding = showMobile ? maxMobile : maxDesktop;
+ const half = Math.floor(padding / 2);
+ const maxPage = pages[pages.length - 1];
+ const minPage = pages[0];
+ let min = Math.max(minPage, currentRefinement - half);
+ const max = Math.min(maxPage, currentRefinement + half);
+
+ if (max === maxPage) {
+ const diff = (maxPage - currentRefinement - half) * -1;
+ min = Math.max(0, min - diff);
+ }
+
+ // create an empty array equal to the number plus padding
+ const pagers = new Array(padding + 1);
+ // fill the array with the correct page numbers
+ // eslint-disable-next-line no-plusplus
+ for (let i = 0; i <= padding; i++) {
+ if (min + i <= maxPage) {
+ pagers[i] = min + i;
+ }
+ }
+
+ return pagers;
+ }, [currentRefinement, maxDesktop, maxMobile, showMobile, pages]);
+
+ if (!canRefine) {
+ return null;
+ }
+
+ const pageItemCommon =
+ 'su-border-b-4 su-border-transparent su-flex su-items-center su-justify-center su-min-w-[3.2rem] md:su-min-w-[3.6rem] su-min-h-[3.2rem] md:su-min-h-[3.6rem] su-font-normal su-leading-none su-no-underline';
+ const pageItemCommonHocus =
+ 'hocus:su-border-b-4 hocus:su-border-digital-red hocus:su-text-digital-red hocus:su-no-underline';
+
+ const directionCta = ({ isShown = false }) =>
+ dcnb(pageItemCommon, pageItemCommonHocus, 'su-text-22', {
+ 'su-invisible': !isShown,
+ 'su-visible': isShown,
+ });
+
+ const pageCta = ({ isActive = false }) =>
+ dcnb(pageItemCommon, pageItemCommonHocus, {
+ 'su-px-9 md:su-px-11 su-text-digital-red-light': !isActive,
+ 'su-px-9 md:su-px-11 su-text-black su-border-b-black-20 su-cursor-default su-pointer-events-none':
+ isActive,
+ });
+
+ /**
+ * Handle scroll and focus.
+ * */
+ const scrollToResults = () => {
+ scrollTo('#number-search-results', 'center');
+ document
+ .querySelector('#number-search-results')
+ .focus({ preventScroll: true });
+ };
+
+ return (
+
+ );
+};
+
+export default SearchPager;
diff --git a/src/components/search/SearchResults.jsx b/src/components/search/SearchResults.jsx
new file mode 100644
index 000000000..46eada2fa
--- /dev/null
+++ b/src/components/search/SearchResults.jsx
@@ -0,0 +1,83 @@
+/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
+/* eslint-disable jsx-a11y/control-has-associated-label */
+import React from 'react';
+import { useHits, useInstantSearch } from 'react-instantsearch';
+import { Skeleton } from '@mui/material';
+import { cnb } from 'cnbuilder';
+import SearchResultAlumniEvent from './Hits/SearchResultAlumniEvent';
+import SearchResultDefault from './Hits/SearchResultDefault';
+
+/**
+ * Main component
+ * @returns Main Search Results
+ */
+const SearchResults = () => {
+ const { results, items } = useHits();
+ const { status } = useInstantSearch();
+ const isLoading = status === 'loading';
+ const isStalled = status === 'stalled';
+
+ // Show loading.
+ if (isStalled) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ // No Results.
+ if (!results || !results.nbHits) {
+ return null;
+ }
+
+ // Show results.
+ return (
+
+
+ {results.nbHits} results:
+
+ {items.map((result) => {
+ switch (result.type) {
+ case 'alumni-event': {
+ return (
+
+ );
+ }
+ default: {
+ return (
+
+ );
+ }
+ }
+ })}
+
+ );
+};
+export default SearchResults;
diff --git a/src/components/search/searchSuggestions.js b/src/components/search/SearchSuggestions.jsx
similarity index 100%
rename from src/components/search/searchSuggestions.js
rename to src/components/search/SearchSuggestions.jsx
diff --git a/src/components/search/searchAutocomplete.js b/src/components/search/searchAutocomplete.js
deleted file mode 100644
index 4561c48e3..000000000
--- a/src/components/search/searchAutocomplete.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import React from 'react';
-import sanitize from 'sanitize-html';
-import useEscape from '../../hooks/useEscape';
-
-const SearchAutocomplete = ({
- autocompleteSuggestions,
- setShowAutocomplete,
- showAutocomplete,
- onSelect,
- selectedSuggestion,
- setSelectedSuggestion,
- autocompleteContainerClasses,
- autocompleteLinkClasses,
- autocompleteLinkFocusClasses,
-}) => {
- // Use Escape key to close autocomplete dropdown if it's currently open
- useEscape(() => {
- if (showAutocomplete) {
- setShowAutocomplete(false);
- }
- });
-
- return (
-
- {Array.isArray(autocompleteSuggestions) && (
-
- {autocompleteSuggestions.map((suggestion, index) => (
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events
- - onSelect(e, suggestion.query)}
- onKeyDown={(e) => {
- // On Enter or Spacebar
- if (e.key === 'Enter' || e.key === ' ') {
- onSelect(e, suggestion.query);
- }
- }}
- onFocus={(e) => setSelectedSuggestion(index)}
- aria-selected={selectedSuggestion === index ? 'true' : 'false'}
- id={`search-autocomplete-listbox-${index}`}
- >
- {
- // eslint-disable-next-line no-underscore-dangle
- suggestion._highlightResult && (
-
- )
- }
-
- ))}
-
- )}
-
- );
-};
-
-export default SearchAutocomplete;
diff --git a/src/components/search/searchFacet.js b/src/components/search/searchFacet.js
deleted file mode 100644
index e342fe8fb..000000000
--- a/src/components/search/searchFacet.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import { Heading } from '../simple/Heading';
-
-const SearchFacet = ({
- label,
- facetValues,
- attribute,
- selectedOptions,
- onChange,
- className,
- optionClasses,
- exclude = [],
-}) => {
- const handleCheckboxChange = (e) => {
- const values = [];
- const checkboxes = document.getElementsByName(e.target.name);
-
- checkboxes.forEach((checkbox) => {
- if (checkbox.checked) {
- values.push(checkbox.value);
- }
- });
-
- onChange(values);
- };
-
- let preparedFacetValues = Object.keys(facetValues).map((value) => {
- if (exclude.includes(value)) {
- return null;
- }
- return {
- name: value,
- count: facetValues[value],
- };
- });
-
- preparedFacetValues = preparedFacetValues.filter((el) => el != null);
- if (preparedFacetValues.length === 0) {
- return null;
- }
-
- return (
-
-
- {label}
-
-
- {preparedFacetValues.map((option, index) => (
-
- ))}
-
- );
-};
-
-export default SearchFacet;
diff --git a/src/components/search/searchField.js b/src/components/search/searchField.js
deleted file mode 100644
index 501e61791..000000000
--- a/src/components/search/searchField.js
+++ /dev/null
@@ -1,182 +0,0 @@
-import React, { useState, createRef, useEffect } from 'react';
-import { X, Search } from 'react-hero-icon/solid';
-import { useLocation } from '@reach/router';
-import SearchAutocomplete from './searchAutocomplete';
-import useOnClickOutside from '../../hooks/useOnClickOutside';
-import { utmParams } from '../../utilities/utmParams';
-
-const SearchField = React.forwardRef(
- (
- {
- onSubmit,
- onReset,
- onInput,
- autocompleteSuggestions,
- defaultValue,
- inputClasses,
- wrapperClasses,
- submitBtnClasses,
- clearBtnClasses,
- autocompleteLinkClasses,
- autocompleteLinkFocusClasses,
- autocompleteContainerClasses,
- placeholder,
- },
- ref
- ) => {
- const [query, setQuery] = useState(defaultValue || '');
- const [showAutocomplete, setShowAutocomplete] = useState(false);
- const [selectedSuggestion, setSelectedSuggestion] = useState(null);
- const inputWrapper = createRef();
- const inputRef = ref || createRef();
-
- const location = useLocation();
- const utms = utmParams(location.search);
-
- const submitHandler = (e) => {
- e.preventDefault();
- setShowAutocomplete(false);
- let queryParams = query;
- if (utms.length > 0) {
- queryParams += `&${utms}`;
- }
- onSubmit(queryParams);
- };
-
- const inputHandler = (e) => {
- setQuery(e.target.value);
- onInput(e.target.value);
- setShowAutocomplete(true);
- setSelectedSuggestion(null);
- };
-
- const clearHandler = (e) => {
- e.preventDefault();
- setQuery('');
- setShowAutocomplete(false);
- setSelectedSuggestion(null);
- onReset();
- };
-
- const selectSuggestion = (e, suggestion) => {
- e.preventDefault();
- setQuery(suggestion);
- setShowAutocomplete(false);
- setSelectedSuggestion(null);
- let suggestionParams = suggestion;
- if (utms.length > 0) {
- suggestionParams += `&${utms}`;
- }
- onSubmit(suggestionParams);
- };
-
- useEffect(() => {
- setQuery(defaultValue);
- }, [defaultValue]);
-
- useOnClickOutside(inputWrapper, () => {
- setShowAutocomplete(false);
- });
-
- // If no suggestion is selected, or if the last suggested item is selected,
- // using the down arrow will set focus on the first suggestion
- const handleArrowKeys = (e) => {
- if (e.key === 'ArrowDown') {
- if (
- selectedSuggestion === null ||
- selectedSuggestion === autocompleteSuggestions.length - 1
- ) {
- setSelectedSuggestion(0);
- } else {
- setSelectedSuggestion(selectedSuggestion + 1);
- }
- // if the first suggested selection is selected,
- // using the up arrow will loop back to set focus on the last suggestion
- } else if (e.key === 'ArrowUp') {
- if (selectedSuggestion === 0) {
- setSelectedSuggestion(autocompleteSuggestions.length - 1);
- } else {
- setSelectedSuggestion(selectedSuggestion - 1);
- }
- } else if (
- e.key === 'Enter' &&
- autocompleteSuggestions[selectedSuggestion]
- ) {
- selectSuggestion(e, autocompleteSuggestions[selectedSuggestion].query);
- }
- };
-
- return (
-
- );
- }
-);
-
-export default SearchField;
diff --git a/src/components/search/searchFieldModal.js b/src/components/search/searchFieldModal.js
deleted file mode 100644
index c2f6c8eca..000000000
--- a/src/components/search/searchFieldModal.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, { useState } from 'react';
-import algoliasearch from 'algoliasearch';
-import SearchField from './searchField';
-
-const SearchFieldModal = React.forwardRef((props, ref) => {
- const client = algoliasearch(
- process.env.GATSBY_ALGOLIA_APP_ID,
- process.env.GATSBY_ALGOLIA_API_KEY
- );
-
- const suggestionsIndex = client.initIndex(
- 'crawler_federated-search_suggestions'
- );
-
- const [suggestions, setSuggestions] = useState([]);
- const [query, setQuery] = useState('');
-
- const wrapperClasses = `su-border-0 su-border-b-2 su-border-black-10`;
-
- const inputClasses = `search-input-modal su-border-0 su-bg-transparent su-text-black-10 su-text-black-40::placeholder su-w-full su-flex-1
- su-rs-px-1 su-py-02em su-text-m2 md:su-text-m4 su-leading-display focus:su-outline-none focus:su-ring-0 focus:su-ring-transparent`;
-
- const submitBtnClasses = `su-flex su-items-center su-justify-center su-min-w-[4rem] su-w-40 su-h-40 md:su-min-w-[7rem] md:su-w-70 md:su-h-70 md:children:su-w-40 md:children:su-h-40 su-rounded-full su-transition-colors
- su-bg-digital-red hocus:su-bg-digital-red-xlight su-origin-center su-rs-ml-0`;
-
- const clearBtnClasses = `su-flex su-items-end su-transition-colors su-bg-transparent hover:su-bg-transparent
- hocus:su-text-digital-red-xlight hocus:su-underline su-text-m0 md:su-text-m1 su-font-semibold
- su-border-none su-text-white su-p-0 focus:su-bg-transparent su-rs-mr-1 su-mt-03em`;
-
- const autocompleteLinkClasses = `su-cursor-pointer su-font-regular su-inline-block su-w-full su-text-white su-no-underline su-px-15 su-py-10 su-rounded-full hover:su-bg-digital-red hover:su-text-white`;
-
- const autocompleteLinkFocusClasses = `su-bg-digital-red`;
-
- const autocompleteContainerClasses = `su-absolute su-top-[100%] su-bg-cardinal-red-xxdark su-p-10 su-shadow-md su-w-full su-border su-border-digital-red su-rounded-b-[0.5rem] su-z-20`;
-
- // Update autocomplete suggestions when search input changes.
- const updateAutocomplete = (queryText) => {
- suggestionsIndex
- .search(queryText, {
- hitsPerPage: 10,
- })
- .then((queryResults) => {
- setSuggestions(queryResults.hits);
- });
- };
-
- const submitSearchQuery = (queryText) => {
- setQuery(queryText);
- props.onSubmit(queryText);
- };
-
- return (
-
- updateAutocomplete(queryText)}
- onSubmit={(queryText) => submitSearchQuery(queryText)}
- onReset={() => null}
- defaultValue={query}
- autocompleteSuggestions={suggestions}
- clearBtnClasses={clearBtnClasses}
- wrapperClasses={wrapperClasses}
- inputClasses={inputClasses}
- submitBtnClasses={submitBtnClasses}
- autocompleteLinkClasses={autocompleteLinkClasses}
- autocompleteLinkFocusClasses={autocompleteLinkFocusClasses}
- autocompleteContainerClasses={autocompleteContainerClasses}
- placeholder="Search"
- ref={ref}
- />
-
- );
-});
-
-export default SearchFieldModal;
diff --git a/src/components/search/searchNoResults.js b/src/components/search/searchNoResults.js
deleted file mode 100644
index 254cb94e1..000000000
--- a/src/components/search/searchNoResults.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import { Heading } from '../simple/Heading';
-import CreateBloks from '../../utilities/createBloks';
-
-const SearchNoResults = ({ heading, body, additionalContent }) => (
-
-
- {heading}
-
-
{body}
-
-
-
-
-);
-
-export default SearchNoResults;
diff --git a/src/components/search/searchPager.js b/src/components/search/searchPager.js
deleted file mode 100644
index 70dc77364..000000000
--- a/src/components/search/searchPager.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import React from 'react';
-import { buildPager, buildMobilePager } from '../../utilities/buildPager';
-
-const SearchPager = ({ activePage, nbPages, maxLinks, selectPage }) => {
- if (activePage === undefined || nbPages === undefined) {
- return ;
- }
-
- const linkClasses = 'su-text-digital-red-light hover:su-border-b-4';
- const activeLinkClasses =
- 'su-text-cardinal-red su-border-b-4 su-cursor-default su-pointer-events-none';
-
- const desktopPagerLinks = buildPager(nbPages, maxLinks, activePage);
- const mobilePagerLinks = buildMobilePager(nbPages, activePage);
-
- const linkHandler = (e, page) => {
- e.preventDefault();
- selectPage(page);
- };
-
- const Pager = ({ pagerLinks, className }) => (
-
- );
-
- return (
-
- );
-};
-
-export default SearchPager;
diff --git a/src/components/search/searchResults.js b/src/components/search/searchResults.js
deleted file mode 100644
index 13cad31fd..000000000
--- a/src/components/search/searchResults.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import React from 'react';
-import sanitize from 'sanitize-html';
-import { useLocation } from '@reach/router';
-import { Heading } from '../simple/Heading';
-import HeroIcon from '../simple/heroIcon';
-import { utmParams } from '../../utilities/utmParams';
-
-const SearchResults = ({ results }) => {
- const location = useLocation();
- const utms = utmParams(location.search);
- const checkParams = (url) => {
- let linkUrl = url;
- if (linkUrl.match(/\?/) && utms.length) {
- linkUrl += `&${utms}`;
- } else if (utms.length) {
- linkUrl += `?${utms}`;
- }
- return linkUrl;
- };
-
- if (!results.hits) {
- return ;
- }
-
- return (
-
-
- {results.nbHits} results:
-
- {results.hits.map((result) => (
-
-
-
- {result.image && (
-
-
-
- )}
-
-
- ))}
-
- );
-};
-export default SearchResults;
diff --git a/src/utilities/checkUTMParams.js b/src/utilities/checkUTMParams.js
new file mode 100644
index 000000000..1a6194e07
--- /dev/null
+++ b/src/utilities/checkUTMParams.js
@@ -0,0 +1,15 @@
+/**
+ * UTM Parameters
+ * @param {*} url
+ * @param {*} utms
+ * @returns
+ */
+export const checkUTMParams = (url, utms) => {
+ let linkUrl = url;
+ if (linkUrl.match(/\?/) && utms.length) {
+ linkUrl += `&${utms}`;
+ } else if (utms.length) {
+ linkUrl += `?${utms}`;
+ }
+ return linkUrl;
+};
diff --git a/src/utilities/decodeHtmlEntities.js b/src/utilities/decodeHtmlEntities.js
new file mode 100644
index 000000000..5f0077a31
--- /dev/null
+++ b/src/utilities/decodeHtmlEntities.js
@@ -0,0 +1,10 @@
+/**
+ * Use the Dom to decode HTML entities
+ * @param {string} str
+ * @returns string
+ */
+export const decodeHtmlEntities = (str) => {
+ const tempElement = document.createElement('textarea');
+ tempElement.innerHTML = str;
+ return tempElement.value;
+};