Skip to main content

GL Product Detail

Version: 1.0.12
Package: @gift-card-market/gl-product-detail
Last Updated: January 16, 2026

A reusable React product detail component for Gift Card Market. This package exposes a React component (GlProductDetail) that renders a product/preview UI and provides programmatic control via a React ref and named exports. It is built in TypeScript and produces ESM/CJS/IIFE bundles for consumption in different environments.

This document describes how to develop, build, and consume the package.

Table of Contents


Features

  • Display detailed information about a product before adding it to the cart or choosing to purchase. This includes product information (product name, product images, rating, view count, address, phone number, product description) and additional required information that the user needs to provide (amount, delivery method, recipient information, sender information, message, delivery date) before deciding to select the product and proceed to the next step.
  • It is possible to choose whether to show or hide the amount section, the custom amount button, the delivery method section, and the footer. You can also choose whether the displayed button is a Buy Now button or an Add To Cart button.
  • Additionally, you can customize the list of amounts, the fee amount, the minimum cart amount, the button text (for both button types), and the footer text.
  • Control API via ref (GlProductDetailHandle) and via controller helpers (setAmount, triggerBuy, etc.).
  • Supports Web Component for embedding in non-React pages.
  • Written in TypeScript, built with tsup + Tailwind CSS.

Requirements

  • React: ^18.0.0 or ^19.0.0.

  • Peer dependencies (install in your app):

  • Node / npm: follow the version used to build in the repo (not strictly required, but Node 18+ is recommended).

Installation

1. Configure GitHub Packages (if package is still hosted on GitHub)

From package.json, the package is published to GitHub Packages with:

"publishConfig": {
"registry": "https://npm.pkg.github.com/@Gift-Card-Market",
"access": "public"
}

You need to configure npm to read from this registry:

  1. Create a GitHub Personal Access Token with read:packages scope at: https://github.com/settings/tokens
  2. Login:
npm login --registry=https://npm.pkg.github.com --scope=@gift-card-market

Or add to your project's .npmrc file:

@gift-card-market:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN

2. Install the package

npm install @gift-card-market/gl-product-detail

3. Import CSS

// GL Product Detail styles
import '@gift-card-market/gl-product-detail/style.css';

Quick Start with React

Basic Example

import { useEffect, useRef, useState } from 'react';
import { GlProductDetail } from '@gift-card-market/gl-product-detail';
import type { GlProductDetailHandle } from '@gift-card-market/gl-product-detail';
import '@gift-card-market/gl-product-detail/dist/style.css'

export default function MyComponent() {
const ref = useRef<GlProductDetailHandle | null>(null);

const data = {
jwt: '{jwt-token}',
businessId: '{businessId}',
environment: 'development' || 'production',
themeColor: '#0052cc',
configuration: {
displayPrice: true,
displayDelivery: true,
displayCustomAmount: true,
minCardAmount: 25,
feeAmount: 10,
denominations: '25,50,100,150',
ctaType: 'buy',
ctaText: 'Buy Now!',
ctaUrl: null,
footerText: '<strong>Thank you for your purchase!</strong>',
showFooter: true }
};

<GlProductDetail ref={ref} data={data} />
}

GlProductDetail expects a data prop that contains at minimum a valid jwt and businessId.


Using ref (GlProductDetailHandle) for control

The package exposes top-level functions that proxy to the currently mounted instance (if any). Use these when you want to call methods from outside React tree code:

function Toolbar() {
const ref = useRef<GlProductDetailHandle | null>(null);
const [selectedMethod, setSelectedMethod] = useState<string>('');
const methods = ['refresh', 'reset', 'setAmount', 'triggerBuy', 'triggerAddToCart', 'clearErrors', 'setFieldError']

const onSetAmount = async () => {
const amountInput = (document.getElementById('input-amount') as HTMLInputElement);
const amount = parseInt(amountInput.value, 10);
if (isNaN(amount)) return alert('Please enter a valid amount');
await ref.current?.setAmount(amount);
}

const onSetFieldError = async () => {
const fieldInput = (document.getElementById('input-field') as HTMLInputElement);
const messageInput = (document.getElementById('input-message') as HTMLInputElement);
const field = fieldInput.value;
const message = messageInput.value;
if (!field || !message) return alert('Please enter both field and message');
await ref.current?.setFieldError(field, message);
}

const handleResponse = (response: any) => {
const responseArea = document.getElementById('response-area') as HTMLTextAreaElement | null;
if (responseArea) responseArea.value = JSON.stringify(response, null, 2);
}

const triggerMethod= async () => {
if (!selectedMethod) return alert('Please select a method');
switch (selectedMethod) {
case 'reset':
await ref.current?.reset();
break;
case 'refresh':
await ref.current?.refresh();
break;
case 'setAmount':
await onSetAmount();
break;
case 'triggerBuy':
const submitResponse = await ref.current?.triggerBuy();
handleResponse(submitResponse);
break;
case 'triggerAddToCart':
const cartResponse = await ref.current?.triggerAddToCart();
handleResponse(cartResponse);
break;
case 'clearErrors':
await ref.current?.clearErrors();
break;
case 'setFieldError':
await onSetFieldError();
break;
default:
break;
}
}

return (
<>
<select className="px-4 py-2"
onChange={(e) => {
const method = (e.target as HTMLSelectElement).value;
switch (method) {
case 'setAmount':
setIsAmount(true);
setIsSetFieldError(false);
setHasResponse(false);
break;
case 'setFieldError':
setIsAmount(false);
setIsSetFieldError(true);
setHasResponse(false);
break;
case 'triggerBuy':
case 'triggerAddToCart':
setIsAmount(false);
setIsSetFieldError(false);
setHasResponse(true);
break;
default:
setIsAmount(false);
setIsSetFieldError(false);
setHasResponse(false);
break;
}
setSelectedMethod(method);
}}>
{methods.map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
<button className="button-demo primary" style={{ width: '200px', padding: '0.6em 1.2em', margin: '5px', }} onClick={triggerMethod}>
Run Method
</button>

<GlProductDetail ref={ref} data={data} />
</>
);
}

Registering DOM event listeners

You can listen to custom DOM events emitted by the component to track lifecycle and user interactions:

 const handleProductDetailLoadedEvent = (event: Event) => {
const detail = (event as CustomEvent).detail;
console.log('Product Detail Loaded Event Received:', detail);
}

const handleBuyClickedEvent = (event: Event) => {
const detail = (event as CustomEvent).detail;

console.log('Product Detail Buy Clicked Event Received:', detail);
}

const handleCartClickedEvent = (event: Event) => {
const detail = (event as CustomEvent).detail;
console.log('Product Detail Cart Clicked Event Received:', detail);
}

const handleAmountChangedEvent = (event: Event) => {
const detail = (event as CustomEvent).detail;
console.log('Product Detail Amount Changed Event Received:', detail);
}

const handleErrorEvent = (event: Event) => {
const detail = (event as CustomEvent).detail;
console.log('Error Event Received:', detail);
}

useEffect(() => {
document.addEventListener('gl:pdp-loaded', handleProductDetailLoadedEvent);
document.addEventListener('gl:buy-clicked', handleBuyClickedEvent);
document.addEventListener('gl:cart-clicked', handleCartClickedEvent);
document.addEventListener('gl:amount-changed', handleAmountChangedEvent);
document.addEventListener('gl:error', handleErrorEvent);

return () => {
document.removeEventListener('gl:pdp-loaded', handleProductDetailLoadedEvent);
document.removeEventListener('gl:buy-clicked', handleBuyClickedEvent);
document.removeEventListener('gl:cart-clicked', handleCartClickedEvent);
document.removeEventListener('gl:amount-changed', handleAmountChangedEvent);
document.removeEventListener('gl:error', handleErrorEvent);
};
}, []);

Detailed API

Props GlProductDetailProps

PropTypeRequiredDescription
jwtstringJWT for calling Gift Card Market API. Access the following link to see instructions for obtaining JWT (access_token).
environmentstring (e.g., 'development', 'production', etc.)Backend environment; used to map to the corresponding domain. If no provided, default will be used.
businessIdstring (e.g., 'v-S4yzrU35jwbwCeMOfQJg')Id of the business to display.
themeColorstring (e.g. '#0052cc')The primary theme color. If not provided, defaults will be used.
configurationRecord<string, any>Component-specific options (examples: displayPrice, displayDelivery, displayCustomAmount, minCardAmount, feeAmount, denominations, ctaType, ctaText, ctaUrl, footerText, showFooter). If not provided, defaults will be used.

ConfigurationDefined

The data types for configuration properties are defined as follows:

{
displayDelivery?: boolean;
displayPrice?: boolean;
displayCustomAmount?: boolean;
minCardAmount?: number;
feeAmount?: number;
denominations?: string;
ctaType?: string;
ctaText?: string;
ctaUrl?: string;
footerText?: string; // can contain HTML
showFooter?: boolean;
}

ConfigurationProps

In GlProductDetail, there is a default configuration:

const configurationDefault = {
displayPrice: true,
displayDelivery: true,
displayCustomAmount: false,
minCardAmount: 25,
feeAmount: null,
denominations: '25,50,100',
ctaType: 'buy',
ctaText: 'Buy Now',
ctaUrl: null,
footerText: `{Default_HTML_footer}`,
showFooter: true,
};

ProductDetailHandle (ref API)

The component implements the following methods that are available when using a React ref:

  • reset(): void — Reset internal state to defaults.
  • refresh(): void — Re-fetch data and re-render the preview.
  • setAmount(amount: number): void — Programmatically set the selected amount.
  • triggerBuy(): Promise<object> — Programmatically trigger the Buy flow (returns submission response if applicable).
  • triggerAddToCart(): Promise<object> — Programmatically trigger Add-to-cart (returns submission response if applicable).
  • clearErrors(): void — Clear any validation errors shown in the UI.
  • setFieldError(field: string, message: string): void — Manually set a validation error for a field.

ProductDetailEvents (Event bus)

The component emits a set of custom DOM events to communicate lifecycle and user interactions. Example events used by the demo:

  • gl:pdp-loaded — fired when product detail/preview is loaded; event.detail contains loaded data.
  • gl:buy-clicked — fired when a buy action is performed; event.detail contains submission info.
  • gl:cart-clicked — fired when Add-to-cart is performed.
  • gl:amount-changed — fired when the selected amount changes.
  • gl:error — fired on errors; event.detail contains the error.

Listen using document.addEventListener('gl:pdp-loaded', (e) => ...).