@liquidcommerceteam/elements-sdk
Version:
LiquidCommerce Elements SDK
1,817 lines (1,606 loc) • 373 kB
JavaScript
var ELEMENTS_ENV;
(function (ELEMENTS_ENV) {
ELEMENTS_ENV["LOCAL"] = "local";
ELEMENTS_ENV["DEVELOPMENT"] = "development";
ELEMENTS_ENV["STAGING"] = "staging";
ELEMENTS_ENV["PRODUCTION"] = "production";
})(ELEMENTS_ENV || (ELEMENTS_ENV = {}));
var ELEMENTS_ACTIONS_EVENT;
(function (ELEMENTS_ACTIONS_EVENT) {
// Product-related events
ELEMENTS_ACTIONS_EVENT["PRODUCT_LOADED"] = "productLoaded";
ELEMENTS_ACTIONS_EVENT["PRODUCT_QUANTITY_INCREASE"] = "productQuantityIncrease";
ELEMENTS_ACTIONS_EVENT["PRODUCT_QUANTITY_DECREASE"] = "productQuantityDecrease";
ELEMENTS_ACTIONS_EVENT["PRODUCT_ADD_TO_CART"] = "productAddToCart";
// Cart-related events
ELEMENTS_ACTIONS_EVENT["CART_INITIALIZED"] = "cartInitialized";
ELEMENTS_ACTIONS_EVENT["CART_CLOSED"] = "cartClosed";
ELEMENTS_ACTIONS_EVENT["CART_OPENED"] = "cartOpened";
ELEMENTS_ACTIONS_EVENT["CART_UPDATED"] = "cartUpdated";
// Address-related events
ELEMENTS_ACTIONS_EVENT["ADDRESS_UPDATED"] = "addressUpdated";
// Checkout-related events
ELEMENTS_ACTIONS_EVENT["CHECKOUT_INITIALIZED"] = "checkoutInitialized";
ELEMENTS_ACTIONS_EVENT["CHECKOUT_OPENED"] = "checkoutOpened";
ELEMENTS_ACTIONS_EVENT["CHECKOUT_CLOSED"] = "checkoutClosed";
ELEMENTS_ACTIONS_EVENT["CHECKOUT_UPDATED"] = "checkoutUpdated";
})(ELEMENTS_ACTIONS_EVENT || (ELEMENTS_ACTIONS_EVENT = {}));
var ELEMENTS_FORMS_EVENT;
(function (ELEMENTS_FORMS_EVENT) {
ELEMENTS_FORMS_EVENT["SHIPPING_FIRST_NAME_INPUT"] = "shippingFirstNameInput";
ELEMENTS_FORMS_EVENT["SHIPPING_LAST_NAME_INPUT"] = "shippingLastNameInput";
ELEMENTS_FORMS_EVENT["SHIPPING_PHONE_INPUT"] = "shippingPhoneInput";
ELEMENTS_FORMS_EVENT["SHIPPING_EMAIL_INPUT"] = "shippingEmailInput";
})(ELEMENTS_FORMS_EVENT || (ELEMENTS_FORMS_EVENT = {}));
var COMPONENT_TYPE;
(function (COMPONENT_TYPE) {
COMPONENT_TYPE["ADDRESS"] = "address";
COMPONENT_TYPE["DRAWER"] = "drawer";
COMPONENT_TYPE["MODAL"] = "modal";
COMPONENT_TYPE["LOADING"] = "loading";
COMPONENT_TYPE["COLLAPSIBLE"] = "collapsible";
COMPONENT_TYPE["INPUT"] = "input";
COMPONENT_TYPE["PRODUCT"] = "product";
COMPONENT_TYPE["PRODUCT_IMAGE_CAROUSEL"] = "product-image-carousel";
COMPONENT_TYPE["PRODUCT_OPTIONS"] = "product-options";
COMPONENT_TYPE["PRODUCT_INTERACTIONS"] = "product-interactions";
COMPONENT_TYPE["PRODUCT_RETAILERS"] = "product-retailers";
COMPONENT_TYPE["PRODUCT_PRICE"] = "product-price";
COMPONENT_TYPE["PRODUCT_ADD_TO_CART_SECTION"] = "product-add-to-cart-section";
COMPONENT_TYPE["PRODUCT_ENGRAVING"] = "product-engraving";
COMPONENT_TYPE["CART"] = "cart";
COMPONENT_TYPE["CART_RETAILER"] = "cart-retailer";
COMPONENT_TYPE["CART_ITEM"] = "cart-item";
COMPONENT_TYPE["CART_ITEM_ENGRAVING"] = "cart-item-engraving";
COMPONENT_TYPE["CART_FOOTER"] = "cart-footer";
COMPONENT_TYPE["CART_FOOTER_PRICE"] = "cart-footer-price";
COMPONENT_TYPE["CART_ITEM_QUANTITY_PRICE"] = "cart-item-quantity-price";
COMPONENT_TYPE["CART_RETAILER_PROGRESS_BAR"] = "cart-retailer-progress-bar";
COMPONENT_TYPE["CART_RETAILER_SUBTOTAL"] = "cart-retailer-subtotal";
COMPONENT_TYPE["CHECKOUT"] = "checkout";
COMPONENT_TYPE["CHECKOUT_SHIPPING_INFORMATION"] = "checkout-shipping-information";
COMPONENT_TYPE["CHECKOUT_PAYMENT_FORM"] = "checkout-payment-form";
COMPONENT_TYPE["CHECKOUT_BUYERS_INFO_FORM"] = "checkout-buyers-info-form";
})(COMPONENT_TYPE || (COMPONENT_TYPE = {}));
var FULFILLMENT_TYPE;
(function (FULFILLMENT_TYPE) {
FULFILLMENT_TYPE["ON_DEMAND"] = "onDemand";
FULFILLMENT_TYPE["SHIPPING"] = "shipping";
})(FULFILLMENT_TYPE || (FULFILLMENT_TYPE = {}));
var CART_EVENT_ENUM;
(function (CART_EVENT_ENUM) {
CART_EVENT_ENUM["OOS"] = "OutOfStock";
CART_EVENT_ENUM["ITEMS_NOT_ADDED"] = "ItemsNotAdded";
CART_EVENT_ENUM["ITEMS_REQUESTED_NOT_ADDED"] = "ItemsRequestedNotAdded";
CART_EVENT_ENUM["ITEM_NOT_ENGRAVED"] = "ItemEngravingError";
CART_EVENT_ENUM["ADDRESS_CHANGE"] = "AddressChange";
CART_EVENT_ENUM["LOCATION_AVAILABILITY"] = "LocationAvailability";
CART_EVENT_ENUM["PARTNER_PRODUCT_CONFIGS"] = "PartnerProductConfigs";
CART_EVENT_ENUM["REMOVED_EXISTING_ITEMS"] = "RemovedExistingCartItems";
CART_EVENT_ENUM["RETAILER_MIN"] = "RetailerMinNotMet";
CART_EVENT_ENUM["NO_ITEMS_IN_CART"] = "NoItemsInCart";
CART_EVENT_ENUM["INVALID_ID"] = "InvalidId";
CART_EVENT_ENUM["NO_ID"] = "NoId";
CART_EVENT_ENUM["CART_CHECKOUT_PROCESSED"] = "CartCheckoutProcessed";
CART_EVENT_ENUM["NEW_CART"] = "NewCart";
CART_EVENT_ENUM["DEFAULT"] = "CartError";
CART_EVENT_ENUM["ITEM_QTY_CHANGE"] = "ItemQuantityChange";
CART_EVENT_ENUM["ITEM_ID_NOT_FOUND"] = "ItemIdNotFound";
CART_EVENT_ENUM["ITEMS_REMOVED"] = "ItemsRemoved";
// Coupon validation events
CART_EVENT_ENUM["COUPON_PROCESSING_ERROR"] = "CouponProcessingError";
CART_EVENT_ENUM["COUPON_NOT_FOUND"] = "CouponNotFound";
CART_EVENT_ENUM["COUPON_EXPIRED"] = "CouponExpired";
CART_EVENT_ENUM["NO_APPLICABLE_DISCOUNT"] = "NoApplicableDiscount";
CART_EVENT_ENUM["COUPON_NOT_STARTED"] = "CouponNotStarted";
CART_EVENT_ENUM["MINIMUM_ORDER_VALUE_NOT_MET"] = "MinimumOrderValueNotMet";
CART_EVENT_ENUM["MINIMUM_ORDER_UNITS_NOT_MET"] = "MinimumOrderUnitsNotMet";
CART_EVENT_ENUM["MINIMUM_DISTINCT_ITEMS_NOT_MET"] = "MinimumDistinctItemsNotMet";
CART_EVENT_ENUM["QUOTA_EXCEEDED"] = "QuotaExceeded";
CART_EVENT_ENUM["USER_LIMIT_EXCEEDED"] = "UserLimitExceeded";
CART_EVENT_ENUM["NOT_FIRST_PURCHASE"] = "NotFirstPurchase";
CART_EVENT_ENUM["INVALID_COUPON"] = "InvalidCoupon";
CART_EVENT_ENUM["INVALID_MEMBERSHIP"] = "InvalidMembership";
CART_EVENT_ENUM["INVALID_DOMAIN"] = "InvalidDomain";
CART_EVENT_ENUM["INVALID_REQUIREMENTS"] = "InvalidRequirements";
CART_EVENT_ENUM["INVALID_ORGANIZATION"] = "InvalidOrganization";
CART_EVENT_ENUM["PRODUCT_NOT_ELIGIBLE"] = "ProductNotEligible";
CART_EVENT_ENUM["NOT_ENOUGH_PREVIOUS_ORDERS"] = "NotEnoughPreviousOrders";
//Presale validation events
CART_EVENT_ENUM["PRESALE_ITEMS_NOT_ALLOWED"] = "PresaleItemsNotAllowed";
CART_EVENT_ENUM["PRESALE_LIMIT_EXCEEDED"] = "PresaleLimitExceeded";
CART_EVENT_ENUM["PRESALE_NOT_STARTED"] = "PresaleNotStarted";
CART_EVENT_ENUM["PRESALE_EXPIRED"] = "PresaleExpired";
CART_EVENT_ENUM["PRESALE_MIXED_CART"] = "PresaleMixedCart";
})(CART_EVENT_ENUM || (CART_EVENT_ENUM = {}));
var CHECKOUT_EVENT_ENUM;
(function (CHECKOUT_EVENT_ENUM) {
CHECKOUT_EVENT_ENUM["ERROR_PROCESSING_GIFT_CARDS"] = "ErrorProcessingGiftCards";
CHECKOUT_EVENT_ENUM["INVALID_GIFT_CARD_CODE"] = "InvalidGiftCardCodes";
CHECKOUT_EVENT_ENUM["INVALID_GIFT_CARD_PARTNER"] = "InvalidGiftCardPartner";
CHECKOUT_EVENT_ENUM["INACTIVE_GIFT_CARD"] = "InactiveGiftCard";
CHECKOUT_EVENT_ENUM["GIFT_CARD_ALREADY_IN_USE"] = "GiftCardAlreadyInUse";
CHECKOUT_EVENT_ENUM["GIFT_CARD_EXPIRED"] = "GiftCardExpired";
CHECKOUT_EVENT_ENUM["GIFT_CARD_BALANCE_DEPLETED"] = "GiftCardBalanceDepleted";
})(CHECKOUT_EVENT_ENUM || (CHECKOUT_EVENT_ENUM = {}));
var ENUM_ADDRESS_TYPE;
(function (ENUM_ADDRESS_TYPE) {
ENUM_ADDRESS_TYPE["SHIPPING"] = "shipping";
ENUM_ADDRESS_TYPE["BILLING"] = "billing";
})(ENUM_ADDRESS_TYPE || (ENUM_ADDRESS_TYPE = {}));
class SingletonManager {
/**
* Retrieves an instance of the specified class using the provided instance creator function.
*
* @template T The type of the instance.
* @param {string} className The name of the class.
* @param {() => T} instanceCreator The function that creates the instance.
* @returns {T} The instance of the specified class.
*/
static getInstance(className, instanceCreator) {
if (!this.instances.has(className)) {
this.instances.set(className, instanceCreator());
}
const instance = this.instances.get(className);
if (!instance) {
throw new Error(`ElementsSdk: Instance for class ${className} could not be created.`);
}
return instance;
}
}
SingletonManager.instances = new Map();
class ApiClientService {
constructor() {
this.client = null;
}
static getInstance() {
return SingletonManager.getInstance('ApiClientService', () => new ApiClientService());
}
async setClient(client) {
this.client = client;
}
async getGlobalConfigs() {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.get('/elements/config');
if (res.statusCode !== 200) {
console.error(res);
throw new Error(`Failed to fetch global configs: ${res.message}`);
}
return res.data;
}
catch (error) {
console.error('Error fetching configs:', error);
throw error;
}
}
async getProductData(identifier, location) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
if (!identifier) {
throw new Error('Identifier is required to fetch product data');
}
const res = await this.client.post('/cloud/catalog/availability', {
upcs: [identifier],
grouping: [identifier],
loc: location,
shouldShowOffHours: true,
});
if (res.statusCode !== 200 || !res.products || !res.products[0]) {
console.error(res);
throw new Error(`Failed to fetch product data: ${res.message}`);
}
return {
product: res.products[0],
retailers: res.retailers,
};
}
catch (error) {
console.error('Error fetching product data:', error);
throw error;
}
}
async getCartData(id) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.get(`/cloud/cart${id ? `?id=${id}` : ''}`);
if (res.statusCode !== 200) {
console.error(res);
throw new Error(`Failed to fetch product data: ${res.message}`);
}
return res.cart;
}
catch (error) {
console.error('Error fetching product data:', error);
throw error;
}
}
async updateCart(params, location) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.put('/cloud/cart', {
...params,
loc: location,
});
if (res.statusCode !== 200) {
console.error(res);
throw new Error(`Failed to fetch product data: ${res.message}`);
}
return res.cart;
}
catch (error) {
console.error('Error fetching product data:', error);
throw error;
}
}
async getAddressSuggestions(query) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.get(`/cloud/address/autocomplete?input=${query}`);
if (res.statusCode !== 200) {
console.error(res);
throw new Error(`Failed to fetch address suggestions: ${res.message}`);
}
return res.data;
}
catch (error) {
console.error('Error fetching address suggestions:', error);
throw error;
}
}
async getAddressDetails(id) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.get(`/cloud/address/details/${id}`);
if (res.statusCode !== 200) {
console.error(res);
throw new Error(`Failed to fetch address details: ${res.message}`);
}
return res.data;
}
catch (error) {
console.error('Error fetching address details:', error);
throw error;
}
}
async prepareCheckout(params) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.post('/cloud/checkout/prepare', params);
if (res.statusCode !== 200) {
console.error(res);
throw new Error(`Failed to fetch prepare checkout data: ${res.message}`);
}
return res.checkout;
}
catch (error) {
console.error('Error fetching prepare checkout data:', error);
throw error;
}
}
async getUserSession(params) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.post('/cloud/user/session', params);
if (res.statusCode !== 201) {
console.error(res);
throw new Error(`Failed to fetch user session: ${res.message}`);
}
return res.data;
}
catch (error) {
console.error('Error fetching user session:', error);
throw error;
}
}
async confirmPaymentSession(sessionSecret, paymentMethodId) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.post('/cloud/payment/confirmSession', {
sessionSecret,
paymentMethodId,
});
if (res.statusCode !== 201) {
console.error(res);
throw new Error(`Failed to confirm session: ${res.message}`);
}
return res.data;
}
catch (error) {
console.error('Error confirming session:', error);
throw error;
}
}
async checkoutComplete(params) {
try {
if (!this.client) {
throw new Error('API client is not initialized');
}
const res = await this.client.post('/cloud/checkout/complete', params);
if (res.statusCode !== 201) {
console.error(res);
throw new Error(`Failed to complete checkout: ${res.message}`);
}
return res.order.legacyOrderNumber;
}
catch (error) {
console.error('Error completing checkout:', error);
throw error;
}
}
}
const getBaseStyles = () => `
.w-full { width: 100%; }
.max-w-full { max-width: 100%; }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-row { flex-direction: row; }
.flex-wrap { flex-wrap: wrap; }
.flex-nowrap { flex-wrap: nowrap; }
.gap-2 { gap: 10px; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.justify-start { justify-content: flex-start; }
.justify-end { justify-content: flex-end; }
.gap-xs { gap: 4px; }
.gap-sm { gap: 8px; }
.gap-md { gap: 12px; }
.gap-lg { gap: 20px; }
.gap-xl { gap: 40px; }
.m-0 { margin: 0; }
.p-xs { padding: 4px; }
.p-sm { padding: 8px; }
.p-md { padding: 12px; }
.rounded-sm { border-radius: 4px}
.rounded { border-radius: 8px; }
.rounded-lg { border-radius: 12px; }
.rounded-xl { border-radius: 20px; }
.cursor-pointer { cursor: pointer; }
.transition { transition: all 0.2s ease-in-out; }
.hidden { display: none; }
.overflow-hidden { overflow: hidden; }
.border { border: 1px solid; }
.shadow-sm { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
.shadow { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
div.divider {
border: none;
border-top: 1px solid #c1c1c1;
margin: 10px 0;
width: 100%;
}
.btn {
padding: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
font-size: 16px;
}
.btn-primary {
width: 100%;
border-radius: 8px;
min-width: 180px;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 8px 0;
}
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
*, :after, :before {
box-sizing: border-box;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
.custom-input-container {
margin-bottom: 1rem;
}
.custom-input-container input {
width: 100%;
padding: 0.75rem;
border: 2px solid #e5e7eb;
border-radius: 0.375rem;
font-size: 1rem;
transition: border-color 0.2s ease-in-out;
}
.custom-input-container input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.custom-input-container input.input-error {
border-color: #ef4444;
}
.custom-input-container .error-container {
margin-top: 0.25rem;
}
.custom-input-container .error-message {
color: #ef4444;
font-size: 0.875rem;
margin-bottom: 0.25rem;
}
`;
const getCollapsibleStyle = () => `
.collapsible-container {
width: 100%;
display: flex;
flex-direction: column;
}
.collapsible-container .header {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
cursor: pointer;
color: #212529;
font-family: Arial, sans-serif;
}
.collapsible-container .header .title {
font-size: 16px;
font-weight: 500;
color: #212529;
line-height: 24px;
}
.collapsible-container .header .icon {
font-size: 20px;
}
.collapsible-icon.expanded {
transform: rotate(180deg);
}
.collapsible-container .content {
max-height: 1000px;
overflow: hidden;
color: #282828;
font-size: 14px;
line-height: 20px;
font-type: Arial;
}
.collapsible-container.collapsed .content {
max-height: 0;
}
`;
function getDrawerStyles() {
return `
.drawer-container {
position: relative;
}
.drawer-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100dvh;
background-color: rgba(0, 0, 0, 0.4);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.drawer-backdrop.visible {
opacity: 1;
visibility: visible;
}
.drawer {
height: 100dvh;
width: 0; /* Start closed */
position: fixed;
z-index: 1000;
top: 0;
right: 0; /* Position from right instead of left */
background-color: #ffffff;
overflow-x: hidden;
transition: width 0.3s ease;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
/* When drawer is open - Desktop */
.drawer-container.open .drawer {
width: 450px; /* Width when open */
}
/* Header section */
.drawer .header {
padding: 16px;
border-bottom: 1px solid #eaeaea;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0; /* Prevent header from shrinking */
}
.drawer .header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333333;
}
.drawer .close-button {
background: transparent;
border: none;
font-size: 24px;
line-height: 1;
color: #666666;
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
height: 32px;
width: 32px;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.drawer .close-button:hover {
background-color: #f5f5f5;
color: #333333;
}
/* Body section */
.drawer .body {
flex: 1;
overflow: hidden;
}
/* Footer section */
.drawer .footer {
padding: 12px 16px;
border-top: 1px solid #eaeaea;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #666666;
flex-shrink: 0; /* Prevent footer from shrinking */
}
.drawer .footer p {
margin: 0;
}
.drawer .footer span {
display: flex;
align-items: center;
}
/* Tablet adjustments */
@media (max-width: 768px) {
.drawer-container.open .drawer {
width: 380px; /* Slightly smaller on tablets */
}
}
/* Mobile adjustments */
@media (max-width: 576px) {
.drawer-container.open .drawer {
width: 100% !important; /* Full width on mobile - using !important to ensure it overrides */
}
/* Adjust padding on mobile for better spacing */
.drawer .header {
padding: 12px 16px;
}
.drawer .footer {
padding: 10px 16px;
}
}
/* Small mobile devices */
@media (max-width: 480px) {
.drawer .header h2 {
font-size: 16px; /* Slightly smaller title on very small screens */
}
.drawer .close-button {
height: 28px;
width: 28px;
font-size: 20px;
}
}
/* Landscape mobile orientation */
@media (max-height: 500px) and (orientation: landscape) {
.drawer .header {
padding: 8px 16px; /* Reduce vertical padding in landscape */
}
.drawer .footer {
padding: 6px 16px;
}
}
/* Smooth transitions for different screen sizes */
@media (prefers-reduced-motion: reduce) {
.drawer,
.drawer-backdrop {
transition: none;
}
}
`;
}
function getModalStyles() {
return `
.modal-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.modal-container.open {
opacity: 1;
visibility: visible;
}
/* Container relative positioning for contained modals */
.modal-container.container-relative {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000; /* Relative to the container, not the entire page */
}
.modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s ease;
}
.modal-backdrop.visible {
opacity: 1;
}
/* When contained, backdrop should only cover the container */
.modal-container.container-relative .modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.modal-content {
position: relative;
background: white;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
width: auto;
max-width: 90vw;
max-height: 90vh;
overflow: auto;
transform: scale(0.9) translateY(-20px);
transition: transform 0.3s ease;
z-index: 1001;
}
.modal-container.open .modal-content {
transform: scale(1) translateY(0);
}
.modal-body {
padding: 0;
}
/* Position variants */
.modal-container.position-top {
align-items: flex-start;
padding-top: 5vh;
}
.modal-container.position-bottom {
align-items: flex-end;
padding-bottom: 5vh;
}
.modal-container.position-left {
justify-content: flex-start;
padding-left: 5vw;
}
.modal-container.position-right {
justify-content: flex-end;
padding-right: 5vw;
}
/* Engraving Modal Specific Styles */
.engraving-modal-content {
padding: 24px;
width: 100%;
max-width: 500px;
min-width: 320px;
}
.engraving-modal-header {
margin-bottom: 24px;
text-align: center;
}
.engraving-modal-header h3 {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 600;
color: #1a1a1a;
}
.engraving-modal-header p {
margin: 4px 0;
color: #666;
font-size: 14px;
}
.engraving-fee {
font-weight: 600;
color: #2563eb;
}
.engraving-form {
margin-bottom: 24px;
}
.engraving-input-group {
margin-bottom: 16px;
}
.engraving-input-group label {
display: block;
margin-bottom: 4px;
font-weight: 500;
color: #374151;
}
.engraving-input-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.engraving-input-group input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.char-count {
display: block;
margin-top: 4px;
font-size: 12px;
color: #6b7280;
text-align: right;
}
.engraving-preview-section {
margin-top: 20px;
padding: 16px;
background-color: #f9fafb;
border-radius: 6px;
}
.engraving-preview-section h4 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 500;
color: #374151;
}
.bottle-preview {
display: flex;
justify-content: center;
align-items: center;
min-height: 80px;
background-color: white;
border: 2px dashed #d1d5db;
border-radius: 4px;
}
.engraving-preview-text {
text-align: center;
font-family: serif;
font-style: italic;
}
.preview-line-1,
.preview-line-2 {
font-size: 14px;
color: #374151;
min-height: 20px;
line-height: 1.4;
}
.preview-line-1 {
font-weight: 600;
}
.engraving-modal-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
flex-wrap: wrap;
}
.engraving-modal-actions button {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary {
background-color: #2563eb;
color: white;
}
.btn-primary:hover {
background-color: #1d4ed8;
}
.btn-secondary {
background-color: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
}
.btn-secondary:hover {
background-color: #e5e7eb;
}
.btn-danger {
background-color: #dc2626;
color: white;
}
.btn-danger:hover {
background-color: #b91c1c;
}
/* Engraving Container in Cart */
.engraving-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
margin-top: 8px;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.engraving-container:hover {
background-color: #f1f5f9;
border-color: #cbd5e1;
}
.engraving-title {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.engraving-title svg {
flex-shrink: 0;
color: #64748b;
}
.engraving-title-text {
margin: 0;
font-size: 14px;
color: #475569;
font-weight: 500;
}
.engraving-preview {
margin: 4px 0 0 0;
font-size: 12px;
color: #64748b;
font-style: italic;
}
.engraving-icon {
color: #94a3b8;
}
.engraving-icon svg {
width: 16px;
height: 16px;
}
/* Responsive Design */
@media (max-width: 640px) {
.engraving-modal-content {
min-width: auto;
max-width: 95vw;
padding: 16px;
}
.modal-content {
max-width: 95vw;
margin: 10px;
}
.engraving-modal-actions {
flex-direction: column;
}
.engraving-modal-actions button {
width: 100%;
}
}
/* High z-index for cart modals */
.modal-container[style*="z-index: 50000"] {
z-index: 50000 !important;
}
.modal-container[style*="z-index: 50000"] .modal-content {
z-index: 50001 !important;
}
/* Smooth transitions */
@media (prefers-reduced-motion: reduce) {
.modal-container,
.modal-backdrop,
.modal-content {
transition: none;
}
}
`;
}
const getLoadingStyles = () => `
.loading-skeleton-container {
width: 100%;
height: 500px;
overflow: hidden;
background-color: #f0f0f0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.loading-skeleton-container::before {
content: '';
position: absolute;
top: 0;
left: -150%;
width: 150%;
height: 100%;
background: linear-gradient(
to right,
transparent 0%,
transparent 25%,
rgba(255, 255, 255, 0.9) 50%,
transparent 75%,
transparent 100%
);
animation: wave 2.5s infinite cubic-bezier(0.4, 0, 0.2, 1);
}
/* Add a second wave with different timing for more dynamic effect */
.loading-skeleton-container::after {
content: '';
position: absolute;
top: 0;
left: -150%;
width: 150%;
height: 100%;
background: linear-gradient(
to right,
transparent 0%,
transparent 25%,
rgba(255, 255, 255, 0.7) 50%,
transparent 75%,
transparent 100%
);
animation: wave 2.5s infinite 1.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.loading-skeleton-container .skeleton-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 2;
}
.loading-skeleton-container .icon-container {
margin-bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 50px;
}
.loading-skeleton-container .message-container {
font-size: 20px;
color: #333;
text-align: center;
}
@keyframes wave {
0% {
left: -150%;
}
100% {
left: 100%;
}
}
.loading-skeleton-container.show {
display: flex;
}
.loading-skeleton-container:not(.show) {
display: none;
}
`;
const getImageCarouselStyle = () => `
.product-image-container {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 100%;
max-height: 620px;
}
.main-image-wrapper {
position: relative;
display: flex;
justify-content: center;
align-items: center;
background: #f8f9fa;
border-radius: 8px;
overflow: hidden;
aspect-ratio: 1;
}
.main-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.carousel-dots {
position: absolute;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
background: rgba(0, 0, 0, 0.5);
padding: 10px 14px;
border-radius: 20px;
}
.carousel-dot {
width: 10px;
height: 10px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, 0.5);
cursor: pointer;
transition: background-color 0.2s ease;
}
.carousel-dot:hover {
background: rgba(255, 255, 255, 0.7);
}
.carousel-dot.active {
background: white;
}
.product-image-gallery {
display: none;
}
.gallery-image {
min-width: 60px;
height: 60px;
object-fit: cover;
border: 2px solid transparent;
border-radius: 6px;
cursor: pointer;
background: #f8f9fa;
opacity: 0.5;
transition: opacity 0.2s ease;
}
.gallery-image.selected {
opacity: 1;
}
.no-images {
display: flex;
align-items: center;
justify-content: center;
height: 300px;
background: #f8f9fa;
color: #6c757d;
border-radius: 8px;
font-size: 16px;
}
/* Small mobile screens */
@container (min-width: 480px) {
.carousel-dot {
width: 8px;
height: 8px;
}
.carousel-dots {
gap: 8px;
padding: 8px 12px;
}
}
/* Tablet and desktop */
@media (min-width: 769px) {
.product-image-container {
gap: 16px;
}
.carousel-dots {
display: none;
}
.product-image-gallery {
display: flex;
gap: 8px;
overflow-x: auto;
padding: 4px;
scrollbar-width: thin;
}
.product-image-gallery::-webkit-scrollbar {
height: 6px;
}
.product-image-gallery::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.product-image-gallery::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.product-image-gallery::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.gallery-image {
min-width: 80px;
height: 80px;
}
}
@media (prefers-reduced-motion: reduce) {
.gallery-image,
.carousel-dot {
transition: none;
}
}
`;
const getProductStyles = () => `
.container {
width: 100%;
height: auto;
display: flex;
flex-direction: column;
}
.container .image-carousel {
width: 100%;
}
.container .main {
width: 100%;
}
.product-address-container {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
font-family: inherit;
align-items: center;
gap: 8px;
padding: 15px 0;
}
.product-address-container .edit-address {
cursor: pointer;
color: #1A56DB;
background: none;
border: none;
}
.product-add-to-cart {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: 20px;
margin: 24px 0;
}
.product-add-to-cart .quantity {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
color: #1A56DB;
border-radius: 4px;
background-color: #F3F4F6;
}
.product-add-to-cart .quantity button {
background: none;
color: inherit;
border: none;
cursor: pointer;
}
.product-add-to-cart .add-to-cart-button {
background: red;
color: white;
border: none;
cursor: pointer;
width: 100%;
border-radius: 8px;
padding: 8px 0;
text-align: center;
}
@media (min-width: 769px) {
.container {
flex-direction: row;
gap: 40px;
}
.container .image-carousel {
width: 45%;
}
.container .main {
width: 55%;
}
}
/** V2 Styles
* These styles are for the new product page design.
*/
.product-main-container {
width: 55%;
display: flex;
flex-direction: column;
gap: 32px;
align-items: flex-start;
align-self: stretch;
}
.product-interactions {
display: flex;
flex-direction: column;
gap: 32px;
align-items: flex-start;
align-self: stretch;
}
.product-options {
width: 100%;
}
.size-container {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-2, 8px);
align-self: stretch;
}
.size-container .size-label {
align-self: stretch;
color: var(--base-foreground, #18181B);
/* text small/leading-none/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 500);
line-height: 100%; /* 14px */
}
.size-container .size-buttons-container {
display: flex;
align-items: flex-start;
align-content: flex-start;
gap: 8px var(--spacing-2, 8px);
align-self: stretch;
flex-wrap: wrap;
}
.size-container .size-buttons-container .size-button {
display: flex;
padding: var(--spacing-2, 8px) var(--spacing-4, 16px);
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
/* This is the normal not selected button */
border-radius: var(--border-radius-default, 6px);
border: 1px solid var(--base-input, #E4E4E7);
color: var(--base-foreground, #18181B);
/* text small/leading-normal/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 500);
line-height: var(--typography-base-sizes-small-line-height, 20px);
}
.size-container .size-buttons-container .size-button.selected {
background: var(--base-primary, #1A56DB);
color: var(--base-foreground, #070707);
border-radius: var(--border-radius-default, 6px);
background: var(--base-primary, #1D4ED8);
/* shadow/sm */
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
color: var(--base-primary-foreground, #F1F5F9);
/* text small/leading-normal/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 500);
line-height: var(--typography-base-sizes-small-line-height, 20px);
}
.product-engraving-container {
display: flex;
padding: var(--spacing-4, 16px);
flex-direction: column;
justify-content: center;
align-items: center;
gap: var(--spacing-2, 8px);
align-self: stretch;
border-radius: var(--border-radius-default, 6px);
border: 1px solid var(--base-input, #E4E4E7);
}
.product-engraving-wrapper {
width: 100%;
}
.product-engraving-container .engraving-header {
display: flex;
justify-content: space-between;
align-items: center;
align-self: stretch;
}
.product-engraving-container .engraving-header h3 {
color: var(--base-foreground, #18181B);
/* text small/leading-none/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 700);
line-height: 100%; /* 14px */
}
.product-engraving-container .engraving-header button {
display: flex;
justify-content: center;
align-items: center;
align-self: stretch;
background: none;
border: none;
cursor: pointer;
color: var(--base-primary, #1D4ED8);
/* text small/leading-none/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 500);
line-height: 100%; /* 14px */
}
.product-engraving-container .engraving-lines {
display: flex;
height: 60px;
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-0, 0px);
align-self: stretch;
}
.product-engraving-container .engraving-lines .engraving-line {
color: var(--base-foreground, #18181B);
/* text small/leading-normal/italic */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: italic;
font-weight: 400;
line-height: var(--typography-base-sizes-small-line-height, 20px);
text-transform: uppercase;
}
.product-main-container .button-engraving-label {
display: flex;
height: var(--height-h-9, 36px);
padding: var(--spacing-2, 8px) var(--spacing-4, 16px);
justify-content: center;
align-items: center;
gap: var(--spacing-2, 8px);
cursor: pointer;
border-radius: var(--border-radius-md, 6px);
border: 1px solid var(--base-input, #E4E4E7);
background: var(--base-background, #FAFAFA);
/* shadow/sm */
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
color: var(--base-foreground, #18181B);
/* text small/leading-normal/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 500);
line-height: var(--typography-base-sizes-small-line-height, 20px);
}
/** Product Retailers and Tabs */
.retailers-container {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
align-self: stretch;
}
.retailers-container .fulfillment-tabs-container {
display: flex;
height: 36px;
align-items: center;
align-self: stretch;
}
.retailers-container .fulfillment-tabs-container .fulfillment-tab {
display: flex;
padding: var(--spacing-0, 0px) 0px;
justify-content: center;
align-items: center;
gap: 8px;
cursor: pointer;
}
.retailers-container .fulfillment-tabs-container .fulfillment-tab.selected {
border-bottom: 2px solid var(--base-primary, #1D4ED8);
}
.retailers-container .fulfillment-tabs-container .fulfillment-tab .fulfillment-tab-wrapper {
display: flex;
padding: var(--spacing-2, 8px) var(--spacing-4, 16px);
justify-content: center;
align-items: center;
gap: 8px;
border-radius: var(--border-radius-md, 6px);
/* Typhography Styles */
color: var(--base-foreground, #707077);
/* text small/leading-normal/regular */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-normal, 400);
line-height: var(--typography-base-sizes-small-line-height, 20px); /* 142.857% */
}
.retailers-container .fulfillment-tabs-container .fulfillment-tab .fulfillment-tab-wrapper.selected {
color: var(--base-foreground, #18181B);
/* text small/leading-normal/regular */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-normal, 400);
line-height: var(--typography-base-sizes-small-line-height, 20px);
}
/** Retailers List */
.retailer-list-container {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--spacing-2, 8px);
align-self: stretch;
overflow-x: auto;
}
.retailer-card {
display: flex;
width: 220px;
height: 150px;
padding: 16px;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
flex-shrink: 0;
transition: all 0.2s ease;
cursor: pointer;
border-radius: var(--border-radius-md, 6px);
border: 1px solid var(--base-input, #E4E4E7);
background: var(--base-background, #FAFAFA);
/* shadow/sm */
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
color: var(--base-foreground, #18181B);
}
.retailer-card.selected {
border-radius: var(--border-radius-default, 6px);
background: var(--base-primary, #1D4ED8);
color: var(--base-primary-foreground, #F1F5F9);
/* shadow/sm */
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
}
.retailer-card .retailer-name {
align-self: stretch;
/* text small/leading-normal/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 500);
line-height: var(--typography-base-sizes-small-line-height, 20px); /* 142.857% */
}
.retailer-card .retailer-info {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-1, 4px);
align-self: stretch;
}
.retailer-card .price-label {
display: flex;
align-items: center;
gap: var(--spacing-2, 8px);
align-self: stretch;
/* text extra large/leading-normal/semibold */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-xlarge-font-size, 20px);
font-style: normal;
font-weight: var(--font-weight-semibold, 600);
line-height: var(--typography-base-sizes-xlarge-line-height, 28px); /* 140% */
}
.retailer-card .expectation {
align-self: stretch;
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-normal, 400);
line-height: var(--typography-base-sizes-small-line-height, 20px); /* 142.857% */
}
/** Product Add to Cart Container */
.add-to-cart-container {
display: flex;
align-items: flex-start;
align-content: flex-start;
gap: 16px;
align-self: stretch;
flex-wrap: wrap;
}
.add-to-cart-container .quantity-container {
display: flex;
align-items: center;
gap: -1px;
}
.add-to-cart-container .quantity-container .quantity-decrease {
display: flex;
width: var(--width-w-9, 36px);
height: var(--height-h-9, 36px);
padding: var(--spacing-2, 8px) var(--spacing-4, 16px);
justify-content: center;
align-items: center;
gap: var(--spacing-2, 8px);
border-radius: var(--border-radius-md, 6px) 0px 0px var(--border-radius-md, 6px);
border: 1px solid var(--base-input, #E4E4E7);
background: var(--base-background, #FAFAFA);
/* shadow/sm */
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
cursor: pointer;
}
.add-to-cart-container .quantity-container .quantity-increase {
display: flex;
width: var(--width-w-9, 36px);
height: var(--height-h-9, 36px);
padding: var(--spacing-2, 8px) var(--spacing-4, 16px);
justify-content: center;
align-items: center;
gap: var(--spacing-2, 8px);
cursor: pointer;
border-radius: 0px var(--border-radius-md, 6px) var(--border-radius-md, 6px) 0px;
border: 1px solid var(--base-input, #E4E4E7);
background: var(--base-background, #FAFAFA);
/* shadow/sm */
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
}
.add-to-cart-container .quantity-container .product-count {
display: flex;
height: var(--height-h-9, 36px);
padding: var(--spacing-2, 8px) var(--spacing-4, 16px);
justify-content: center;
align-items: center;
gap: var(--spacing-2, 8px);
background: var(--base-background, #FAFAFA);
/* shadow/sm */
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
border-top: 1px solid var(--base-input, #E4E4E7);
border-bottom: 1px solid var(--base-input, #E4E4E7);
color: var(--base-foreground, #18181B);
/* text small/leading-normal/medium */
font-family: var(--typography-font-family-font-sans, Poppins);
font-size: var(--typography-base-sizes-small-font-size, 14px);
font-style: normal;
font-weight: var(--font-weight-medium, 500);
line-height: var(--typography-base-sizes-small-line-height, 20px); /* 142.857% */
}
.add-to-cart-container .add-to-cart-button {
display: flex;
height: var(--height-h-9, 36px);
min-width: 182px;
padding: var(--spacing-2, 8px) var(--spacing-4, 16px);
justify-content: center;
align-items: center;
gap: var(--spacing-2, 8px);
flex: 1 0 0;
border-radiu