https://lineageprovisions.com/cdn/shop/t/134/assets/skio-add-to-su…?v=149746686293585459321783538328
1import{LitElement,css,html}from"https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js";class SkioAddToSubscription extends LitElement{static styles=css` 2 :host { 3 --color-primary:#111111; 4 --color-accent: #fff; 5 --color-bg: #f7f2ee; 6 --color-card-bg: #f5f5f5; 7 --color-border: #e0e0e0; 8 --color-text: #333; 9 --color-muted: #666; 10 --color-strikethrough: #999; 11 position: relative; 12 display: block; 13 font-family: inherit; 14 font-size: 16px; 15 } 16 .callout { 17 background: var(--color-card-bg); 18 border-radius: 8px; 19 padding: 24px; 20 margin-bottom: 24px; 21 border: 1px solid var(--color-border); 22 position: relative; 23 } 24 .callout h3 { 25 margin: 0; 26 font-size: 1em; 27 font-weight: 700; 28 color: var(--color-text); 29 } 30 .callout small { 31 display: block; 32 margin-bottom: 16px; 33 } 34 .callout p { 35 margin: 0 0 16px 0; 36 color: var(--color-muted); 37 font-size: 0.9em; 38 } 39 .add-to-subscription-button { 40 background: var(--color-primary); 41 color: var(--color-accent); 42 border: none; 43 border-radius: 4px; 44 padding: 12px 0; 45 width: 100%; 46 font-size: 0.9em; 47 font-weight: 700; 48 cursor: pointer; 49 transition: background 0.2s; 50 text-transform: uppercase; 51 } 52 .add-to-subscription-button:hover { 53 background: color-mix(in srgb, var(--color-primary) 80%, black); 54 } 55 dialog { 56 position: fixed; 57 top: 50%; 58 left: 50%; 59 transform: translate(-50%, -50%); 60 margin: 0; 61 z-index: 2147483647; 62 background: var(--color-accent); 63 border-radius: 12px; 64 border: none; 65 width: 100%; 66 max-width: 500px; 67 padding: 0; 68 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); 69 } 70 dialog::backdrop { 71 background: rgba(0, 0, 0, 0.5); 72 } 73 .modal-content { 74 position: relative; 75 } 76 .modal-header { 77 display: flex; 78 justify-content: space-between; 79 align-items: center; 80 gap: 16px; 81 padding: 16px 32px 16px 32px; 82 border-bottom: 1px solid var(--color-border); 83 } 84 .modal-title { 85 font-size: 1.1em; 86 font-weight: 700; 87 color: var(--color-text); 88 margin: 0; 89 line-height: 1.3; 90 } 91 .modal-title .date-badge { 92 border: 2px solid var(--color-primary); 93 border-radius: 4px; 94 padding: 2px 8px; 95 font-size: 0.8em; 96 color: var(--color-primary); 97 font-weight: 500; 98 background: var(--color-accent); 99 display: inline-block; 100 } 101 .close-button { 102 background: none; 103 border: none; 104 font-size: 2em; 105 color: var(--color-muted); 106 cursor: pointer; 107 padding: 0; 108 margin: 0; 109 width: 2em; 110 height: 2em; 111 display: flex; 112 align-items: center; 113 justify-content: center; 114 line-height: 1; 115 } 116 .close-button:hover { 117 color: var(--color-text); 118 } 119 form { 120 padding: 24px 32px; 121 display: flex; 122 flex-direction: column; 123 gap: 16px; 124 } 125 .option-label { 126 display: flex; 127 align-items: center; 128 gap: 8px; 129 background: var(--color-card-bg); 130 border: 1px solid var(--color-border); 131 border-radius: 8px; 132 padding: 12px; 133 cursor: pointer; 134 transition: border 0.2s, background 0.2s; 135 position: relative; 136 } 137 .option-label:hover { 138 border-color: var(--color-primary); 139 } 140 .option-label.selected { 141 border: 2px solid var(--color-primary); 142 background: var(--color-accent); 143 } 144 .option-label input[type="radio"] { 145 accent-color: var(--color-primary); 146 width: 1.5em; 147 height: 1.5em; 148 flex-shrink: 0; 149 } 150 .option-content { 151 display: flex; 152 align-items: flex-start; 153 flex-direction: column; 154 gap: 4px; 155 } 156 .option-title { 157 font-size: 1em; 158 font-weight: 700; 159 color: var(--color-text); 160 } 161 .delivery-policy { 162 font-size: 0.8em; 163 color: var(--color-muted); 164 font-weight: 500; 165 text-transform: capitalize; 166 } 167 .discount-badge { 168 background: var(--color-primary); 169 color: var(--color-accent); 170 font-weight: 700; 171 border-radius: 4px; 172 padding: 4px 12px; 173 font-size: 0.8em; 174 text-transform: uppercase; 175 letter-spacing: 0.5px; 176 } 177 .price-container { 178 margin-left: auto; 179 text-align: right; 180 display: flex; 181 flex-direction: column; 182 align-items: flex-end; 183 gap: 2px; 184 } 185 .original-price { 186 text-decoration: line-through; 187 color: var(--color-strikethrough); 188 font-size: 0.9em; 189 font-weight: 500; 190 } 191 .discounted-price { 192 color: var(--color-primary); 193 font-size: 1em; 194 font-weight: 700; 195 }
196 .modal-policy { 197 font-size: 0.8em; 198 color: var(--color-muted); 199 padding: 0px 32px 16px 32px; 200 } 201 .modal-footer { 202 display: flex; 203 gap: 16px; 204 padding: 24px 32px 32px 32px; 205 border-top: 1px solid var(--color-border); 206 } 207 .modal-footer button { 208 flex: 1; 209 padding: 16px 24px; 210 border-radius: 4px; 211 font-size: 0.9em; 212 font-weight: 700; 213 border: none; 214 cursor: pointer; 215 transition: all 0.2s; 216 text-transform: uppercase; 217 letter-spacing: 0.5px; 218 } 219 .primary-button { 220 background: var(--color-primary); 221 color: var(--color-accent); 222 } 223 .primary-button:hover { 224 background: color-mix(in srgb, var(--color-primary) 80%, black); 225 } 226 .secondary-button { 227 background: transparent; 228 color: var(--color-text); 229 border: 2px solid var(--color-border); 230 } 231 .secondary-button:hover { 232 background: var(--color-card-bg); 233 border-color: var(--color-primary); 234 } 235 .success-screen { 236 background: #e8f5e8; 237 border: 2px solid #4caf50; 238 border-radius: 8px; 239 padding: 24px; 240 margin-bottom: 24px; 241 text-align: left; 242 } 243 .success-title { 244 font-size: 1em; 245 font-weight: 700; 246 color: var(--color-text); 247 margin: 0 0 16px 0; 248 text-transform: uppercase; 249 letter-spacing: 0.5px; 250 } 251 .success-message { 252 font-size: 0.9em; 253 color: var(--color-text); 254 margin: 0 0 8px 0; 255 line-height: 1.4; 256 } 257 .success-date { 258 font-weight: 700; 259 color: var(--color-text); 260 } 261 .success-link { 262 color: var(--color-primary); 263 text-decoration: none; 264 font-weight: 700; 265 font-size: 0.9em; 266 margin-top: 16px; 267 display: inline-block; 268 transition: color 0.2s; 269 } 270 .success-link:hover { 271 color: color-mix(in srgb, var(--color-primary) 80%, black); 272 text-decoration: underline; 273 } 274 .loading-spinner { 275 display: inline-block; 276 width: 16px; 277 height: 16px; 278 border: 2px solid var(--color-accent); 279 border-radius: 50%; 280 border-top-color: transparent; 281 animation: spin 1s linear infinite; 282 margin-right: 8px; 283 } 284 @keyframes spin { 285 to { transform: rotate(360deg); } 286 } 287 .primary-button:disabled { 288 opacity: 0.6; 289 cursor: not-allowed; 290 } 291 .error-message { 292 background: #fee; 293 border: 1px solid #fcc; 294 border-radius: 4px; 295 padding: 12px; 296 margin-bottom: 16px; 297 color: #c33; 298 font-size: 0.9em; 299 text-align: center; 300 } 301 @media(max-width: 500px) { 302 .modal-footer { 303 flex-direction: column; 304 gap: 8px; 305 padding: 12px 16px 16px 16px 306 } 307 .modal-header { 308 padding: 12px 16px 12px 16px; 309 } 310 form { 311 padding: 12px 16px; 312 } 313 .modal-policy { 314 padding: 0px 16px 16px 16px; 315 } 316 } 317 `;static properties={isLoading:{type:Boolean},isAuthenticated:{type:Boolean},productId:{type:String},product:{type:Object},selectedVariant:{type:Object},upcomingSubscription:{type:Object},eligibleForSubscription:{type:Boolean},eligibleForOneTimeUpsell:{type:Boolean},oneTimeUpsellPercentageOff:{type:Number},selectedOption:{type:String},selectedSellingPlan:{type:Object},showSuccessScreen:{type:Boolean},previouslyAddedProducts:{type:String},isAddingToSubscription:{type:Boolean},addToSubscriptionError:{type:String}};constructor(){super(),this.initializeDefaultState()}async connectedCallback(){super.connectedCallback(),await this.initialize(),this.isLoading=!1,this.bindFormEvents()}initializeDefaultState(){this.isLoading=!1,this.eligibleForSubscription=!1,this.eligibleForOneTimeUpsell=!1,this.selectedOption="subscription",this.showSuccessScreen=!1,this.isAddingToSubscription=!1,this.addToSubscriptionError=null}async initialize(){if(this.checkAuthentication(),!this.isAuthenticated)return;const subscription=await this.fetchUpcomingSubscription();this.upcomingSubscription=subscription;const product=await this.fetchProduct();this.product=product}checkAuthentication(){this.isAuthenticated=!!window.Skio?.storefrontUser?.idToken}async fetchUpcomingSubscription(){const cachedSubscription=localStorage.getItem("upcoming-subscription");if(cachedSubscription)return JSON.parse(cachedSubscription);const query=` 318 query storefrontGetUpcomingSubscription($storefrontUserId: uuid) { 319 Subscriptions(where: { 320 status: { 321 _eq: "ACTIVE" 322 } 323 storefrontUserId: { 324 _eq: $storefrontUserId 325 } 326 }, order_by: { 327 nextBillingDate: asc 328 }, limit: 1) { 329 id 330 status 331 nextBillingDate
332 SubscriptionLines( 333 where: { removedAt: { _is_null: true } } 334 limit: 1 335 order_by: { createdAt: asc } 336 ) { 337 OriginalSellingPlan { 338 PricingPolicies { 339 percentageOff 340 fixedPrice 341 } 342 } 343 } 344 DeliveryPolicy { 345 interval 346 intervalCount 347 } 348 BillingPolicy { 349 interval 350 intervalCount 351 } 352 } 353 } 354 `,variables={storefrontUserId:window.Skio?.storefrontUser?.id};try{const subscription=(await window.Skio.graphQLRequest({query,variables})).data.Subscriptions[0];return localStorage.setItem("upcoming-subscription",JSON.stringify(subscription)),this.isPrepaidSubscription(subscription)?null:subscription}catch(error){return console.error("Error fetching upcoming subscription:",error),null}}async fetchProduct(){const query=` 355 query Products($where: Product_bool_exp) { 356 Products(where: $where) { 357 title 358 canUpsell 359 oneTimeUpsellPercentageOff 360 ProductVariants { 361 enableOneTimeUpsell 362 platformId 363 price 364 compareAtPrice 365 SellingPlanGroupResources(where: {SellingPlanGroup: { name: { _eq: "Subscription" } }}) { 366 SellingPlanGroup { 367 customName 368 SellingPlans { 369 id 370 platformId 371 PricingPolicies { 372 percentageOff 373 fixedPrice 374 } 375 DeliveryPolicy { 376 interval 377 intervalCount 378 } 379 } 380 } 381 } 382 } 383 } 384 } 385 `,variables={where:{platformId:{_eq:`gid://shopify/Product/${this.productId}`}}};try{const response=await window.Skio.graphQLRequest({query,variables});return console.log("Product data:",response.data.Products[0]),response.data.Products[0]}catch(error){return console.error("Error fetching product:",error),null}}isPrepaidSubscription(subscription){const{DeliveryPolicy,BillingPolicy}=subscription;return DeliveryPolicy.interval!==BillingPolicy.interval||DeliveryPolicy.intervalCount!==BillingPolicy.intervalCount}formatDate(timestamp){const date=new Date(timestamp);return`${date.toLocaleString("en-US",{month:"short"})} ${date.getDate()}, ${date.getFullYear()}`}formatMoney(amount){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD"}).format(amount)}bindFormEvents(){const variantInput=document.querySelector("[name=id]");variantInput&&(this.updateSelectedVariant(variantInput.value),this.checkVariantEligibility(this.selectedVariant),this.checkPreviouslyAddedProducts(),variantInput.addEventListener("change",e=>{this.updateSelectedVariant(e.target.value),this.checkVariantEligibility(this.selectedVariant),this.checkPreviouslyAddedProducts()}))}checkPreviouslyAddedProducts(){(window.sessionStorage.getItem("skio_upsell_added_products")||"").includes(this.selectedVariant.platformId)?this.showSuccessScreen=!0:this.showSuccessScreen=!1}updateSelectedVariant(variantId){this.selectedVariant=this.product.ProductVariants.find(variant=>variant.platformId.includes(variantId))}checkVariantEligibility(variant){variant&&(this.eligibleForOneTimeUpsell=variant.enableOneTimeUpsell&&this.product.canUpsell,this.eligibleForSubscription=variant.SellingPlanGroupResources.length>0,this.eligibleForOneTimeUpsell||(this.selectedOption="subscription"))}getSubscriptionPrice(){const sellingPlanGroup=this.findMatchingSellingPlanGroup(),sellingPlan=this.findMatchingSellingPlan(sellingPlanGroup);if(!sellingPlan)return{discount:null,price:this.formatMoney(this.selectedVariant.price)};this.selectedSellingPlan=sellingPlan;const pricingPolicy=sellingPlan.PricingPolicies[0];let discount,price;return pricingPolicy.percentageOff?(discount=`${pricingPolicy.percentageOff}%`,price=this.selectedVariant.price*(1-pricingPolicy.percentageOff/100)):pricingPolicy.fixedPrice&&(discount=this.formatMoney(pricingPolicy.fixedPrice),price=this.selectedVariant.price-pricingPolicy.fixedPrice),{discount,price:this.formatMoney(price)}}getOneTimePrice(){if(!this.product.oneTimeUpsellPercentageOff)return{discount:null,price:this.formatMoney(this.selectedVariant.price)};const discount=`${this.product.oneTimeUpsellPercentageOff*100}%`,price=this.selectedVariant.price*(1-this.product.oneTimeUpsellPercentageOff);return{discount,price:this.formatMoney(price)}}
385findMatchingSellingPlanGroup(){return console.log(this.selectedVariant),this.selectedVariant.SellingPlanGroupResources.find(group=>group.SellingPlanGroup.SellingPlans.some(plan=>this.isMatchingDeliveryPolicy(plan.DeliveryPolicy)))}findMatchingSellingPlan(sellingPlanGroup){return sellingPlanGroup?sellingPlanGroup.SellingPlanGroup.SellingPlans.find(plan=>this.isMatchingDeliveryPolicy(plan.DeliveryPolicy)):null}isMatchingDeliveryPolicy(deliveryPolicy){return deliveryPolicy.interval===this.upcomingSubscription.DeliveryPolicy.interval&&deliveryPolicy.intervalCount===this.upcomingSubscription.DeliveryPolicy.intervalCount}async addToSubscription(){this.addToSubscriptionError=null,this.isAddingToSubscription=!0;const productVariantPlatformId=this.selectedVariant.platformId,sellingPlanPlatformId=this.selectedSellingPlan.platformId,isOneTimeUpsell=this.selectedOption==="one-time",subscriptionId=this.upcomingSubscription.id,mutation=` 386 mutation storefrontEditSubscriptionLines($input: EditSubscriptionLinesInput!) { 387 editSubscriptionLines(input: $input) { 388 subscriptionId 389 } 390 } 391 `,variables={input:{subscriptionId,subscriptionLinesToCreate:[{customAttributes:[{key:"add-to-subscription-component",value:"true"}],productVariantPlatformId,quantity:1,isOneTimeUpsell}]}};console.log("Adding to subscription:",{mutation,variables});try{const response=await window.Skio.graphQLRequest({query:mutation,variables});this.closeModal();let previouslyAddedProducts=window.sessionStorage.getItem("skio_upsell_added_products")||"";return window.sessionStorage.setItem("skio_upsell_added_products",`${previouslyAddedProducts}, ${this.selectedVariant.platformId}`),this.showSuccessScreen=!0,response}catch(error){return console.error("Error adding to subscription:",error),this.addToSubscriptionError="Error adding to subscription. Please try again.",error}finally{this.isAddingToSubscription=!1}}openModal(){const dialog=this.shadowRoot.querySelector("dialog");dialog&&(dialog.showModal(),this.setupModalEventListeners(dialog))}closeModal(){const dialog=this.shadowRoot.querySelector("dialog");dialog&&(dialog.close(),this.removeModalEventListeners(dialog))}setupModalEventListeners(dialog){dialog.addEventListener("click",this.handleBackdropClick.bind(this))}removeModalEventListeners(dialog){dialog.removeEventListener("click",this.handleBackdropClick.bind(this))}handleBackdropClick(e){e.target.tagName==="DIALOG"&&this.closeModal()}renderModal(){const oneTimePrice=this.getOneTimePrice(),subscriptionPrice=this.getSubscriptionPrice();return html` 392 <dialog> 393 <div class="modal-content"> 394 <div class="modal-header"> 395 <h2 class="modal-title"> 396 One Click Add to Upcoming Subscription 397 <span class="date-badge" 398 >${this.formatDate(this.upcomingSubscription.nextBillingDate)}</span 399 > 400 </h2> 401 <button class="close-button" @click=${this.closeModal}> 402 × 403 </button> 404 </div> 405 <form> 406 ${this.addToSubscriptionError?html`<div class="error-message">${this.addToSubscriptionError}</div>`:null} 407 ${this.eligibleForOneTimeUpsell?this.renderOneTimeOption(oneTimePrice):null} 408 ${this.eligibleForSubscription?this.renderSubscriptionOption(subscriptionPrice):null} 409 </form> 410 411 <div class="modal-policy"> 412 ${this.selectedOption==="subscription"?html`<small>By clicking Add to Order, youâre adding this item to your subscription. Youâll be charged automatically at the listed price and frequency until canceled. All cancellations are subject to the <a href="/policies/subscription-policy">cancellation policy</a>.</small>`:html`<small>By selecting Add to Order, you agree to a one-time charge for this item, which will be added to your next subscription shipment. This item will not renew or be billed again. All cancellations are subject to the <a href="/policies/subscription-policy">cancellation policy</a>.</small>`} 413 </div> 414 415 <div class="modal-footer"> 416 <button 417 class="primary-button" 418 @click=${this.addToSubscription} 419 ?disabled=${this.isAddingToSubscription} 420 > 421 ${this.isAddingToSubscription?html`<span class="loading-spinner"></span>`:"ADD TO ORDER"} 422 </button> 423 <button 424 class="secondary-button" 425 @click=${this.closeModal} 426 type="button" 427 ?disabled=${this.isAddingToSubscription} 428 > 429 Cancel 430 </button> 431 </div> 432 </div> 433 </dialog> 434 `}renderOneTimeOption(oneTimePrice){const isSelected=this.selectedOption==="one-time";return html` 435 <label 436 class="option-label ${isSelected?"selected":""}" 437 @click=${()=>this.selectedOption="one-time"} 438 > 439 <input 440 type="radio" 441 name="addType" 442 value="one-time" 443 .checked=${isSelected} 444 @change=${()=>this.selectedOption="one-time"} 445 /> 446 <div class="option-content">
447 <span class="option-title">Get One-Time</span> 448 </div> 449 ${oneTimePrice.discount?html`<span class="discount-badge text-2xl" 450 >Save ${oneTimePrice.discount}</span 451 >`:null} 452 <div class="price-container"> 453 ${this.getOriginalPrice()===oneTimePrice.price?null:html`<span class="original-price"> ${this.getOriginalPrice()} </span>`} 454 <span class="discounted-price">${oneTimePrice.price}</span> 455 </div> 456 </label> 457 `}renderSubscriptionOption(subscriptionPrice){const isSelected=this.selectedOption==="subscription";return html` 458 <label 459 class="option-label ${isSelected?"selected":""}" 460 @click=${()=>this.selectedOption="subscription"} 461 > 462 <input 463 type="radio" 464 name="addType" 465 value="subscription" 466 .checked=${isSelected} 467 @change=${()=>this.selectedOption="subscription"} 468 /> 469 <div class="option-content"> 470 <span class="option-title">Subscribe</span> 471 <div class="delivery-policy"> 472 Every ${this.upcomingSubscription.DeliveryPolicy.intervalCount} 473 ${this.upcomingSubscription.DeliveryPolicy.interval.toLowerCase()} 474 </div> 475 </div> 476 ${subscriptionPrice.discount?html`<span class="discount-badge" 477 >Save ${subscriptionPrice.discount}</span 478 >`:null} 479 <div class="price-container"> 480 ${this.getOriginalPrice()===subscriptionPrice.price?null:html`<span class="original-price"> ${this.getOriginalPrice()} </span>`} 481 <span class="discounted-price">${subscriptionPrice.price}</span> 482 </div> 483 </label> 484 `}getOriginalPrice(){return this.selectedVariant.compareAtPrice?this.formatMoney(this.selectedVariant.compareAtPrice):this.formatMoney(this.selectedVariant.price)}renderSuccessScreen(){return html` 485 <div class="success-screen"> 486 <h2 class="success-title">Added to Your Next Order</h2> 487 <p class="success-message"> 488 This item will be included in your next subscription order shipment on 489 <span class="success-date" 490 >${this.formatDate(this.upcomingSubscription.nextBillingDate)}</span 491 > 492 </p> 493 <a href="/a/account/subscriptions" class="success-link"> 494 View and manage my subscriptions -> 495 </a> 496 </div> 497 `}render(){return this.shouldRender()?this.showSuccessScreen?this.renderSuccessScreen():html` 498 <div class="callout"> 499 <h3>One Click Add to Upcoming Subscription</h3> 500 <small>You can easily add this item to your upcoming subscription order and save 10%</small> 501 <p> 502 Next order on: 503 <b>${this.formatDate(this.upcomingSubscription.nextBillingDate)}</b> 504 </p> 505 <button 506 class="add-to-subscription-button" 507 @click=${()=>this.openModal()} 508 > 509 Add To Next Order and Save 510 </button> 511 </div> 512 ${this.renderModal()} 513 `:null}shouldRender(){return this.isAuthenticated&&this.upcomingSubscription&&this.eligibleForSubscription&&this.selectedVariant}}customElements.define("skio-add-to-subscription",SkioAddToSubscription); 514//# sourceMappingURL=/cdn/shop/t/134/assets/skio-add-to-subscription.js.map?v=149746686293585459321783538328
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.