# Product Component

The Product component displays product information with add-to-cart functionality, image carousel, size selection, and fulfillment options.

## Overview

The Product component automatically:
- Displays product images in a carousel
- Shows product name, description, and pricing
- Provides size selection
- Offers fulfillment type options (shipping/on-demand delivery)
- Handles retailer selection
- Includes add-to-cart functionality
- Supports product personalization/engraving
- Adjusts pricing based on delivery location

## Basic Usage

### Declarative Setup

The simplest way to add a product is using HTML data attributes:

```html
<script
  defer
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-container-1="product-display"
  data-product-1="00619947000020"
  type="text/javascript"
  src="https://elements.reservebar-worker.workers.dev/all/elements.js"
></script>

<div id="product-display"></div>
```

**Multiple products:**

```html
<script
  defer
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-container-1="product-1"
  data-product-1="00619947000020"
  data-container-2="product-2"
  data-product-2="08504405135"
  type="text/javascript"
  src="https://elements.reservebar-worker.workers.dev/all/elements.js"
></script>

<div id="product-1"></div>
<div id="product-2"></div>
```

### Alternative: Annotated Elements

Use `data-lce-product` on any div:

```html
<script
  defer
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  type="text/javascript"
  src="https://elements.reservebar-worker.workers.dev/all/elements.js"
></script>

<div data-lce-product="00619947000020"></div>
<div data-lce-product="08504405135"></div>
```

The SDK automatically generates IDs and injects products into these elements.

### Alternative: JSON Configuration

For many products, use a JSON script tag:

```html
<script
  defer
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  type="text/javascript"
  src="https://elements.reservebar-worker.workers.dev/all/elements.js"
></script>

<script data-liquid-commerce-elements-products type="application/json">
[
  { "containerId": "product-1", "identifier": "00619947000020" },
  { "containerId": "product-2", "identifier": "08504405135" },
  { "containerId": "product-3", "identifier": "08068660001" }
]
</script>

<div id="product-1"></div>
<div id="product-2"></div>
<div id="product-3"></div>
```

### Programmatic Setup

Use the JavaScript API for dynamic product injection:

```javascript
const client = await Elements('YOUR_API_KEY', {
  env: 'production'
});

await client.injectProductElement([
  { containerId: 'product-display', identifier: '00619947000020' }
]);
```

**With NPM:**

```javascript
import { Elements } from '@liquidcommerce/elements-sdk';

const client = await Elements('YOUR_API_KEY', { env: 'production' });

await client.injectProductElement([
  { containerId: 'product-1', identifier: '00619947000020' },
  { containerId: 'product-2', identifier: '08504405135' }
]);
```

## Product Identifiers

Products can be identified using:

- **UPC**: `00619947000020`
- **Salsify Grouping**: `GROUPING-12345`

The SDK automatically resolves any of these identifier types.

## Features

### Image Carousel

Products with multiple images display in an interactive carousel:

- Swipe/arrow navigation
- Thumbnail preview
- Lazy loading for performance

### Size Selection

For products with multiple sizes:

- Size selector
- Price updates per size
- Availability checking per size
- Out-of-stock indication

### Fulfillment Types

Two fulfillment options:

**Shipping**
- Standard delivery
- Nationwide availability
- Carrier-based shipping

**On-Demand Delivery**
- Same-day or scheduled delivery
- Local availability only
- Location-dependent pricing

The component shows only available fulfillment types based on the user's location.

### Retailer Selection

For products with multiple retailers:

**Carousel View** (default)
- Swipeable carousel of retailer cards
- Shows retailer address, shipping/delivery expectation time, and pricing
- Select with one tap

**Popup View**
- "See Delivery Options" button (shows the available fulfillment count, e.g. "See Delivery Options (3)")
- Modal with full retailer list
- Filter and search capabilities

### Personalization/Engraving

For products that support personalization:

