Skip to content

Commit

Permalink
feat: basic rating display (#2653)
Browse files Browse the repository at this point in the history
## What's the purpose of this pull request?

To add rating stars to existing components:
- `<ProductCardContent/>`
- `<ProductTitle/>`

## How it works?

If the feature flag for reviews and ratings, defined at
`discovery.config`, it's turned on, then everywhere a `<ProductCard/>`
is used then it will display the product star rating

## How to test it?


[preview](https://starter-git-feat-basic-rating-display-vtex.vercel.app/)

## References

[JIRA TASK: SFS-2063](https://vtex-dev.atlassian.net/browse/SFS-2063)
[JIRA TASK: SFS-2065](https://vtex-dev.atlassian.net/browse/SFS-2065)


![image](https://github.com/user-attachments/assets/379da3f2-22cb-4e86-a3ac-3cf049fb0c0e)

![image](https://github.com/user-attachments/assets/05df5d00-6a78-44b1-bf59-6b8f46ef4c4d)

![image](https://github.com/user-attachments/assets/721c3121-8382-427f-bb2a-bfb5adb091ba)

![image](https://github.com/user-attachments/assets/85875b3a-53d6-4a0a-bbd2-9e28826fb227)

## Checklist

<em>You may erase this after checking them all 😉</em>

**PR Description**

- [ ] Displays rating at `Just Arrived`
- [ ] Displays rating at `Most Wanted`
- [ ] Displays rating at `Deals & Promotions`
- [ ] Displays rating at `Product Details`
- [ ] Displays rating at `Product Gallery`
  • Loading branch information
Guilera committed Feb 19, 2025
1 parent 3480f2a commit 33db057
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const ProductCardContent = forwardRef<HTMLElement, ProductCardContentProps>(
{includeTaxes && (
<Label data-fs-product-card-taxes-label>{includeTaxesLabel}</Label>
)}
{ratingValue && (
{ratingValue !== undefined && (
<Rating value={ratingValue} icon={<Icon name="Star" />} />
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ const ProductTitle = forwardRef<HTMLElement, ProductTitleProps>(
{!!label && label}
</div>

{(refNumber || ratingValue) && (
{(refNumber || ratingValue !== undefined) && (
<div data-fs-product-title-addendum>
{ratingValue && <Rating value={ratingValue} />}
{ratingValue !== undefined && <Rating value={ratingValue} />}
{refNumber && (
<>
{refTag} {refNumber}
Expand Down
8 changes: 4 additions & 4 deletions packages/core/@generated/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
22 changes: 18 additions & 4 deletions packages/core/@generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,7 @@ export type ProductSummary_ProductFragment = {
valueReference: any
}>
advertisement: { adId: string; adResponseId: string } | null
rating: { average: number; totalCount: number }
}

type Filter_Facets_StoreFacetBoolean_Fragment = {
Expand Down Expand Up @@ -1307,6 +1308,7 @@ export type ProductDetailsFragment_ProductFragment = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
}

export type ProductSkuMatrixSidebarFragment_ProductFragment = {
Expand Down Expand Up @@ -1444,6 +1446,7 @@ export type ServerProductQueryQuery = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
}
}

Expand Down Expand Up @@ -1650,6 +1653,7 @@ export type ClientManyProductsQueryQuery = {
valueReference: any
}>
advertisement: { adId: string; adResponseId: string } | null
rating: { average: number; totalCount: number }
}
}>
}
Expand Down Expand Up @@ -1743,6 +1747,7 @@ export type ClientProductQueryQuery = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
}
}

Expand Down Expand Up @@ -1783,6 +1788,7 @@ export type ClientSearchSuggestionsQueryQuery = {
valueReference: any
}>
advertisement: { adId: string; adResponseId: string } | null
rating: { average: number; totalCount: number }
}>
}
products: { pageInfo: { totalCount: number } }
Expand Down Expand Up @@ -1922,6 +1928,10 @@ export const ProductSummary_ProductFragmentDoc = new TypedDocumentString(
adId
adResponseId
}
rating {
average
totalCount
}
}
`,
{ fragmentName: 'ProductSummary_product' }
Expand Down Expand Up @@ -2036,6 +2046,10 @@ export const ProductDetailsFragment_ProductFragmentDoc =
value
valueReference
}
rating {
average
totalCount
}
...CartProductItem
}
fragment CartProductItem on StoreProduct {
Expand Down Expand Up @@ -2312,7 +2326,7 @@ export const ServerCollectionPageQueryDocument = {
export const ServerProductQueryDocument = {
__meta__: {
operationName: 'ServerProductQuery',
operationHash: '46103bee661405bde706d72126fdbf9b0a0c9e6e',
operationHash: '0a3f449b2a88dc1f692fe1ae981370be53a02cce',
},
} as unknown as TypedDocumentString<
ServerProductQueryQuery,
Expand Down Expand Up @@ -2348,7 +2362,7 @@ export const ClientAllVariantProductsQueryDocument = {
export const ClientManyProductsQueryDocument = {
__meta__: {
operationName: 'ClientManyProductsQuery',
operationHash: '14148671fbf53498fad5c600ee87765920145019',
operationHash: 'e1ccf9e73ec6c0b8580c6e789d8a2af7618fb1eb',
},
} as unknown as TypedDocumentString<
ClientManyProductsQueryQuery,
Expand All @@ -2366,7 +2380,7 @@ export const ClientProductGalleryQueryDocument = {
export const ClientProductQueryDocument = {
__meta__: {
operationName: 'ClientProductQuery',
operationHash: '7d121ef8d4dc99174e64e4429a9b977b8bbebed8',
operationHash: 'e1599e2efe3664aad09c026919c1c104b4085f00',
},
} as unknown as TypedDocumentString<
ClientProductQueryQuery,
Expand All @@ -2375,7 +2389,7 @@ export const ClientProductQueryDocument = {
export const ClientSearchSuggestionsQueryDocument = {
__meta__: {
operationName: 'ClientSearchSuggestionsQuery',
operationHash: '47e48eaee91d16a4237eb2c1241bc2ed3e2ad9bb',
operationHash: '3599746571e06012a61a20f92d30ede456564c4b',
},
} as unknown as TypedDocumentString<
ClientSearchSuggestionsQueryQuery,
Expand Down
1 change: 1 addition & 0 deletions packages/core/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type {
StoreProductGroupRoot,
StoreProductRoot,
StoreOrganizationRoot,
StoreProductRating,
} from '@faststore/api'

export * from '../@generated/graphql'
1 change: 1 addition & 0 deletions packages/core/discovery.config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module.exports = {
hideUnavailableItems: false,
showSponsored: false,
incrementAddress: true,
reviewsAndRatings: true,
},

// Default session
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -88,6 +89,7 @@ function ProductCard({
lowPriceWithTaxes,
offers: [{ listPrice: listPriceBase, availability, listPriceWithTaxes }],
},
rating,
} = product

const linkProps = {
Expand Down Expand Up @@ -146,7 +148,7 @@ function ProductCard({
listPrice: listPrice,
formatter: useFormattedPrice,
}}
ratingValue={ratingValue}
ratingValue={apiConfig.reviewsAndRatings ? rating.average : undefined}
outOfStock={outOfStock}
onButtonClick={onButtonClick}
linkProps={linkProps}
Expand Down Expand Up @@ -211,6 +213,11 @@ export const fragment = gql(`
adId
adResponseId
}
rating {
average
totalCount
}
}
`)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -144,6 +145,7 @@ function ProductDetails({
lowPrice,
lowPriceWithTaxes,
},
rating,
} = product

useEffect(() => {
Expand Down Expand Up @@ -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={<h1>{name}</h1>}
{...ProductTitle.props}
ratingValue={
apiConfig.reviewsAndRatings ? rating.average : undefined
}
label={
showDiscountBadge && (
<DiscountBadge.Component
Expand Down Expand Up @@ -364,6 +369,11 @@ export const fragment = gql(`
valueReference
}
rating {
average
totalCount
}
# Contains necessary info to add this item to cart
...CartProductItem
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down

0 comments on commit 33db057

Please sign in to comment.