Skip to content

Commit f71a81f

Browse files
feat(promotions): PROMO-1574 add featured promotions callout on homepage
1 parent 0d42b12 commit f71a81f

13 files changed

Lines changed: 38 additions & 6 deletions

File tree

.changeset/promo-1507-promotion-callout.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"@bigcommerce/catalyst-core": minor
33
---
44

5-
Wire promotion callouts into PDP and PLP pages using live data from the Storefront GraphQL API (`featuredPromotions` on the `Product` type).
5+
Wire promotion callouts into PDP, PLP, and homepage pages using live data from the Storefront GraphQL API (`featuredPromotions` on the `Product` type).
66

77
- **PDP**: stacked callout boxes render inline below the price, one per active promotion.
88
- **PLP (category, brand, search)**: each product card shows its first promotion inline below the price; if there are multiple, a "+N more" label appears within the same callout.
9+
- **Homepage**: featured and newest product cards display their promotion callouts, consistent with PDP/PLP.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export default async function Brand(props: Props) {
9696
setRequestLocale(locale);
9797

9898
const t = await getTranslations('Faceted');
99+
const tProductCard = await getTranslations('Components.ProductCard');
99100

100101
const brandId = Number(slug);
101102

@@ -223,6 +224,7 @@ export default async function Brand(props: Props) {
223224
filtersPanelTitle={t('FacetedSearch.filters')}
224225
maxCompareLimitMessage={t('Compare.maxCompareLimit')}
225226
maxItems={MAX_COMPARE_LIMIT}
227+
moreOffersLabel={(count) => tProductCard('moreOffers', { count })}
226228
paginationInfo={streamablePagination}
227229
products={streamableProducts}
228230
rangeFilterApplyLabel={t('FacetedSearch.Range.apply')}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export default async function Category(props: Props) {
103103
setRequestLocale(locale);
104104

105105
const t = await getTranslations('Faceted');
106+
const tProductCard = await getTranslations('Components.ProductCard');
106107

107108
const categoryId = Number(slug);
108109

@@ -269,6 +270,7 @@ export default async function Category(props: Props) {
269270
filtersPanelTitle={t('FacetedSearch.filters')}
270271
maxCompareLimitMessage={t('Compare.maxCompareLimit')}
271272
maxItems={MAX_COMPARE_LIMIT}
273+
moreOffersLabel={(count) => tProductCard('moreOffers', { count })}
272274
paginationInfo={streamablePagination}
273275
products={streamableProducts}
274276
rangeFilterApplyLabel={t('FacetedSearch.Range.apply')}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export default async function Search(props: Props) {
7575
setRequestLocale(locale);
7676

7777
const t = await getTranslations('Faceted');
78+
const tProductCard = await getTranslations('Components.ProductCard');
7879

7980
const { settings } = await getSearchPageData();
8081

@@ -255,6 +256,7 @@ export default async function Search(props: Props) {
255256
filtersPanelTitle={t('FacetedSearch.filters')}
256257
maxCompareLimitMessage={t('Compare.maxCompareLimit')}
257258
maxItems={MAX_COMPARE_LIMIT}
259+
moreOffersLabel={(count) => tProductCard('moreOffers', { count })}
258260
paginationInfo={streamablePagination}
259261
products={streamableProducts}
260262
rangeFilterApplyLabel={t('FacetedSearch.Range.apply')}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ export default async function Home({ params }: Props) {
3232
setRequestLocale(locale);
3333

3434
const t = await getTranslations('Home');
35+
const tProductCard = await getTranslations('Components.ProductCard');
3536
const format = await getFormatter();
3637

38+
const moreOffersLabel = (count: number) => tProductCard('moreOffers', { count });
39+
3740
const streamablePageData = Streamable.from(async () => {
3841
const customerAccessToken = await getSessionCustomerAccessToken();
3942
const currencyCode = await getPreferredCurrencyCode();
@@ -56,7 +59,7 @@ export default async function Home({ params }: Props) {
5659
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
5760
showBackorderMessage,
5861
taxDisplay,
59-
).map((p) => ({ ...p, promotions: undefined }));
62+
);
6063
});
6164

6265
const streamableNewestProducts = Streamable.from(async () => {
@@ -74,7 +77,7 @@ export default async function Home({ params }: Props) {
7477
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
7578
showBackorderMessage,
7679
taxDisplay,
77-
).map((p) => ({ ...p, promotions: undefined }));
80+
);
7881
});
7982

8083
const streamableShowNewsletterSignup = Streamable.from(async () => {
@@ -94,6 +97,7 @@ export default async function Home({ params }: Props) {
9497
description={t('FeaturedProducts.description')}
9598
emptyStateSubtitle={t('FeaturedProducts.emptyStateSubtitle')}
9699
emptyStateTitle={t('FeaturedProducts.emptyStateTitle')}
100+
moreOffersLabel={moreOffersLabel}
97101
products={streamableFeaturedProducts}
98102
title={t('FeaturedProducts.title')}
99103
/>
@@ -103,6 +107,7 @@ export default async function Home({ params }: Props) {
103107
description={t('NewestProducts.description')}
104108
emptyStateSubtitle={t('NewestProducts.emptyStateSubtitle')}
105109
emptyStateTitle={t('NewestProducts.emptyStateTitle')}
110+
moreOffersLabel={moreOffersLabel}
106111
nextLabel={t('NewestProducts.nextProducts')}
107112
previousLabel={t('NewestProducts.previousProducts')}
108113
products={streamableNewestProducts}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export default async function Product({ params, searchParams }: Props) {
8686
setRequestLocale(locale);
8787

8888
const t = await getTranslations('Product');
89+
const tProductCard = await getTranslations('Components.ProductCard');
8990
const format = await getFormatter();
9091

9192
const productId = Number(slug);
@@ -623,6 +624,7 @@ export default async function Product({ params, searchParams }: Props) {
623624
cta={{ label: t('RelatedProducts.cta'), href: '/shop-all' }}
624625
emptyStateSubtitle={t('RelatedProducts.browseCatalog')}
625626
emptyStateTitle={t('RelatedProducts.noRelatedProducts')}
627+
moreOffersLabel={(count) => tProductCard('moreOffers', { count })}
626628
nextLabel={t('RelatedProducts.nextProducts')}
627629
previousLabel={t('RelatedProducts.previousProducts')}
628630
products={streameableRelatedProducts}

core/messages/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,9 @@
568568
"search": "Search"
569569
},
570570
"Components": {
571+
"ProductCard": {
572+
"moreOffers": "+{count, plural, =1 {# more offer} other {# more offers}}"
573+
},
571574
"Header": {
572575
"home": "Home",
573576
"toggleNavigation": "Toggle navigation",

core/vibes/soul/primitives/product-card/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface ProductCardProps {
4040
imageSizes?: string;
4141
compareLabel?: string;
4242
compareParamName?: string;
43-
moreOffersLabel?: string;
43+
moreOffersLabel?: (count: number) => string;
4444
product: Product;
4545
showRating?: boolean;
4646
}
@@ -88,7 +88,7 @@ export function ProductCard({
8888
aspectRatio = '5:6',
8989
compareLabel,
9090
compareParamName,
91-
moreOffersLabel = 'more offers',
91+
moreOffersLabel = (count) => `+${count} more ${count === 1 ? 'offer' : 'offers'}`,
9292
imagePriority = false,
9393
imageSizes = '(min-width: 80rem) 20vw, (min-width: 64rem) 25vw, (min-width: 42rem) 33vw, (min-width: 24rem) 50vw, 100vw',
9494
}: ProductCardProps) {
@@ -196,7 +196,7 @@ export function ProductCard({
196196
</CalloutTitle>
197197
{promotions.length > 1 && (
198198
<CalloutDescription className="text-xs text-yellow-900/70">
199-
+{promotions.length - 1} {moreOffersLabel}
199+
{moreOffersLabel(promotions.length - 1)}
200200
</CalloutDescription>
201201
)}
202202
</CalloutHeader>

core/vibes/soul/sections/featured-product-carousel/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface FeaturedProductCarouselProps {
2121
previousLabel?: string;
2222
nextLabel?: string;
2323
hideOverflow?: boolean;
24+
moreOffersLabel?: (count: number) => string;
2425
}
2526

2627
// eslint-disable-next-line valid-jsdoc
@@ -49,6 +50,7 @@ export function FeaturedProductCarousel({
4950
previousLabel,
5051
nextLabel,
5152
hideOverflow = false,
53+
moreOffersLabel,
5254
}: FeaturedProductCarouselProps) {
5355
return (
5456
<SectionLayout containerSize="2xl">
@@ -74,6 +76,7 @@ export function FeaturedProductCarousel({
7476
emptyStateSubtitle={emptyStateSubtitle}
7577
emptyStateTitle={emptyStateTitle}
7678
hideOverflow={hideOverflow}
79+
moreOffersLabel={moreOffersLabel}
7780
nextLabel={nextLabel}
7881
placeholderCount={placeholderCount}
7982
previousLabel={previousLabel}

core/vibes/soul/sections/featured-product-list/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface FeaturedProductsListProps {
1717
emptyStateTitle?: Streamable<string>;
1818
emptyStateSubtitle?: Streamable<string>;
1919
placeholderCount?: number;
20+
moreOffersLabel?: (count: number) => string;
2021
}
2122

2223
// eslint-disable-next-line valid-jsdoc
@@ -41,6 +42,7 @@ export function FeaturedProductList({
4142
emptyStateTitle,
4243
emptyStateSubtitle,
4344
placeholderCount,
45+
moreOffersLabel,
4446
}: FeaturedProductsListProps) {
4547
return (
4648
<StickySidebarLayout
@@ -68,6 +70,7 @@ export function FeaturedProductList({
6870
<ProductList
6971
emptyStateSubtitle={emptyStateSubtitle}
7072
emptyStateTitle={emptyStateTitle}
73+
moreOffersLabel={moreOffersLabel}
7174
placeholderCount={placeholderCount}
7275
products={products}
7376
showCompare={false}

0 commit comments

Comments
 (0)