- Engraving form appears automatically
- Character limits enforced
- Additional fees displayed

### Quantity Selection

Adjust product quantity before adding to cart:

- Increment/decrement buttons
- Direct input field
- Inventory limits enforced

## Actions API

Programmatically interact with products:

### Get Product Details

Retrieve product information:

```javascript
const productData = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');

console.log(productData);
// {
//   identifier: '00619947000020',
//   name: 'Premium Whiskey',
//   priceInfo: { currency: 'USD', minimum: 4999, average: 4999, maximum: 4999 },
//   selectedSizeId: '750ml',
//   selectedFulfillmentType: 'shipping',
//   selectedFulfillmentId: 'fulfillment_123',
//   productHasAvailability: true,
//   fulfillmentHasAvailability: true,
//   sizes: { '750ml': { ... } },
//   ...
// }
```

**Note:** The product must be injected and loaded before calling `getDetails()`. If the product hasn't been loaded, an error is thrown.

### Get Product Availability by State

Check availability for one or more products in a given state. Returns a `Promise<IProductAvailabilityResponse>`:

```javascript
const availability = await window.LiquidCommerce.elements.actions.product.getProductAvailabilityByState(
  ['00619947000020', '08504405135'],
  'NY'
);

console.log(availability);
// {
//   products: [...],
//   retailers: { ... }
// }
```

The `state` argument is optional; at least one product identifier is required.

## Events

Listen for product-related events:

### Product Loaded

Fired when product data is successfully loaded:

```javascript
window.addEventListener('lce:actions.product_loaded', (event) => {
  const { identifier, name, priceInfo } = event.detail.data;
  console.log(`Product loaded: ${name} - $${priceInfo.minimum / 100}`);
});
```

### Product Add to Cart

Fired when user clicks "Add to Cart":

```javascript
window.addEventListener('lce:actions.product_add_to_cart', (event) => {
  const { identifier, quantity, fulfillmentId } = event.detail.data;
  console.log(`Adding ${quantity}x ${identifier} (${fulfillmentId})`);
});
```

### Size Changed

Fired when user selects a different size:

```javascript
window.addEventListener('lce:actions.product_size_changed', (event) => {
  const { identifier, selectedSizeId, selectedSize } = event.detail.data;
  console.log(`Size changed to ${selectedSize} (${selectedSizeId})`);
});
```

### Fulfillment Type Changed

Fired when user switches between shipping and on-demand:

```javascript
window.addEventListener('lce:actions.product_fulfillment_type_changed', (event) => {
  const { identifier, selectedFulfillmentType } = event.detail.data;
  console.log(`Fulfillment type changed to: ${selectedFulfillmentType}`);
});
```

### Fulfillment (Retailer) Changed

Fired when user selects a different retailer:

```javascript
window.addEventListener('lce:actions.product_fulfillment_changed', (event) => {
  const { identifier, selectedFulfillmentId, selectedFulfillmentType } = event.detail.data;
  console.log(`Fulfillment changed to ${selectedFulfillmentId} (${selectedFulfillmentType})`);
});
```

### Quantity Increased/Decreased

Fired when user adjusts quantity:

```javascript
window.addEventListener('lce:actions.product_quantity_increase', (event) => {
  const { identifier, quantity } = event.detail.data;
  console.log(`Quantity increased to: ${quantity}`);
});

window.addEventListener('lce:actions.product_quantity_decrease', (event) => {
  const { identifier, quantity } = event.detail.data;
  console.log(`Quantity decreased to: ${quantity}`);
});
```

## Customization

### Theme Configuration

Customize product appearance globally:

