feat: Implemented pagination on explore page.
fix: Fixed persistent search suggestion value Signed-off-by: Raul Kele <raulkeleblk@gmail.com> Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
parent
33dcc936bb
commit
52c1559c62
@ -22,6 +22,7 @@ const StateExploreWrapper = (props) => {
|
||||
};
|
||||
const mockImageList = {
|
||||
GlobalSearch: {
|
||||
Page: { TotalCount: 20, ItemCount: 10 },
|
||||
Repos: [
|
||||
{
|
||||
Name: 'alpine',
|
||||
@ -128,6 +129,18 @@ const mockImageList = {
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// IntersectionObserver isn't available in test environment
|
||||
const mockIntersectionObserver = jest.fn();
|
||||
mockIntersectionObserver.mockReturnValue({
|
||||
observe: () => null,
|
||||
unobserve: () => null,
|
||||
disconnect: () => null
|
||||
});
|
||||
window.IntersectionObserver = mockIntersectionObserver;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// restore the spy created with spyOn
|
||||
jest.restoreAllMocks();
|
||||
|
@ -11,110 +11,112 @@ jest.mock('react-router-dom', () => ({
|
||||
}));
|
||||
|
||||
const mockImageList = {
|
||||
RepoListWithNewestImage: [
|
||||
{
|
||||
Name: 'alpine',
|
||||
Size: '2806985',
|
||||
LastUpdated: '2022-08-09T17:19:53.274069586Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: 'w',
|
||||
IsSigned: false,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'LOW',
|
||||
Count: 7
|
||||
RepoListWithNewestImage: {
|
||||
Results: [
|
||||
{
|
||||
Name: 'alpine',
|
||||
Size: '2806985',
|
||||
LastUpdated: '2022-08-09T17:19:53.274069586Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: 'w',
|
||||
IsSigned: false,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'LOW',
|
||||
Count: 7
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'mongo',
|
||||
Size: '231383863',
|
||||
LastUpdated: '2022-08-02T01:30:49.193203152Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'HIGH',
|
||||
Count: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'node',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'CRITICAL',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'centos',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'NONE',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'debian',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'MEDIUM',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'mysql',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'UNKNOWN',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'mongo',
|
||||
Size: '231383863',
|
||||
LastUpdated: '2022-08-02T01:30:49.193203152Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'HIGH',
|
||||
Count: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'node',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'CRITICAL',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'centos',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'NONE',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'debian',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'MEDIUM',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: 'mysql',
|
||||
Size: '369311301',
|
||||
LastUpdated: '2022-08-23T00:20:40.144281895Z',
|
||||
NewestImage: {
|
||||
Tag: 'latest',
|
||||
Description: '',
|
||||
IsSigned: true,
|
||||
Licenses: '',
|
||||
Vendor: '',
|
||||
Labels: '',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'UNKNOWN',
|
||||
Count: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@ -139,7 +141,7 @@ describe('Home component', () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
|
||||
render(<Home />);
|
||||
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(2);
|
||||
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(4);
|
||||
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('renders vulnerability icons', async () => {
|
||||
@ -148,7 +150,6 @@ describe('Home component', () => {
|
||||
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(2);
|
||||
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(2);
|
||||
expect(await screen.findAllByTestId('critical-vulnerability-icon')).toHaveLength(1);
|
||||
expect(await screen.findAllByTestId('none-vulnerability-icon')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should log an error when data can't be fetched", async () => {
|
||||
|
@ -2,6 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { api } from 'api';
|
||||
import SearchSuggestion from 'components/SearchSuggestion';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
// router mock
|
||||
@ -11,6 +12,15 @@ jest.mock('react-router-dom', () => ({
|
||||
useNavigate: () => mockedUsedNavigate
|
||||
}));
|
||||
|
||||
const RouterSearchWrapper = (props) => {
|
||||
const queryString = props.search || '';
|
||||
return (
|
||||
<MemoryRouter initialEntries={[queryString]}>
|
||||
<SearchSuggestion />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const mockImageList = {
|
||||
GlobalSearch: {
|
||||
Repos: [
|
||||
@ -71,7 +81,7 @@ afterEach(() => {
|
||||
describe('Search component', () => {
|
||||
it('should display suggestions when user searches', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
|
||||
render(<SearchSuggestion />);
|
||||
render(<RouterSearchWrapper />);
|
||||
const searchInput = screen.getByPlaceholderText(/search for content/i);
|
||||
expect(searchInput).toBeInTheDocument();
|
||||
userEvent.type(searchInput, 'test');
|
||||
@ -80,7 +90,7 @@ describe('Search component', () => {
|
||||
|
||||
it('should navigate to repo page when a repo suggestion is clicked', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
|
||||
render(<SearchSuggestion />);
|
||||
render(<RouterSearchWrapper />);
|
||||
const searchInput = screen.getByPlaceholderText(/search for content/i);
|
||||
userEvent.type(searchInput, 'test');
|
||||
const suggestionItemRepo = await screen.findByText(/alpine/i);
|
||||
@ -90,7 +100,7 @@ describe('Search component', () => {
|
||||
|
||||
it('should navigate to repo page when a image suggestion is clicked', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageList } });
|
||||
render(<SearchSuggestion />);
|
||||
render(<RouterSearchWrapper />);
|
||||
const searchInput = screen.getByPlaceholderText(/search for content/i);
|
||||
userEvent.type(searchInput, 'debian:test');
|
||||
const suggestionItemImage = await screen.findByText(/debian:testTag/i);
|
||||
@ -101,7 +111,7 @@ describe('Search component', () => {
|
||||
it('should log an error if it doesnt receive an ok response for repo search', async () => {
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<SearchSuggestion />);
|
||||
render(<RouterSearchWrapper />);
|
||||
const searchInput = screen.getByPlaceholderText(/search for content/i);
|
||||
userEvent.type(searchInput, 'debian');
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
@ -110,7 +120,7 @@ describe('Search component', () => {
|
||||
it('should log an error if it doesnt receive an ok response for image search', async () => {
|
||||
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<SearchSuggestion />);
|
||||
render(<RouterSearchWrapper />);
|
||||
const searchInput = screen.getByPlaceholderText(/search for content/i);
|
||||
userEvent.type(searchInput, 'debian:test');
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
|
@ -63,7 +63,7 @@ const endpoints = {
|
||||
repoList: ({ pageNumber = 1, pageSize = 15 } = {}) =>
|
||||
`/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
}}){Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Logo Title Source IsSigned Documentation Vendor Labels} DownloadCount}}`,
|
||||
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Logo Title Source IsSigned Documentation Vendor Labels} DownloadCount}}}`,
|
||||
detailedRepoInfo: (name) =>
|
||||
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Digest Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor Size Platform {Os Arch}} Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {RepoName IsSigned Vulnerabilities {MaxSeverity Count} Layers {Size Digest} Digest Tag Logo Title Documentation DownloadCount Source Description Licenses History {Layer {Size Digest} HistoryDescription {Created CreatedBy Author Comment EmptyLayer}}}}}}`,
|
||||
detailedImageInfo: (name, tag) =>
|
||||
@ -95,7 +95,7 @@ const endpoints = {
|
||||
if (filter.HasToBeSigned) filterParam += ` HasToBeSigned: ${filter.HasToBeSigned}`;
|
||||
filterParam += '}';
|
||||
if (Object.keys(filter).length === 0) filterParam = '';
|
||||
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Repos {Name LastUpdated Size Platforms { Os Arch } NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned Logo Licenses Vendor Labels } DownloadCount}}}`;
|
||||
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Page {TotalCount ItemCount} Repos {Name LastUpdated Size Platforms { Os Arch } NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned Logo Licenses Vendor Labels } DownloadCount}}}`;
|
||||
},
|
||||
imageSuggestions: ({ searchQuery = '""', pageNumber = 1, pageSize = 15 }) => {
|
||||
const searchParam = searchQuery !== '' ? `query:"${searchQuery}"` : `query:""`;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// react global
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
// components
|
||||
import RepoCard from './RepoCard.jsx';
|
||||
@ -19,6 +19,7 @@ import FilterCard from './FilterCard.jsx';
|
||||
import { isEmpty } from 'lodash';
|
||||
import filterConstants from 'utilities/filterConstants.js';
|
||||
import { sortByCriteria } from 'utilities/sortCriteria.js';
|
||||
import { EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants.js';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
gridWrapper: {
|
||||
@ -63,6 +64,11 @@ function Explore() {
|
||||
const [imageFilters, setImageFilters] = useState(false);
|
||||
const [osFilters, setOSFilters] = useState([]);
|
||||
const [archFilters, setArchFilters] = useState([]);
|
||||
// pagination props
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [totalItems, setTotalItems] = useState(0);
|
||||
const [isEndOfList, setIsEndOfList] = useState(false);
|
||||
const listBottom = useRef(null);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
const classes = useStyles();
|
||||
|
||||
@ -77,11 +83,17 @@ function Explore() {
|
||||
return filter;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const getPaginatedResults = () => {
|
||||
setIsLoading(true);
|
||||
api
|
||||
.get(
|
||||
`${host()}${endpoints.globalSearch({ searchQuery: search, sortBy: sortFilter, filter: buildFilterQuery() })}`,
|
||||
`${host()}${endpoints.globalSearch({
|
||||
searchQuery: search,
|
||||
pageNumber,
|
||||
pageSize: EXPLORE_PAGE_SIZE,
|
||||
sortBy: sortFilter,
|
||||
filter: buildFilterQuery()
|
||||
})}`,
|
||||
abortController.signal
|
||||
)
|
||||
.then((response) => {
|
||||
@ -90,19 +102,71 @@ function Explore() {
|
||||
let repoData = repoList.map((responseRepo) => {
|
||||
return mapToRepo(responseRepo);
|
||||
});
|
||||
setExploreData(repoData);
|
||||
setTotalItems(response.data.data.GlobalSearch.Page?.TotalCount);
|
||||
setIsEndOfList(response.data.data.GlobalSearch.Page?.ItemCount < EXPLORE_PAGE_SIZE);
|
||||
setExploreData((previousState) => [...previousState, ...repoData]);
|
||||
setIsLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setIsLoading(false);
|
||||
setIsEndOfList(true);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) return;
|
||||
getPaginatedResults();
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [pageNumber]);
|
||||
|
||||
const resetPagination = () => {
|
||||
if (pageNumber === 1) {
|
||||
getPaginatedResults();
|
||||
} else {
|
||||
setPageNumber(1);
|
||||
}
|
||||
setIsEndOfList(false);
|
||||
setExploreData([]);
|
||||
};
|
||||
|
||||
// if filters changed, reset pagination and restart lookup
|
||||
useEffect(() => {
|
||||
resetPagination();
|
||||
}, [search, queryParams, imageFilters, osFilters, archFilters, sortFilter]);
|
||||
|
||||
const handleSortChange = (event) => {
|
||||
setSortFilter(event.target.value);
|
||||
};
|
||||
|
||||
// setup intersection obeserver for infinite scroll
|
||||
useEffect(() => {
|
||||
if (isLoading || isEndOfList) return;
|
||||
const handleIntersection = (entries) => {
|
||||
if (isLoading || isEndOfList) return;
|
||||
const [target] = entries;
|
||||
if (target?.isIntersecting) {
|
||||
setPageNumber((pageNumber) => pageNumber + 1);
|
||||
}
|
||||
};
|
||||
const intersectionObserver = new IntersectionObserver(handleIntersection, {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 0
|
||||
});
|
||||
|
||||
if (listBottom.current) {
|
||||
intersectionObserver.observe(listBottom.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
intersectionObserver.disconnect();
|
||||
};
|
||||
}, [isLoading, isEndOfList]);
|
||||
|
||||
const renderRepoCards = () => {
|
||||
return (
|
||||
exploreData &&
|
||||
@ -154,8 +218,14 @@ function Explore() {
|
||||
);
|
||||
};
|
||||
|
||||
const handleSortChange = (event) => {
|
||||
setSortFilter(event.target.value);
|
||||
const renderListBottom = () => {
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
if (!isLoading && !isEndOfList) {
|
||||
return <div ref={listBottom} />;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
@ -166,7 +236,7 @@ function Explore() {
|
||||
<Grid item xs={9}>
|
||||
<Stack direction="row" className={classes.resultsRow}>
|
||||
<Typography variant="body2" className={classes.results}>
|
||||
Results {exploreData.length}
|
||||
Showing {exploreData?.length} results out of {totalItems}
|
||||
</Typography>
|
||||
<FormControl sx={{ m: '1', minWidth: '4.6875rem' }} className={classes.sortForm} size="small">
|
||||
<InputLabel>Sort</InputLabel>
|
||||
@ -201,7 +271,8 @@ function Explore() {
|
||||
</Grid>
|
||||
) : (
|
||||
<Stack direction="column" spacing={2}>
|
||||
{isLoading ? <Loading /> : renderRepoCards()}
|
||||
{renderRepoCards()}
|
||||
{renderListBottom()}
|
||||
</Stack>
|
||||
)}
|
||||
</Grid>
|
||||
|
@ -73,7 +73,7 @@ function Home() {
|
||||
.get(`${host()}${endpoints.repoList()}`, abortController.signal)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
let repoList = response.data.data.RepoListWithNewestImage;
|
||||
let repoList = response.data.data.RepoListWithNewestImage.Results;
|
||||
let repoData = repoList.map((responseRepo) => {
|
||||
return mapToRepo(responseRepo);
|
||||
});
|
||||
@ -92,7 +92,7 @@ function Home() {
|
||||
const renderMostPopular = () => {
|
||||
return (
|
||||
homeData &&
|
||||
homeData.slice(0, 4).map((item, index) => {
|
||||
homeData.slice(0, 3).map((item, index) => {
|
||||
return (
|
||||
<RepoCard
|
||||
name={item.name}
|
||||
|
@ -6,9 +6,10 @@ import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { api, endpoints } from 'api';
|
||||
import { host } from 'host';
|
||||
import { mapToImage, mapToRepo } from 'utilities/objectModels';
|
||||
import { createSearchParams, useNavigate } from 'react-router-dom';
|
||||
import { createSearchParams, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { debounce, isEmpty } from 'lodash';
|
||||
import { useCombobox } from 'downshift';
|
||||
import { HEADER_SEARCH_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
searchContainer: {
|
||||
@ -94,12 +95,16 @@ const useStyles = makeStyles(() => ({
|
||||
}));
|
||||
|
||||
function SearchSuggestion() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [queryParams] = useSearchParams();
|
||||
const search = queryParams.get('search');
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState(search || '');
|
||||
const [suggestionData, setSuggestionData] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isFailedSearch, setIsFailedSearch] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const handleSuggestionSelected = (event) => {
|
||||
@ -114,16 +119,15 @@ function SearchSuggestion() {
|
||||
|
||||
const handleSearch = (event) => {
|
||||
const { key, type } = event;
|
||||
const { value } = event.target;
|
||||
if (key === 'Enter' || type === 'click') {
|
||||
navigate({ pathname: `/explore`, search: createSearchParams({ search: value || '' }).toString() });
|
||||
navigate({ pathname: `/explore`, search: createSearchParams({ search: inputValue || '' }).toString() });
|
||||
}
|
||||
};
|
||||
|
||||
const repoSearch = (value) => {
|
||||
api
|
||||
.get(
|
||||
`${host()}${endpoints.globalSearch({ searchQuery: value, pageNumber: 1, pageSize: 9 })}`,
|
||||
`${host()}${endpoints.globalSearch({ searchQuery: value, pageNumber: 1, pageSize: HEADER_SEARCH_PAGE_SIZE })}`,
|
||||
abortController.signal
|
||||
)
|
||||
.then((suggestionResponse) => {
|
||||
@ -198,10 +202,11 @@ function SearchSuggestion() {
|
||||
debounceSuggestions.cancel();
|
||||
abortController.abort();
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const {
|
||||
// selectedItem,
|
||||
inputValue,
|
||||
getInputProps,
|
||||
getMenuProps,
|
||||
getItemProps,
|
||||
@ -214,7 +219,8 @@ function SearchSuggestion() {
|
||||
items: suggestionData,
|
||||
onInputValueChange: handleSeachChange,
|
||||
onSelectedItemChange: handleSuggestionSelected,
|
||||
itemToString: (item) => item.name ?? ''
|
||||
initialInputValue: search ?? '',
|
||||
itemToString: (item) => item.name ?? item
|
||||
});
|
||||
|
||||
const renderSuggestions = () => {
|
||||
@ -255,7 +261,7 @@ function SearchSuggestion() {
|
||||
>
|
||||
<InputBase
|
||||
style={{ paddingLeft: 10, height: 46, color: 'rgba(0, 0, 0, 0.6)' }}
|
||||
placeholder="Search for content..."
|
||||
placeholder={'Search for content...'}
|
||||
className={classes.input}
|
||||
onKeyUp={handleSearch}
|
||||
onFocus={() => openMenu()}
|
||||
|
5
src/utilities/paginationConstants.js
Normal file
5
src/utilities/paginationConstants.js
Normal file
@ -0,0 +1,5 @@
|
||||
const HEADER_SEARCH_PAGE_SIZE = 9;
|
||||
const EXPLORE_PAGE_SIZE = 10;
|
||||
const HOME_PAGE_SIZE = 10;
|
||||
|
||||
export { HEADER_SEARCH_PAGE_SIZE, EXPLORE_PAGE_SIZE, HOME_PAGE_SIZE };
|
Loading…
Reference in New Issue
Block a user