Skip to content

Commit

Permalink
Gift card filter in product list view (#1621)
Browse files Browse the repository at this point in the history
* Add GiftCard or Normal filter in Product List View

* Fix tests

* Fix type check

* Don't filter if query param is not in enum

* Update messages

* Update tests

* Code cleanup

* Add default messages

* Pass intl rather than initialise it in util
  • Loading branch information
Cloud11PL committed Jan 13, 2022
1 parent 8e9838a commit 953c277
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 4 deletions.
12 changes: 12 additions & 0 deletions locale/defaultMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -6277,6 +6277,10 @@
"context": "product is hidden",
"string": "Hidden"
},
"src_dot_products_dot_components_dot_ProductListPage_dot_kind": {
"context": "product kind",
"string": "Product Kind"
},
"src_dot_products_dot_components_dot_ProductListPage_dot_outOfStock": {
"context": "product status",
"string": "Out Of Stock"
Expand Down Expand Up @@ -6889,6 +6893,14 @@
"src_dot_products_dot_views_dot_ProductList_dot_44832327": {
"string": "We are currently exporting your requested CSV. As soon as it is available it will be sent to your email address"
},
"src_dot_products_dot_views_dot_ProductList_dot_giftCardLabel": {
"context": "label",
"string": "Gift Card"
},
"src_dot_products_dot_views_dot_ProductList_dot_normalLabel": {
"context": "label",
"string": "Normal"
},
"src_dot_products_dot_views_dot_ProductUpdate_dot_3423943948": {
"string": "Manage Products Channel Availability"
},
Expand Down
4 changes: 4 additions & 0 deletions src/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@ export function generateCode(charNum: number) {
return result;
}

export function isInEnum<TEnum extends {}>(needle: string, haystack: TEnum) {
return Object.keys(haystack).includes(needle);
}

export function findInEnum<TEnum extends {}>(needle: string, haystack: TEnum) {
const match = Object.keys(haystack).find(key => key === needle);
if (!!match) {
Expand Down
18 changes: 17 additions & 1 deletion src/products/components/ProductListPage/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export enum ProductFilterKeys {
price = "price",
productType = "productType",
stock = "stock",
channel = "channel"
channel = "channel",
productKind = "productKind"
}

export type AttributeFilterOpts = FilterOpts<string[]> & {
Expand All @@ -42,6 +43,7 @@ export interface ProductListFilterOpts {
productType: FilterOpts<string[]> & AutocompleteFilterOpts;
stockStatus: FilterOpts<StockAvailability>;
channel: FilterOpts<string> & { choices: SingleAutocompleteChoiceType[] };
productKind: FilterOpts<string> & { choices: SingleAutocompleteChoiceType[] };
}

const messages = defineMessages({
Expand All @@ -53,6 +55,10 @@ const messages = defineMessages({
defaultMessage: "Channel",
description: "sales channel"
},
kind: {
defaultMessage: "Product Kind",
description: "product kind"
},
hidden: {
defaultMessage: "Hidden",
description: "product is hidden"
Expand Down Expand Up @@ -118,6 +124,16 @@ export function createFilterStructure(
),
active: opts.channel.active
},
{
...createOptionsField(
ProductFilterKeys.productKind,
intl.formatMessage(messages.kind),
[opts.productKind.value],
false,
opts.productKind.choices
),
active: opts.productKind.active
},
{
...createOptionsField(
ProductFilterKeys.stock,
Expand Down
3 changes: 2 additions & 1 deletion src/products/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const ProductList: React.FC<RouteComponentProps<any>> = ({ location }) => {
categories: getArrayQueryParam(qs.categories),
collections: getArrayQueryParam(qs.collections),
ids: getArrayQueryParam(qs.ids),
productTypes: getArrayQueryParam(qs.productTypes)
productTypes: getArrayQueryParam(qs.productTypes),
productKind: qs.productKind
},
ProductListUrlSortField
);
Expand Down
3 changes: 2 additions & 1 deletion src/products/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export enum ProductListUrlFiltersEnum {
status = "status",
stockStatus = "stockStatus",
query = "query",
channel = "channel"
channel = "channel",
productKind = "productKind"
}
export enum ProductListUrlFiltersWithMultipleValues {
categories = "categories",
Expand Down
4 changes: 4 additions & 0 deletions src/products/views/ProductList/ProductList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import {
saveFilterTab
} from "./filters";
import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort";
import { getAvailableProductKinds, getProductKindOpts } from "./utils";

interface ProductListProps {
params: ProductListUrlQueryParams;
Expand Down Expand Up @@ -165,6 +166,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
},
skip: params.action !== "export"
});
const availableProductKinds = getAvailableProductKinds();
const { availableChannels } = useAppChannel(false);
const limitOpts = useShopLimitsQuery({
variables: {
Expand Down Expand Up @@ -280,6 +282,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
})
);

const kindOpts = getProductKindOpts(availableProductKinds, intl);
const paginationState = createPaginationState(settings.rowNumber, params);
const channelOpts = availableChannels
? mapNodeToChoice(availableChannels, channel => channel.slug)
Expand Down Expand Up @@ -347,6 +350,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
initial: mapEdgesToItems(initialFilterProductTypes?.productTypes) || [],
search: searchProductTypes
},
kindOpts,
channelOpts
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ Object {
],
"priceFrom": "10",
"priceTo": "20",
"productKind": "NORMAL",
"productTypes": Array [
"UHJvZHVjdFR5cGU6MQ==",
],
"stockStatus": "IN_STOCK",
}
`;

exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B%5D=878752&collections%5B%5D=Q29sbGVjdGlvbjoc&productTypes%5B%5D=UHJvZHVjdFR5cGU6MQ%3D%3D&attributes%5Bauthor%5D%5B%5D=john-doe&attributes%5Bauthor%5D%5B%5D=false&attributes%5Bbox-size%5D%5B%5D=100g&attributes%5Bbox-size%5D%5B%5D=500g&attributes%5Bbrand%5D%5B%5D=saleor&attributes%5Bbrand%5D%5B%5D=false&attributes%5Bcandy-box-size%5D%5B%5D=100g&attributes%5Bcandy-box-size%5D%5B%5D=500g&attributes%5Bcoffee-genre%5D%5B%5D=arabica&attributes%5Bcoffee-genre%5D%5B%5D=false&attributes%5Bcollar%5D%5B%5D=round&attributes%5Bcollar%5D%5B%5D=polo&attributes%5Bcolor%5D%5B%5D=blue&attributes%5Bcolor%5D%5B%5D=false&attributes%5Bcover%5D%5B%5D=soft&attributes%5Bcover%5D%5B%5D=middle-soft&attributes%5Bflavor%5D%5B%5D=sour&attributes%5Bflavor%5D%5B%5D=false&attributes%5Blanguage%5D%5B%5D=english&attributes%5Blanguage%5D%5B%5D=false&attributes%5Bpublisher%5D%5B%5D=mirumee-press&attributes%5Bpublisher%5D%5B%5D=false&attributes%5Bsize%5D%5B%5D=xs&attributes%5Bsize%5D%5B%5D=m"`;
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&productKind=NORMAL&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B%5D=878752&collections%5B%5D=Q29sbGVjdGlvbjoc&productTypes%5B%5D=UHJvZHVjdFR5cGU6MQ%3D%3D&attributes%5Bauthor%5D%5B%5D=john-doe&attributes%5Bauthor%5D%5B%5D=false&attributes%5Bbox-size%5D%5B%5D=100g&attributes%5Bbox-size%5D%5B%5D=500g&attributes%5Bbrand%5D%5B%5D=saleor&attributes%5Bbrand%5D%5B%5D=false&attributes%5Bcandy-box-size%5D%5B%5D=100g&attributes%5Bcandy-box-size%5D%5B%5D=500g&attributes%5Bcoffee-genre%5D%5B%5D=arabica&attributes%5Bcoffee-genre%5D%5B%5D=false&attributes%5Bcollar%5D%5B%5D=round&attributes%5Bcollar%5D%5B%5D=polo&attributes%5Bcolor%5D%5B%5D=blue&attributes%5Bcolor%5D%5B%5D=false&attributes%5Bcover%5D%5B%5D=soft&attributes%5Bcover%5D%5B%5D=middle-soft&attributes%5Bflavor%5D%5B%5D=sour&attributes%5Bflavor%5D%5B%5D=false&attributes%5Blanguage%5D%5B%5D=english&attributes%5Blanguage%5D%5B%5D=false&attributes%5Bpublisher%5D%5B%5D=mirumee-press&attributes%5Bpublisher%5D%5B%5D=false&attributes%5Bsize%5D%5B%5D=xs&attributes%5Bsize%5D%5B%5D=m"`;
14 changes: 14 additions & 0 deletions src/products/views/ProductList/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
ProductListUrlFiltersWithMultipleValues,
ProductListUrlQueryParams
} from "../../urls";
import { getProductGiftCardFilterParam } from "./utils";
export const PRODUCT_FILTERS_KEY = "productFilters";

export function getFilterOpts(
Expand All @@ -77,6 +78,7 @@ export function getFilterOpts(
initial: InitialProductFilterProductTypes_productTypes_edges_node[];
search: UseSearchResult<SearchProductTypes, SearchProductTypesVariables>;
},
productKind: SingleAutocompleteChoiceType[],
channels: SingleAutocompleteChoiceType[]
): ProductListFilterOpts {
return {
Expand Down Expand Up @@ -162,6 +164,11 @@ export function getFilterOpts(
onSearchChange: collections.search.search,
value: dedupeFilter(params.collections || [])
},
productKind: {
active: params?.productKind !== undefined,
choices: productKind,
value: params?.productKind
},
price: {
active: maybe(
() =>
Expand Down Expand Up @@ -313,6 +320,7 @@ export function getFilterVariables(
productTypes:
params.productTypes !== undefined ? params.productTypes : null,
search: params.query,
giftCard: getProductGiftCardFilterParam(params.productKind),
stockAvailability:
params.stockStatus !== undefined
? findValueInEnum(params.stockStatus, StockAvailability)
Expand Down Expand Up @@ -377,6 +385,12 @@ export function getFilterQueryParam(
filter,
ProductListUrlFiltersEnum.channel
);

case ProductFilterKeys.productKind:
return getSingleValueQueryParam(
filter,
ProductListUrlFiltersEnum.productKind
);
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/products/views/ProductList/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ export const productListFilterOpts: ProductListFilterOpts = {
}
]
},
productKind: {
active: false,
value: "NORMAL",
choices: [
{
value: "NORMAL",
label: "Normal"
}
]
},
collections: {
...fetchMoreProps,
...searchPageProps,
Expand Down
12 changes: 12 additions & 0 deletions src/products/views/ProductList/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineMessages } from "react-intl";

export const productKindMessages = defineMessages({
giftCardLabel: {
defaultMessage: "Gift Card",
description: "label"
},
normalLabel: {
defaultMessage: "Normal",
description: "label"
}
});
46 changes: 46 additions & 0 deletions src/products/views/ProductList/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { isInEnum } from "@saleor/misc";
import { ProductTypeKindEnum } from "@saleor/types/globalTypes";
import { IntlShape } from "react-intl";

import { productKindMessages as messages } from "./messages";

interface ProductKindChoice {
label: string;
value: ProductTypeKindEnum;
}

export const getAvailableProductKinds = (): ProductKindChoice[] =>
Object.keys(ProductTypeKindEnum).map(kind => ({
label: kind,
value: kind as ProductTypeKindEnum
}));

export const getProductKindOpts = (
availableProducts: ProductKindChoice[],
intl: IntlShape
): ProductKindChoice[] =>
availableProducts.map(kind => {
switch (kind.value) {
case ProductTypeKindEnum.GIFT_CARD:
return {
...kind,
label: intl.formatMessage(messages.giftCardLabel)
};
case ProductTypeKindEnum.NORMAL:
return {
...kind,
label: intl.formatMessage(messages.normalLabel)
};
}
});

export const getProductGiftCardFilterParam = (productKind?: string) => {
if (
productKind === undefined ||
!isInEnum(productKind, ProductTypeKindEnum)
) {
return null;
}

return productKind === ProductTypeKindEnum.GIFT_CARD;
};

0 comments on commit 953c277

Please sign in to comment.