```javascript
const client = await Elements('YOUR_API_KEY', {
  env: 'production',
  customTheme: {
    product: {
      theme: {
        backgroundColor: '#ffffff'
      },
      layout: {
        showImages: true,
        showOnlyMainImage: false,  // Show all images or just the main one
        showTitle: true,
        showDescription: true,
        showQuantityCounter: true,
        showOffHours: true,  // Show when retailer is closed
        quantityCounterStyle: 'outlined',  // or 'ghost'
        fulfillmentDisplay: 'carousel',  // or 'popup'
        enableShippingFulfillment: true,
        enableOnDemandFulfillment: true,
        addToCartButtonText: 'Add to Cart',
        addToCartButtonShowTotalPrice: true,
        buyNowButtonText: 'Buy Now',
        preSaleButtonText: 'Pre-Order',
        noAvailabilityText: 'Not available in your area'
      }
    }
  }
});
```

### Global Theme

Set colors, fonts, and styles that apply to all components:

```javascript
customTheme: {
  global: {
    theme: {
      primaryColor: '#007bff',
      accentColor: '#28a745',
      buttonCornerRadius: '8px',
      cardCornerRadius: '12px',
      headingFont: {
        name: 'Poppins',
        weights: [400, 600, 700]
      },
      paragraphFont: {
        name: 'Inter',
        weights: [400, 500]
      }
    }
  }
}
```

See [Theming Guide](./theming.md) for complete theming options.

## Address Requirement

Products require a delivery address for:
- Availability checking
- Accurate pricing
- Delivery options

### Automatic Address Collection

If no address is set, the SDK automatically:
1. Prompts for address when user clicks "Add to Cart"
2. Shows address input drawer
3. Validates and saves address
4. Completes the add-to-cart action

### Pre-set Address

Set address programmatically to skip prompting:

```javascript
// Using Google Places ID
await window.LiquidCommerce.elements.actions.address.setAddressByPlacesId('ChIJ...');

// Or manually
await window.LiquidCommerce.elements.actions.address.setAddressManually(
  {
    one: '123 Main St',
    two: 'Apt 4',
    city: 'New York',
    state: 'NY',
    zip: '10001',
    country: 'US'
  },
  {
    latitude: 40.7128,
    longitude: -74.0060
  }
);
```

## Presale Products

Products in presale mode:
- Display "Pre-Order" button (customizable text)
- Cannot be added to cart, it will send the user to checkout directly
- Show presale countdown if configured
- Display expected availability date

Presale products are handled automatically; no special configuration needed.

## Component Management

### Rerender Product

Force a product to reload and rerender:

```javascript
const components = window.LiquidCommerce.elements.getInjectedComponents();
const productComponent = components.get('product-1');

if (productComponent) {
  productComponent.rerender();
}
```

### Get Component Type

Check if a component is a product:

```javascript
const component = components.get('product-1');
const type = component.getType();
console.log(type); // 'product'
```

### Get Container Element

Access the container DOM element:

```javascript
const component = components.get('product-1');
const container = component.getElement();
console.log(container); // <div id="product-1">...</div>
```

## Error Handling

### Product Not Found

If a product identifier doesn't exist:

```javascript
// An error view is shown in the container, and the store entry's `error`
// is set to 'Product data not found'. In debug/logging mode the SDK warns:
// "No product data found for the provided product IDs."
```

### No Availability

If a product isn't available in the user's location:

```javascript
// Shows: "Not available in your area" (customizable)
// User cannot add to cart
```

### Loading Errors

If product data fails to load:

```javascript
// Shows error view with retry option
// Console logs detailed error information
```

## Use Cases

### Basic Product Page

```html
<!DOCTYPE html>
<html>
<head>
  <script
    defer
    data-liquid-commerce-elements
    data-token="YOUR_API_KEY"
    data-env="production"
    data-container-1="product"
    data-product-1="00619947000020"
    type="text/javascript"
    src="https://elements.reservebar-worker.workers.dev/all/elements.js"
  ></script>
</head>
<body>
  <div id="product"></div>
</body>
</html>
```

### Dynamic Product Selection

