Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .changeset/tricky-plants-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
"@bigcommerce/catalyst-core": minor
---

Add out-of-stock / backorder message to product cards on PLPs based on store settings:
- Add out of stock message if the product is out of stock and stock is set to display it.
- Add the backorder message if the product has no on-hand stock and is available for backorder and the store/product is set to display the backorder message

## Migration

### Option 1: Automatic Migration (Recommended)
For existing Catalyst stores, the simplest way to get the newly added feature is to rebase the existing code with the new release code. The files that will be updated are listed below.

### Option 2: Manual Migration
If you prefer not to rebase or have made customizations that prevent rebasing, follow these manual steps:

#### Step 1: Update GraphQL Fragment
Add the inventory fields to your product card fragment in `core/components/product-card/fragment.ts` under `Product`:
```graphql
inventory {
hasVariantInventory
isInStock
aggregated {
availableForBackorder
unlimitedBackorder
availableOnHand
}
}
variants(first: 1) {
edges {
node {
entityId
sku
inventory {
byLocation {
edges {
node {
locationEntityId
backorderMessage
}
}
}
}
}
}
}
```

#### Step 2: Update Product interface in Product Card component
Update the `Product` interface in `core/vibes/soul/primitives/product-card/index.tsx` adding the following field to it:

`inventoryMessage?: string;`

#### Step 3: Update Data Transformer
Modify `core/data-transformers/product-card-transformer.ts` to include inventory message in the transformed data. You can simply copy the whole file from this release as it does not have UI breaking changes.

#### Step 4: Update Product Card Layout
Update `core/vibes/soul/primitives/product-card/index.tsx` layout to display the new `inventoryMessage` product field.

#### Step 5: Update Page Data GraphQL queries
Add inventory settings queries to the pages data. Add the following query to the main GQL query under `site.settings`:
```
inventory {
defaultOutOfStockMessage
showOutOfStockMessage
showBackorderMessage
}
```
to the following page data files:
- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts`
- `core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts`
- `core/app/[locale]/(default)/(faceted)/search/page-data.ts`
- `core/app/[locale]/(default)/page-data.ts`

#### Step 6: Update Page Components
Update the corresponding page components to use the `productCardTransformer` method (if not already using it) to get the product card, and pass inventory data to those product cards based on the store inventory settings. Use the following code while retrieving the product lists:
```
const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } =
data.site.settings?.inventory ?? {};

return productCardTransformer(
featuredProducts,
format,
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
showBackorderMessage,
);
```
in the following files:
- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx`
- `core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx`
- `core/app/[locale]/(default)/(faceted)/search/page.tsx`
- `core/app/[locale]/(default)/page.tsx`

