Skip to content

Commit

Permalink
wip: rating summary
Browse files Browse the repository at this point in the history
  • Loading branch information
Guilera committed Feb 13, 2025
1 parent 03f103e commit 8b569e0
Show file tree
Hide file tree
Showing 20 changed files with 376 additions and 31 deletions.
9 changes: 0 additions & 9 deletions packages/api/src/platforms/vtex/resolvers/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,6 @@ export const Query = {
}: QuerySearchArgs,
ctx: Context
) => {
console.log('search', {
first,
after: maybeAfter,
sort,
term,
selectedFacets,
sponsoredCount,
})

// Insert channel in context for later usage
const channel = findChannel(selectedFacets)
const locale = findLocale(selectedFacets)
Expand Down
2 changes: 0 additions & 2 deletions packages/api/src/platforms/vtex/utils/rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ export function buildRatingDistribution(
(percentage, index) => (rating.distribution[index + 1] = percentage)
)

console.log('distribution', rating.distribution)

return rating
}

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/atoms/ProgressBar/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { forwardRef, useMemo, HTMLAttributes } from 'react'
import React, { forwardRef, useMemo, type HTMLAttributes } from 'react'

export interface ProgressBarProps extends HTMLAttributes<HTMLDivElement> {
/* Current value of the progress */
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/atoms/ProgressBar/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default } from './ProgressBar'
export { ProgressBarProps } from './ProgressBar'
export type { ProgressBarProps } from './ProgressBar'
9 changes: 9 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export {
ProductCardImage,
ProductCardContent,
} from './molecules/ProductCard'
export {
default as ProgressStatus,
ProgressStatusProps,
} from './molecules/ProgressStatus'
export type {
ProductCardProps,
ProductCardImageProps,
Expand Down Expand Up @@ -369,3 +373,8 @@ export type {
SKUMatrixTriggerProps,
SKUMatrixSidebarProps,
} from './organisms/SKUMatrix'

export {
default as RatingSummary,
RatingSummaryProps,
} from './organisms/RatingSummary'
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { forwardRef, type HTMLAttributes, type ReactNode } from 'react'
import ProgressBar from '../../atoms/ProgressBar'

export interface ProgressStatusProps extends HTMLAttributes<HTMLDivElement> {
/**
* The progress status value.
*/
progressValue: number
/**
* The left side element. Most of time it's gonna be a label
*/
leftSideElement?: string | ReactNode
/**
* The left side element. Most of time it's gonna be a label
*/
rightSideElement?: string | ReactNode
/**
* Optional test ID for testing.
*/
testId?: string
}

export const ProgressStatus = forwardRef<HTMLDivElement, ProgressStatusProps>(
function ProgressStatus(
{
progressValue,
leftSideElement,
rightSideElement,
testId = 'fs-progress-status',
...props
},
ref
) {
return (
<div ref={ref} data-fs-progress-status data-testid={testId} {...props}>
{leftSideElement && <p>{leftSideElement}</p>}
<ProgressBar value={progressValue} />
{rightSideElement && <p>{rightSideElement}</p>}
</div>
)
}
)

export default ProgressStatus
1 change: 1 addition & 0 deletions packages/components/src/molecules/ProgressStatus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, type ProgressStatusProps } from './ProgressStatus'
120 changes: 120 additions & 0 deletions packages/components/src/organisms/RatingSummary/RatingSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, { forwardRef, type HTMLAttributes } from 'react'
import Rating from '../../molecules/Rating'
import ProgressStatus from '../../molecules/ProgressStatus'
import Icon from '../../atoms/Icon'
import Button from '../../atoms/Button'

export interface RatingSummaryProps extends HTMLAttributes<HTMLDivElement> {
/**
* The average rating of the product
*/
average: number
/**
* The total number of reviews of the product
*/
totalCount: number
/**
* The distribution of the ratings
*/
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
/**
* The total count text to be displayed after the total count number
* @default 'Reviews'
*/
totalCountText?: string
/**
* The text to be displayed when there are no reviews
* @default 'No reviews yet'
*/
zeroCountText?: string
/**
* The text to be displayed on the button for writing a new review
* @default 'Write a review'
*/
buttonText?: string
/**
* The text to be displayed on the button for writing the very first review
* @default 'Write the first review'
*/
buttonTextFirstReview?: string
/**
* Optional test ID for testing.
*/
testId?: string
}

function buildProgressStatus(key: number, value: number) {
return (
<ProgressStatus
data-fs-rating-summary-distribution-status
leftSideElement={
<span data-fs-rating-summary-distribution-key>
<p>{key}</p>
<Icon data-fs-rating-summary-distribution-key-star name="Star" />
</span>
}
progressValue={value}
rightSideElement={
<p data-fs-rating-summary-distribution-value>{value}%</p>
}
/>
)
}

