@magic-spells/gift-with-purchase
v1.0.0
Published
Gift with purchase web component for e-commerce threshold promotions.
Downloads
16
Maintainers
Readme
Gift with Purchase
A powerful, e-commerce web component for automatic gift-with-purchase threshold promotions. Seamlessly integrates with Shopify and automatically manages gift items in the cart based on spending thresholds.
Features
- 🎁 Automatic Gift Management - Adds/removes gifts based on cart thresholds using smart pricing logic
- 🛒 Shopify Integration - Built-in Cart API support with proper line item properties
- 📱 Cart Panel Sync - Automatically syncs with cart-panel components using
calculated_subtotal - 🎨 Highly Customizable - CSS custom properties and swappable content elements
- ⚡ Event-Driven - Custom events for gift addition, removal, and errors
- 🔧 Flexible Content - Data attributes for dynamic image, title, and variant updates
- 📱 Responsive - Mobile-optimized with responsive design
Installation
npm install @magic-spells/gift-with-purchaseBasic Usage
<gift-with-purchase
threshold="75.00"
current="45.00"
variant-id="12345678"
money-format="${{amount}}"
message-above="🎉 Congratulations! You've qualified for your FREE gift!"
message-below="Add [amount] more to unlock your free gift! 🎁">
<div class="gwp-product">
<img src="gift-image.jpg" alt="Free Gift" />
<div class="gwp-content">
<h4 class="gwp-title">Free Sample Set</h4>
<p class="gwp-variant">Travel Size Collection</p>
</div>
</div>
<p data-content-gwp-message class="text-sm font-medium"></p>
</gift-with-purchase>@import '@magic-spells/gift-with-purchase/css';Cart Integration
The component automatically listens for cart data changes when placed inside a <cart-panel> component from the @magic-spells/cart-panel package:
<cart-panel>
<gift-with-purchase
threshold="75.00"
variant-id="12345678"
money-format={{ shop.money_format | json }}
message-above="🎉 Congratulations! You've qualified for your FREE gift!"
message-below="Add [amount] more to unlock your free gift! 🎁">
<!-- Gift content -->
</gift-with-purchase>
</cart-panel>When the cart-panel emits a cart-panel:data-changed event (typically from Shopify cart updates), the gift component will automatically:
- Update the current cart amount using
calculated_subtotalfor accurate threshold calculation - Check if the threshold is met (excludes other gifts and honors pricing exclusions)
- Add the gift to cart if threshold is reached
- Remove the gift if cart falls below threshold
Smart Pricing Logic
The component uses intelligent threshold calculation:
- Uses
calculated_subtotalfrom cart-panel which properly handles item exclusions - Excludes gifts: Other gifts with purchase won't count toward this threshold
- Includes bundle items: Hidden bundle components that should count are included
- Multi-currency support: Automatically converts threshold using
Shopify.currency.ratefor stores with multiple currencies - Requires cart-panel: The component requires
calculated_subtotalin cart events to function
JavaScript API
const gwp = document.querySelector('gift-with-purchase');
// Setters - update state programmatically
gwp.setCurrentAmount(85.5);
gwp.setThreshold(100.0);
gwp.setVariantId('87654321');
// Get full state object
const state = gwp.getState();
console.log(state.isActive, state.isAdded, state.remainingAmount);
// Readonly getters - access individual properties
console.log(gwp.currentAmount); // number - current cart amount
console.log(gwp.threshold); // number - threshold amount
console.log(gwp.variantId); // string - gift variant ID
console.log(gwp.isActive); // boolean - threshold met
console.log(gwp.isAdded); // boolean - gift in cart
console.log(gwp.promoEnded); // boolean - promo has ended
console.log(gwp.productAvailable); // boolean - gift product available
console.log(gwp.isDisabled); // boolean - promo ended OR product unavailableAttributes
| Attribute | Description | Example |
| --------------- | -------------------------------------------------------------------- | ------------------------------------------------------------ |
| threshold | Spending threshold to unlock the gift (auto-converts for multi-currency) | "75.00" |
| current | Current cart amount (typically set automatically via cart-panel) | "45.00" |
| variant-id | Shopify variant ID for the gift product | "12345678" |
| promo-ended | Disables the promo and hides the component | "true" |
| product-available | Whether the gift product is available (disables if false) | "true" |
| message-above | Message shown when threshold is met | "🎉 Congratulations! You've qualified for your FREE gift!" |
| message-below | Message shown below threshold; use [amount] for remaining amount | "Add [amount] more to unlock your free gift! 🎁" |
| money-format | Shopify-style money format for currency display | "${{amount}}" |
The [amount] Placeholder
In the message-below attribute, use [amount] (with square brackets) to insert the remaining amount needed to reach the threshold. The component automatically:
- Calculates the remaining amount (
threshold - currentAmount) - Converts to the customer's currency using
Shopify.currency.rate - Formats using your
money-formatattribute
<!-- Example: If threshold is $75 and cart is $45, displays "Add $30 more..." -->
<gift-with-purchase
threshold="75.00"
money-format="${{amount}}"
message-below="Add [amount] more to unlock your free gift!">
</gift-with-purchase>Why square brackets? We use [amount] instead of {{amount}} to avoid conflicts with Shopify Liquid templates and JavaScript template literals.
Multi-Currency Support
The component automatically handles currency conversion for multi-currency Shopify stores:
- Set
thresholdin your store's base currency (e.g., USD) - The component reads
Shopify.currency.ratefrom the browser - Threshold is automatically converted to the customer's selected currency
- The
[amount]placeholder displays in the converted currency
Message Element
The component requires a message element to display threshold messages:
<gift-with-purchase threshold="75.00" variant-id="12345678">
<!-- Your gift content with any styling -->
<div class="gift-content">
<h4 class="font-bold">Free Gift</h4>
<p class="text-gray-600">Sample Description</p>
</div>
<!-- Required: Element where messages will be injected -->
<p data-content-gwp-message class="bg-green-100 p-2 rounded"></p>
</gift-with-purchase>Currency Formatting
Use the money-format attribute to format amounts with the correct currency symbol and formatting:
<gift-with-purchase
threshold="75.00"
variant-id="12345678"
money-format="${{amount}}"
message-below="Add [amount] more to unlock your free gift!">
<!-- Gift content -->
</gift-with-purchase>In Shopify themes, you can pass the shop's money format using the json filter:
<gift-with-purchase
threshold="75.00"
variant-id="12345678"
money-format={{ shop.money_format | json }}
message-below="Add [amount] more to unlock your free gift!">
<!-- Gift content -->
</gift-with-purchase>Available Shopify money formats:
shop.money_format- Basic format (e.g.,${{amount}})shop.money_with_currency_format- With currency code (e.g.,${{amount}} USD)
Supported Format Placeholders
| Placeholder | Output | Example |
| ----------- | ------ | ------- |
| {{amount}} | Amount with decimals | 19.99 |
| {{amount_no_decimals}} | Rounded whole number | 20 |
| {{amount_with_comma_separator}} | Comma as decimal | 19,99 |
| {{amount_no_decimals_with_comma_separator}} | Whole with comma thousands | 1,000 |
Examples by Currency
<!-- USD -->
money-format="${{amount}}" <!-- $30.00 -->
<!-- EUR -->
money-format="€{{amount}}" <!-- €30.00 -->
money-format="{{amount}} €" <!-- 30.00 € -->
<!-- GBP -->
money-format="£{{amount}}" <!-- £30.00 -->
<!-- JPY (no decimals) -->
money-format="¥{{amount_no_decimals}}" <!-- ¥3000 -->Events
The component emits custom events for integration:
// Gift successfully added to cart
gwp.addEventListener('gwp:added', (event) => {
console.log('Gift added:', event.detail.variantId);
});
// Gift successfully removed from cart
gwp.addEventListener('gwp:removed', (event) => {
console.log('Gift removed:', event.detail.variantId);
});
// Error occurred during add/remove
gwp.addEventListener('gwp:error', (event) => {
console.error('Error:', event.detail.error);
console.log('Action:', event.detail.action); // 'add' or 'remove'
});Customization
Use CSS custom properties to customize the appearance:
gift-with-purchase {
--gwp-border-radius: 12px;
--gwp-padding: 1.5rem;
--gwp-bg-active: #e8f5e8;
--gwp-border-active: #28a745;
--gwp-text-active: #155724;
--gwp-image-size: 80px;
}Available CSS Custom Properties
| Property | Description | Default |
| --------------------- | ---------------------- | --------- |
| --gwp-border-radius | Border radius | 8px |
| --gwp-padding | Internal padding | 1rem |
| --gwp-bg-active | Background when active | #e8f5e8 |
| --gwp-bg-added | Background when added | #d4edda |
| --gwp-border-active | Border when active | #28a745 |
| --gwp-border-added | Border when added | #155724 |
| --gwp-text-active | Text color when active | #155724 |
| --gwp-text-added | Text color when added | #155724 |
| --gwp-gap | Gap between elements | 1rem |
Component States
The component automatically applies a state attribute based on its current condition:
state="active"- Threshold met, gift available to addstate="added"- Gift successfully added to cartstate="inactive"- Below threshold (component idle)state="ended"- Promo ended (component hidden)state="disabled"- Gift unavailable (component hidden)
Note: ended and disabled both hide the component while still removing any existing gift items.
Shopify Integration Details
Cart API Calls
The component uses Shopify's Cart API endpoints:
POST /cart/add.js- Adds the gift to cartGET /cart.js- Gets current cart statePOST /cart/change.js- Removes gift from cart
Line Item Properties
When adding gifts to the cart, the component automatically sets these properties for proper cart integration:
{
id: variantId,
quantity: 1,
properties: {
_gwp_item: "true",
_hide_in_cart: "true",
_ignore_price_in_subtotal: "true"
}
}Property Functions:
_gwp_item: "true"- Identifies gift items in cart templates and for removal logic_hide_in_cart: "true"- Hides gifts from cart display (handled by@magic-spells/cart-panel)_ignore_price_in_subtotal: "true"- Excludes gift price from subtotal calculations and threshold checks- Automatic management - These properties ensure gifts don't interfere with other promotions or cart totals
Cart Template Integration
In your Shopify cart template, you can identify and handle gift items:
{% for item in cart.items %}
{% if item.properties._gwp_item == 'true' %}
<!-- This is a gift item -->
<div class="cart-gift-item">
<span class="gift-badge">FREE GIFT</span>
{{ item.product.title }}
</div>
{% else %}
<!-- Regular cart item -->
{% endif %}
{% endfor %}Advanced Usage
Multiple Thresholds
<!-- Low tier gift -->
<gift-with-purchase
threshold="50.00"
variant-id="111111"
message-above="🎉 You've unlocked a free tote bag!"
message-below="Add [amount] more for a free tote bag! 👜">
<div class="gwp-product">
<img src="tote-bag.jpg" alt="Free Tote Bag" />
<h4 class="gwp-title">Free Tote Bag</h4>
</div>
<p data-content-gwp-message class="message-low-tier"></p>
</gift-with-purchase>
<!-- High tier gift -->
<gift-with-purchase
threshold="100.00"
variant-id="222222"
message-above="✨ Amazing! You've earned our premium gift set!"
message-below="Spend [amount] more for our exclusive premium gift set! ✨">
<div class="gwp-product">
<img src="gift-set.jpg" alt="Premium Gift Set" />
<h4 class="gwp-title">Premium Gift Set</h4>
</div>
<p data-content-gwp-message class="message-premium"></p>
</gift-with-purchase>Custom Styling Examples
/* Minimal style */
.gwp-minimal {
--gwp-padding: 0.75rem;
--gwp-border-radius: 4px;
--gwp-bg-active: #f0f8f0;
}
/* Bold style */
.gwp-bold {
--gwp-padding: 1.5rem;
--gwp-border-radius: 12px;
--gwp-bg-active: #28a745;
--gwp-text-active: white;
}
/* Compact mobile style */
@media (max-width: 768px) {
gift-with-purchase {
--gwp-image-size: 40px;
--gwp-padding: 0.5rem;
--gwp-gap: 0.5rem;
}
}Browser Support
- Chrome/Edge 88+
- Firefox 85+
- Safari 14+
- All modern browsers with Custom Elements support
TypeScript Support
Type definitions are included in the package:
interface GiftWithPurchaseState {
currentAmount: number;
threshold: number;
convertedThreshold: number;
variantId: string | null;
isActive: boolean;
isAdded: boolean;
promoEnded: boolean;
productAvailable: boolean;
isDisabled: boolean;
remainingAmount: number;
currencyRate: number;
}
// Component automatically handles message injection
// Style your content and message elements with any CSS classesContributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
MIT License - see LICENSE file for details.