### Files Modified in This Change
- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts`
- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx`
- `core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts`
- `core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx`
- `core/app/[locale]/(default)/(faceted)/search/page-data.ts`
- `core/app/[locale]/(default)/(faceted)/search/page.tsx`
- `core/app/[locale]/(default)/page-data.ts`
- `core/app/[locale]/(default)/page.tsx`
- `core/components/product-card/fragment.ts`
- `core/data-transformers/product-card-transformer.ts`
- `core/vibes/soul/primitives/product-card/index.tsx`
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const BrandPageQuery = graphql(`
}
}
settings {
inventory {
defaultOutOfStockMessage
Comment thread
jorgemoya marked this conversation as resolved.
showOutOfStockMessage
showBackorderMessage
Comment thread
jorgemoya marked this conversation as resolved.
}
storefront {
catalog {
productComparisonsEnabled
Expand Down
21 changes: 10 additions & 11 deletions core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { getFilterParsers } from '@/vibes/soul/sections/products-list-section/fi
import { getSessionCustomerAccessToken } from '~/auth';
import { facetsTransformer } from '~/data-transformers/facets-transformer';
import { pageInfoTransformer } from '~/data-transformers/page-info-transformer';
import { pricesTransformer } from '~/data-transformers/prices-transformer';
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
import { getPreferredCurrencyCode } from '~/lib/currency';

import { MAX_COMPARE_LIMIT } from '../../../compare/page-data';
Expand Down Expand Up @@ -132,16 +132,15 @@ export default async function Brand(props: Props) {
const search = await streamableFacetedSearch;
const products = search.products.items;

return products.map((product) => ({
id: product.entityId.toString(),
title: product.name,
href: product.path,
image: product.defaultImage
? { src: product.defaultImage.url, alt: product.defaultImage.altText }
: undefined,
price: pricesTransformer(product.prices, format),
subtitle: product.brand?.name ?? undefined,
}));
const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } =
settings?.inventory ?? {};

return productCardTransformer(
products,
format,
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
showBackorderMessage,
);
});

const streamableTotalCount = Streamable.from(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const CategoryPageQuery = graphql(
}
}
settings {
inventory {
defaultOutOfStockMessage
showOutOfStockMessage
showBackorderMessage
}
storefront {
catalog {
productComparisonsEnabled
Expand Down
21 changes: 10 additions & 11 deletions core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getFilterParsers } from '@/vibes/soul/sections/products-list-section/fi
import { getSessionCustomerAccessToken } from '~/auth';
import { facetsTransformer } from '~/data-transformers/facets-transformer';
import { pageInfoTransformer } from '~/data-transformers/page-info-transformer';
import { pricesTransformer } from '~/data-transformers/prices-transformer';
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
import { getPreferredCurrencyCode } from '~/lib/currency';

import { MAX_COMPARE_LIMIT } from '../../../compare/page-data';
Expand Down Expand Up @@ -145,16 +145,15 @@ export default async function Category(props: Props) {
const search = await streamableFacetedSearch;
const products = search.products.items;

return products.map((product) => ({
id: product.entityId.toString(),
title: product.name,
href: product.path,
image: product.defaultImage
? { src: product.defaultImage.url, alt: product.defaultImage.altText }
: undefined,
price: pricesTransformer(product.prices, format),
subtitle: product.brand?.name ?? undefined,
}));
const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } =
settings?.inventory ?? {};

return productCardTransformer(
products,
format,
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
showBackorderMessage,
);
});

const streamableTotalCount = Streamable.from(async () => {
Expand Down
5 changes: 5 additions & 0 deletions core/app/[locale]/(default)/(faceted)/search/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const SearchPageQuery = graphql(`
query SearchPageQuery {
site {
settings {
inventory {
defaultOutOfStockMessage
showOutOfStockMessage
showBackorderMessage
}
storefront {
catalog {
productComparisonsEnabled
Expand Down
21 changes: 10 additions & 11 deletions core/app/[locale]/(default)/(faceted)/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getFilterParsers } from '@/vibes/soul/sections/products-list-section/fi
import { getSessionCustomerAccessToken } from '~/auth';
import { facetsTransformer } from '~/data-transformers/facets-transformer';
import { pageInfoTransformer } from '~/data-transformers/page-info-transformer';
import { pricesTransformer } from '~/data-transformers/prices-transformer';
import { productCardTransformer } from '~/data-transformers/product-card-transformer';
import { getPreferredCurrencyCode } from '~/lib/currency';

import { MAX_COMPARE_LIMIT } from '../../compare/page-data';
Expand Down Expand Up @@ -117,16 +117,15 @@ export default async function Search(props: Props) {
const search = await streamableFacetedSearch;
const products = search.products.items;

return products.map((product) => ({
id: product.entityId.toString(),
title: product.name,
href: product.path,
image: product.defaultImage
? { src: product.defaultImage.url, alt: product.defaultImage.altText }
: undefined,
price: pricesTransformer(product.prices, format),
subtitle: product.brand?.name ?? undefined,
}));
const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } =
settings?.inventory ?? {};

return productCardTransformer(
products,
format,
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
showBackorderMessage,
);
});

const streamableTitle = Streamable.from(async () => {
Expand Down
7 changes: 7 additions & 0 deletions core/app/[locale]/(default)/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ const HomePageQuery = graphql(
}
}
}
settings {
inventory {
defaultOutOfStockMessage
showOutOfStockMessage
showBackorderMessage
}
}
}
}
`,
Expand Down
20 changes: 18 additions & 2 deletions core/app/[locale]/(default)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,31 @@ export default async function Home({ params }: Props) {

const featuredProducts = removeEdgesAndNodes(data.site.featuredProducts);

return productCardTransformer(featuredProducts, format);
const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } =
data.site.settings?.inventory ?? {};

return productCardTransformer(
featuredProducts,
format,
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
showBackorderMessage,
);
});

const streamableNewestProducts = Streamable.from(async () => {
const data = await streamablePageData;

const newestProducts = removeEdgesAndNodes(data.site.newestProducts);

return productCardTransformer(newestProducts, format);
const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } =
data.site.settings?.inventory ?? {};

return productCardTransformer(
newestProducts,
format,
showOutOfStockMessage ? defaultOutOfStockMessage : undefined,
showBackorderMessage,
);
});

return (
Expand Down
27 changes: 27 additions & 0 deletions core/components/product-card/fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,37 @@ export const ProductCardFragment = graphql(
name
path
}
inventory {
hasVariantInventory
isInStock
aggregated {
availableForBackorder
unlimitedBackorder
availableOnHand
}
}
reviewSummary {
numberOfReviews
averageRating
}
variants(first: 1) {
edges {
node {
entityId
sku
inventory {
byLocation {
edges {
node {
locationEntityId
backorderMessage
}
}
}
}
}
}
}
...PricingFragment
}
`,
Expand Down
Loading