\n \n);\n\nconst StyledArrow = styled(Link)`\n display: flex;\n align-items: center;\n height: 100%;\n margin-top: 2px;\n margin-left: 10px;\n margin-right: 0;\n\n svg {\n stroke: ${colors.black};\n\n * {\n stroke: ${colors.black};\n }\n }\n\n &.inverted {\n margin-left: 0;\n margin-right: 10px;\n transform: rotate(180deg);\n }\n\n &.disabled {\n cursor: default;\n\n svg {\n stroke: ${colors.grey};\n\n * {\n stroke: ${colors.grey};\n }\n }\n }\n`;\n\nconst Arrow = ({onClick = null, className = ''}) => (\n \n \n \n);\n\nArrow.propTypes = {\n onClick: PropTypes.func,\n className: PropTypes.string,\n};\n\nArrow.defaultProps = {\n onClick: null,\n className: '',\n};\n\nconst ArrowNext = ({disabled, onClick}) => (\n \n);\n\nArrowNext.propTypes = {\n disabled: PropTypes.bool.isRequired,\n onClick: PropTypes.func.isRequired,\n};\n\nconst ArrowBefore = ({disabled, onClick}) => (\n \n);\n\nArrowBefore.propTypes = {\n disabled: PropTypes.bool.isRequired,\n onClick: PropTypes.func.isRequired,\n};\n\n\nconst StyledSliderPagination = styled.div`\n padding: 5px;\n`;\n\nconst PaginationDot = styled.span`\n display: inline-block;\n position: relative;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background-color: ${colors.white};\n margin: 10px;\n cursor: pointer;\n\n &.active {\n background-color: ${colors.darkPurple};\n }\n`;\n\n\n/*\n * Public Elements\n */\n\n/**\n * Pagination element\n *\n * @param {number} numberOfElements - total number of elements to paginate\n * @param {number} elementsPerPage - number of elements per page\n * @param {number} currentPage - the current page\n * @param {function} setCurrentPage - the function to change the page\n * @param {Object} scrollToRef - React Ref of the Component to scroll to on page change\n */\nconst Pagination = ({numberOfElements, elementsPerPage, currentPage, setCurrentPage, scrollToRef = null}) => {\n const pages = [];\n const totalNumberOfPages = Math.ceil(numberOfElements / elementsPerPage);\n\n const prefersReducedMotion = usePrefersReducedMotion();\n\n // Define the change page method\n const changePage = targetPage => {\n if (scrollToRef) {\n smoothScroll(scrollToRef.current, prefersReducedMotion);\n }\n setCurrentPage(targetPage);\n };\n\n // Define the prev/next page method\n const goToPage = nextPage => () => {\n if (scrollToRef) {\n smoothScroll(scrollToRef.current, prefersReducedMotion);\n }\n setCurrentPage(nextPage);\n };\n\n // Define the page item generator\n const generatePage = number => (\n \n );\n\n // Show the first page (it is ALWAYS shown)\n pages.push(generatePage(1));\n\n // Calculate which are the first and last pages to show in the component (between page 1 and the actual last, since\n // those are always present)\n const firstPageToShow = Math.max(currentPage - ellipsisMargin, 2);\n const lastPageToShow = Math.min(currentPage + ellipsisMargin, totalNumberOfPages - 1);\n\n // Show the initial ellipsis if needed\n if (firstPageToShow > 2) {\n pages.push();\n }\n\n // Generate the pages\n for (let i = firstPageToShow; i <= lastPageToShow; i++) {\n pages.push(generatePage(i));\n }\n\n // Show the final ellipsis if needed\n if (lastPageToShow < totalNumberOfPages - 1) {\n pages.push();\n }\n\n // Show the last page (it is ALWAYS shown)\n if (totalNumberOfPages > 1) {\n pages.push(generatePage(totalNumberOfPages));\n }\n\n return (\n \n \n \n\n {pages}\n\n = totalNumberOfPages} onClick={goToPage(currentPage + 1)} />\n \n \n );\n};\n\nPagination.propTypes = {\n numberOfElements: PropTypes.number.isRequired,\n elementsPerPage: PropTypes.number.isRequired,\n currentPage: PropTypes.number.isRequired,\n setCurrentPage: PropTypes.func.isRequired,\n scrollToRef: PropTypes.object,\n};\n\nPagination.defaultProps = {\n scrollToRef: null,\n};\n\n/**\n * Slider Pagination element\n *\n * @param {number} numberOfElements - total number of elements to paginate\n * @param {number} currentIndex - the current element's position\n * @param {function} setIndex - the function to change the current element\n * @param {number | null} rotationTimeout - the interval to rotate slides\n */\nconst SliderPagination = ({numberOfElements, currentIndex, setIndex, rotationTimeout = null}) => {\n const [_rotationTimeout, setRotationTimeout] = useState(rotationTimeout);\n\n const setNewIndex = newIndex => {\n // Stop the counter\n setRotationTimeout(null);\n // Set the new index\n setIndex(newIndex);\n\n // Start the counter again from the beginning (note: it must be set asynchronously so that a full render of the\n // \"null\" timeout is executed, in order to reset the interval)\n setTimeout(() => setRotationTimeout(rotationTimeout), 0);\n };\n\n // Update the current slide being displayed\n useInterval(() => {\n // If we are at the last one, go to the beginning\n const newIndex = currentIndex + 1 >= numberOfElements ? 0 : currentIndex + 1;\n setIndex(newIndex);\n }, _rotationTimeout);\n\n // Add one \"dot\" for each page\n const dots = [];\n for (let i = 0; i < numberOfElements; i++) {\n dots.push(\n setNewIndex(i)} />,\n );\n }\n\n // Return\n return (\n \n {dots}\n \n );\n};\n\nSliderPagination.propTypes = {\n numberOfElements: PropTypes.number.isRequired,\n currentIndex: PropTypes.number.isRequired,\n setIndex: PropTypes.func.isRequired,\n rotationTimeout: PropTypes.number,\n};\n\nSliderPagination.defaultProps = {\n rotationTimeout: null,\n};\n\n\n/*\n * Exports\n */\nexport default Pagination;\nexport {\n SliderPagination,\n};\n","import {useContext, useEffect, useMemo, useState} from 'react';\nimport {I18nextContext} from 'gatsby-plugin-react-i18next';\n\n\n/*\n * Helpers\n */\n/**\n * Get the value of an item we are applying filters to\n *\n * @param {string} filterKey - the name of the filter to apply\n * @param {Object} item - the item to apply the filter to\n * @param {string} path - the object path to the item's value (ex: payment_method.countries)\n * @returns {*} the item's value\n */\nconst getItemValue = (filterKey, item, path) => {\n // If the schema has no path, just return the property with the filter's name\n if (!path) {\n return item[filterKey];\n }\n\n // Otherwise, split the path and iterate it until finding the value\n let itemValue = item;\n path.split('.').forEach(fragment => {\n itemValue = itemValue[fragment];\n });\n\n // Return\n return itemValue;\n};\n\n// Equality checks\n/* eslint-disable max-len */\nconst checkEquality = (filter, item, path) => filter.value === getItemValue(filter.key, item, path);\nconst checkInsensitiveContains = (filter, item, path) => filter.value.toLowerCase().includes(getItemValue(filter.key, item, path).toLowerCase());\nconst checkArrayContains = (filter, item, path) => getItemValue(filter.key, item, path).map(value => value.name).includes(filter.value);\nconst checkArrayObjectValue = (filter, item, path) => getItemValue(filter.key, item, path).some(obj => obj[filter.key] === filter.value);\n/* eslint-enable max-len */\n\n// Available operators and their equality functions\nconst equalityOperations = {\n /* eslint-disable i18next/no-literal-string */\n array: {\n contains: checkArrayContains,\n objectValue: checkArrayObjectValue,\n },\n string: {\n equals: checkEquality,\n icontains: checkInsensitiveContains,\n },\n /* eslint-enable i18next/no-literal-string */\n};\n\n/**\n * Apply a set of filters to an element to check if it matches all of them\n *\n * @param {Object} schema - the filter's schema\n * @returns {function} the equality function to use\n */\nconst getOperation = schema => equalityOperations[schema.type][schema.operator] || null;\n\n/**\n * Apply a set of filters to an element to check if it matches all of them\n *\n * @param {Object} element - the element to apply the filters tp\n * @param {Object} filters - the filters to apply\n * @param {Object} schema - the filters schema\n * @returns {boolean} whether the element passed all the filters or not\n */\nconst applyFilter = (element, filters, schema) => {\n // Apply all the filters to the element\n for (let i = 0; i < filters.length; i++) {\n const filter = filters[i];\n\n // Only filter by this key if there is a valid value\n if (filter.value) {\n const filterSchema = schema[filter.key];\n const equalityOperation = !!filterSchema ? getOperation(filterSchema) : null;\n\n // The filter check fails if:\n // a) there is no equality operator for that filter\n // b) the equality operation returned false\n if (!equalityOperation || !equalityOperation(filter, element, filterSchema.path)) {\n return false;\n }\n }\n }\n\n // Return true if it passed all checks\n return true;\n};\n\n\n/*\n * Hook\n */\n/**\n * Apply filters to a dataset\n *\n * The function is memoized as to avoid unnecessary calculations for the same arguments\n *\n * @param {Object[]} dataset - the dataset to filter\n * @param {Object} filters - the filters to apply\n * @param {Object} schema - the filters schema\n * @returns {Object[]} the filtered elements\n */\nexport default (dataset, filters, schema) => {\n const i18next = useContext(I18nextContext);\n const [lunrIndex, setLunrIndex] = useState(null);\n\n // Make the Lunr index available\n useEffect(() => {\n setLunrIndex(window.__LUNR__[i18next.language]);\n }, [i18next.language]);\n\n // Return filtered data\n return useMemo(() => {\n const filteredDataset = [];\n let textSearchResults = null;\n\n // If there is a search query and the Lunr index is available, ge those results first\n if (filters.search && lunrIndex) {\n const searchResults = lunrIndex.index.search(filters.search);\n textSearchResults = searchResults ? searchResults.map(({ref}) => {\n const splitString = ref.split('_');\n return splitString[splitString.length - 1];\n }) : null;\n }\n\n // Only use the other filters if there was NOT a text search, or it had results\n if (!textSearchResults || textSearchResults.length) {\n const filtersEntries = Object.entries(filters)\n .filter(filter => filter[0] !== 'search')\n .map(filter => ({key: filter[0], value: filter[1]}));\n\n // Iterate each element of the dataset and apply the filters\n dataset.forEach(item => {\n // If there are text search results and they dont match the item in the dataset, there's not a match\n if (textSearchResults && !textSearchResults.includes(item.id)) return;\n\n // Apply the remaining filters to the item\n if (applyFilter(item, filtersEntries, schema)) {\n filteredDataset.push(item);\n }\n });\n }\n\n // Return\n return filteredDataset;\n }, [filters, dataset, lunrIndex, schema]);\n};\n","import useQueryParameters from './useQueryParameters';\nimport {objectFilter} from '../utils';\n\n\n/**\n * Get filters from URL query parameters and location.state\n *\n * @param {Object} location - the location (preferably from a page's props, but can be window.location)\n * @param {string[]} allowedFilters - list of allowed keys for the filters\n * @returns {[{}, function]} - selected filters; method to set URL query params\n */\nexport default (location, allowedFilters) => {\n // Get the current query params, as well as its setter\n const [queryParams, setQueryParams] = useQueryParameters(location);\n\n // Get selected filters from location.state and from URL query params\n const stateSelectedFilters = location.state?.selectedFilters || {};\n const cleanedQueryParams = objectFilter(queryParams, allowedFilters);\n const filters = ({...cleanedQueryParams, ...stateSelectedFilters});\n\n // Return the selected filters and the method to se URL query params\n return [filters, setQueryParams];\n};\n","import {useEffect, useState} from 'react';\n\n\n/**\n * Get and set query string parameters\n *\n * @param {Object} location - the location (preferably from a page's props, but can be window.location)\n * @returns {[{}, function]} the current query params, as well as a method to set them\n */\nexport default location => {\n const [queryParameters, setQueryParameters] = useState(new URLSearchParams(location.search));\n\n useEffect(() => {\n // Set the URL with the parameters\n const queryParamsString = queryParameters.toString();\n const newUrl = queryParamsString.length\n ? `${location.pathname}?${queryParamsString}`\n : `${location.pathname}`;\n window.history.replaceState('{}', '', newUrl);\n });\n\n // Define the setter method\n const setQueryParams = queryParamsForUrl => {\n if (queryParameters.toString() !== new URLSearchParams(queryParamsForUrl).toString()) {\n setQueryParameters(new URLSearchParams(queryParamsForUrl));\n }\n };\n\n // Convert the current parameters into an object (the URLSearchParams object has no such property)\n const parsedQueryParams = {};\n queryParameters.forEach((value, key) => {\n parsedQueryParams[key] = value;\n });\n\n // Return the current parameters, as well as the setter method\n return [parsedQueryParams, setQueryParams];\n};\n","import React, {useEffect, useRef} from 'react';\n\n\n/**\n * A wrapper for Javascript's setInterval (with the same exact signature) that wraps the existing imperative API into\n * React's declarative API.\n * It handles changing either the handler or timeout graciously, as well as pausing the counter by setting the timeout\n * to null.\n *\n * Props to https://overreacted.io/making-setinterval-declarative-with-react-hooks/\n *\n * @param {TimerHandler} handler - function to run in the specified interval\n * @param {number || null} timeout - the interval to use, or null for pausing\n */\nexport default (handler, timeout) => {\n const savedCallback = useRef();\n\n // Remember the latest callback\n useEffect(() => {\n savedCallback.current = handler;\n }, [handler]);\n\n // Set up the interval\n useEffect(() => {\n const tick = () => savedCallback.current();\n if (timeout !== null) {\n const id = setInterval(tick, timeout);\n return () => clearInterval(id);\n }\n\n return undefined;\n }, [timeout]);\n};\n","/**\n * Get a subset of the data list, based on the page number\n *\n * It can handle partial page navigation, such as showing a page of 10 elements, but only adding new 5 elements on the\n * next page (and removing 5 elements from the previous page). This is useful for navigating with arrows instead of page\n * numbers, because you can show several elements in the same page, but only moving one element at a time instead of the\n * whole page.\n *\n * @param {Object[]} dataset - the data to paginate\n * @param {number} currentPage - the current page to show\n * @param {number} elementsPerPage - the number of elements per page to show\n * @returns {Object[]} list of the paginated (subset of) items\n */\nexport default (dataset, currentPage, elementsPerPage) => {\n // Get the first and last elements to display based on the page\n const initialElement = elementsPerPage * (currentPage - 1);\n const finalElement = initialElement + elementsPerPage;\n\n // If the last element is higher than the total number of elements, it means we are in the last page, so we only\n // display until the last item.\n const maxIteration = Math.min(finalElement, dataset.length);\n\n // Return the subset of items related to the specified page\n return dataset.slice(initialElement, maxIteration);\n};\n"],"sourceRoot":""}