From 388ba25eedb335ad3b3b4e4d8388428f18593b47 Mon Sep 17 00:00:00 2001 From: YulikK Date: Sat, 8 Jun 2024 11:49:45 +0200 Subject: [PATCH 1/4] feat: add sale category request --- src/shared/API/discount/DiscountApi.ts | 11 ++++- .../API/discount/model/DiscountModel.ts | 40 ++++++++++++++++++- src/shared/API/types/validation.ts | 16 ++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/shared/API/discount/DiscountApi.ts b/src/shared/API/discount/DiscountApi.ts index 30511ba4..04d9075c 100644 --- a/src/shared/API/discount/DiscountApi.ts +++ b/src/shared/API/discount/DiscountApi.ts @@ -1,4 +1,8 @@ -import type { ClientResponse, DiscountCodePagedQueryResponse } from '@commercetools/platform-sdk'; +import type { + ClientResponse, + DiscountCodePagedQueryResponse, + ProductDiscountPagedQueryResponse, +} from '@commercetools/platform-sdk'; import getApiClient, { type ApiClient } from '../sdk/client.ts'; @@ -13,6 +17,11 @@ export class DiscountApi { const data = await this.client.apiRoot().discountCodes().get().execute(); return data; } + + public async getDiscounts(): Promise> { + const data = await this.client.apiRoot().productDiscounts().get().execute(); + return data; + } } const createDiscountApi = (): DiscountApi => new DiscountApi(); diff --git a/src/shared/API/discount/model/DiscountModel.ts b/src/shared/API/discount/model/DiscountModel.ts index 9104d855..20be31f4 100644 --- a/src/shared/API/discount/model/DiscountModel.ts +++ b/src/shared/API/discount/model/DiscountModel.ts @@ -1,9 +1,20 @@ import type { Coupon } from '@/shared/types/cart.ts'; -import type { ClientResponse, DiscountCode, DiscountCodePagedQueryResponse } from '@commercetools/platform-sdk'; +import type { Category } from '@/shared/types/product.ts'; +import type { + ClientResponse, + DiscountCode, + DiscountCodePagedQueryResponse, + ProductDiscountPagedQueryResponse, +} from '@commercetools/platform-sdk'; import { showErrorMessage } from '@/shared/utils/userMessage.ts'; -import { isClientResponse, isDiscountCodePagedQueryResponse } from '../../types/validation.ts'; +import getProductModel from '../../product/model/ProductModel.ts'; +import { + isClientResponse, + isDiscountCodePagedQueryResponse, + isProductDiscountPagedQueryResponse, +} from '../../types/validation.ts'; import getDiscountApi, { type DiscountApi } from '../DiscountApi.ts'; export class DiscountModel { @@ -23,6 +34,25 @@ export class DiscountModel { }; } + private getCategorySaleFromData( + data: ClientResponse, + categories: Category[], + ): Category[] { + const result: Category[] = []; + if (isClientResponse(data) && isProductDiscountPagedQueryResponse(data.body)) { + const allReferences = data.body.results.flatMap((result) => result.references); + allReferences.forEach((rule) => { + if (rule.typeId === 'category') { + const categorySale = categories.find((category) => category.id === rule.id); + if (categorySale) { + result.push(categorySale); + } + } + }); + } + return result; + } + private getCouponsFromData(data: ClientResponse): Coupon[] { const coupons: Coupon[] = []; if (isClientResponse(data) && isDiscountCodePagedQueryResponse(data.body)) { @@ -42,6 +72,12 @@ export class DiscountModel { public getAllCoupons(): Coupon[] { return this.coupons; } + + public async getCategorySales(): Promise { + const data = await this.root.getDiscounts(); + const categories = await getProductModel().getCategories(); + return this.getCategorySaleFromData(data, categories); + } } const createDiscountModel = (): DiscountModel => new DiscountModel(); diff --git a/src/shared/API/types/validation.ts b/src/shared/API/types/validation.ts index 51c14a93..a334eb98 100644 --- a/src/shared/API/types/validation.ts +++ b/src/shared/API/types/validation.ts @@ -13,6 +13,7 @@ import type { FacetTerm, LocalizedString, Product, + ProductDiscountPagedQueryResponse, ProductPagedQueryResponse, ProductProjection, ProductProjectionPagedQueryResponse, @@ -314,3 +315,18 @@ export function isDiscountCodePagedQueryResponse(data: unknown): data is Discoun Array.isArray(data.results), ); } + +export function isProductDiscountPagedQueryResponse(data: unknown): data is ProductDiscountPagedQueryResponse { + return Boolean( + typeof data === 'object' && + data && + 'count' in data && + typeof data.count === 'number' && + 'limit' in data && + typeof data.limit === 'number' && + 'total' in data && + typeof data.total === 'number' && + 'results' in data && + Array.isArray(data.results), + ); +} From 7dac1052de25c35d313de924c14e49447494c593 Mon Sep 17 00:00:00 2001 From: YulikK Date: Sat, 8 Jun 2024 14:55:04 +0200 Subject: [PATCH 2/4] feat: filter * --- src/shared/API/product/ProductApi.ts | 23 ++++++++++--------- src/shared/API/product/model/ProductModel.ts | 14 +++++++++++- src/shared/API/product/utils/filter.ts | 24 +++++++++++++------- src/shared/constants/product.ts | 2 +- src/widgets/Catalog/model/CatalogModel.ts | 4 ++-- 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/shared/API/product/ProductApi.ts b/src/shared/API/product/ProductApi.ts index 31ff209b..c147a546 100644 --- a/src/shared/API/product/ProductApi.ts +++ b/src/shared/API/product/ProductApi.ts @@ -5,14 +5,15 @@ import type { ProductProjectionPagedSearchResponse, } from '@commercetools/platform-sdk'; -import { DEFAULT_PAGE, MAX_PRICE, MIN_PRICE, PRICE_FRACTIONS, PRODUCT_LIMIT } from '@/shared/constants/product.ts'; +import { DEFAULT_PAGE, MAX_PRICE, MIN_PRICE, PRODUCT_LIMIT } from '@/shared/constants/product.ts'; + +import type { OptionsRequest, PriceRange } from '../types/type.ts'; import getApiClient, { type ApiClient } from '../sdk/client.ts'; -import { type OptionsRequest } from '../types/type.ts'; +import { getDefaultPriceRange } from './utils/filter.ts'; import makeSortRequest from './utils/sort.ts'; const Search = 'text'; -const FACET_ADD = 1; enum Facets { category = 'categories.id counting products', price = 'variants.price.centAmount', @@ -63,25 +64,25 @@ export class ProductApi { return data; } - public async getProducts(options?: OptionsRequest): Promise> { + public async getProducts( + options?: OptionsRequest, + productsPriceRange?: PriceRange, + ): Promise> { const { filter, limit = PRODUCT_LIMIT, page = DEFAULT_PAGE, search, sort } = options || {}; - const filterQuery = filter?.getFilter(); - const priceRange = filter?.getPriceRange(); - const min = Math.round((priceRange?.min ?? MIN_PRICE) * PRICE_FRACTIONS - FACET_ADD); - const max = Math.round((priceRange?.max ?? MAX_PRICE) * PRICE_FRACTIONS + FACET_ADD); + const filterQuery = filter?.getFilter(productsPriceRange); + const priceRange = filter ? filter.getPriceRange(productsPriceRange) : getDefaultPriceRange(); const fuzzyLevel = this.getFuzzyLevel(search?.value ? search?.value : ''); - const data = await this.client .apiRoot() .productProjections() .search() .get({ queryArgs: { - facet: [Facets.category, Facets.size, `${Facets.price}:${QueryParams.range}(${min} to ${max})`], + facet: [Facets.category, Facets.size, priceRange], limit, markMatchingVariants: true, offset: (page - 1) * PRODUCT_LIMIT, - ...(search && { [`${Search}.${search.locale}`]: search.value }), + ...(search && { [`${Search}.${search.locale}`]: `*${search.value}*` }), ...(search && { fuzzy: true }), ...(search?.value && { fuzzyLevel }), ...(sort && { sort: makeSortRequest(sort) }), diff --git a/src/shared/API/product/model/ProductModel.ts b/src/shared/API/product/model/ProductModel.ts index 0e7972e1..1fa89e28 100644 --- a/src/shared/API/product/model/ProductModel.ts +++ b/src/shared/API/product/model/ProductModel.ts @@ -52,6 +52,11 @@ enum ProductConstant { export class ProductModel { private categories: Category[] = []; + private priceRange: PriceRange = { + max: 0, + min: 0, + }; + private root: ProductApi; constructor() { @@ -268,6 +273,9 @@ export class ProductModel { }); } } + if (this.priceRange.min === 0 && this.priceRange.max === 0) { + this.priceRange = priceRange; + } return priceRange; } @@ -395,7 +403,7 @@ export class ProductModel { public async getProducts(options?: OptionsRequest): Promise { await getProductModel().getCategories(); - const data = await this.root.getProducts(options); + const data = await this.root.getProducts(options, this.priceRange); const products = this.getProductsFromData(data); if (options?.sort) { this.sortVariants(products, options?.sort); @@ -413,6 +421,10 @@ export class ProductModel { }; return result; } + + public getProductsPriceRange(): PriceRange { + return this.priceRange; + } } const createProductModel = (): ProductModel => new ProductModel(); diff --git a/src/shared/API/product/utils/filter.ts b/src/shared/API/product/utils/filter.ts index cd4594d5..6fff88a9 100644 --- a/src/shared/API/product/utils/filter.ts +++ b/src/shared/API/product/utils/filter.ts @@ -12,8 +12,8 @@ export default class FilterProduct { private newArrival = ''; private price: PriceRange = { - max: MAX_PRICE, - min: MIN_PRICE, + max: 0, + min: 0, }; private sale = ''; @@ -55,7 +55,7 @@ export default class FilterProduct { return this.getFilter(); } - public getFilter(): string[] { + public getFilter(productsPriceRange?: PriceRange): string[] { const result = []; if (this.id.length) { result.push(`${FilterFields.ID}:${this.id.map((id) => `"${id}"`).join(',')}`); @@ -70,9 +70,7 @@ export default class FilterProduct { result.push(this.newArrival); } if (this.price) { - result.push( - `${FilterFields.PRICE}: range(${Math.round(this.price.min * PRICE_FRACTIONS)} to ${Math.round(this.price.max * PRICE_FRACTIONS)})`, - ); + result.push(this.getPriceRange(productsPriceRange)); } if (this.sale) { result.push(this.sale); @@ -80,7 +78,17 @@ export default class FilterProduct { return result; } - public getPriceRange(): PriceRange { - return this.price; + public getPriceRange(productsPriceRange?: PriceRange): string { + const min = Math.round(this.price.min * PRICE_FRACTIONS); + const max = + this.price.max && productsPriceRange && this.price.max !== productsPriceRange.max + ? Math.round(this.price.max * PRICE_FRACTIONS) + : MAX_PRICE; + + return `${FilterFields.PRICE}: range(${min} to ${max})`; } } + +export function getDefaultPriceRange(): string { + return `${FilterFields.PRICE}: range(${MIN_PRICE} to ${MAX_PRICE})`; +} diff --git a/src/shared/constants/product.ts b/src/shared/constants/product.ts index ea8f9f37..f63e7011 100644 --- a/src/shared/constants/product.ts +++ b/src/shared/constants/product.ts @@ -2,7 +2,7 @@ export const PRICE_FRACTIONS = 100; export const PRODUCT_LIMIT = 9; export const DEFAULT_PAGE = 1; export const MIN_PRICE = 0; -export const MAX_PRICE = 1000000; +export const MAX_PRICE = '*'; export const CURRENCY = 'USD'; export const EMPTY_PRODUCT = { diff --git a/src/widgets/Catalog/model/CatalogModel.ts b/src/widgets/Catalog/model/CatalogModel.ts index ca8e0697..14e7f91f 100644 --- a/src/widgets/Catalog/model/CatalogModel.ts +++ b/src/widgets/Catalog/model/CatalogModel.ts @@ -159,14 +159,14 @@ class CatalogModel { result = { filter, page: Number(page), - search: { locale: currentLanguage, value: searchValue }, + search: searchValue ? { locale: currentLanguage, value: searchValue } : null, sort: currentSort ?? null, }; } else { result = { filter, page: Number(page), - search: { locale: currentLanguage, value: searchValue }, + search: searchValue ? { locale: currentLanguage, value: searchValue } : null, }; } From 7c65f658738c4fee43fb648a79f3bb14eb291383 Mon Sep 17 00:00:00 2001 From: YulikK Date: Sat, 8 Jun 2024 15:41:43 +0200 Subject: [PATCH 3/4] feat: options for shop list --- .../API/shopping-list/ShoppingListApi.ts | 39 +++++++++++++++++-- .../shopping-list/model/ShoppingListModel.ts | 19 +++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/shared/API/shopping-list/ShoppingListApi.ts b/src/shared/API/shopping-list/ShoppingListApi.ts index d57e0369..ab110cdc 100644 --- a/src/shared/API/shopping-list/ShoppingListApi.ts +++ b/src/shared/API/shopping-list/ShoppingListApi.ts @@ -8,6 +8,11 @@ import type { ShoppingListSetAnonymousIdAction, } from '@commercetools/platform-sdk'; +import { DEFAULT_PAGE, PRODUCT_LIMIT } from '@/shared/constants/product.ts'; + +import type { OptionsRequest } from '../types/type.ts'; + +import makeSortRequest from '../product/utils/sort.ts'; import getApiClient, { type ApiClient } from '../sdk/client.ts'; export class ShoppingListApi { @@ -92,13 +97,39 @@ export class ShoppingListApi { return data; } - public async get(): Promise> { - const data = await this.client.apiRoot().me().shoppingLists().get().execute(); + public async get(options?: OptionsRequest): Promise> { + const { limit = PRODUCT_LIMIT, page = DEFAULT_PAGE, sort } = options || {}; + const data = await this.client + .apiRoot() + .me() + .shoppingLists() + .get({ + queryArgs: { + limit, + offset: (page - 1) * PRODUCT_LIMIT, + ...(sort && { sort: makeSortRequest(sort) }), + withTotal: true, + }, + }) + .execute(); return data; } - public async getAnonymList(ID: string): Promise> { - const data = await this.client.apiRoot().shoppingLists().withId({ ID }).get().execute(); + public async getAnonymList(ID: string, options?: OptionsRequest): Promise> { + const { limit = PRODUCT_LIMIT, page = DEFAULT_PAGE, sort } = options || {}; + const data = await this.client + .apiRoot() + .shoppingLists() + .withId({ ID }) + .get({ + queryArgs: { + limit, + offset: (page - 1) * PRODUCT_LIMIT, + ...(sort && { sort: makeSortRequest(sort) }), + withTotal: true, + }, + }) + .execute(); return data; } diff --git a/src/shared/API/shopping-list/model/ShoppingListModel.ts b/src/shared/API/shopping-list/model/ShoppingListModel.ts index 599e82cb..85eac94b 100644 --- a/src/shared/API/shopping-list/model/ShoppingListModel.ts +++ b/src/shared/API/shopping-list/model/ShoppingListModel.ts @@ -12,6 +12,8 @@ import getStore from '@/shared/Store/Store.ts'; import { setAnonymousId, setAnonymousShopListId } from '@/shared/Store/actions.ts'; import { showErrorMessage } from '@/shared/utils/userMessage.ts'; +import type { OptionsRequest } from '../../types/type.ts'; + import { isClientResponse, isErrorResponse, @@ -66,8 +68,11 @@ export class ShoppingListModel { }; } - private async getAnonymousShoppingList(anonymousShopListId: string): Promise { - const dataAnonymList = await this.root.getAnonymList(anonymousShopListId); + private async getAnonymousShoppingList( + anonymousShopListId: string, + options?: OptionsRequest, + ): Promise { + const dataAnonymList = await this.root.getAnonymList(anonymousShopListId, options); const anonymShoppingList = this.getShopListFromData(dataAnonymList); if (anonymShoppingList.id && !dataAnonymList.body.customer?.id) { this.shoppingList = anonymShoppingList; @@ -94,8 +99,8 @@ export class ShoppingListModel { return cart; } - private async getUserShoppingLists(): Promise { - const data = await this.root.get(); + private async getUserShoppingLists(options?: OptionsRequest): Promise { + const data = await this.root.get(options); if (data.body.count === 0) { const newShopList = await this.root.create(); this.shoppingList = this.getShopListFromData(newShopList); @@ -183,14 +188,14 @@ export class ShoppingListModel { return this.shoppingList; } - public async getShoppingList(): Promise { + public async getShoppingList(options?: OptionsRequest): Promise { if (!this.shoppingList) { const { anonymousId, anonymousShopListId } = getStore().getState(); if (anonymousShopListId && anonymousId) { - this.shoppingList = await this.getAnonymousShoppingList(anonymousShopListId); + this.shoppingList = await this.getAnonymousShoppingList(anonymousShopListId, options); } if (!this.shoppingList) { - this.shoppingList = await this.getUserShoppingLists(); + this.shoppingList = await this.getUserShoppingLists(options); } } return this.shoppingList; From b27b87ecbaee5e9ee9b3f22f9b80892c9873ec76 Mon Sep 17 00:00:00 2001 From: YulikK Date: Sat, 8 Jun 2024 15:47:31 +0200 Subject: [PATCH 4/4] feat: merge