Skip to content

Commit 825d262

Browse files
added search suggestion service
1 parent 9559372 commit 825d262

File tree

40 files changed

+1416
-289
lines changed

40 files changed

+1416
-289
lines changed

client/src/actions/index.js

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ import {
1111
PAYMENT_RESPONSE,
1212
HANDLE_GOOGLE_AUTH_SIGN_IN,
1313
HANDLE_GOOGLE_AUTH_SIGN_OUT,
14-
PAYMENT_RESPONSE_ERROR,
14+
PAYMENT_RESPONSE_ERROR, SEARCH_KEYWORD_ERROR, SEARCH_KEYWORD,
1515
} from './types';
1616
import {INTERNAL_SERVER_ERROR_CODE, BAD_REQUEST_ERROR_CODE} from '../constants/http_error_codes'
1717
import {SHOPPERS_PRODUCT_INFO_COOKIE, CART_TOTAL_COOKIE, AUTH_DETAILS_COOKIE} from '../constants/cookies'
1818
import history from "../history";
1919
import {Base64} from 'js-base64';
2020
import Cookies from 'js-cookie';
2121
import log from "loglevel";
22-
import {commonServiceAPI, authServiceAPI} from "../api/service_api";
22+
import {commonServiceAPI, authServiceAPI, searchSuggestionServiceAPI} from "../api/service_api";
2323
import axios from 'axios';
24+
import {DEFAULT_SEARCH_SUGGESTION_API, SEARCH_SUGGESTION_API} from "../constants/api_routes";
2425

