Skip to content

Commit

Permalink
feat: Add Availability filter to artwork filters (#11100)
Browse files Browse the repository at this point in the history
* feat: Add artworks availability filter

* add filter
  • Loading branch information
olerichter00 authored Nov 26, 2024
1 parent b41ba9f commit 5510c64
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 3 deletions.
15 changes: 13 additions & 2 deletions src/app/Components/ArtworkFilter/ArtworkFilterHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum FilterDisplayName {
artistsIFollow = "Artist",
artistSeriesIDs = "Artist Series",
attributionClass = "Rarity",
availability = "Availability",
categories = "Medium",
colors = "Color",
estimateRange = "Price/Estimate Range",
Expand Down Expand Up @@ -50,6 +51,7 @@ export enum FilterParamName {
colors = "colors",
earliestCreatedYear = "earliestCreatedYear",
estimateRange = "estimateRange",
forSale = "forSale",
height = "height",
keyword = "keyword",
latestCreatedYear = "latestCreatedYear",
Expand Down Expand Up @@ -87,6 +89,7 @@ export const QueryParamsToFilterValueMapping: Record<string, FilterParamName> =
colors: FilterParamName.colors,
earliest_created_year: FilterParamName.earliestCreatedYear,
estimate_range: FilterParamName.estimateRange,
for_sale: FilterParamName.forSale,
height: FilterParamName.height,
keyword: FilterParamName.keyword,
latest_created_year: FilterParamName.latestCreatedYear,
Expand Down Expand Up @@ -140,6 +143,7 @@ export const ParamDefaultValues = {
colors: [],
earliestCreatedYear: undefined,
estimateRange: "",
forSale: undefined,
height: "*-*",
includeArtworksByFollowedArtists: false,
inquireableOnly: false,
Expand Down Expand Up @@ -175,6 +179,7 @@ export const defaultCommonFilterOptions = {
colors: ParamDefaultValues.colors,
earliestCreatedYear: ParamDefaultValues.earliestCreatedYear,
estimateRange: ParamDefaultValues.estimateRange,
forSale: ParamDefaultValues.forSale,
height: ParamDefaultValues.height,
includeArtworksByFollowedArtists: ParamDefaultValues.includeArtworksByFollowedArtists,
inquireableOnly: ParamDefaultValues.inquireableOnly,
Expand Down Expand Up @@ -255,7 +260,7 @@ export interface FilterCounts {
}

export type SelectedFiltersCounts = {
[Name in FilterParamName | "waysToBuy" | "year"]: number
[Name in FilterParamName | "waysToBuy" | "year" | "availability"]: number
}

export const filterKeyFromAggregation: Record<
Expand Down Expand Up @@ -326,6 +331,8 @@ const DEFAULT_TAG_ARTWORK_PARAMS = {
sort: "-partner_updated_at",
} as FilterParams

const availabilityFilterNames = [FilterParamName.forSale]

const createdYearsFilterNames = [
FilterParamName.earliestCreatedYear,
FilterParamName.latestCreatedYear,
Expand Down Expand Up @@ -413,7 +420,7 @@ export const aggregationNameFromFilter: Record<string, AggregationName | undefin

export const aggregationForFilter = (filterKey: string, aggregations: Aggregations) => {
const aggregationName = aggregationNameFromFilter[filterKey]
const aggregation = aggregations!.find((value) => value.slice === aggregationName)
const aggregation = aggregations.find((value) => value.slice === aggregationName)
return aggregation
}

Expand Down Expand Up @@ -565,6 +572,10 @@ export const getSelectedFiltersCounts = (selectedFilters: FilterArray) => {

selectedFilters.forEach(({ paramName, paramValue }: FilterData) => {
switch (true) {
case availabilityFilterNames.includes(paramName): {
counts.availability = 1
break
}
case waysToBuyFilterNames.includes(paramName): {
counts.waysToBuy = (counts.waysToBuy ?? 0) + 1
break
Expand Down
3 changes: 3 additions & 0 deletions src/app/Components/ArtworkFilter/ArtworkFilterNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ArtistIDsOptionsScreen } from "app/Components/ArtworkFilter/Filters/Art
import { ArtistNationalitiesOptionsScreen } from "app/Components/ArtworkFilter/Filters/ArtistNationalitiesOptions"
import { ArtistSeriesOptionsScreen } from "app/Components/ArtworkFilter/Filters/ArtistSeriesOptions.tsx"
import { AttributionClassOptionsScreen } from "app/Components/ArtworkFilter/Filters/AttributionClassOptions"
import { AvailabilityOptionsScreen } from "app/Components/ArtworkFilter/Filters/AvailabilityOptions"
import { CategoriesOptionsScreen } from "app/Components/ArtworkFilter/Filters/CategoriesOptions"
import { ColorsOptionsScreen } from "app/Components/ArtworkFilter/Filters/ColorsOptions"
import { EstimateRangeOptionsScreen } from "app/Components/ArtworkFilter/Filters/EstimateRangeOptions"
Expand Down Expand Up @@ -83,6 +84,7 @@ export type ArtworkFilterNavigationStack = {
ArtistSeriesOptionsScreen: undefined
AttributionClassOptionsScreen: undefined
AuctionHouseOptionsScreen: undefined
AvailabilityOptionsScreen: undefined
CategoriesOptionsScreen: undefined
ColorOptionsScreen: undefined
ColorsOptionsScreen: undefined
Expand Down Expand Up @@ -340,6 +342,7 @@ export const ArtworkFilterNavigator: React.FC<ArtworkFilterProps> = (props) => {
component={AttributionClassOptionsScreen}
/>
<Stack.Screen name="AuctionHouseOptionsScreen" component={AuctionHouseOptionsScreen} />
<Stack.Screen name="AvailabilityOptionsScreen" component={AvailabilityOptionsScreen} />
<Stack.Screen name="ColorsOptionsScreen" component={ColorsOptionsScreen} />
<Stack.Screen
name="EstimateRangeOptionsScreen"
Expand Down
12 changes: 11 additions & 1 deletion src/app/Components/ArtworkFilter/ArtworkFilterOptionsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const ArtworkFilterOptionsScreen: React.FC<
StackScreenProps<ArtworkFilterNavigationStack, "FilterOptionsScreen">
> = ({ navigation, route }) => {
const enableArtistSeriesFilter = useFeatureFlag("AREnableArtistSeriesFilter")
const enableAvailabilityFilter = useFeatureFlag("AREnableAvailabilityFilter")
const tracking = useTracking()
const { closeModal, id, mode, slug, title = "Sort & Filter" } = route.params

Expand Down Expand Up @@ -104,7 +105,9 @@ export const ArtworkFilterOptionsScreen: React.FC<
.filter((filterOption) => filterOption.filterType)
// Filter out the Artist Series filter if the feature flag is disabled
.filter(
(filterOption) => enableArtistSeriesFilter || filterOption.filterType !== "artistSeriesIDs"
(filterOption) =>
(enableArtistSeriesFilter || filterOption.filterType !== "artistSeriesIDs") &&
(enableAvailabilityFilter || filterOption.filterType !== "availability")
)

const clearAllFilters = () => {
Expand Down Expand Up @@ -215,6 +218,7 @@ export const getStaticFilterOptionsByMode = (
default:
return [
filterOptionToDisplayConfigMap.attributionClass,
filterOptionToDisplayConfigMap.availability,
filterOptionToDisplayConfigMap.sort,
filterOptionToDisplayConfigMap.waysToBuy,
]
Expand Down Expand Up @@ -414,6 +418,11 @@ export const filterOptionToDisplayConfigMap: Record<string, FilterDisplayConfig>
filterType: "estimateRange",
ScreenComponent: "EstimateRangeOptionsScreen",
},
availability: {
displayText: FilterDisplayName.availability,
filterType: "availability",
ScreenComponent: "AvailabilityOptionsScreen",
},
partnerIDs: {
displayText: FilterDisplayName.partnerIDs,
filterType: "partnerIDs",
Expand Down Expand Up @@ -507,6 +516,7 @@ const ArtistArtworksFiltersSorted: FilterScreen[] = [
"artistSeriesIDs",
"sizes",
"waysToBuy",
"availability",
"materialsTerms",
"locationCities",
"majorPeriods",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { fireEvent } from "@testing-library/react-native"
import { FilterParamName } from "app/Components/ArtworkFilter/ArtworkFilterHelpers"
import {
ArtworkFiltersState,
ArtworkFiltersStoreProvider,
getArtworkFiltersModel,
} from "app/Components/ArtworkFilter/ArtworkFilterStore"
import { MockFilterScreen } from "app/Components/ArtworkFilter/FilterTestHelper"
import { AvailabilityOptionsScreen } from "app/Components/ArtworkFilter/Filters/AvailabilityOptions.tsx"
import { __globalStoreTestUtils__ } from "app/store/GlobalStore"
import { renderWithWrappers } from "app/utils/tests/renderWithWrappers"
import { getEssentialProps } from "./helper"

describe(AvailabilityOptionsScreen, () => {
beforeEach(() => {
__globalStoreTestUtils__?.injectFeatureFlags({ AREnableAvailabilityFilter: true })
})

const initialState: ArtworkFiltersState = {
aggregations: [],
appliedFilters: [],
applyFilters: false,
counts: {
total: null,
followedArtists: null,
},
showFilterArtworksModal: false,
sizeMetric: "cm",
filterType: "artwork",
previouslyAppliedFilters: [],
selectedFilters: [],
}

const MockAvailabilityOptionsScreen = ({
initialData = initialState,
}: {
initialData?: ArtworkFiltersState
}) => {
return (
<ArtworkFiltersStoreProvider
runtimeModel={{
...getArtworkFiltersModel(),
...initialData,
}}
>
<AvailabilityOptionsScreen {...getEssentialProps()} />
</ArtworkFiltersStoreProvider>
)
}

describe("no filters are selected", () => {
it("renders all options", () => {
const { getByText } = renderWithWrappers(
<MockAvailabilityOptionsScreen initialData={initialState} />
)

expect(getByText("Only works for sale")).toBeTruthy()
})
})

describe("a filter is selected", () => {
const state: ArtworkFiltersState = {
...initialState,
selectedFilters: [
{
displayText: "Only works for sale",
paramName: FilterParamName.forSale,
paramValue: true,
},
],
}

it("displays the number of the selected filters on the filter modal screen", () => {
const { getByText } = renderWithWrappers(<MockFilterScreen initialState={state} />)

expect(getByText("Availability • 1")).toBeTruthy()
})

it("toggles selected filters 'ON' and unselected filters 'OFF", async () => {
const { getAllByA11yState } = renderWithWrappers(
<MockAvailabilityOptionsScreen initialData={state} />
)

let options = getAllByA11yState({ checked: true })

expect(options).toHaveLength(1)
expect(options[0]).toHaveTextContent("Only works for sale")

fireEvent.press(options[0])

options = getAllByA11yState({ checked: false })

expect(options).toHaveLength(1)
expect(options[0]).toHaveTextContent("Only works for sale")
})
})
})
79 changes: 79 additions & 0 deletions src/app/Components/ArtworkFilter/Filters/AvailabilityOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { StackScreenProps } from "@react-navigation/stack"
import {
FilterData,
FilterDisplayName,
FilterParamName,
} from "app/Components/ArtworkFilter/ArtworkFilterHelpers"
import { ArtworkFilterNavigationStack } from "app/Components/ArtworkFilter/ArtworkFilterNavigator"
import {
ArtworksFiltersStore,
useSelectedOptionsDisplay,
} from "app/Components/ArtworkFilter/ArtworkFilterStore"
import React, { useState } from "react"
import { MultiSelectOptionScreen } from "./MultiSelectOption"

type AvailabilityOptionsScreenProps = StackScreenProps<
ArtworkFilterNavigationStack,
"AvailabilityOptionsScreen"
>

export const OPTIONS: FilterData[] = [
{
displayText: "Only works for sale",
paramName: FilterParamName.forSale,
},
]

export const AvailabilityOptionsScreen: React.FC<AvailabilityOptionsScreenProps> = ({
navigation,
}) => {
const selectFiltersAction = ArtworksFiltersStore.useStoreActions(
(state) => state.selectFiltersAction
)

const selectedOptions = useSelectedOptionsDisplay()
const options = OPTIONS.map((option) => {
const selectedOptionByParamName = selectedOptions.find(
(selectedOption) => selectedOption.paramName === option.paramName
)

return {
...option,
paramValue: selectedOptionByParamName?.paramValue || undefined,
}
})

const [key, setKey] = useState(0)

const handleSelect = (option: FilterData, updatedValue: boolean) => {
selectFiltersAction({
displayText: option.displayText,
paramValue: updatedValue || undefined,
paramName: option.paramName,
})
}

const handleClear = () => {
options.map((option) => {
selectFiltersAction({ ...option, paramValue: undefined })
})

// Force re-render
setKey((n) => n + 1)
}

const selected = options.filter((option) => option.paramValue)

return (
<MultiSelectOptionScreen
key={key}
onSelect={handleSelect}
filterHeaderText={FilterDisplayName.availability}
filterOptions={options}
navigation={navigation}
{...(selected.length > 0
? { rightButtonText: "Clear", onRightButtonPress: handleClear }
: {})}
/>
)
}
1 change: 1 addition & 0 deletions src/app/Components/ArtworkFilter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type FilterScreen =
| "artistSeriesIDs"
| "artistsIFollow"
| "attributionClass"
| "availability"
| "categories"
| "color"
| "colors"
Expand Down
6 changes: 6 additions & 0 deletions src/app/store/config/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ export const features = {
showInDevMenu: true,
echoFlagKey: "AREnableNewSearchModal",
},
AREnableAvailabilityFilter: {
description: "Enable availability filter",
readyForRelease: false,
showInDevMenu: true,
// echoFlagKey: "AREnableAvailabilityFilter",
},
} satisfies { [key: string]: FeatureDescriptor }

export interface DevToggleDescriptor {
Expand Down

0 comments on commit 5510c64

Please sign in to comment.