export const RatingSummary = forwardRef<HTMLDivElement, RatingSummaryProps>(
function RatingSummary(
{
average,
totalCount,
distribution,
testId = 'fs-rating-summary',
totalCountText = 'Reviews',
zeroCountText = 'No reviews yet',
buttonText = 'Write a review',
buttonTextFirstReview = 'Write the first review',
...props
},
ref
) {
const finalTotalCountText =
totalCount > 0 ? `${totalCount} ${totalCountText}` : zeroCountText
const finalButtonText = totalCount > 0 ? buttonText : buttonTextFirstReview
const formattedAverage = average > 0 ? average.toPrecision(2) : ''

return (
<div ref={ref} data-fs-rating-summary data-testid={testId} {...props}>
<div data-fs-rating-summary-header>
<h2 data-fs-rating-summary-header-average>{formattedAverage}</h2>
<Rating data-fs-rating-summary-header-stars value={average} />
<p data-fs-rating-summary-header-total-count>{finalTotalCountText}</p>
</div>
<div>
<Button
data-fs-rating-summary-button
variant="secondary"
onClick={() => alert('Write a review button clicked!')}
>
{finalButtonText}
</Button>
</div>
{totalCount > 0 && (
<div data-fs-rating-summary-distribution>
{buildProgressStatus(5, distribution.starsFive)}
{buildProgressStatus(4, distribution.starsFour)}
{buildProgressStatus(3, distribution.starsThree)}
{buildProgressStatus(2, distribution.starsTwo)}
{buildProgressStatus(1, distribution.starsOne)}
</div>
)}
</div>
)
}
)

export default RatingSummary
1 change: 1 addition & 0 deletions packages/components/src/organisms/RatingSummary/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, type RatingSummaryProps } from './RatingSummary'
4 changes: 2 additions & 2 deletions packages/core/@generated/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const documents = {
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 rating {\n average\n totalCount\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 distribution {\n starsOne\n starsTwo\n starsThree\n starsFour\n starsFive\n }\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 @@ -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 rating {\n average\n totalCount\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 distribution {\n starsOne\n starsTwo\n starsThree\n starsFour\n starsFive\n }\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
63 changes: 58 additions & 5 deletions packages/core/@generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1048,10 +1048,26 @@ export type StoreProductListReviewsSort =
export type StoreProductRating = {
/** Product average rating. */
average: Scalars['Float']['output']
/** Product rating distribution in percentages. */
distribution: StoreProductRatingDistribution
/** Product amount of ratings received. */
totalCount: Scalars['Int']['output']
}

/** Product rating distribution in percentages. */
export type StoreProductRatingDistribution = {
/** 5 star rating percentage. */
starsFive: Scalars['Int']['output']
/** 4 star rating percentage. */
starsFour: Scalars['Int']['output']
/** 1 star rating percentage. */
starsOne: Scalars['Int']['output']
/** 3 star rating percentage. */
starsThree: Scalars['Int']['output']
/** 2 star rating percentage. */
starsTwo: Scalars['Int']['output']
}

export type StoreProductReview = {
/** Indicates if the review was approved by the store owner. */
approved: Scalars['Boolean']['output']
Expand Down Expand Up @@ -1289,7 +1305,17 @@ export type ProductDetailsFragment_ProductFragment = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
rating: {
average: number
totalCount: number
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
}
}

export type ProductSkuMatrixSidebarFragment_ProductFragment = {
Expand Down Expand Up @@ -1427,7 +1453,17 @@ export type ServerProductQueryQuery = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
rating: {
average: number
totalCount: number
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
}
}
}

Expand Down Expand Up @@ -1728,7 +1764,17 @@ export type ClientProductQueryQuery = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
rating: {
average: number
totalCount: number
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
}
}
}

Expand Down Expand Up @@ -2030,6 +2076,13 @@ export const ProductDetailsFragment_ProductFragmentDoc =
rating {
average
totalCount
distribution {
starsOne
starsTwo
starsThree
starsFour
starsFive
}
}
...CartProductItem
}
Expand Down Expand Up @@ -2307,7 +2360,7 @@ export const ServerCollectionPageQueryDocument = {
export const ServerProductQueryDocument = {
__meta__: {
operationName: 'ServerProductQuery',
operationHash: '0a3f449b2a88dc1f692fe1ae981370be53a02cce',
operationHash: '312acab1a14a3b35d6c70887b5cf289b5cf6cf76',
},
} as unknown as TypedDocumentString<
ServerProductQueryQuery,
Expand Down Expand Up @@ -2361,7 +2414,7 @@ export const ClientProductGalleryQueryDocument = {
export const ClientProductQueryDocument = {
__meta__: {
operationName: 'ClientProductQuery',
operationHash: 'e1599e2efe3664aad09c026919c1c104b4085f00',
operationHash: 'e678f7fc4d59a3e4cbf61295fc1e669f44724464',
},
} as unknown as TypedDocumentString<
ClientProductQueryQuery,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/cms/faststore/sections.json
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,12 @@
"title": "Title",
"type": "string",
"default": "Reviews"
},
"ratingSummary": {
"totalCountText": "Reviews",
"zeroCountText": "No reviews yet",
"buttonText": "Write a review",
"buttonTextFirstReview": "Write the first review"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,13 @@ export const fragment = gql(`
rating {
average
totalCount
distribution {
starsOne
starsTwo
starsThree
starsFour
starsFive
}
}
# Contains necessary info to add this item to cart
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
@layer components {
.section {
// TODO: Ajustar esses componentes para a nova section ReviewsAndRatings
// @import '@faststore/ui/src/components/atoms/Ratings/styles';
@import '@faststore/ui/src/components/molecules/Rating/styles';
@import '@faststore/ui/src/components/atoms/ProgressBar/styles';
@import '@faststore/ui/src/components/molecules/ProgressStatus/styles';
@import '@faststore/ui/src/components/organisms/RatingSummary/styles';
@import '@faststore/ui/src/components/atoms/Button/styles';
}
}
Loading

0 comments on commit 8b569e0

Please sign in to comment.