From 0630e01ea3321a714f602ed6e0fae80aa6dde545 Mon Sep 17 00:00:00 2001 From: gutchenzo Date: Wed, 29 Jan 2025 18:10:18 -0300 Subject: [PATCH 1/4] fix: displays rating component when it's zero --- .../src/molecules/ProductCard/ProductCardContent.tsx | 2 +- .../components/src/molecules/ProductTitle/ProductTitle.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/src/molecules/ProductCard/ProductCardContent.tsx b/packages/components/src/molecules/ProductCard/ProductCardContent.tsx index b13a8be039..76438b2aea 100644 --- a/packages/components/src/molecules/ProductCard/ProductCardContent.tsx +++ b/packages/components/src/molecules/ProductCard/ProductCardContent.tsx @@ -128,7 +128,7 @@ const ProductCardContent = forwardRef( {includeTaxes && ( )} - {ratingValue && ( + {ratingValue != undefined && ( } /> )} diff --git a/packages/components/src/molecules/ProductTitle/ProductTitle.tsx b/packages/components/src/molecules/ProductTitle/ProductTitle.tsx index 090b4369d2..cbb9c12f00 100644 --- a/packages/components/src/molecules/ProductTitle/ProductTitle.tsx +++ b/packages/components/src/molecules/ProductTitle/ProductTitle.tsx @@ -56,9 +56,9 @@ const ProductTitle = forwardRef( {!!label && label} - {(refNumber || ratingValue) && ( + {(refNumber || ratingValue != undefined) && (
- {ratingValue && } + {ratingValue != undefined && } {refNumber && ( <> {refTag} {refNumber} From 25e9c9a33ccb3199becdf8595f957b9d4db342a9 Mon Sep 17 00:00:00 2001 From: gutchenzo Date: Wed, 29 Jan 2025 18:12:28 -0300 Subject: [PATCH 2/4] chore: creates feature flag for Reviews & Ratings --- packages/core/discovery.config.default.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/discovery.config.default.js b/packages/core/discovery.config.default.js index 68776f8f7e..d87a0b098d 100644 --- a/packages/core/discovery.config.default.js +++ b/packages/core/discovery.config.default.js @@ -34,6 +34,7 @@ module.exports = { hideUnavailableItems: false, showSponsored: false, incrementAddress: true, + reviewsAndRatings: true, }, // Default session From ede5a5d6695931ac24647f2ae96d7d86692a82e3 Mon Sep 17 00:00:00 2001 From: gutchenzo Date: Wed, 29 Jan 2025 18:13:39 -0300 Subject: [PATCH 3/4] feat: retrieves rating value from api and pass it through props --- packages/core/@generated/gql.ts | 8 ++-- packages/core/@generated/graphql.ts | 44 +++++++++++++++++-- packages/core/api/index.ts | 1 + .../product/ProductCard/ProductCard.tsx | 9 +++- .../ProductDetails/ProductDetails.tsx | 10 +++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/packages/core/@generated/gql.ts b/packages/core/@generated/gql.ts index 8fcec3cc26..79bc1e0df2 100644 --- a/packages/core/@generated/gql.ts +++ b/packages/core/@generated/gql.ts @@ -12,11 +12,11 @@ import * as types from './graphql' * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n advertisement {\n adId\n adResponseId\n }\n }\n': + '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n advertisement {\n adId\n adResponseId\n }\n\n rating {\n average\n totalCount\n }\n }\n': types.ProductSummary_ProductFragmentDoc, '\n fragment Filter_facets on StoreFacet {\n ... on StoreFacetRange {\n key\n label\n\n min {\n selected\n absolute\n }\n\n max {\n selected\n absolute\n }\n\n __typename\n }\n ... on StoreFacetBoolean {\n key\n label\n values {\n label\n value\n selected\n quantity\n }\n\n __typename\n }\n }\n': types.Filter_FacetsFragmentDoc, - '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n': + '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n rating {\n average\n totalCount\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n': types.ProductDetailsFragment_ProductFragmentDoc, '\n fragment ProductSKUMatrixSidebarFragment_product on StoreProduct {\n id: productID\n isVariantOf {\n name\n productGroupID\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n allVariantProducts {\n\t\t\t\t\tsku\n name\n image {\n url\n alternateName\n }\n offers {\n highPrice\n lowPrice\n lowPriceWithTaxes\n offerCount\n priceCurrency\n offers {\n listPrice\n listPriceWithTaxes\n sellingPrice\n priceCurrency\n price\n priceWithTaxes\n priceValidUntil\n itemCondition\n availability\n quantity\n }\n }\n additionalProperty {\n propertyID\n value\n name\n valueReference\n }\n }\n }\n }\n }\n': types.ProductSkuMatrixSidebarFragment_ProductFragmentDoc, @@ -66,7 +66,7 @@ const documents = { * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql( - source: '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n advertisement {\n adId\n adResponseId\n }\n }\n' + source: '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n advertisement {\n adId\n adResponseId\n }\n\n rating {\n average\n totalCount\n }\n }\n' ): typeof import('./graphql').ProductSummary_ProductFragmentDoc /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. @@ -78,7 +78,7 @@ export function gql( * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql( - source: '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n' + source: '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n rating {\n average\n totalCount\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n' ): typeof import('./graphql').ProductDetailsFragment_ProductFragmentDoc /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. diff --git a/packages/core/@generated/graphql.ts b/packages/core/@generated/graphql.ts index 0f71d6e880..c96d36ef7b 100644 --- a/packages/core/@generated/graphql.ts +++ b/packages/core/@generated/graphql.ts @@ -156,6 +156,19 @@ export type DeliveryIds = { warehouseId: Maybe } +export type ICreateProductReview = { + /** Product ID. */ + productId: Scalars['String']['input'] + /** Review rating. */ + rating: Scalars['Int']['input'] + /** Review author name. */ + reviewerName: Scalars['String']['input'] + /** Review content. */ + text: Scalars['String']['input'] + /** Review title. */ + title: Scalars['String']['input'] +} + export type IGeoCoordinates = { /** The latitude of the geographic coordinates. */ latitude: Scalars['Float']['input'] @@ -954,6 +967,8 @@ export type StoreProduct = { offers: StoreAggregateOffer /** Product ID, such as [ISBN](https://www.isbn-international.org/content/what-isbn) or similar global IDs. */ productID: Scalars['String']['output'] + /** Product rating. */ + rating: StoreProductRating /** The product's release date. Formatted using https://en.wikipedia.org/wiki/ISO_8601 */ releaseDate: Scalars['String']['output'] /** Array with review information. */ @@ -1008,6 +1023,13 @@ export type StoreProductGroup = { skuVariants: Maybe } +export type StoreProductRating = { + /** Product average rating. */ + average: Scalars['Float']['output'] + /** Product amount of ratings received. */ + totalCount: Scalars['Int']['output'] +} + /** Properties that can be associated with products and products groups. */ export type StorePropertyValue = { /** Property name. */ @@ -1159,6 +1181,7 @@ export type ProductSummary_ProductFragment = { valueReference: any }> advertisement: { adId: string; adResponseId: string } | null + rating: { average: number; totalCount: number } } type Filter_Facets_StoreFacetBoolean_Fragment = { @@ -1221,6 +1244,7 @@ export type ProductDetailsFragment_ProductFragment = { value: any valueReference: any }> + rating: { average: number; totalCount: number } } export type ProductSkuMatrixSidebarFragment_ProductFragment = { @@ -1358,6 +1382,7 @@ export type ServerProductQueryQuery = { value: any valueReference: any }> + rating: { average: number; totalCount: number } } } @@ -1564,6 +1589,7 @@ export type ClientManyProductsQueryQuery = { valueReference: any }> advertisement: { adId: string; adResponseId: string } | null + rating: { average: number; totalCount: number } } }> } @@ -1657,6 +1683,7 @@ export type ClientProductQueryQuery = { value: any valueReference: any }> + rating: { average: number; totalCount: number } } } @@ -1697,6 +1724,7 @@ export type ClientSearchSuggestionsQueryQuery = { valueReference: any }> advertisement: { adId: string; adResponseId: string } | null + rating: { average: number; totalCount: number } }> } products: { pageInfo: { totalCount: number } } @@ -1836,6 +1864,10 @@ export const ProductSummary_ProductFragmentDoc = new TypedDocumentString( adId adResponseId } + rating { + average + totalCount + } } `, { fragmentName: 'ProductSummary_product' } @@ -1950,6 +1982,10 @@ export const ProductDetailsFragment_ProductFragmentDoc = value valueReference } + rating { + average + totalCount + } ...CartProductItem } fragment CartProductItem on StoreProduct { @@ -2226,7 +2262,7 @@ export const ServerCollectionPageQueryDocument = { export const ServerProductQueryDocument = { __meta__: { operationName: 'ServerProductQuery', - operationHash: '46103bee661405bde706d72126fdbf9b0a0c9e6e', + operationHash: '0a3f449b2a88dc1f692fe1ae981370be53a02cce', }, } as unknown as TypedDocumentString< ServerProductQueryQuery, @@ -2262,7 +2298,7 @@ export const ClientAllVariantProductsQueryDocument = { export const ClientManyProductsQueryDocument = { __meta__: { operationName: 'ClientManyProductsQuery', - operationHash: '14148671fbf53498fad5c600ee87765920145019', + operationHash: 'e1ccf9e73ec6c0b8580c6e789d8a2af7618fb1eb', }, } as unknown as TypedDocumentString< ClientManyProductsQueryQuery, @@ -2280,7 +2316,7 @@ export const ClientProductGalleryQueryDocument = { export const ClientProductQueryDocument = { __meta__: { operationName: 'ClientProductQuery', - operationHash: '7d121ef8d4dc99174e64e4429a9b977b8bbebed8', + operationHash: 'e1599e2efe3664aad09c026919c1c104b4085f00', }, } as unknown as TypedDocumentString< ClientProductQueryQuery, @@ -2289,7 +2325,7 @@ export const ClientProductQueryDocument = { export const ClientSearchSuggestionsQueryDocument = { __meta__: { operationName: 'ClientSearchSuggestionsQuery', - operationHash: '47e48eaee91d16a4237eb2c1241bc2ed3e2ad9bb', + operationHash: '3599746571e06012a61a20f92d30ede456564c4b', }, } as unknown as TypedDocumentString< ClientSearchSuggestionsQueryQuery, diff --git a/packages/core/api/index.ts b/packages/core/api/index.ts index 3060957adf..4aa6a03b9f 100644 --- a/packages/core/api/index.ts +++ b/packages/core/api/index.ts @@ -12,6 +12,7 @@ export type { StoreProductGroupRoot, StoreProductRoot, StoreOrganizationRoot, + StoreProductRating, } from '@faststore/api' export * from '../@generated/graphql' diff --git a/packages/core/src/components/product/ProductCard/ProductCard.tsx b/packages/core/src/components/product/ProductCard/ProductCard.tsx index f5f7555ce6..3fc14f0a08 100644 --- a/packages/core/src/components/product/ProductCard/ProductCard.tsx +++ b/packages/core/src/components/product/ProductCard/ProductCard.tsx @@ -12,6 +12,7 @@ import NextLink from 'next/link' import { Image } from 'src/components/ui/Image' import { useFormattedPrice } from 'src/sdk/product/useFormattedPrice' import { useProductLink } from 'src/sdk/product/useProductLink' +import { api as apiConfig } from 'discovery.config' type Variant = 'wide' | 'default' @@ -88,6 +89,7 @@ function ProductCard({ lowPriceWithTaxes, offers: [{ listPrice: listPriceBase, availability, listPriceWithTaxes }], }, + rating, } = product const linkProps = { @@ -146,7 +148,7 @@ function ProductCard({ listPrice: listPrice, formatter: useFormattedPrice, }} - ratingValue={ratingValue} + ratingValue={apiConfig.reviewsAndRatings ? rating.average : undefined} outOfStock={outOfStock} onButtonClick={onButtonClick} linkProps={linkProps} @@ -211,6 +213,11 @@ export const fragment = gql(` adId adResponseId } + + rating { + average + totalCount + } } `) diff --git a/packages/core/src/components/sections/ProductDetails/ProductDetails.tsx b/packages/core/src/components/sections/ProductDetails/ProductDetails.tsx index 7f89b7e0e5..259f1a6c29 100644 --- a/packages/core/src/components/sections/ProductDetails/ProductDetails.tsx +++ b/packages/core/src/components/sections/ProductDetails/ProductDetails.tsx @@ -16,6 +16,7 @@ import { getOverridableSection } from '../../../sdk/overrides/getOverriddenSecti import { useOverrideComponents } from '../../../sdk/overrides/OverrideContext' import { usePDP } from '../../../sdk/overrides/PageProvider' import { ProductDetailsDefaultComponents } from './DefaultComponents' +import { api as apiConfig } from 'discovery.config' type StoreConfig = typeof storeConfig & { experimental: { @@ -144,6 +145,7 @@ function ProductDetails({ lowPrice, lowPriceWithTaxes, }, + rating, } = product useEffect(() => { @@ -197,6 +199,9 @@ function ProductDetails({ // Maybe now it's worth to make title always a h1 and receive only the name, as it would be easier for users to override. title={

{name}

} {...ProductTitle.props} + ratingValue={ + apiConfig.reviewsAndRatings ? rating.average : undefined + } label={ showDiscountBadge && ( Date: Wed, 29 Jan 2025 18:13:58 -0300 Subject: [PATCH 4/4] fix: adds missing import --- .../src/components/sections/ProductGallery/section.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/sections/ProductGallery/section.module.scss b/packages/core/src/components/sections/ProductGallery/section.module.scss index b7a29f170a..3ea3f39cb4 100644 --- a/packages/core/src/components/sections/ProductGallery/section.module.scss +++ b/packages/core/src/components/sections/ProductGallery/section.module.scss @@ -45,6 +45,7 @@ @import "@faststore/ui/src/components/molecules/DiscountBadge/styles.scss"; @import "@faststore/ui/src/components/molecules/InputField/styles.scss"; @import "@faststore/ui/src/components/molecules/LinkButton/styles.scss"; + @import "@faststore/ui/src/components/molecules/Rating/styles"; @import "@faststore/ui/src/components/molecules/ProductCard/styles.scss"; @import "@faststore/ui/src/components/molecules/ProductCardSkeleton/styles"; @import "@faststore/ui/src/components/molecules/ProductPrice/styles.scss";