diff --git a/packages/api/src/platforms/vtex/clients/commerce/index.ts b/packages/api/src/platforms/vtex/clients/commerce/index.ts index 3f34e049b0..03049d9c8c 100644 --- a/packages/api/src/platforms/vtex/clients/commerce/index.ts +++ b/packages/api/src/platforms/vtex/clients/commerce/index.ts @@ -20,6 +20,14 @@ import type { MasterDataResponse } from './types/Newsletter' import type { Address, AddressInput } from './types/Address' import type { DeliveryMode, SelectedAddress } from './types/ShippingData' import { getStoreCookie, getWithCookie } from '../../utils/cookies' +import type { ProductRating } from './types/ProductRating' +import type { + CreateProductReviewInput, + ProductReviewsInput, + ProductReviewsResult, +} from './types/ProductReview' +import { adaptObject } from '../../utils/adaptObject' +import { camelToSnakeCase } from '../../utils/camelToSnakeCase' type ValueOf = T extends Record ? K : never @@ -30,6 +38,8 @@ const BASE_INIT = { }, } +const REVIEWS_AND_RATINGS_API_PATH = 'api/io/reviews-and-ratings/api' + export const VtexCommerce = ( { account, environment, incrementAddress, subDomainPrefix }: Options, ctx: Context @@ -364,5 +374,48 @@ export const VtexCommerce = ( { storeCookies } ) }, + rating: (productId: string): Promise => { + return fetchAPI( + `${base}/${REVIEWS_AND_RATINGS_API_PATH}/rating/${productId}`, + undefined, + { storeCookies } + ) + }, + reviews: { + create: (input: CreateProductReviewInput): Promise => { + return fetchAPI( + `${base}/${REVIEWS_AND_RATINGS_API_PATH}/review`, + { + ...BASE_INIT, + body: JSON.stringify(input), + method: 'POST', + }, + { storeCookies } + ) + }, + list: ({ + orderBy, + orderWay, + ...partialInput + }: ProductReviewsInput): Promise => { + const formattedInput = adaptObject( + { + orderBy: orderBy ? `${orderBy}:${orderWay ?? 'asc'}` : undefined, + ...partialInput, + }, + (_, value) => value !== undefined, + camelToSnakeCase, + String + ) + + const params = new URLSearchParams(formattedInput) + + return fetchAPI( + `${base}/${REVIEWS_AND_RATINGS_API_PATH}/reviews?${params.toString()}`, + undefined, + { storeCookies } + ) + }, + }, } } diff --git a/packages/api/src/platforms/vtex/clients/commerce/types/ProductRating.ts b/packages/api/src/platforms/vtex/clients/commerce/types/ProductRating.ts new file mode 100644 index 0000000000..9aec165964 --- /dev/null +++ b/packages/api/src/platforms/vtex/clients/commerce/types/ProductRating.ts @@ -0,0 +1,4 @@ +export interface ProductRating { + average: number + totalCount: number +} diff --git a/packages/api/src/platforms/vtex/clients/commerce/types/ProductReview.ts b/packages/api/src/platforms/vtex/clients/commerce/types/ProductReview.ts new file mode 100644 index 0000000000..23f9ee3acf --- /dev/null +++ b/packages/api/src/platforms/vtex/clients/commerce/types/ProductReview.ts @@ -0,0 +1,56 @@ +export interface ProductReview { + id: string + productId: string + rating: number + title: string + text: string + reviewerName: string + shopperId: string + reviewDateTime: string + searchDate: string + verifiedPurchaser: boolean + sku: string | null + approved: boolean + location: string | null + locale: string | null + pastReviews: string | null +} + +export enum ProductReviewsInputOrderBy { + productId = 'ProductId', + shopperId = 'ShopperId', + approved = 'Approved', + reviewDateTime = 'ReviewDateTime', + searchDate = 'SearchDate', + rating = 'Rating', + locale = 'Locale', +} + +export interface ProductReviewsInput { + searchTerm?: string + from?: number + to?: number + orderBy?: ProductReviewsInputOrderBy + orderWay?: 'asc' | 'desc' + status?: boolean + productId?: string + rating?: number +} + +export interface ProductReviewsResult { + data: ProductReview[] + range: { + from: number + to: number + total: number + } +} + +export interface CreateProductReviewInput { + productId: string + rating: number + title: string + text: string + reviewerName: string + approved: boolean +} diff --git a/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts b/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts index 6f73f64ae8..eba4e392bc 100644 --- a/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts +++ b/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts @@ -93,6 +93,10 @@ export interface Product { selectedProperties: Array<{ key: string; value: string }> releaseDate: string advertisement?: Advertisement + rating: { + average: number + totalCount: number + } } interface Image { diff --git a/packages/api/src/platforms/vtex/utils/adaptObject.ts b/packages/api/src/platforms/vtex/utils/adaptObject.ts new file mode 100644 index 0000000000..0336d79ed8 --- /dev/null +++ b/packages/api/src/platforms/vtex/utils/adaptObject.ts @@ -0,0 +1,39 @@ +/** + * Transforms an object's keys and values based on provided formatters and a predicate filter. + * + * @template T - The type of the transformed values. + * @param obj - The object to transform. + * @param predicate - A predicate function that determines whether a key-value pair should be included in the output. + * @param keyFormatter - A function that formats the object keys. Defaults to returning the key as is. + * @param valueFormatter - A function that formats the object values. Defaults to returning the value as is. + * @returns A new object with transformed keys and values, including only the key-value pairs that satisfy the predicate. + * + * @example Select all keys that have a defined value and also makes all keys uppercase and all values as numbers + * ```ts + * const obj = { john: "25", will: "10", bob: undefined }; + * const result = adaptObject( + * obj, + * (key, value) => value !== undefined, + * key => key.toUpperCase(), + * Integer.parseInt + * ); + * console.log(result); // { JOHN: 25, WILL: 10 } + * ``` + */ +export function adaptObject( + obj: Record, + predicate: (key: string, value: unknown) => boolean, + keyFormatter: (key: string) => string = (key) => key, + valueFormatter: (value: unknown) => T = (value) => value as T +): Record { + return Object.entries(obj).reduce( + (acc, [key, value]) => { + if (predicate(key, value)) { + acc[keyFormatter(key)] = valueFormatter(value) + } + + return acc + }, + {} as Record + ) +} diff --git a/packages/api/src/platforms/vtex/utils/camelToSnakeCase.ts b/packages/api/src/platforms/vtex/utils/camelToSnakeCase.ts new file mode 100644 index 0000000000..4b94b1f6d4 --- /dev/null +++ b/packages/api/src/platforms/vtex/utils/camelToSnakeCase.ts @@ -0,0 +1,3 @@ +export function camelToSnakeCase(str: string): string { + return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`) +}