Commit 47f3f35a authored by Denis Sedura's avatar Denis Sedura

Add search

parent c257a9cf
import React, { Component } from 'react'
import { enhanceNearbyStoreList } from '../NearbyStoreList'
import { enhanceNearbyStoreList } from './NearbyStoreList'
import { enhanceMenuButton } from '../../components/MenuButton'
function enhanceMain(ComposedComponent, NearbyStoreList, MenuButton) {
......
......@@ -2,12 +2,13 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
import { Router } from 'tipsi-router'
import { storesSelector } from 'tipsi_api/selectors'
import { loadStores } from 'tipsi_api/actions'
import { isEqual } from 'lodash'
import { updateCurrentLocation } from '../../actions'
import sortStoresByDistance from '../../utils/sortStoresByDistance'
import unimplementedFeature from '../../utils/unimplementedFeature'
import { updateCurrentLocation } from '../../../actions'
import sortStoresByDistance from '../../../utils/sortStoresByDistance'
import unimplementedFeature from '../../../utils/unimplementedFeature'
function enhanceNearbyStoreList(ComposedComponent) {
class Wrapper extends Component {
......@@ -32,8 +33,7 @@ function enhanceNearbyStoreList(ComposedComponent) {
)
handleSubmitEditing = query => (
// this.props.navigator.push('Search', { query })
console.log('Push Search', query)
Router.push(null, Router.routes.search, { query })
)
handlePressSuggestion = suggestion => (
......
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { enhanceSearchResultInput } from './SearchResultInput'
import { enhanceSearchResultList } from './SearchResultList'
function enhanceSearch(ComposedComponent, SearchResultInput, SearchResultList) {
const SearchResultInputHOC = enhanceSearchResultInput(SearchResultInput)
const SearchResultListHOC = enhanceSearchResultList(SearchResultList)
return class Wrapper extends Component {
static propTypes = {
query: PropTypes.string.isRequired,
}
render() {
return (
<ComposedComponent {...this.props}>
<SearchResultInputHOC query={this.props.query} />
<SearchResultListHOC />
</ComposedComponent>
)
}
}
}
export default enhanceSearch
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { debounce } from 'lodash'
import { clearPaginationCache } from 'tipsi_api/actions'
import {
changeSearchQuery,
clearSearchQuery,
GLOBAL_INVENTORY_SEARCH_KEY,
GLOBAL_SEARCH_KEY,
} from '../../actions'
export function enhanceSearchResultInput(ComposedComponent) {
class Wrapper extends Component {
static propTypes = {
query: PropTypes.string.isRequired,
changeSearchQuery: PropTypes.func.isRequired,
clearSearchQuery: PropTypes.func.isRequired,
clearPaginationCache: PropTypes.func.isRequired,
}
state = {
value: this.props.query,
}
componentWillMount() {
this.changeSearchQuery(this.state.value)
}
handleValueChange = (value) => {
const clearedQuery = value.trim()
const previousClearedQuery = this.state.value.trim()
if (clearedQuery !== previousClearedQuery) {
this.debouncedChangeSearchQuery(clearedQuery)
}
this.setState({ value })
}
changeSearchQuery = query => (
this.props.changeSearchQuery({ query, key: GLOBAL_INVENTORY_SEARCH_KEY })
)
debouncedChangeSearchQuery = debounce(this.changeSearchQuery, 600)
handlePressClear = () => {
this.setState({ value: '' })
this.props.clearSearchQuery({ keys: [GLOBAL_INVENTORY_SEARCH_KEY, GLOBAL_SEARCH_KEY] })
this.props.clearPaginationCache({
search: GLOBAL_SEARCH_KEY,
searchInventory: GLOBAL_INVENTORY_SEARCH_KEY,
})
}
render() {
return (
<ComposedComponent
{...this.props}
{...this.state}
handleValueChange={this.handleValueChange}
handlePressClear={this.handlePressClear}
/>
)
}
}
const mapDispatchToProps = { changeSearchQuery, clearSearchQuery, clearPaginationCache }
return connect(null, mapDispatchToProps)(Wrapper)
}
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
import { mapValues, reduce, groupBy, maxBy } from 'lodash'
import { search, searchInventory, clearPaginationCache } from 'tipsi_api/actions'
import { createSearchListSelector, createSearchInventoryListSelector } from 'tipsi_api/selectors'
import { GLOBAL_INVENTORY_SEARCH_KEY, GLOBAL_SEARCH_KEY } from '../../actions'
import { currentLocationSelector } from '../../selectors'
import sortStoresByDistance from '../../utils/sortStoresByDistance'
import tabItems from '../../constants/storeTabItems'
export function enhanceSearchResultList(ComposedComponent) {
class Wrapper extends Component {
static propTypes = {
searchPageQuery: PropTypes.string,
searchNextPage: PropTypes.number,
search: PropTypes.func.isRequired,
searchInventory: PropTypes.func.isRequired,
clearPaginationCache: PropTypes.func.isRequired,
results: PropTypes.array.isRequired,
inventoryLoaded: PropTypes.bool.isRequired,
regularSearchIsLoading: PropTypes.bool.isRequired,
inventorySearchIsLoading: PropTypes.bool.isRequired,
currentLocation: PropTypes.object.isRequired,
}
static defaultProps = {
searchPageQuery: '',
searchNextPage: null,
}
componentWillMount() {
this.loadInventorySearchResults(this.props.searchPageQuery)
}
componentWillReceiveProps({ searchPageQuery }) {
if (searchPageQuery !== this.props.searchPageQuery) {
if (searchPageQuery) {
this.loadInventorySearchResults(searchPageQuery)
} else {
this.cancelSearchRequests()
}
}
}
componentWillUnmount() {
this.clearSearchCache()
}
loadInventorySearchResults = (query) => {
const fields = {
wine: 'id,name,vintage,type,region,sub_regions,label_url,pro_rating,' +
'winery,country,vintage_note,style_scoring',
fts: 'id,inventory,rank,wine,drink',
drink: 'id,name,description,drink_type,producer,country,label_url',
inventory: 'id,wine,drink,price,store,value_pick,staff_pick,special_price,' +
'special_price_on,special_price_amount,in_stock',
country: 'id,name',
store: 'id,name,address,lat,lng',
pro_rating: 'shortcut,rating,name,rating_description',
winery: 'id,name',
producer: 'id,name,description',
region: 'name',
sub_region: 'name',
style: 'id,name,score',
}
this.cancelSearchRequests()
this.lastSearchInventory = this.props.searchInventory({
query,
fields,
key: GLOBAL_INVENTORY_SEARCH_KEY,
})
}
loadRegularSearchResults = (query) => {
const { searchNextPage } = this.props
const fields = {
pro_rating: 'shortcut,rating,name,rating_description',
wine: 'id,name,country,winery,label_url,vintage,pro_rating',
fts: 'id,rank,wine,drink',
drink: 'id,name,country,drink_type,label_url',
winery: 'id,name',
country: 'id,name',
}
if (this.lastRegularSearch) {
this.lastRegularSearch.cancel()
}
if (searchNextPage) {
this.lastRegularSearch = this.props.search({
query,
fields,
key: GLOBAL_SEARCH_KEY,
page: searchNextPage,
})
}
}
clearSearchCache = () => (
this.props.clearPaginationCache({
search: GLOBAL_SEARCH_KEY,
searchInventory: GLOBAL_INVENTORY_SEARCH_KEY,
})
)
cancelSearchRequests = () => {
this.clearSearchCache()
if (this.lastSearchInventory) {
this.lastSearchInventory.cancel()
}
if (this.lastRegularSearch) {
this.lastRegularSearch.cancel()
}
}
handleEndReached = () => {
const { searchPageQuery, inventoryLoaded, regularSearchIsLoading } = this.props
if (searchPageQuery && inventoryLoaded && !regularSearchIsLoading) {
this.loadRegularSearchResults(searchPageQuery)
}
}
handleStoreWineRowPress = ({ storeId, storeWineId: inventoryId }) => (
// this.props.navigator.push('ItemDetails', {
// storeId,
// inventoryId,
// itemType: 'wine',
// title: 'Wine Details',
// detailsType: 'inventory',
// })
console.log('Push ItemDetails')
)
handleWineRowPress = ({ wineId: itemId }) => (
// this.props.navigator.push('ItemDetails', {
// itemId,
// itemType: 'wine',
// detailsType: 'item',
// title: 'Wine Details',
// })
console.log('Push ItemDetails')
)
handleStoreDrinkRowPress = ({ storeId, storeDrinkId: inventoryId }) => (
// this.props.navigator.push('ItemDetails', {
// storeId,
// inventoryId,
// itemType: 'drink',
// title: 'Drink Details',
// detailsType: 'inventory',
// })
console.log('Push ItemDetails')
)
handleDrinkRowPress = ({ drinkId: itemId }) => (
// this.props.navigator.push('ItemDetails', {
// detailsType: 'item',
// itemType: 'drink',
// itemId,
// title: 'Drink Details',
// })
console.log('Push ItemDetails')
)
handleSectionPress = (storeId, title, tabIndex) => (
// this.props.navigator.push('Store', {
// title,
// storeId,
// tabIndex,
// searchQuery: this.props.searchPageQuery,
// })
console.log('Push Store')
)
render() {
return (
<ComposedComponent
{...this.props}
{...this.state}
handleStoreWineRowPress={this.handleStoreWineRowPress}
handleWineRowPress={this.handleWineRowPress}
handleStoreDrinkRowPress={this.handleStoreDrinkRowPress}
handleDrinkRowPress={this.handleDrinkRowPress}
handleEndReached={this.handleEndReached}
handleSectionPress={this.handleSectionPress}
/>
)
}
}
const MAX_RESULTS = 3
const NOT_ON_SALE_KEY = 'Not on sale'
const searchQueriesSelector = createSelector(
state => state.searchQueries,
searchQueries => searchQueries[GLOBAL_INVENTORY_SEARCH_KEY]
)
const searchResultListSelector = (searchList, searchInventoryList, searchPageQuery, location) => {
const inventory = reduce(searchInventoryList.result, ({ stores, groups }, item) => {
const { store } = item.inventory
const storeId = store.id
if (groups[storeId]) {
return {
stores,
groups: {
...groups,
[storeId]: groups[storeId].concat(item),
},
}
}
return {
stores: stores.concat(store),
groups: {
...groups,
[storeId]: [].concat(item),
},
}
}, { stores: [], groups: {} })
const sortedStores = sortStoresByDistance(inventory.stores, location)
const inventoryResults = sortedStores.map((store, index) => {
const inventoryGroupedByCount = groupBy(inventory.groups[store.id], item => (
item.inventory.wine ? 'wine' : item.inventory.drink.drink_type
))
const drinkType = maxBy(
Object.keys(inventoryGroupedByCount),
key => inventoryGroupedByCount[key].length
)
return {
store,
index,
title: store.name,
data: inventory.groups[store.id].slice(0, MAX_RESULTS),
tabIndex: tabItems.findIndex(item => drinkType.toUpperCase() === item.title),
}
})
const regularResults = !searchList.result.length ? [] : [{
index: sortedStores.length,
title: NOT_ON_SALE_KEY,
data: searchList.result,
}]
return {
searchPageQuery,
results: [...inventoryResults, ...regularResults],
regularLoaded: Boolean(searchList.result.length),
inventoryLoaded: Boolean(searchInventoryList.result.length),
inventoryCounts: mapValues(inventory.groups, items => items.length),
regularSearchIsLoading: searchList.isLoading,
inventorySearchIsLoading: searchInventoryList.isLoading,
searchNextPage: searchList.nextPage,
currentLocation: location,
}
}
const mapStateToProps = createSelector(
createSearchListSelector(() => GLOBAL_SEARCH_KEY),
createSearchInventoryListSelector(() => GLOBAL_INVENTORY_SEARCH_KEY),
searchQueriesSelector,
currentLocationSelector,
searchResultListSelector
)
const mapDispatchToProps = { search, searchInventory, clearPaginationCache }
return connect(mapStateToProps, mapDispatchToProps)(Wrapper)
}
export { default as enhanceSearch } from './Search'
export { enhanceAppComponent } from './App'
export { enhanceInitialComponent } from './InitialScreen'
export { enhanceAgeConfirmation } from './AgeConfirmation'
export { enhanceNearbyStoreList } from './NearbyStoreList'
export { enhanceMain } from './Main'
export default [{
title: 'WINE',
key: 'Wine',
id: 'storeTabView/wine',
storeKey: 'wines',
}, {
title: 'BEER',
key: 'Beer',
id: 'storeTabView/beer',
storeKey: 'drinks/beer',
}, {
title: 'SPIRITS',
key: 'Spirits',
id: 'storeTabView/spirits',
storeKey: 'drinks/spirits',
}, {
title: 'OTHER',
key: 'Other',
id: 'storeTabView/other',
storeKey: 'drinks/other',
}]
import { reduce, keys } from 'lodash'
import { enhanceAppComponent } from './Screens/App'
import { enhanceSearch } from './Screens/Search'
const routesMap = {
home: {
path: '/',
component: enhanceAppComponent,
},
search: {
path: 'search',
component: enhanceSearch,
},
}
export function createRoutes(routes = {}) {
return reduce(keys(routesMap), (result, key) => {
const mappedRoute = routes[key]
const { component, children = [] } = mappedRoute
if (mappedRoute) {
result[key] = {
...routesMap[key],
...mappedRoute,
component: routesMap[key].component(mappedRoute.component),
component: routesMap[key].component(component, ...children),
}
}
return result
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment