Skip to content

Commit cf9d614

Browse files
committed
feat(reviews): conditionally display reviews based on display settings
1 parent 9163ebc commit cf9d614

10 files changed

Lines changed: 90 additions & 9 deletions

File tree

.changeset/cute-trees-swim.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@bigcommerce/catalyst-core": minor
3+
---
4+
5+
Conditionally display product ratings in the storefront based on `site.settings.display.showProductRating`. The storefront logic when this setting is enabled/disabled matches exactly the logic of Stencil + Cornerstone.

core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const BrandPageQuery = graphql(`
2121
productComparisonsEnabled
2222
}
2323
}
24+
display {
25+
showProductRating
26+
}
2427
reviews {
2528
enabled
2629
}

core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export default async function Brand(props: Props) {
103103
return notFound();
104104
}
105105

106-
const reviewsEnabled = settings?.reviews.enabled ?? false;
106+
const showRating = Boolean(settings?.reviews.enabled && settings.display.showProductRating);
107107

108108
const productComparisonsEnabled =
109109
settings?.storefront.catalog?.productComparisonsEnabled ?? false;
@@ -223,7 +223,7 @@ export default async function Brand(props: Props) {
223223
rangeFilterApplyLabel={t('FacetedSearch.Range.apply')}
224224
removeLabel={t('Compare.remove')}
225225
resetFiltersLabel={t('FacetedSearch.resetFilters')}
226-
reviewsEnabled={reviewsEnabled}
226+
reviewsEnabled={showRating}
227227
showCompare={productComparisonsEnabled}
228228
sortDefaultValue="featured"
229229
sortLabel={t('Search.title')}

core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ const CategoryPageQuery = graphql(
4040
productComparisonsEnabled
4141
}
4242
}
43+
display {
44+
showProductRating
45+
}
4346
reviews {
4447
enabled
4548
}

core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export default async function Category(props: Props) {
113113
href: path ?? '#',
114114
}));
115115

116-
const reviewsEnabled = settings?.reviews.enabled ?? false;
116+
const showRating = Boolean(settings?.reviews.enabled && settings.display.showProductRating);
117117

118118
const productComparisonsEnabled =
119119
settings?.storefront.catalog?.productComparisonsEnabled ?? false;
@@ -259,7 +259,7 @@ export default async function Category(props: Props) {
259259
rangeFilterApplyLabel={t('FacetedSearch.Range.apply')}
260260
removeLabel={t('Compare.remove')}
261261
resetFiltersLabel={t('FacetedSearch.resetFilters')}
262-
reviewsEnabled={reviewsEnabled}
262+
reviewsEnabled={showRating}
263263
showCompare={productComparisonsEnabled}
264264
sortDefaultValue="featured"
265265
sortLabel={t('SortBy.sortBy')}

core/app/[locale]/(default)/(faceted)/search/page-data.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const SearchPageQuery = graphql(`
1313
productComparisonsEnabled
1414
}
1515
}
16+
display {
17+
showProductRating
18+
}
1619
reviews {
1720
enabled
1821
}

