diff --git a/src/components/Pagination/Pagination.jsx b/src/components/Pagination/Pagination.jsx
index b89b15a6..d72fe185 100644
--- a/src/components/Pagination/Pagination.jsx
+++ b/src/components/Pagination/Pagination.jsx
@@ -1,5 +1,3 @@
-/* eslint-disable no-unused-vars */
-
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
@@ -8,6 +6,8 @@ import Input from '@material-ui/core/Input';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { withStyles } from '@material-ui/core/styles';
import { Button, Typography, Link } from '@material-ui/core';
+import NavigateNextIcon from '@material-ui/icons/NavigateNext';
+import NavigateBeforeIcon from '@material-ui/icons/NavigateBefore';
import useQuery from '../../hooks/useQuery';
@@ -39,6 +39,8 @@ const styles = () => ({
flexGrow: 1,
justifyContent: 'center',
fontSize: '16px',
+ height: '100%',
+
},
navigationContainer: {
display: 'flex',
@@ -90,7 +92,7 @@ const styles = () => ({
},
mobilePaginationListContainer: {
display: 'flex',
- marginTop: 44,
+ marginTop: 0,
justifyContent: 'center',
},
paginationLabel: {
@@ -108,9 +110,10 @@ const styles = () => ({
color: '#FFFFFF',
backgroundColor: '#633AA3',
borderRadius: '2px',
+ fontWeight: 500,
},
hideDisplay: {
- display: 'none',
+ visibility: 'hidden',
},
buttonOpenGoToPage: {
maxWidth: 28,
@@ -125,6 +128,17 @@ const styles = () => ({
fontSize: 30,
fontWeight: 700,
},
+ centerText: {
+ height: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ alignPaginationRange: {
+ height: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ },
});
const getPageLink = (history, query, perPage, page) => {
@@ -139,8 +153,8 @@ const NextButton = ({ pageLink, classes }) => {
const removeUnderline = classes.paginationLabel;
const nextClasses = `${color} ${removeUnderline} ${classes.expandNavigationButtons}`;
return (
-
);
};
@@ -151,11 +165,24 @@ const BackButton = ({ pageLink, classes }) => {
const backClasses = `${color} ${removeUnderline} ${classes.expandNavigationButtons}`;
return (
);
};
+const CheckValidPageNumber = (totalPages, input) => {
+ if (typeof input !== 'string' && typeof input !== 'number') {
+ return false;
+ }
+ const pageNum = Number(input);
+ const checkValidType = Number.isInteger(pageNum);
+ const checkValidRange = pageNum >= 1 && pageNum <= totalPages;
+ if (checkValidType && checkValidRange) {
+ return true;
+ }
+ return false;
+};
+
const GoToPage = ({
classes,
totalPages,
@@ -168,13 +195,20 @@ const GoToPage = ({
const GoToPageClasses = `${classes.goToPageContainer} ${ShowContent}`;
const handleSubmit = (event) => {
event.preventDefault();
+ if (CheckValidPageNumber(totalPages, input) === false) {
+ return;
+ }
navigationObject.history.push(getPageLink(...Object.values(navigationObject), input));
+ navigationObject.history.go(
+ navigationObject.history.location.pathname + navigationObject.history.location.search,
+ );
};
return (
@@ -380,3 +435,22 @@ Pagination.propTypes = {
Pagination.defaultProps = {};
export default withStyles(styles)(Pagination);
+
+// eslint-disable-next-line import/no-mutable-exports
+export let Tests = {
+ getPageLink,
+ NextButton,
+ BackButton,
+ CheckValidPageNumber,
+ GoToPage,
+ RangeOfResults,
+ OpenGoToPageButton,
+ PaginationButton,
+ RenderPaginationButtons,
+ calculatePaginationMenu,
+ CalculatePageRange,
+};
+
+if (process.env.NODE_ENV !== 'test') {
+ Tests = undefined;
+}
diff --git a/src/components/Pagination/Pagination.test.jsx b/src/components/Pagination/Pagination.test.jsx
index 083ecc8e..2fefcaed 100644
--- a/src/components/Pagination/Pagination.test.jsx
+++ b/src/components/Pagination/Pagination.test.jsx
@@ -1,9 +1,122 @@
+/* eslint-disable */
import React from 'react';
-import { render } from '@testing-library/react';
+import { cleanup, render, screen } from '@testing-library/react';
-import Pagination from './Pagination';
+import NavigateNextIcon from '@material-ui/icons/NavigateNext';
+
+import Pagination, { Tests } from './Pagination';
+import { Check } from '@material-ui/icons';
+
+const {
+ getPageLink,
+ NextButton,
+ BackButton,
+ CheckValidPageNumber,
+ GoToPage,
+ RangeOfResults,
+ OpenGoToPageButton,
+ PaginationButton,
+ RenderPaginationButtons,
+ calculatePaginationMenu,
+ CalculatePageRange,
+} = Tests;
test.skip('Pagination', () => {
const { asFragment } = render(
);
expect(asFragment()).toMatchSnapshot();
});
+
+describe('Pagination Component', () => {
+ describe('NextButton', () => {
+ const testProps = {
+ classes: {},
+ pageLink: '/',
+ }
+ const { container } = render(
);
+ const iconHtml = container.innerHTML;
+ cleanup();
+ it('It should have a correct icon label', () => {
+ render(
);
+ const nextButton = screen.getByTestId('next-button-icon');
+ expect(nextButton.innerHTML).toBe(iconHtml);
+ });
+ it('It should have an href value if passed in', () => {
+ render(
);
+ const nextButton = screen.getByTestId('next-button-icon');
+ expect(nextButton).toHaveAttribute('href', '/');
+ });
+ it('It should not have an href if pageLink was undefined', () => {
+ const noLinkProps = {
+ classes: {},
+ pageLink: undefined,
+ };
+ render(
);
+ const nextButton = screen.getByTestId('next-button-icon');
+ expect(nextButton).not.toHaveAttribute('href');
+ });
+ it('It should have a correct aria-label value', () => {
+ render(
);
+ expect(screen.getByLabelText('Go next page')).toBeInTheDocument();
+ });
+ });
+
+ describe('CheckValidPageNumber', () => {
+ it('It should return false if the input was not of type string or type number', () => {
+ expect(CheckValidPageNumber(10, null)).toBe(false);
+ expect(CheckValidPageNumber(20, undefined)).toBe(false);
+ expect(CheckValidPageNumber(10, { input: 1 })).toBe(false);
+ });
+ it('It should return false if the value is a string or number but is not a integer', () => {
+ expect(CheckValidPageNumber(10, 4.52)).toBe(false);
+ expect(CheckValidPageNumber(20, '11.11')).toBe(false);
+ });
+ it('It should return false if the input is greater than totalPages', () => {
+ expect(CheckValidPageNumber(1, '2')).toBe(false);
+ expect(CheckValidPageNumber(1000000, 10000001)).toBe(false);
+ });
+ it('It should return true if the input is of type number or string, an integer and within the range 1-totalPages', () => {
+ expect(CheckValidPageNumber(20, '20')).toBe(true);
+ expect(CheckValidPageNumber(20, 20)).toBe(true);
+ expect(CheckValidPageNumber(20, '15')).toBe(true);
+ expect(CheckValidPageNumber(20, 15)).toBe(true);
+ expect(CheckValidPageNumber(40, 1)).toBe(true);
+ expect(CheckValidPageNumber(5000000, 102460)).toBe(true);
+ });
+ });
+
+ describe('RangeOfResults', () => {
+ it('It should not render if it is smaller than the media query, desktopDimensions is false', () => {
+ const noRenderProps = {
+ classes: {},
+ totalCount: 40,
+ calculateRange: 'Showing',
+ desktopDimensions: false,
+ };
+ render(
);
+ const rangeOfResults = screen.queryByText('Showing', { exact: false });
+ expect(rangeOfResults).not.toBeInTheDocument();
+ });
+ it('It should not render if the totalCount of results is 0', () => {
+ const noRenderProps = {
+ classes: {},
+ totalCount: 0,
+ calculateRange: 'Showing',
+ desktopDimensions: true,
+ };
+ render(
);
+ const rangeOfResults = screen.queryByText('Showing', { exact: false });
+ expect(rangeOfResults).not.toBeInTheDocument();
+ });
+ it('It should render the range of results', () => {
+ const dummyProps = {
+ classes: {},
+ totalCount: 40,
+ calculateRange: `Showing ${5} - ${10} of ${40} results`,
+ desktopDimensions: true,
+ };
+ render(
);
+ const rangeOfResults = screen.queryByText('Showing', { exact: false });
+ expect(rangeOfResults).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/components/SupportButton/SupportButton.jsx b/src/components/SupportButton/SupportButton.jsx
new file mode 100644
index 00000000..017adc3f
--- /dev/null
+++ b/src/components/SupportButton/SupportButton.jsx
@@ -0,0 +1,87 @@
+import React, { useState } from 'react';
+
+import {
+ Box,
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+ withStyles,
+} from '@material-ui/core/';
+
+const styles = (theme) => ({
+ centerContent: {
+ display: 'flex',
+ justifyContent: 'center',
+ },
+ searchButton: {
+ [theme.breakpoints.up('lg')]: {
+ minWidth: 122.4,
+ minHeight: 40.8,
+ },
+ backgroundColor: '#FCFBFE',
+ textTransform: 'none',
+ border: '1px solid #EBE5F6',
+ fontWeight: 600,
+ marginBottom: 4,
+ },
+});
+
+const SupportButton = ({ classes }) => {
+ const [showSupportDialog, setShowSupportDialog] = useState(false);
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+SupportButton.propTypes = {};
+
+SupportButton.defaultProps = {};
+
+export default withStyles(styles)(SupportButton);
diff --git a/src/components/SupportButton/SupportButton.md b/src/components/SupportButton/SupportButton.md
new file mode 100644
index 00000000..43da2d23
--- /dev/null
+++ b/src/components/SupportButton/SupportButton.md
@@ -0,0 +1,5 @@
+## SupportButton
+
+```jsx
+
+```
\ No newline at end of file
diff --git a/src/components/SupportButton/SupportButton.test.jsx b/src/components/SupportButton/SupportButton.test.jsx
new file mode 100644
index 00000000..007d8eda
--- /dev/null
+++ b/src/components/SupportButton/SupportButton.test.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import { screen, render } from '@testing-library/react';
+
+import SupportButton from './SupportButton';
+
+describe('SupportButton', () => {
+ it('It should render a correct label', () => {
+ render(
);
+ const button = screen.getByText('Report a Problem');
+ expect(button).toBeInTheDocument();
+ });
+});
diff --git a/src/components/SupportButton/index.js b/src/components/SupportButton/index.js
new file mode 100644
index 00000000..75dc2130
--- /dev/null
+++ b/src/components/SupportButton/index.js
@@ -0,0 +1,3 @@
+import SupportButton from './SupportButton';
+
+export default SupportButton;
diff --git a/src/routes/Search/Search.jsx b/src/routes/Search/Search.jsx
index 71ff0aab..df1f4dab 100644
--- a/src/routes/Search/Search.jsx
+++ b/src/routes/Search/Search.jsx
@@ -1,30 +1,30 @@
/* eslint-disable no-unused-vars */
-import React, { useEffect, useState } from 'react';
-
+import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { geolocated, geoPropTypes } from 'react-geolocated';
import cx from 'classnames';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { withStyles } from '@material-ui/core/styles';
-
import Button from '@material-ui/core/Button';
+import Menu from '@material-ui/core/Menu';
+import MenuItem from '@material-ui/core/MenuItem';
import CircularProgress from '@material-ui/core/CircularProgress';
import Typography from '@material-ui/core/Typography';
import SearchIcon from '@material-ui/icons/Search';
+import List from '@material-ui/core/List';
+import ListItem from '@material-ui/core/ListItem';
+import ListItemText from '@material-ui/core/ListItemText';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import BusinessCard from '../../components/BusinessCard';
import FilterPanel from '../../components/FilterPanel';
import Pagination from '../../components/Pagination';
-
import useSearch from './hooks/useSearch';
import SearchForm from './SearchForm';
+import SupportButton from '../../components/SupportButton/SupportButton';
-const styles = (theme) => ({
- result: {
- // This is for setting the margins of the results returned from search.
- },
+const styles = () => ({
content: {
backgroundColor: '#FFFFFF',
width: '100%',
@@ -37,15 +37,17 @@ const styles = (theme) => ({
paddingTop: '32px',
backgroundColor: '#f2f2f2',
},
- searchResult: {
+ desktopMargins: {
marginLeft: 40,
marginBottom: 20,
marginRight: 142,
+ flex: 1,
},
- searchResultBreakpoint: {
+ mobileMargins: {
marginLeft: 20,
marginRight: 20,
marginBottom: 20,
+ flex: 1,
},
emptyStateWrapper: {
marginTop: 60,
@@ -64,67 +66,340 @@ const styles = (theme) => ({
marginRight: 20,
},
},
- searchresultsText: {
- flexGrow: 2,
- },
filterButton: {
- width: '100px !important',
+ width: '76px !important',
marginBottom: '16px',
height: 36,
border: '#EBE5F6 1px solid',
+ padding: 8,
+ gap: 10,
+ borderRadius: 4,
+ marginRight: 8,
},
filterButtonText: {
fontWeight: 600,
fontSize: '14px',
lineHeight: '20px',
+ textTransform: 'none',
+ },
+ filterTextColor: {
color: '#1E1131',
},
- searchCountHeader: {
+ clearAllColor: {
+ border: 0,
+ color: '#633AA3',
+ },
+ numOfResultsContainer: {
fontWeight: 600,
fontSize: '32px',
color: '#1E1131',
lineHeight: '32px',
marginBottom: 16,
},
- leftSideFilter: {
+ numResultsMobileFont: {
+ fontSize: '20px',
+ lineHeight: '25px',
+ marginBottom: 8,
+ },
+ sortLeftContainer: {
flex: 1,
},
- rightSideFilter: {
+ sortMenuContainer: {
display: 'flex',
+ textAlign: 'center',
+ alignItems: 'center',
+ color: '#633AA3',
+ textTransform: 'none',
},
- filterDropdown: {
+ sortMobileMenuContainer: {
display: 'flex',
+ textAlign: 'center',
+ alignItems: 'center',
+ width: '100%',
+ minHeight: '40px',
+ backgroundColor: '#F2F2F2',
+ marginBottom: 8,
+ lineHeight: '24px',
+ fontSize: '16px',
},
- boldedText: {
+ searchSettingLabel: {
fontWeight: 600,
lineHeight: '17.5px',
color: '#1E1131',
marginRight: 6,
},
- purpleText: {
+ searchSettingMobileLabel: {
+ fontWeight: 400,
+ lineHeight: '24px',
+ color: '#1E1131',
+ fontSize: '16px',
+ marginLeft: 32,
+ },
+ rowContainer: {
display: 'flex',
- color: '#633AA3',
},
- arrowDropdown: {
- marginRight: 6,
- paddingBottom: 4,
- verticalAlign: 'middle',
+ columnContainer: {
+ display: 'flex',
+ flexDirection: 'column',
},
- hiddenElement: {
- display: 'none',
+ cardMargins: {
+ marginBottom: 40,
},
- searchBodyContainer: {
- width: '100%',
+ headerMargins: {
+ marginTop: 24,
+ },
+ overrideListItem: {
+ backgroundColor: 'pink',
+ },
+ selectedChoice: {
+ fontWeight: 600,
+ backgroundColor: '#E5E5E5',
+ boxShadow: '0px 2px 2px rgba(0, 0, 0, 0.2)',
},
});
+const ReusableMenu = (
+ {
+ classes, menuValues, menuStrings, onMenuClick, sortLabel, currentValue = menuValues[0], mobile,
+ },
+) => {
+ const SortContainerClass = mobile ? classes.sortMobileMenuContainer : classes.sortMenuContainer;
+ const SearchSettingClass = mobile ? classes.searchSettingMobileLabel : classes.searchSettingLabel;
+
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const [selectedIndex, setSelectedIndex] = React.useState(1);
+ const open = Boolean(anchorEl);
+ const handleClick = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+ const handleMenuItemClick = (event, index) => {
+ setSelectedIndex(index);
+ setAnchorEl(null);
+ };
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+ <>
+
+
+ {sortLabel}
+
+
+
+
+ >
+ );
+};
+
+const querySearch = () => (
+ null
+);
+
+const DistanceMenu = ({ classes, mobile }) => (
+
+);
+
+const SortByMenu = ({ classes, mobile }) => (
+
+);
+
+const ShowingPerPageMenu = ({ classes, mobile }) => (
+
+);
+
+const TopSortRow = ({ classes, mobile }) => {
+ const ContainerClass = mobile ? classes.columnContainer : classes.rowContainer;
+ return (
+
+ {mobile ? null
+ : (
+
+
+
+ )}
+
+
+
+
+
+ );
+};
+
+const SortRowMobile = ({ classes, useDesktop }) => {
+ if (useDesktop) {
+ return null;
+ }
+ return (
+
+ );
+};
+
+const NumberOfResultsHeader = ({ classes, pagination, mobile }) => {
+ const resultString = pagination.total_count >= 2 ? 'Results' : 'Result';
+ const numOfResultsString = `${pagination.total_count} Search ${resultString}`;
+ const applyFonts = mobile ? classes.numResultsMobileFont : '';
+ return (
+
+ {numOfResultsString}
+
+ );
+};
+
+const OpenFilterPanelButton = (
+ {
+ classes, openFilter, setOpenFilter, clearFilters, display,
+ },
+) => {
+ if (display === false) {
+ return null;
+ }
+ return (
+
+
+
+
+ );
+};
+
+const FilterPanelAside = ({
+ openFilter,
+ setOpenFilter,
+ isWiderThanBreakpoint,
+ indicators,
+ search,
+ updateSearch,
+ searchResults,
+ classes,
+}) => {
+ const ShowFilterPanel = searchResults !== null && searchResults.length > 0
+ && (isWiderThanBreakpoint || openFilter);
+ if (ShowFilterPanel === false) {
+ return null;
+ }
+ return (
+
+ setOpenFilter(false)}
+ type={isWiderThanBreakpoint ? 'desktop' : 'mobile'}
+ allIndicators={indicators}
+ search={search}
+ updateSearch={updateSearch}
+ resultCount={searchResults.length}
+ />
+
+ );
+};
+
+const SearchBodyHeader = ({
+ classes, pagination, setOpenFilter, openFilter, useDesktop, display,
+}) => {
+ if (display === false) {
+ return null;
+ }
+ return (
+
+ );
+};
+
const Search = ({
classes,
coords,
isGeolocationEnabled,
}) => {
- const isWiderThanBreakpoint = useMediaQuery('(min-width:1376px)');
-
const [openFilter, setOpenFilter] = useState(false);
const {
updateSearch,
@@ -136,6 +411,7 @@ const Search = ({
userLocation,
indicators = [],
} = useSearch({ userCoords: coords, isGeolocationEnabled });
+
const onSearchSubmit = async (searchTerm) => {
updateSearch(searchTerm);
};
@@ -156,6 +432,11 @@ const Search = ({
updateFilters('rating', changedFilters.stars);
};
+ const isWiderThanBreakpoint = useMediaQuery('(min-width:1376px)');
+ const useDesktop = useMediaQuery('(min-width:838px)');
+ const SearchPageMargins = isWiderThanBreakpoint ? classes.desktopMargins : classes.mobileMargins;
+ const FoundOneOrMoreResults = searchResults !== null && searchResults.length > 0;
+
const isGeoLoading = isGeolocationEnabled && coords === null;
return (
@@ -170,109 +451,49 @@ const Search = ({
/>
)}
- {searchResults !== null
- && searchResults.length > 0
- && (isWiderThanBreakpoint || openFilter)
- && (
-
- setOpenFilter(false)}
- type={isWiderThanBreakpoint ? 'desktop' : 'mobile'}
- allIndicators={indicators}
- search={search}
- updateSearch={updateSearch}
- resultCount={searchResults.length}
- />
-
- )}
-
- {searchResults !== null && searchResults.length > 0
- ? (
-
-
- {`${pagination.total_count} Search ${pagination.totalCount >= 2 ? 'Result' : 'Results'}`}
-
- {!isWiderThanBreakpoint && (
-
-
-
- )}
-
-
-
-
Showing:
-
- 10 per page
-
-
-
-
-
-
-
Distance:
-
- 5 miles
-
-
-
-
-
Sort by:
-
- Highly Rated
-
-
-
-
-
-
- )
- : null}
+
+
+
- {searchResults !== null && searchResults.length > 0
- && searchResults.map((result, index) => (
-
(
+
+
-
-
- ))}
+ count={(pagination.page - 1) * 10 + index + 1}
+ />
+
+ ))}
- {pagination && pagination !== null && (
-
+ {pagination && pagination !== null && !loading && (
+
)}
-
{searchResults !== null
&& search.searchTerm !== null
&& searchResults.length === 0
@@ -311,3 +532,20 @@ Search.props = {
Search.props = { ...Search.props, ...geoPropTypes };
export default geolocated({ positionOptions: { timeout: 5000 } })(withStyles(styles)(Search));
+
+// eslint-disable-next-line import/no-mutable-exports
+export let Tests = {
+ ReusableMenu,
+ DistanceMenu,
+ SortByMenu,
+ ShowingPerPageMenu,
+ TopSortRow,
+ SortRowMobile,
+ NumberOfResultsHeader,
+ OpenFilterPanelButton,
+ FilterPanelAside,
+};
+
+if (process.env.NODE_ENV !== 'test') {
+ Tests = undefined;
+}
diff --git a/src/routes/Search/Search.test.jsx b/src/routes/Search/Search.test.jsx
new file mode 100644
index 00000000..15dfb161
--- /dev/null
+++ b/src/routes/Search/Search.test.jsx
@@ -0,0 +1,48 @@
+/* eslint-disable */
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+
+import Search, { Tests } from './Search';
+import userEvent from '@testing-library/user-event';
+
+const {
+ SortByMenu,
+} = Tests;
+
+describe('Search Page', () => {
+ describe('SortByMenu', () => {
+ const dummyProps = {
+ classes: {},
+ mobile: false,
+ };
+ it('It should render a button', () => {
+ render(
);
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ });
+ it('It should render a label', () => {
+ render(
);
+ const label = screen.getByTestId('menu-dropdown-label');
+ expect(label).toBeInTheDocument();
+ });
+ it('It should render a label that prefixes the value', () => {
+ render(
);
+ const label = screen.getByTestId('menu-dropdown-label');
+ expect(label.textContent).toContain('Sort by:');
+ });
+ it('It should render the current value on the label', () => {
+ render(
);
+ const currentValue = screen.getByText('Highly Rated');
+ expect(currentValue).toBeInTheDocument();
+ });
+ it('It should render menu items on click', () => {
+ render(
);
+ const button = screen.getByRole('button');
+ const menuItem = screen.queryByText('Recently Added');
+ expect(menuItem).not.toBeInTheDocument();
+
+ userEvent.click(button);
+ expect(screen.getByText('Recently Added')).toBeInTheDocument();
+ });
+ });
+});