The Product page
๐ Heads up
If you are using Starter Kit, there's no need need to manually create the Sections and Components below.
All the code examples below are just an implementation suggestions. For brevity, the styles are omitted.
For this guide, we will be creating Sections and Components locally. Make sure you have your Local Development Environment ready.
๐ Remember to commit and push newly created Sections and Components to your remote repository to sync the changes with Shogun Frontend.
The Product page, commonly known as PDP, will display all relevant product-specific information. Customers might land on this page directly or via the homepage. This page will allow customers to select between one or more product variants, change quantity, and add a product to the cart.
Products template
The Product CMS group, imported and updated via BigCommerce or Shopify, will be the focus of this portion of the guide.
Unlike the homepage, there is no need to create a page for each product. Attaching the Product CMS group to a template will auto-generate a page for each content item within the group.
With that in mind, we will create our first template:
Navigate to Templates by clicking the icon on the sidebar.
Click ADD TEMPLATE.
Name the template Products.
Under USE THIS TEMPLATE FOR... select Products.
Click SAVE TEMPLATE.
A CMS Group can only be linked to one template. In this example, there is already a Products template, and Products is grayed out in the screenshot above.
Saving the template will auto-generate the product pages. They will be blank until we create the necessary components and sections and add those to the template.
If you open any of these product pages youโll see a blank page, and we will address this next. First, mark some pages as Ready to Publish, so that the published store will have pages to navigate.
Creating the ProductBox section
We will now create a Section that will hold all the product details, named ProductBox
.
sections/ProductBox/index.js
// ProductBox section
import * as React from 'react'
import Carousel from 'Components/Carousel'
import Select from 'Components/Select'
import NumberInput from 'Components/NumberInput'
const ProductBox = ({ product }) => {
const { name, variants, media, descriptionHtml = '' } = product || {}
// Local state to deal with product quantity that will added to the Cart
const [productQuantity, setProductQuantity] = React.useState(1)
// Local state to handle the product variant select
const [currentVariant, setCurrentVariant] = React.useState(variants && variants[0])
const { price, storefrontId } = currentVariant || {}
// Map through `media` array to create our `productMedia` array that will
// be passed to Carousel as prop
const productMedia =
media &&
media.map(({ details }) => ({
name: details.name,
src: details.src,
alt: details.alt,
width: details.width,
height: details.height,
}))
// Array that we will pass to our Select component to allow users
// select between product's variant
const variantOptions =
variants &&
variants.map(variant => ({
value: variant.storefrontId,
text: variant.name,
}))
const handleProductQuantity = (_, quantityAsNumber) => {
setProductQuantity(quantityAsNumber)
}
const handleVariantsSelect = event => {
if (!variants) return
// We want to find the variant that match with our local selected variant
const selectedVariant = variants.find(
variant => variant.storefrontId === event.currentTarget.value,
)
if (!selectedVariant) return
setCurrentVariant(selectedVariant)
}
return (
<div>
<div>
<Carousel media={productMedia} />
</div>
<section>
<h1>
{name}
</h1>
<strong>
${price}
</strong>
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
<hr />
<div>
<label>
Variants
<Select options={variantOptions} onChange={handleVariantsSelect} />
</label>
</div>
<div}>
<label>
Quantity
<NumberInput
inputProps={{ 'aria-label': 'Product quantity' }}
/>
</label>
</div>
<div>
<button>Add to Cart</button>
</div>
</section>
</div>
)
}
export default ProductBox
Next, we will create a variable matching the prop that the section will receive.
Set the TYPE as
Reference
Under CONTENT TYPE select
Products
.Under variants select
externalId
,storefrontId
,name
andprice
.Select
details
under media and finallydescriptionHtml
.
Navigate back to the Product template, and we will add the ProductBox section to our template.
Click on ProductBox in the sidebar and connect the variable
PRODUCT
toCurrent Products
. This ensures that each product page will pull in dynamic content from the Product CMS Group.Save the template.
Each product page will now display product-specific details in the ProductBox section. However, if you try to add the given product to the cart, nothing will happen. We will next connect everything with the frontend-checkout
package.
Creating the AddToCartButton
We need to create a new component to handle the add to cart action: create a new component called AddToCartButton
or a name of your preference.
This component will handle the logic of adding a product to the cart. In the example code below, we take into account the product's inventory level and trigger the CartDrawer
to open. We will cover that in the next step.
components/AddToCartButton/index.js
// ProductBox section
import * as React from 'react'
import Carousel from 'Components/Carousel'
import Select from 'Components/Select'
import NumberInput from 'Components/NumberInput'
const ProductBox = ({ product }) => {
const { name, variants, media, descriptionHtml = '' } = product || {}
// Local state to deal with product quantity that will added to the Cart
const [productQuantity, setProductQuantity] = React.useState(1)
// Local state to handle the product variant select
const [currentVariant, setCurrentVariant] = React.useState(variants && variants[0])
const { price, storefrontId } = currentVariant || {}
// Map through `media` array to create our `productMedia` array that will
// be passed to Carousel as prop
const productMedia =
media &&
media.map(({ details }) => ({
name: details.name,
src: details.src,
alt: details.alt,
width: details.width,
height: details.height,
}))
// Array that we will pass to our Select component to allow users
// select between product's variant
const variantOptions =
variants &&
variants.map(variant => ({
value: variant.storefrontId,
text: variant.name,
}))
const handleProductQuantity = (_, quantityAsNumber) => {
setProductQuantity(quantityAsNumber)
}
const handleVariantsSelect = event => {
if (!variants) return
// We want to find the variant that match with our local selected variant
const selectedVariant = variants.find(
variant => variant.storefrontId === event.currentTarget.value,
)
if (!selectedVariant) return
setCurrentVariant(selectedVariant)
}
return (
<div>
<div>
<Carousel media={productMedia} />
</div>
<section>
<h1>
{name}
</h1>
<strong>
${price}
</strong>
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
<hr />
<div>
<label>
Variants
<Select options={variantOptions} onChange={handleVariantsSelect} />
</label>
</div>
<div}>
<label>
Quantity
<NumberInput
inputProps={{ 'aria-label': 'Product quantity' }}
/>
</label>
</div>
<div>
<button>Add to Cart</button>
</div>
</section>
</div>
)
}
export default ProductBox
With the AddToCartButton
ready, we will add it to the ProductBox
:
sections/ProductBox/index.js
// ProductBox section
import * as React from 'react'
import Carousel from 'Components/Carousel'
import Select from 'Components/Select'
import NumberInput from 'Components/NumberInput'
import AddToCartButton from 'Components/AddToCartButton'
const ProductBox = ({ product }) => {
const { name, variants, media, descriptionHtml = '' } = product || {}
// Local state to deal with product quantity that will added to the Cart
const [productQuantity, setProductQuantity] = React.useState(1)
// Local state to handle the product variant select
const [currentVariant, setCurrentVariant] = React.useState(variants && variants[0])
const { price, storefrontId } = currentVariant || {}
// Map through `media` array to create our `productMedia` array that will
// be passed to Carousel as prop
const productMedia =
media &&
media.map(({ details }) => ({
name: details.name,
src: details.src,
alt: details.alt,
width: details.width,
height: details.height,
}))
// Array that we will pass to our Select component to allow users
// select between product's variant
const variantOptions =
variants &&
variants.map(variant => ({
value: variant.storefrontId,
text: variant.name,
}))
const handleProductQuantity = (_, quantityAsNumber) => {
setProductQuantity(quantityAsNumber)
}
const handleVariantsSelect = event => {
if (!variants) return
// We want to find the variant that match with our local selected variant
const selectedVariant = variants.find(
variant => variant.storefrontId === event.currentTarget.value,
)
if (!selectedVariant) return
setCurrentVariant(selectedVariant)
}
return (
<div>
<div>
<Carousel media={productMedia} />
</div>
<section>
<h1>
{name}
</h1>
<strong>
${price}
</strong>
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
<hr />
<div>
<label>
Variants
<Select options={variantOptions} onChange={handleVariantsSelect} />
</label>
</div>
<div}>
<label>
Quantity
<NumberInput
inputProps={{ 'aria-label': 'Product quantity' }}
/>
</label>
</div>
<div>
<AddToCartButton productId={storefrontId} quantity={productQuantity} />
</div>
</section>
</div>
)
}
export default ProductBox
๐ง If your Shopify store is using the REST API instead of GraphQL, make sure to use externalId
instead of storefrontId
. If you are using a BigCommerce store, make sure to use the id
from the product
.