core/app/[locale]/(default)/(faceted)/search/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default async function Search(props: Props) {
7878

7979
const { settings } = await getSearchPageData();
8080

81-
const reviewsEnabled = settings?.reviews.enabled ?? false;
81+
const showRating = Boolean(settings?.reviews.enabled && settings.display.showProductRating);
8282

8383
const productComparisonsEnabled =
8484
settings?.storefront.catalog?.productComparisonsEnabled ?? false;
@@ -255,7 +255,7 @@ export default async function Search(props: Props) {
255255
rangeFilterApplyLabel={t('FacetedSearch.Range.apply')}
256256
removeLabel={t('Compare.remove')}
257257
resetFiltersLabel={t('FacetedSearch.resetFilters')}
258-
reviewsEnabled={reviewsEnabled}
258+
reviewsEnabled={showRating}
259259
showCompare={productComparisonsEnabled}
260260
sortDefaultValue="featured"
261261
sortLabel={t('SortBy.sortBy')}

core/app/[locale]/(default)/product/[slug]/page-data.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ const ProductQuery = graphql(
174174
reviews {
175175
enabled
176176
}
177+
display {
178+
showProductRating
179+
}
177180
}
178181
product(entityId: $entityId) {
179182
entityId

core/app/[locale]/(default)/product/[slug]/page.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import { SearchParams } from 'nuqs/server';
77
import { Stream, Streamable } from '@/vibes/soul/lib/streamable';
88
import { FeaturedProductCarousel } from '@/vibes/soul/sections/featured-product-carousel';
99
import { ProductDetail } from '@/vibes/soul/sections/product-detail';
10-
import { getSessionCustomerAccessToken } from '~/auth';
10+
import { auth, getSessionCustomerAccessToken } from '~/auth';
1111
import { pricesTransformer } from '~/data-transformers/prices-transformer';
1212
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
1313
import { productOptionsTransformer } from '~/data-transformers/product-options-transformer';
1414
import { getPreferredCurrencyCode } from '~/lib/currency';
1515

1616
import { addToCart } from './_actions/add-to-cart';
17+
import { submitReview } from './_actions/submit-review';
1718
import { ProductAnalyticsProvider } from './_components/product-analytics-provider';
1819
import { ProductSchema } from './_components/product-schema';
1920
import { ProductViewed } from './_components/product-viewed';
@@ -79,7 +80,8 @@ export default async function Product({ params, searchParams }: Props) {
7980

8081
const { product: baseProduct, settings } = await getProduct(productId, customerAccessToken);
8182

82-
const reviewsEnabled = settings?.reviews.enabled ?? false;
83+
const reviewsEnabled = Boolean(settings?.reviews.enabled && !settings.display.showProductRating);
84+
const showRating = Boolean(settings?.reviews.enabled && settings.display.showProductRating);
8385

8486
if (!baseProduct) {
8587
return notFound();
@@ -339,6 +341,21 @@ export default async function Product({ params, searchParams }: Props) {
339341
};
340342
});
341343

344+
const streamableUser = Streamable.from(async () => {
345+
const session = await auth();
346+
const firstName = session?.user?.firstName ?? '';
347+
const lastName = session?.user?.lastName ?? '';
348+
349+
if (!firstName || !lastName) {
350+
return { email: session?.user?.email ?? '', name: '' };
351+
}
352+
353+
const lastInitial = lastName.charAt(0).toUpperCase();
354+
const obfuscatedName = `${firstName} ${lastInitial}.`;
355+
356+
return { email: session?.user?.email ?? '', name: obfuscatedName };
357+
});
358+
342359
return (
343360
<>
344361
<ProductAnalyticsProvider data={streamableAnalyticsData}>
@@ -367,6 +384,7 @@ export default async function Product({ params, searchParams }: Props) {
367384
images: streamableImages,
368385
price: streamablePrices,
369386
reviewsEnabled,
387+
showRating,
370388
subtitle: baseProduct.brand?.name,
371389
rating: baseProduct.reviewSummary.averageRating,
372390
accordions: streameableAccordions,
@@ -375,7 +393,9 @@ export default async function Product({ params, searchParams }: Props) {
375393
stockLevelMessage: streamableStockLevelMessage,
376394
}}
377395
quantityLabel={t('ProductDetails.quantity')}
396+
reviewFormAction={submitReview}
378397
thumbnailLabel={t('ProductDetails.thumbnail')}
398+
user={streamableUser}
379399
/>
380400
</ProductAnalyticsProvider>
381401

@@ -390,7 +410,7 @@ export default async function Product({ params, searchParams }: Props) {
390410
title={t('RelatedProducts.title')}
391411
/>
392412

393-
{reviewsEnabled && (
413+
{showRating && (
394414
<Reviews
395415
productId={productId}
396416
searchParams={searchParams}

core/vibes/soul/sections/product-detail/index.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { ReactNode } from 'react';
22

33
import { Stream, Streamable } from '@/vibes/soul/lib/streamable';
44
import { Accordion, AccordionItem } from '@/vibes/soul/primitives/accordion';
5+
import { AnimatedUnderline } from '@/vibes/soul/primitives/animated-underline';
56
import { Price, PriceLabel } from '@/vibes/soul/primitives/price-label';
67
import { Rating } from '@/vibes/soul/primitives/rating';
78
import * as Skeleton from '@/vibes/soul/primitives/skeleton';
89
import { type Breadcrumb, Breadcrumbs } from '@/vibes/soul/sections/breadcrumbs';
910
import { ProductGallery } from '@/vibes/soul/sections/product-detail/product-gallery';
11+
import { ReviewForm, SubmitReviewAction } from '@/vibes/soul/sections/reviews/review-form';
1012

1113
import { ProductDetailForm, ProductDetailFormAction } from './product-detail-form';
1214
import { Field } from './schema';
@@ -21,6 +23,7 @@ interface ProductDetailProduct {
2123
badge?: string;
2224
rating?: Streamable<number | null>;
2325
reviewsEnabled?: boolean;
26+
showRating?: boolean;
2427
summary?: Streamable<string>;
2528
description?: Streamable<string | ReactNode | null>;
2629
accordions?: Streamable<
@@ -49,6 +52,15 @@ export interface ProductDetailProps<F extends Field> {
4952
thumbnailLabel?: string;
5053
additionalInformationTitle?: string;
5154
additionalActions?: ReactNode;
55+
reviewFormEmailLabel?: string;
56+
reviewFormModalTitle?: string;
57+
reviewFormNameLabel?: string;
58+
reviewFormRatingLabel?: string;
59+
reviewFormReviewLabel?: string;
60+
reviewFormSubmitLabel?: string;
61+
reviewFormTitleLabel?: string;
62+
reviewFormAction: SubmitReviewAction;
63+
user: Streamable<{ email: string; name: string }>;
5264
}
5365

5466
// eslint-disable-next-line valid-jsdoc
@@ -81,6 +93,15 @@ export function ProductDetail<F extends Field>({
8193
thumbnailLabel,
8294
additionalInformationTitle = 'Additional information',
8395
additionalActions,
96+
reviewFormEmailLabel,
97+
reviewFormModalTitle,
98+
reviewFormNameLabel,
99+
reviewFormRatingLabel,
100+
reviewFormReviewLabel,
101+
reviewFormSubmitLabel,
102+
reviewFormTitleLabel,
103+
reviewFormAction,
104+
user,
84105
}: ProductDetailProps<F>) {
85106
return (
86107
<section className="@container">
@@ -110,6 +131,29 @@ export function ProductDetail<F extends Field>({
110131
{product.title}
111132
</h1>
112133
{product.reviewsEnabled && (
134+
<div className="group/product-rating">
135+
<ReviewForm
136+
action={reviewFormAction}
137+
formEmailLabel={reviewFormEmailLabel}
138+
formModalTitle={reviewFormModalTitle}
139+
formNameLabel={reviewFormNameLabel}
140+
formRatingLabel={reviewFormRatingLabel}
141+
formReviewLabel={reviewFormReviewLabel}
142+
formSubmitLabel={reviewFormSubmitLabel}
143+
formTitleLabel={reviewFormTitleLabel}
144+
productId={Number(product.id)}
145+
streamableImages={product.images}
146+
streamableProduct={{ name: product.title }}
147+
streamableUser={user}
148+
trigger={
149+
<AnimatedUnderline className="cursor-pointer">
150+
Write a review
151+
</AnimatedUnderline>
152+
}
153+
/>
154+
</div>
155+
)}
156+
{product.showRating && (
113157
<div className="group/product-rating">
114158
<Stream fallback={<RatingSkeleton />} value={product.rating}>
115159
{(rating) => <Rating rating={rating ?? 0} />}

0 commit comments

Comments
 (0)