
In this walkthrough, we’ll be examining how we built the commerce app’s Product Detail Page (PDP). We will go through:
Querying Products.
We will go over the required parameters and how to query the required information through the commerce-sdk and the REST API.
Page Overview (Basic)
We will discuss page design specifically around a single product.
Prior to loading content specific to the requested product we need to know, certain information prior, Specifically:
Resolve the tenant and application
Send a request to authenticate the user and determine permissions, whether as anonymous or signed-in
What Product are you requesting
Products can be requested by two different methods, by url or by product id
So before querying there are only two things required, first is the Application Context, because we need to what tenant and application to know where to query the document from. The other needed information is a way to identity the product, which can be the product’s id or the product url/slug. Depending on which will determine on the endpoint that will be queried.
METHOD |
URL |
SDK |
|
By Product ID |
GET |
/products/<PRODUCT_ID> |
getProductById() |
Details can be found in the Products Details OpenAPI Documentation
import { ClientOptions } from '@broadleaf/commerce-core';
import { BrowseClient } from '@broadleaf/commerce-browse';
async function getProductDetails(productId: string, options: ClientOptions) {
const client = new BrowseClient(options);
return client.getProductById(productId);
}
Alternatively client.getProduct({productIds: [<PRODUCT_ID>]})
can be used, however the connical method for query a single product by id is getProductById
METHOD |
URL |
SDK |
|
By Product URL |
GET |
/api/catalog/product-details?productUris=<PRODUCT_URL> |
getProductByURL() |
Details can be found in the Products Details OpenAPI Documentation
import { ClientOptions } from '@broadleaf/commerce-core';
import { BrowseClient } from '@broadleaf/commerce-browse';
// example: `const productSlug = '/hot-sauces/green-host'`
async function getProductDetails(productSlug: string, options: ClientOptions) {
const client = new BrowseClient(options);
return client.getProductByURL(productSlug);
};
Alternatively client.getProduct({productUris: [<PRODUCT_URL>]})
can be used, however the connical method for query a single product by URL is getProductByUrl
The Response return is a Product
object:
const product = {
"id": "product4",
"sku": "HS-HHS-20",
"priceInfo": {
"target": {
"targetId": "HS-HHS-20",
"targetType": "SKU",
"priceableFields": {
"basePrice": {
"amount": 8.99,
"currency": "USD"
}
},
"attributes": {
"skuRef": {
"id": "product4"
}
}
},
"price": {
"amount": 8.99,
"currency": "USD"
},
"priceType": "basePrice",
"priceTypeDetails": {
"basePrice": {
"type": "basePrice",
"bestPrice": {
"amount": 8.99,
"currency": "USD"
},
"priceDetails": {}
}
}
},
"currency": "USD",
"options": [],
"variants": [],
"includedProducts": [],
"promotionalProducts": {},
"assets": [
{
"tags": [],
"primary": true,
"contentUrl": "https://admin.blcdemo.com/api/asset/content/Hoppin-Hot-Sauce-Bottle.jpg?contextRequest=%7B%22forceCatalogForFetch%22:false,%22tenantId%22:%225DF1363059675161A85F576D%22%7D",
"sorted": true,
"altText": "Bottle of Hoppin' Hot Sauce",
"productId": "product4",
"provider": "BROADLEAF",
"tenantId": "5DF1363059675161A85F576D",
"id": "product4_primary",
"type": "IMAGE",
"title": "Bottle of Hoppin' Hot Sauce",
"url": "/Hoppin-Hot-Sauce-Bottle.jpg",
"parentId": "product4"
}
],
"primaryAsset": {
"tags": [],
"primary": true,
"contentUrl": "https://admin.blcdemo.com/api/asset/content/Hoppin-Hot-Sauce-Bottle.jpg?contextRequest=%7B%22forceCatalogForFetch%22:false,%22tenantId%22:%225DF1363059675161A85F576D%22%7D",
"sorted": true,
"altText": "Bottle of Hoppin' Hot Sauce",
"productId": "product4",
"provider": "BROADLEAF",
"tenantId": "5DF1363059675161A85F576D",
"id": "product4_primary",
"type": "IMAGE",
"title": "Bottle of Hoppin' Hot Sauce",
"url": "/Hoppin-Hot-Sauce-Bottle.jpg",
"parentId": "product4"
},
"activeStartDate": "2021-04-27T18:41:40.120Z",
"keywords": [],
"description": "Tangy, ripe cayenne peppers flow together with garlic, onion, tomato paste and a hint of cane sugar to make this a smooth sauce with a bite. Wonderful on eggs, poultry, pork, or fish, this sauce blends to make rich marinades and soups.",
"metaDescription": "Tangy, ripe cayenne peppers flow together with garlic, onion, tomato paste and a hint of cane sugar to make this a smooth sauce with a bite. Wonderful on eggs, poultry, pork, or fish, this sauce blends to make rich marinades and soups.",
"fulfillmentFlatRates": {},
"eligibleForPickup": false,
"onSale": false,
"discountable": true,
"parentCategories": [
{
"id": "category1",
"name": "Hot Sauces",
"url": "/hot-sauces"
},
{
"id": "category7",
"name": "Top Sellers",
"url": "/"
}
],
"reviewsSummary": {
"numberOfReviews": 0
},
"inventoryType": "PHYSICAL",
"merchandisingProduct": false,
"active": true,
"individuallySold": true,
"uri": "/hot-sauces/hoppin-hot-sauce",
"searchable": true,
"tags": [
"z9",
"salsa"
],
"inventoryCheckStrategy": "NEVER",
"inventoryReservationStrategy": "NEVER",
"metaTitle": "Hoppin' Hot Sauce Test 3",
"name": "Hoppin' Hot Sauce Test 3",
"online": true,
"attributes": {},
"availableOnline": true,
"breadcrumbs": [
{
"label": "Hoppin' Hot Sauce Test 3"
}
]
}
We will discuss page design specifically around a single product. so given that we have a valid product object return to us, we can begin constructing a simple product page.
from the image above we can see that there are 5 pieces of information that we need from the product object that we need to display. specifically the: description, name, price, sku, and image(s).
for the name
and description
and sku
, we can obtain as top-level properties. We then use this to populated their respective components
// let product be a non-null Product object
const {name, description, sku} = product;
price
is located in pricingInfo
and it is a object that contains amount
and currency
.
// let product be a non-null Product object
const { price } = product.pricingInfo;
console.log(`${price.amount} ${price.currency}`) // '8.99 USD'
Image assets are split between two properties; assets
, and primaryAsset
. for the page we use the assets
array that is iterated though to display
product.assets
is a list of image resources, which contains a Asset object that contains contentUrl
, altText
that are to display the image. This is not to be confused with primaryAsset
which is a single object that is considered the main object when only on is displayed. primaryAsset
will have a matching object in assets
{
"assets" : [
{
"tags": [],
"primary": true,
"contentUrl": "https://admin.blcdemo.com/api/asset/content/Hoppin-Hot-Sauce-Bottle.jpg?contextRequest=%7B%22forceCatalogForFetch%22:false,%22tenantId%22:%225DF1363059675161A85F576D%22%7D",
"sorted": true,
"altText": "Bottle of Hoppin' Hot Sauce",
"productId": "product4",
"provider": "BROADLEAF",
"tenantId": "5DF1363059675161A85F576D",
"id": "product4_primary",
"type": "IMAGE",
"title": "Bottle of Hoppin' Hot Sauce",
"url": "/Hoppin-Hot-Sauce-Bottle.jpg",
"parentId": "product4"
}
],
"primaryAsset": {
"tags": [],
"primary": true,
"contentUrl": "https:/admin.blcdemo.com/api/asset/content/Hoppin-Hot-Sauce-Bottle.jpg?contextRequest=%7B%22forceCatalogForFetch%22:false,%22tenantId%22:%225DF1363059675161A85F576D%22%7D",
"sorted": true,
"altText": "Bottle of Hoppin' Hot Sauce",
"productId": "product4",
"provider": "BROADLEAF",
"tenantId": "5DF1363059675161A85F576D",
"id": "product4_primary",
"type": "IMAGE",
"title": "Bottle of Hoppin' Hot Sauce",
"url": "/Hoppin-Hot-Sauce-Bottle.jpg",
"parentId": "product4"
}
}
// let product be a non-null Product object
const { price } = product.pricingInfo;
console.log(`${price.amount} ${price.currency}`) // '8.99 USD'