Skip to content

Instantly share code, notes, and snippets.

@fnhipster
Created March 24, 2025 14:22
Show Gist options
  • Save fnhipster/1542f4a5ba852736438de013cf6c6230 to your computer and use it in GitHub Desktop.
Save fnhipster/1542f4a5ba852736438de013cf6c6230 to your computer and use it in GitHub Desktop.
PDP "Quick View"
/* eslint-disable import/no-cycle */
/* eslint-disable import/no-unresolved */
import {
InLineAlert,
Icon,
Button,
Image,
provider as UI,
} from '@dropins/tools/components.js';
import { events } from '@dropins/tools/event-bus.js';
import * as pdpApi from '@dropins/storefront-pdp/api.js';
import { render as pdpRendered } from '@dropins/storefront-pdp/render.js';
import { initializers } from '@dropins/tools/initializer.js';
// Containers
import ProductPrice from '@dropins/storefront-pdp/containers/ProductPrice.js';
import ProductOptions from '@dropins/storefront-pdp/containers/ProductOptions.js';
import ProductQuantity from '@dropins/storefront-pdp/containers/ProductQuantity.js';
import ProductDescription from '@dropins/storefront-pdp/containers/ProductDescription.js';
import { getConfigValue } from '../configs.js';
import { fetchPlaceholders } from '../aem.js';
// Initializers
import '../initializers/cart.js';
import { commerceEndpointWithQueryParams } from '../commerce.js';
import { IMAGES_SIZES } from './product-item.js';
// Set Fetch Endpoint (Service)
pdpApi.setEndpoint(await commerceEndpointWithQueryParams());
// Set Fetch Headers (Service)
pdpApi.setFetchGraphQlHeaders({
'Content-Type': 'application/json',
'x-api-key': await getConfigValue('commerce-x-api-key'),
});
/**
* Renders a "Quick View" PDP for a given SKU.
* @param sku
* @returns
*/
export default async function renderQuickView(sku) {
// get placeholders (labels)
const langDefinitions = {
default: await fetchPlaceholders(),
};
// initialize the PDP API with the SKU and language definitions
// NOTE: multiple PDP in the same page will conflict. Only one PDP can be initialized at a time.
await initializers.mountImmediately(pdpApi.initialize, {
sku,
langDefinitions,
acdl: true,
persistURLParams: false,
});
const block = document.createElement('div');
// eslint-disable-next-line no-underscore-dangle
const labels = await fetchPlaceholders();
// the initilizer will trigger the pdp/data event when the PDP is ready
const product = events.lastPayload('pdp/data');
// Layout
const fragment = document.createRange().createContextualFragment(/* html */`
<div class="quick-view__wrapper">
<div class="quick-view__alert"></div>
<div class="quick-view__left-column">
<div class="quick-view__gallery"></div>
</div>
<div class="quick-view__right-column">
<div class="quick-view__header">
<a href="/products/${product.urlKey}/${product.sku}" class="quick-view__close">
${product.name}
</a>
</div>
<div class="quick-view__price"></div>
<div class="quick-view__configuration">
<div class="quick-view__options"></div>
<div class="quick-view__quantity__wrapper">
<div class="quick-view__quantity__label">
Quantity
</div>
<div class="quick-view__quantity"></div>
</div>
<div class="quick-view__buttons">
<div class="quick-view__buttons__add-to-cart"></div>
<div class="quick-view__buttons__redirect-to-pdp">
<a href="/products/${product.urlKey}/${product.sku}">
</a>
</div>
</div>
</div>
</div>
<div class="quick-view__description"></div>
</div>
`);
const $alert = fragment.querySelector('.quick-view__alert');
const $gallery = fragment.querySelector('.quick-view__gallery');
const $price = fragment.querySelector('.quick-view__price');
const $options = fragment.querySelector('.quick-view__options');
const $quantity = fragment.querySelector('.quick-view__quantity');
const $addToCart = fragment.querySelector('.quick-view__buttons__add-to-cart');
const $description = fragment.querySelector('.quick-view__description');
block.appendChild(fragment);
// Alert
let inlineAlert = null;
// Render Containers
const [
_gallery,
_price,
_options,
_quantity,
addToCart,
_description,
] = await Promise.all([
// Gallery (Desktop)
UI.render(Image, {
src: product.images[0].url,
alt: product.images[0].label || product.name,
width: IMAGES_SIZES.width,
height: IMAGES_SIZES.height,
imageParams: {
...IMAGES_SIZES,
},
})($gallery),
// Price
pdpRendered.render(ProductPrice, {})($price),
// Configuration - Swatches
pdpRendered.render(ProductOptions, { hideSelectedValue: true })($options),
// Configuration Quantity
pdpRendered.render(ProductQuantity, {})($quantity),
// Configuration – Button - Add to Cart
UI.render(Button, {
children: labels.PDP?.Product?.AddToCart?.label,
onClick: async () => {
try {
addToCart.setProps((prev) => ({
...prev,
children: labels.Custom?.AddingToCart?.label,
disabled: true,
}));
// get the current selection values
const values = pdpApi.getProductConfigurationValues();
const valid = pdpApi.isProductConfigurationValid();
// add the product to the cart
if (valid) {
const { addProductsToCart } = await import('@dropins/storefront-cart/api.js');
await addProductsToCart([{ ...values }]);
}
// reset any previous alerts if successful
inlineAlert?.remove();
} catch (error) {
// add alert message
inlineAlert = await UI.render(InLineAlert, {
heading: 'Error',
description: error.message,
icon: Icon({ source: 'Warning' }),
'aria-live': 'assertive',
role: 'alert',
onDismiss: () => {
inlineAlert.remove();
},
})($alert);
// Scroll the alertWrapper into view
$alert.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
} finally {
addToCart.setProps((prev) => ({
...prev,
children: labels.PDP?.Product?.AddToCart?.label,
disabled: false,
}));
}
},
})($addToCart),
// Description
pdpRendered.render(ProductDescription, {})($description),
]);
// Lifecycle Events
events.on('pdp/valid', (valid) => {
// update add to cart button disabled state based on product selection validity
addToCart.setProps((prev) => ({ ...prev, disabled: !valid }));
}, { eager: true });
return block.firstElementChild;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment