Skip to content

Commit

Permalink
feat: add price range filter to saved searches (#9415)
Browse files Browse the repository at this point in the history
* feat: add price range filter to saved searches

* chore: add tests

* chore: self review

* chore: improve skeleton

* chore: address review comment

* fix: test, again
  • Loading branch information
MounirDhahri authored Oct 15, 2023
1 parent 9edad5a commit d278566
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 6 deletions.
5 changes: 3 additions & 2 deletions src/app/Components/PriceRange/PriceRangeContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface RecentPriceRangeEntity {
interface PriceRangeContainerProps {
filterPriceRange: string // *-*
histogramBars: HistogramBarEntity[]
header: React.ReactNode
header?: React.ReactNode
onPriceRangeUpdate: (range: PriceRange) => void
onRecentPriceRangeSelected?: (isCollectorProfileSources: boolean) => void
}
Expand Down Expand Up @@ -103,7 +103,8 @@ export const PriceRangeContainer: React.FC<PriceRangeContainerProps> = ({

return (
<ScrollView ref={screenScrollViewRef} keyboardShouldPersistTaps="handled">
<Flex m={2}>{header}</Flex>
{!!header && <Flex m={2}>{header}</Flex>}

<Flex flexDirection="row" mx={2}>
<Input
containerStyle={{ flex: 1 }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { OwnerType } from "@artsy/cohesion"
import { fireEvent, waitFor } from "@testing-library/react-native"
import { SavedSearchFilterPriceRangeQR } from "app/Scenes/SavedSearchAlert/Components/SavedSearchFilterPriceRange"
import {
SavedSearchModel,
SavedSearchStoreProvider,
savedSearchModel,
} from "app/Scenes/SavedSearchAlert/SavedSearchStore"
import { setupTestWrapper } from "app/utils/tests/setupTestWrapper"

describe("SavedSearchFilterPriceRange", () => {
it("shows the right price range when available", () => {
const { renderWithRelay } = setupTestWrapper({
Component: () => (
<SavedSearchStoreProvider runtimeModel={initialData}>
<SavedSearchFilterPriceRangeQR />
</SavedSearchStoreProvider>
),
})

const { getByText } = renderWithRelay({
Artist: () => ({
internalID: "artistID",
name: "Banksy",
}),
})

waitFor(() => {
expect(getByText("200")).toBeDefined()
expect(getByText("3000")).toBeDefined()
})
})

it("Updates the price range appropriately", async () => {
const { renderWithRelay } = setupTestWrapper({
Component: () => (
<SavedSearchStoreProvider runtimeModel={initialData}>
<SavedSearchFilterPriceRangeQR />
</SavedSearchStoreProvider>
),
})

const { getByTestId, getByText } = renderWithRelay({
Artist: () => ({
internalID: "artistID",
name: "Banksy",
}),
})

waitFor(() => {
expect(getByText("200")).toBeDefined()
expect(getByText("3000")).toBeDefined()

fireEvent.changeText(getByTestId("price-min-input"), "300")
fireEvent.changeText(getByTestId("price-max-input"), "5000")

expect(getByText("300")).toBeDefined()
expect(getByText("5000")).toBeDefined()
})
})
})

const initialData: SavedSearchModel = {
...savedSearchModel,
attributes: {
atAuction: true,
priceRange: "200-3000",
},
entity: {
artists: [{ id: "artistID", name: "Banksy" }],
owner: {
type: OwnerType.artist,
id: "ownerId",
slug: "ownerSlug",
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Flex, Skeleton, SkeletonBox, SkeletonText, Spacer, Text } from "@artsy/palette-mobile"
import { SavedSearchFilterPriceRangeQuery } from "__generated__/SavedSearchFilterPriceRangeQuery.graphql"
import { SearchCriteria } from "app/Components/ArtworkFilter/SavedSearch/types"
import { PriceRangeContainer } from "app/Components/PriceRange/PriceRangeContainer"
import { DEFAULT_PRICE_RANGE } from "app/Components/PriceRange/constants"
import { PriceRange } from "app/Components/PriceRange/types"
import { getBarsFromAggregations } from "app/Components/PriceRange/utils"
import { SavedSearchStore } from "app/Scenes/SavedSearchAlert/SavedSearchStore"
import { useSearchCriteriaAttributes } from "app/Scenes/SavedSearchAlert/helpers"
import { GlobalStore } from "app/store/GlobalStore"
import { withSuspense } from "app/utils/hooks/withSuspense"
import { useEffect, useState } from "react"
import { graphql, useLazyLoadQuery } from "react-relay"
import useDebounce from "react-use/lib/useDebounce"

interface SavedSearchFilterPriceRangeProps {
artist: SavedSearchFilterPriceRangeQuery["response"]["artist"]
}

const SavedSearchFilterPriceRange: React.FC<SavedSearchFilterPriceRangeProps> = ({ artist }) => {
const histogramBars = getBarsFromAggregations(
(artist as any)?.filterArtworksConnection?.aggregations
)

const storePriceRangeValue = useSearchCriteriaAttributes(SearchCriteria.priceRange) as string

const [filterPriceRange, setFilterPriceRange] = useState(
storePriceRangeValue || DEFAULT_PRICE_RANGE
)

const setValueToAttributesByKeyAction = SavedSearchStore.useStoreActions(
(actions) => actions.setValueToAttributesByKeyAction
)

useDebounce(
() => {
setValueToAttributesByKeyAction({
key: SearchCriteria.priceRange,
value: filterPriceRange,
})
GlobalStore.actions.recentPriceRanges.addNewPriceRange(filterPriceRange)
},
200,
[filterPriceRange]
)

// Make sure to keep the slider and the histograms up to date with the store
useEffect(() => {
if (filterPriceRange !== storePriceRangeValue) {
setFilterPriceRange(storePriceRangeValue || DEFAULT_PRICE_RANGE)
}
}, [storePriceRangeValue])

const handleUpdateRange = (updatedRange: PriceRange) => {
setFilterPriceRange(updatedRange.join("-"))
}

return (
<Flex>
<Text variant="sm" fontWeight={500} px={2}>
Price Range
</Text>
<Spacer y={1} />
<PriceRangeContainer
filterPriceRange={filterPriceRange}
histogramBars={histogramBars}
onPriceRangeUpdate={handleUpdateRange}
/>
</Flex>
)
}

const Placeholder: React.FC<{}> = () => (
<Flex testID="loading-skeleton">
<Skeleton>
<SkeletonText variant="sm" fontWeight={500} mx={2}>
Price Range
</SkeletonText>

<Spacer y={2} />

<Flex mx={2} flexDirection="row">
<SkeletonBox width="100%" height={50} />
</Flex>

<Spacer y={2} />

<Flex mx={2} flexDirection="row">
<SkeletonBox width="100%" height={170} />
</Flex>

<Spacer y={4} />

<Flex mx={2} flexDirection="row">
<SkeletonBox width="100%" height={90} />
</Flex>
</Skeleton>
</Flex>
)

const savedSearchFilterPriceRangeQuery = graphql`
query SavedSearchFilterPriceRangeQuery($artistID: String!) {
artist(id: $artistID) {
filterArtworksConnection(aggregations: [SIMPLE_PRICE_HISTOGRAM], first: 0) {
aggregations {
slice
counts {
count
name
value
}
}
}
}
}
`

export const SavedSearchFilterPriceRangeQR: React.FC<{}> = withSuspense(() => {
const artistID = SavedSearchStore.useStoreState((state) => state.entity.artists[0].id)
const data = useLazyLoadQuery<SavedSearchFilterPriceRangeQuery>(
savedSearchFilterPriceRangeQuery,
{
artistID: artistID,
}
)

if (!data.artist) {
return null
}

return <SavedSearchFilterPriceRange artist={data.artist} />
}, Placeholder)
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Flex, Join, Separator, Text, Touchable } from "@artsy/palette-mobile"
import { Join, Separator, Text, Touchable } from "@artsy/palette-mobile"
import { useNavigation } from "@react-navigation/native"
import { SearchCriteria } from "app/Components/ArtworkFilter/SavedSearch/types"
import { FancyModalHeader } from "app/Components/FancyModal/FancyModalHeader"
import { SavedSearchAppliedFilters } from "app/Scenes/SavedSearchAlert/Components/SavedSearchFilterAppliedFilters"
import { SavedSearchFilterColour } from "app/Scenes/SavedSearchAlert/Components/SavedSearchFilterColour"
import { SavedSearchFilterPriceRangeQR } from "app/Scenes/SavedSearchAlert/Components/SavedSearchFilterPriceRange"
import { SavedSearchRarity } from "app/Scenes/SavedSearchAlert/Components/SavedSearchFilterRarity"
import { SavedSearchStore } from "app/Scenes/SavedSearchAlert/SavedSearchStore"
import { MotiView } from "moti"
import { Alert } from "react-native"
import { Alert, ScrollView } from "react-native"

export const SavedSearchFilterScreen: React.FC<{}> = () => {
const navigation = useNavigation()

return (
<Flex>
<ScrollView>
<FancyModalHeader
hideBottomDivider
onLeftButtonPress={navigation.goBack}
Expand All @@ -24,10 +25,11 @@ export const SavedSearchFilterScreen: React.FC<{}> = () => {
</FancyModalHeader>
<Join separator={<Separator my={2} borderColor="black10" />}>
<SavedSearchAppliedFilters />
<SavedSearchFilterPriceRangeQR />
<SavedSearchRarity />
<SavedSearchFilterColour />
</Join>
</Flex>
</ScrollView>
)
}

Expand All @@ -40,6 +42,10 @@ export const ClearAllButton = () => {
Object.entries(attributes).filter((keyValue) => {
const key = keyValue[0]
const value = keyValue[1]
if (key === SearchCriteria.priceRange) {
return value && value !== "*-*"
}

if (key !== SearchCriteria.artistID && key !== SearchCriteria.artistIDs) {
// Values might be empty arrays
if (Array.isArray(value)) {
Expand Down

0 comments on commit d278566

Please sign in to comment.