diff --git a/package.json b/package.json
index 2942599..13858cf 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"version": "1.6.3",
"private": true,
"dependencies": {
+ "@ant-design/icons": "^4.7.0",
"antd": "^3.12.1",
"debounce": "^1.2.0",
"react": "^16.7.0",
diff --git a/src/components/App.js b/src/components/App.js
index 24949b2..58e763d 100644
--- a/src/components/App.js
+++ b/src/components/App.js
@@ -16,6 +16,7 @@ window.apiUrl = process.env.REACT_APP_API_URL;
export default function App({ match }) {
const { page } = match.params;
const [services, setServices] = useState([]);
+ const [filteredServices, setFilteredServices] = useState();
const [username, setUsername] = useState('');
const fetchServices = async () => {
@@ -33,6 +34,12 @@ export default function App({ match }) {
setUsername(text);
};
+ // Can not listen localstorage changes on the same window
+ // Had to use callback
+ const onFilterChange = filters => {
+ setFilteredServices(filters);
+ };
+
return useMemo(() => {
// main content of page
let content;
@@ -40,7 +47,7 @@ export default function App({ match }) {
if (username.length > 0) {
content = (
{illustrationEnabled && (
diff --git a/src/components/Results.js b/src/components/Results.js
index 28bb795..3b66ef1 100644
--- a/src/components/Results.js
+++ b/src/components/Results.js
@@ -3,14 +3,16 @@ import debounce from 'debounce';
import '../styles/Results.css';
import ResultCard from './ResultCard';
-export default function Results({ username, services }) {
+export default function Results({ username, services, filteredServices }) {
+ const [selectedServices] = useState(filteredServices || []);
+
// these are cards with loading state enabled. Create once, use many times
const spinningCards = useMemo(
() =>
- services.map(({ service: serviceName }) => (
+ (selectedServices?.length ? selectedServices : services).map(({ service: serviceName }) => (
)),
- [services],
+ [services, selectedServices],
);
// cards to render in this component
@@ -18,12 +20,14 @@ export default function Results({ username, services }) {
// returns real functional cards
const createCards = (username, services) =>
- services.map(({ service: serviceName, endpoint }) => {
- const checkEndpoint = endpoint.replace('{username}', username);
- return (
-
- );
- });
+ (selectedServices?.length ? selectedServices : services).map(
+ ({ service: serviceName, endpoint }) => {
+ const checkEndpoint = endpoint.replace('{username}', username);
+ return (
+
+ );
+ },
+ );
// sets the cards with a debounce. This allows us not to calculate real cards while user keeps typing
const debouncedSetCards = useCallback(
diff --git a/src/components/Search.js b/src/components/Search.js
index 31015b1..cf991c0 100644
--- a/src/components/Search.js
+++ b/src/components/Search.js
@@ -1,27 +1,60 @@
-import React, { useMemo } from 'react';
-import { Input, Icon } from 'antd';
+import React, { useMemo, useRef, useEffect, useState, useCallback } from 'react';
+import { Input, Icon, Select, Button } from 'antd';
import { Link } from 'react-router-dom';
+import { SettingOutlined } from '@ant-design/icons';
+import useLocalStorage from '../hooks/useLocalStorage';
// TODO: use styled components instead
import '../styles/Search.css';
-export default function Search({ input, onChange }) {
- const inputChanged = ({ target }) => {
+export default function Search({ input, onChange, services, onFilterChange }) {
+ const inputRef = useRef();
+ const [filters, setFilters] = useLocalStorage('filters', []);
+ const [selectedObjects, setSelectedObjects] = useLocalStorage('filterObjects', []);
+ const [isFilterActive, setFilterActive] = useState(Boolean(selectedObjects.length));
+
+ useEffect(() => {
+ onFilterChange(selectedObjects);
+ }, [onFilterChange, selectedObjects]);
+
+ const prettifyInput = useCallback(input => {
// niceInput is the url friendly version of the input
- let niceInput = target.value.replace(/[^a-zA-Z0-9-_.]/g, '');
- onChange(niceInput);
- };
+ return input.replace(/[^a-zA-Z0-9-_.]/g, '');
+ }, []);
+
+ const inputChanged = useCallback(
+ ({ target }) => {
+ onChange(prettifyInput(target.value));
+ },
+ [onChange, prettifyInput],
+ );
- const clearInput = () => {
- onChange('');
+ const findServiceObjects = useCallback(
+ selections => {
+ return services?.filter(s => selections?.includes(s.service));
+ },
+ [services],
+ );
+
+ const onSearchTargetChange = useCallback(
+ values => {
+ setFilters(values);
+ setSelectedObjects(findServiceObjects(values));
+ onChange('');
+ },
+ [onChange, setSelectedObjects, setFilters, findServiceObjects],
+ );
+
+ const onSearchOptionButtonClick = () => {
+ setFilterActive(oldState => !oldState);
};
- return useMemo(() => {
+ const searchContent = useMemo(() => {
return (
-
+ <>
{
- clearInput();
+ onChange('');
}}
>
@@ -30,6 +63,8 @@ export default function Search({ input, onChange }) {
-
+ >
+ );
+ }, [input, onChange, inputChanged]);
+
+ const filterContent = useMemo(() => {
+ return (
+ <>
+
+
+ >
);
- // eslint-disable-next-line
- }, [input]);
+ }, [services, filters, onSearchTargetChange]);
+
+ const filterContentWrapper = useMemo(() => {
+ return (
+
{filterContent}
+ );
+ }, [filterContent, isFilterActive]);
+
+ return (
+
+ {searchContent}
+ {filterContentWrapper}
+
+ );
}
diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js
new file mode 100644
index 0000000..bbbe942
--- /dev/null
+++ b/src/hooks/useLocalStorage.js
@@ -0,0 +1,34 @@
+import { useState } from 'react';
+
+function useLocalStorage(key, initialValue) {
+ const [storedValue, setStoredValue] = useState(() => {
+ try {
+ // Get from local storage by key
+ const item = window.localStorage.getItem(key);
+ // Parse stored json or if none return initialValue
+ return item ? JSON.parse(item) : initialValue;
+ } catch (error) {
+ // If error also return initialValue
+ console.log(error);
+ return initialValue;
+ }
+ });
+ // Return a wrapped version of useState's setter function that ...
+ // ... persists the new value to localStorage.
+ const setValue = value => {
+ try {
+ // Allow value to be a function so we have same API as useState
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
+ // Save state
+ setStoredValue(valueToStore);
+ // Save to local storage
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
+ } catch (error) {
+ // A more advanced implementation would handle the error case
+ console.log(error);
+ }
+ };
+ return [storedValue, setValue];
+}
+
+export default useLocalStorage;
diff --git a/src/styles/Search.css b/src/styles/Search.css
index 9435f30..6f391bd 100644
--- a/src/styles/Search.css
+++ b/src/styles/Search.css
@@ -16,6 +16,45 @@
margin-right: auto;
}
+.searchInput {
+ z-index: 1;
+}
+
+.advancedSearchWrapper {
+ position: relative;
+ width: 90%;
+ color: white;
+ font-size: 1.125rem;
+ padding: 0.25em;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ top: -45px;
+ transition: 0.3s;
+}
+
+.advancedSearchWrapper > div.ant-select {
+ opacity: 0;
+ transition: 0.3s;
+}
+
+.advancedSearchWrapper.active > div.ant-select {
+ opacity: 1;
+}
+
+.advancedSearchWrapper.active {
+ top: 0;
+}
+
+.searchOptionButton {
+ margin-top: 4.5px;
+}
+
+.targetSelector {
+ width: 100%;
+ background: transparent;
+}
+
.header {
display: flex;
flex-direction: row;
@@ -46,10 +85,10 @@
.anticon-thunderbolt {
font-size: 2.4rem !important;
}
- .ant-input.ant-input-lg {
+ /* .ant-input.ant-input-lg {
font-size: 0.9rem;
height: 1.8rem !important;
- }
+ } */
}
@media (min-width: 360px) {
@@ -62,10 +101,10 @@
.anticon-thunderbolt {
font-size: 3rem !important;
}
- .ant-input.ant-input-lg {
+ /* .ant-input.ant-input-lg {
font-size: 1.05rem;
height: 2.1rem !important;
- }
+ } */
}
@media (min-width: 768px) {
@@ -78,10 +117,10 @@
.anticon-thunderbolt {
font-size: 3.7rem !important;
}
- .ant-input.ant-input-lg {
+ /* .ant-input.ant-input-lg {
font-size: 1.3rem;
height: 2.6rem !important;
- }
+ } */
}
@media (min-width: 992px) {
.header {
@@ -93,8 +132,8 @@
.anticon-thunderbolt {
font-size: 3.8rem !important;
}
- .ant-input.ant-input-lg {
+ /* .ant-input.ant-input-lg {
font-size: 1.25rem;
height: 2.7rem !important;
- }
+ } */
}