UNPKG

@liquidcommerceteam/elements-sdk

Version:

LiquidCommerce Elements SDK

1,817 lines (1,606 loc) 373 kB
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