```javascript
import { Elements } from '@liquidcommerce/elements-sdk';

const client = await Elements('YOUR_API_KEY', { env: 'production' });

// User selects product from dropdown
document.getElementById('product-selector').addEventListener('change', async (e) => {
  const selectedId = e.target.value;
  
  // Clear existing product
  document.getElementById('product').innerHTML = '';
  
  // Inject new product
  await client.injectProductElement([
    { containerId: 'product', identifier: selectedId }
  ]);
});
```

### Analytics Integration

```javascript
// Track product views
window.addEventListener('lce:actions.product_loaded', (event) => {
  gtag('event', 'view_item', {
    items: [{
      item_id: event.detail.data.identifier,
      item_name: event.detail.data.name,
      price: event.detail.data.priceInfo.minimum / 100
    }]
  });
});

// Track add to cart
window.addEventListener('lce:actions.product_add_to_cart', (event) => {
  gtag('event', 'add_to_cart', {
    items: [{
      item_id: event.detail.data.identifier,
      quantity: event.detail.data.quantity
    }]
  });
});
```

### Multi-Product Gallery

```javascript
const products = [
  '00619947000020',
  '08504405135',
  '08068660001',
  '07549900125'
];

const client = await Elements('YOUR_API_KEY', { env: 'production' });

// Create containers
const gallery = document.getElementById('product-gallery');
const productParams = products.map((id, index) => {
  const container = document.createElement('div');
  container.className = 'product-card';
  container.id = `product-${index}`;
  gallery.appendChild(container);
  
  return { containerId: `product-${index}`, identifier: id };
});

// Inject all products
await client.injectProductElement(productParams);
```

## Best Practices

### Container Sizing

Provide adequate space for the product component:

```css
#product-display {
  min-height: 600px;  /* Prevents layout shift */
  max-width: 1200px;
  margin: 0 auto;
}
```

### Loading States

Show a loading indicator while the product loads:

```javascript
// Show loader
document.getElementById('product').innerHTML = '<div class="loader">Loading...</div>';

await client.injectProductElement([
  { containerId: 'product', identifier: '00619947000020' }
]);

// Loader is automatically replaced when product loads
```

### Error Handling

Listen for errors and provide fallback:

```javascript
try {
  await client.injectProductElement([
    { containerId: 'product', identifier: productId }
  ]);
} catch (error) {
  console.error('Failed to load product:', error);
  document.getElementById('product').innerHTML = 
    '<p>Unable to load product. Please try again later.</p>';
}
```

### Performance

For product listings, lazy load products as they come into view:

```javascript
const observer = new IntersectionObserver(async (entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      const container = entry.target;
      const productId = container.dataset.productId;
      
      await client.injectProductElement([
        { containerId: container.id, identifier: productId }
      ]);
      
      observer.unobserve(container);
    }
  }
});

// Observe all product containers
document.querySelectorAll('.product-placeholder').forEach(el => {
  observer.observe(el);
});
```

## Troubleshooting

### Product Not Displaying

1. Check browser console for errors
2. Verify container ID exists in the DOM
3. Confirm product identifier is valid
4. Check that SDK is initialized (`window.LiquidCommerce.elements` exists)

### Wrong Pricing

- Ensure user's address is set correctly
- Verify product has availability in user's location
- Check that fulfillment type is supported

### Images Not Loading

- Check network tab for 404 errors
- Verify product has images in the catalog
- Ensure no ad blockers are interfering

### Size Selector Not Showing

- Product must have multiple sizes to show selector
- Check that sizes have availability
- Verify theme config hasn't hidden the selector

## See Also

- [Cart Component](./cart-component.md) - Shopping cart functionality
- [Address Component](./address-component.md) - Location management
- [Checkout Component](./checkout-component.md) - Complete purchase flow
- [Theming](./theming.md) - Customize appearance
- [Events](./events.md) - All available events
- [Actions API](../api/actions/product-actions.md) - Product actions reference