2526
export const setAuthDetailsFromCookie = savedResponse => {
2627
log.info(`[ACTION]: setTokenFromCookie savedResponse = ${savedResponse}`)
@@ -101,45 +102,45 @@ export const signInUsingOAuth = googleAuth => async dispatch => {
101102
history.push("/");
102103

103104
// try {
104-
// let userProfile = googleAuth.currentUser.get().getBasicProfile()
105-
// if (userProfile) {
106-
// const response = await authServiceAPI.post('/signin-using-google-auth', {
107-
// 'id': userProfile.getId(),
108-
// 'firstname': userProfile.getGivenName(),
109-
// 'lastname': userProfile.getFamilyName(),
110-
// 'email': userProfile.getEmail(),
111-
// 'username': null,
112-
// 'password': null,
113-
// }).catch(err => {
114-
// log.info(`[ACTION]: signUp dispatch HANDLE_SIGN_UP_ERROR err.message = ${err.message}.`)
115-
// });
116-
//
117-
// if(response.data === "success") {
118-
// // here we are sure that we signed in and now dispatch.
119-
// dispatch({
120-
// type: HANDLE_GOOGLE_AUTH_SIGN_IN,
121-
// payload: {
122-
// oAuth: googleAuth
123-
// }
124-
// })
125-
// history.push("/");
126-
// } else {
127-
// dispatch({type: HANDLE_SIGN_IN_ERROR, payload: response.data.error});
128-
// }
129-
130-
// dispatch({
131-
// type: HANDLE_GOOGLE_AUTH_SIGN_IN,
132-
// payload: {
133-
// oAuth: googleAuth
134-
// }
135-
// })
136-
// history.push("/");
137-
// }
138-
// } catch
139-
// (e) {
140-
// log.info(`[signInUsingOAuth] Unable to retrieve user profile.`)
141-
// dispatch({type: HANDLE_SIGN_IN_ERROR, payload: "Unable to retrieve user profile."});
142-
// }
105+
// let userProfile = googleAuth.currentUser.get().getBasicProfile()
106+
// if (userProfile) {
107+
// const response = await authServiceAPI.post('/signin-using-google-auth', {
108+
// 'id': userProfile.getId(),
109+
// 'firstname': userProfile.getGivenName(),
110+
// 'lastname': userProfile.getFamilyName(),
111+
// 'email': userProfile.getEmail(),
112+
// 'username': null,
113+
// 'password': null,
114+
// }).catch(err => {
115+
// log.info(`[ACTION]: signUp dispatch HANDLE_SIGN_UP_ERROR err.message = ${err.message}.`)
116+
// });
117+
//
118+
// if(response.data === "success") {
119+
// // here we are sure that we signed in and now dispatch.
120+
// dispatch({
121+
// type: HANDLE_GOOGLE_AUTH_SIGN_IN,
122+
// payload: {
123+
// oAuth: googleAuth
124+
// }
125+
// })
126+
// history.push("/");
127+
// } else {
128+
// dispatch({type: HANDLE_SIGN_IN_ERROR, payload: response.data.error});
129+
// }
130+
131+
// dispatch({
132+
// type: HANDLE_GOOGLE_AUTH_SIGN_IN,
133+
// payload: {
134+
// oAuth: googleAuth
135+
// }
136+
// })
137+
// history.push("/");
138+
// }
139+
// } catch
140+
// (e) {
141+
// log.info(`[signInUsingOAuth] Unable to retrieve user profile.`)
142+
// dispatch({type: HANDLE_SIGN_IN_ERROR, payload: "Unable to retrieve user profile."});
143+
// }
143144
}
144145
}
145146
)
@@ -194,15 +195,15 @@ export const signUp = formValues => async dispatch => {
194195

195196
export const sendPaymentToken = (token) => async dispatch => {
196197
log.info(`Token = ${JSON.stringify(token)}`)
197-
if(!token || (token && !token.hasOwnProperty("id"))) {
198+
if (!token || (token && !token.hasOwnProperty("id"))) {
198199
dispatch({
199200
type: PAYMENT_RESPONSE_ERROR,
200201
payload: {errorMsg: "Unable to fetch token. Try again later"}
201202
})
202203
}
203204

204205
let url
205-
if(process.env.REACT_APP_PAYMENT_SERVICE_URL) {
206+
if (process.env.REACT_APP_PAYMENT_SERVICE_URL) {
206207
url = `${process.env.REACT_APP_PAYMENT_SERVICE_URL}/payment`
207208
} else {
208209
url = `http://localhost:${process.env.REACT_APP_PAYMENT_SERVICE_PORT}/payment`
@@ -318,3 +319,48 @@ export const loadFilterAttributes = filterQuery => async dispatch => {
318319
}
319320
}
320321
};
322+
323+
export const getSearchSuggestions = (prefix) => async dispatch => {
324+
log.info(`[ACTION]: getSearchSuggestions Calling API.`)
325+
326+
if (prefix) {
327+
let responseError = false
328+
const uri = SEARCH_SUGGESTION_API + prefix
329+
const response = await searchSuggestionServiceAPI.get(uri)
330+
.catch(err => {
331+
log.info(`[ACTION]: unable to fetch response for API = ${uri}`)
332+
dispatch({type: SEARCH_KEYWORD_ERROR});
333+
responseError = true
334+
});
335+
336+
if (responseError) {
337+
return
338+
}
339+
340+
log.debug(`[ACTION]: Data = ${JSON.parse(JSON.stringify(response.data))}.`)
341+
dispatch({
342+
type: SEARCH_KEYWORD, payload: {data: JSON.parse(JSON.stringify(response.data))}
343+
});
344+
}
345+
346+
}
347+
348+
export const setDefaultSearchSuggestions = () => async dispatch => {
349+
log.info(`[ACTION]: getSearchSuggestions Calling API.`)
350+
let responseError = false
351+
const response = await searchSuggestionServiceAPI.get(DEFAULT_SEARCH_SUGGESTION_API)
352+
.catch(err => {
353+
log.info(`[ACTION]: unable to fetch response for API = ${DEFAULT_SEARCH_SUGGESTION_API}`)
354+
dispatch({type: SEARCH_KEYWORD_ERROR});
355+
responseError = true
356+
});
357+
358+
if (responseError) {
359+
return
360+
}
361+
362+
log.debug(`[ACTION]: Data = ${JSON.parse(JSON.stringify(response.data))}.`)
363+
dispatch({
364+
type: SEARCH_KEYWORD, payload: {data: JSON.parse(JSON.stringify(response.data))}
365+
});
366+
}

client/src/actions/types.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ export const PAYMENT_RESPONSE_ERROR = "PAYMENT_RESPONSE_ERROR";
4343
export const RESET_PAYMENT_RESPONSE = "RESET_PAYMENT_RESPONSE";
4444
export const RESET_PAYMENT_RESPONSE_ERROR = "RESET_PAYMENT_RESPONSE_ERROR";
4545

46+
export const SEARCH_KEYWORD = "SEARCH_KEYWORD";
47+
export const SEARCH_KEYWORD_ERROR = "SEARCH_KEYWORD_ERROR";
48+
4649

client/src/api/service_api.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import axios from 'axios';
33
const {
44
REACT_APP_COMMON_DATA_SERVICE_PORT,
55
REACT_APP_AUTHENTICATION_SERVICE_PORT,
6+
REACT_APP_SEARCH_SUGGESTION_SERVICE_PORT,
67
REACT_APP_COMMON_DATA_SERVICE_URL,
78
REACT_APP_AUTHENTICATION_SERVICE_URL,
9+
REACT_APP_SEARCH_SUGGESTION_SERVICE_URL
810
} = process.env
911

1012
export const authServiceAPI = axios.create({
@@ -13,4 +15,8 @@ export const authServiceAPI = axios.create({
1315

1416
export const commonServiceAPI = axios.create({
1517
baseURL: REACT_APP_COMMON_DATA_SERVICE_URL || `http://localhost:${REACT_APP_COMMON_DATA_SERVICE_PORT}`
18+
})
19+
20+
export const searchSuggestionServiceAPI = axios.create({
21+
baseURL: REACT_APP_SEARCH_SUGGESTION_SERVICE_URL || `http://localhost:${REACT_APP_SEARCH_SUGGESTION_SERVICE_PORT}`
1622
})

client/src/components/routes/navbar/navBar.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import MoreIcon from '@material-ui/icons/MoreVert';
88
import Cookies from 'js-cookie';
99
import {
1010
getDataViaAPI, setAuthDetailsFromCookie,
11-
signOut, signOutUsingOAuth
11+
signOut, signOutUsingOAuth, setDefaultSearchSuggestions
1212
} from '../../../actions';
1313
import {connect, useDispatch} from 'react-redux'
1414

@@ -124,6 +124,9 @@ const NavBar = props => {
124124
// set the cart values
125125
setAddToCartValuesFromCookie()
126126

127+
// set default search suggestions
128+
props.setDefaultSearchSuggestions()
129+
127130
// eslint-disable-next-line
128131
}, [isSignedIn, tabsDataReducer]);
129132

@@ -343,4 +346,5 @@ const NavBar = props => {
343346
);
344347
};
345348

346-
export default connect(null, {setAuthDetailsFromCookie, signOut, signOutUsingOAuth, getDataViaAPI})(NavBar);
349+
export default connect(null, {setAuthDetailsFromCookie, signOut,
350+
signOutUsingOAuth, getDataViaAPI, setDefaultSearchSuggestions})(NavBar);
Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,97 @@
1-
import React from 'react';
1+
import React, {useRef, useState} from 'react';
22
import TextField from '@material-ui/core/TextField';
33
import Autocomplete from '@material-ui/lab/Autocomplete';
44
import CloseIcon from '@material-ui/icons/Close';
55
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
66
import {Grid} from "@material-ui/core";
77
import SearchIcon from '@material-ui/icons/Search';
8+
import log from 'loglevel';
9+
import {connect, useSelector} from "react-redux";
10+
import {getSearchSuggestions} from "../../../actions";
11+
import {makeStyles} from "@material-ui/core/styles";
12+
13+
export const useSearchBarStyles = makeStyles(() => ({
14+
paper: {
15+
height: 300
16+
},
17+
listbox: {
18+
maxHeight: 340
19+
}
20+
}));
821

922
function SearchBar(props) {
10-
const [value, setValue] = React.useState(null);
23+
const [value, setValue] = useState(null);
24+
const searchSuggestions = useSelector(state => state.searchKeywordReducer)
25+
const classes = useSearchBarStyles()
26+
const textFieldRef = useRef(null)
1127

12-
const handleClose = () => {
28+
const handleClose = (event, reason) => {
1329
if (props.handleClose) {
1430
props.handleClose();
1531
}
32+
33+
// search is selected
34+
if(reason === "select-option") {
35+
log.info("Search is selected.... value = "
36+
+ event.target.getAttributeNames())
37+
}
1638
}
1739

1840
const renderDesktopTextField = (params) => {
19-
return <TextField {...params} label="Search for products, brands and more" variant="outlined"/>
41+
return <TextField {...params} label="Search for products, brands and more" variant="outlined"/>
2042
}
2143

2244
const renderMobileTextField = (params) => {
2345
return (
24-
<TextField
25-
style={{position: "absolute", left: 0, top: 15}}
26-
label="Search for products, brands and more"
27-
variant="outlined"
28-
{...params}
29-
InputProps={{
30-
...params.InputProps,
31-
startAdornment: <ArrowBackIcon onClick={props.handleClose} fontSize="large"/>,
32-
endAdornment: <SearchIcon fontSize="large"/>
33-
}}
34-
/>
46+
<TextField
47+
style={{position: "absolute", left: 0, top: 15}}
48+
label="Search for products, brands and more"
49+
variant="outlined"
50+
{...params}
51+
InputProps={{
52+
...params.InputProps,
53+
startAdornment: <ArrowBackIcon onClick={props.handleClose} fontSize="large"/>,
54+
endAdornment: <SearchIcon fontSize="large"/>
55+
}}
56+
/>
3557
)
3658
}
3759

60+
const handleInputChange = (event, newValue) => {
61+
props.getSearchSuggestions(newValue)
62+
}
63+
64+
log.info("[Search Bar] Rendering search bar....")
65+
3866
return (
3967
<Grid container alignItems="center">
4068
<Autocomplete
4169
value={value}
42-
autoHighlight
70+
autoComplete={true}
71+
autoHighlight={true}
4372
onChange={(event, newValue) => {
4473
if (typeof newValue === 'string') {
4574
setValue({
46-
title: newValue,
75+
keyword: newValue,
4776
});
4877
} else if (newValue && newValue.inputValue) {
4978
// Create a new value from the user input
5079
setValue({
51-
title: newValue.inputValue,
80+
keyword: newValue.inputValue,
5281
});
5382
} else {
5483
setValue(newValue);
5584
}
5685
}}
86+
onInputChange={handleInputChange}
5787
selectOnFocus
5888
clearOnBlur
5989
handleHomeEndKeys
90+
ref={textFieldRef}
91+
open
6092
closeIcon={<CloseIcon/>}
61-
id="free-solo-with-text-demo"
62-
options={top100Films}
93+
id="free-solo"
94+
options={searchSuggestions.data}
6395
getOptionLabel={(option) => {
6496
// Value selected with enter, right from the input
6597
if (typeof option === 'string') {
@@ -70,24 +102,19 @@ function SearchBar(props) {
70102
return option.inputValue;
71103
}
72104
// Regular option
73-
return option.title;
105+
return option.keyword;
74106
}}
75-
renderOption={(option) => option.title}
107+
renderOption={(option) => option.keyword}
76108
freeSolo
77109
fullWidth
78110
onClose={handleClose}
79111
size={props.size}
112+
classes={{paper: classes.paper, listbox: classes.listbox}}
80113
renderInput={(params) =>
81-
props.device ? renderMobileTextField(params): renderDesktopTextField(params)}
114+
props.device ? renderMobileTextField(params) : renderDesktopTextField(params)}
82115
/>
83116
</Grid>
84117
);
85118
}
86119

87-
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
88-
const top100Films = [
89-
{title: 'Not Working Right Now', year: 1994},
90-
91-
];
92-
93-
export default SearchBar;
120+
export default connect(null, {getSearchSuggestions})(SearchBar);

client/src/constants/api_routes.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export const HOME_PAGE_DATA_API = "/home";
22
export const TABS_DATA_API = "/tabs";
33
export const PRODUCT_BY_ID_DATA_API = "/products?product_id=";
4-
export const PRODUCT_BY_CATEGORY_DATA_API = "/products?q=";
4+
export const PRODUCT_BY_CATEGORY_DATA_API = "/products?q=";
5+
export const SEARCH_SUGGESTION_API = "/search-suggestion?q=";
6+
export const DEFAULT_SEARCH_SUGGESTION_API = "/default-search-suggestion";

0 commit comments

Comments
 (0)