PageSourceSearch

https://nuts.com/cdn/shop/t/3/assets/skio-add-to-subscription.js?v=152525021207288618591786133901

js nuts.com collected 2026-09-24 17:15:17 UTC 44,448 bytes, 831 lines download raw bytes

1import{css,html,LitElement}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: #111827;
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      --color-badge: #ffcf42;
12      position: relative;
13      display: block;
14      font-family: inherit;
15      font-size: 16px;
16    }
17    .callout {
18      background: var(--color-card-bg);
19      border-radius: 8px;
20      padding: 24px;
21      margin-bottom: 24px;
22      border: 1px solid var(--color-border);
23      position: relative;
24    }
25    .callout h3 {
26      margin: 0;
27      font-size: 1em;
28      font-weight: 700;
29      color: var(--color-text);
30    }
31    .callout small {
32      display: block;
33      margin-bottom: 16px;
34    }
35    .callout p {
36      margin: 0 0 16px 0;
37      color: var(--color-muted);
38      font-size: 0.9em;
39    }
40    .add-to-subscription-button,
41    .primary-button,
42    .secondary-button {
43      --tw-bg-opacity: 1;
44      background-color: rgb(51 51 51 / var(--tw-bg-opacity));
45      --tw-text-opacity: 1;
46      color: rgb(255 255 255 / var(--tw-text-opacity));
47      border-radius: 9999px;
48      width: 100%;
49      font-size: 1rem;
50      line-height: 1.5rem;
51      font-weight: 600;
52      cursor: pointer;
53      padding-top: 0.75rem;
54      padding-bottom: 0.75rem;
55      padding-left: 2.5rem;
56      padding-right: 2.5rem;
57      font-family:
58        Proxima Nova,
59        sans-serif;
60      transition-property: color, background-color, border-color,
61        text-decoration-color, fill, stroke;
62      transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
63      transition-duration: 0.15s;
64    }
65    .add-to-subscription-button:hover,
66    .primary-button:hover,
67    .secondary-button:hover {
68      --tw-bg-opacity: 1;
69      background-color: rgb(82 82 82 / var(--tw-bg-opacity));
70      transition-property: color, background-color, border-color,
71        text-decoration-color, fill, stroke;
72      transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
73      transition-duration: 0.15s;
74    }
75    dialog {
76      position: fixed;
77      top: 50%;
78      left: 50%;
79      transform: translate(-50%, -50%);
80      margin: 0;
81      z-index: 2147483647;
82      background: var(--color-accent);
83      border-radius: 12px;
84      border: none;
85      width: 100%;
86      max-width: 500px;
87      padding: 0;
88      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
89      overflow: hidden;
90      box-sizing: border-box;
91    }
92    dialog:not([open]) {
93      display: none;
94    }
95    dialog::backdrop {
96      background: rgba(0, 0, 0, 0.5);
97    }
98    .modal-content {
99      position: relative;
100      overflow-x: hidden;
101      max-width: 100%;
102    }
103    .modal-header {
104      display: flex;
105      justify-content: space-between;
106      align-items: center;
107      gap: 16px;
108      padding: 16px 32px 16px 32px;
109      border-bottom: 1px solid var(--color-border);
110    }
111    .modal-title {
112      font-size: 1.1em;
113      font-weight: 700;
114      color: var(--color-text);
115      margin: 0;
116      line-height: 1.3;
117    }
118    .modal-title .date-badge {
119      border: 2px solid var(--color-primary);
120      border-radius: 4px;
121      padding: 2px 8px;
122      font-size: 0.8em;
123      color: var(--color-primary);
124      font-weight: 500;
125      background: var(--color-accent);
126      display: inline-block;
127    }
128    .close-button {
129      background: none;
130      border: none;
131      font-size: 2em;
132      color: var(--color-muted);
133      cursor: pointer;
134      padding: 0;
135      margin: 0;
136      width: 2em;
137      height: 2em;
138      display: flex;
139      align-items: center;
140      justify-content: center;
141      line-height: 1;
142    }
143    .close-button:hover {
144      color: var(--color-text);
145    }
146    .modal-body {
147      padding: 16px 32px;
148      overflow-x: hidden;
149    }
150    .modal-body p {
151      margin: 0;
152      color: var(--color-muted);
153      font-size: 0.9em;
154      line-height: 1.5;
155      max-width: 100%;
156      white-space: normal;
157      overflow-wrap: break-word;
158    }
159    form {
160      padding: 24px 32px;
161      display: flex;
162      flex-direction: column;
163      gap: 16px;
164    }
165    .option-label {
166      display: flex;
167      align-items: center;
168      gap: 8px;
169      background: var(--color-card-bg);
170      border: 1px solid var(--color-border);
171      border-radius: 8px;
172      padding: 12px;
173      cursor: pointer;
174      transition:
175        border 0.2s,
176        background 0.2s;
177      position: relative;
178    }
179    .option-label:hover {
180      border-color: var(--color-primary);
181    }
182    .option-label.selected {
183      border: 2px solid var(--color-primary);
184      background: var(--color-accent);
185    }
186    .option-label input[type='radio'] {
187      accent-color: var(--color-primary);
188      width: 1.5em;
189      height: 1.5em;
190      flex-shrink: 0;
191    }
192    .option-content {
193      display: flex;
194      align-items: flex-start;
195      flex-direction: column;
196      gap: 4px;
197    }
198    .option-title {
199      font-size: 1em;
200      font-weight: 700;
201      color: var(--color-text);
202    }
203    .delivery-policy {
204      font-size: 0.8em;
205      color: var(--color-muted);
206      font-weight: 500;
207      text-transform: capitalize;
208    }
209    .discount-badge {
210      background: var(--color-badge);
211      color: var(--color-text);
212      font-weight: 600;
213      border-radius: 50px;
214      padding: 4px 12px;
215      font-size: 0.8em;
216      text-transform: uppercase;
217      letter-spacing: 0.5px;
218      line-height: normal;
219    }
220    .price-container {
221      margin-left: auto;
222      text-align: right;
223      display: flex;
224      flex-direction: column;
225      align-items: flex-end;
226      gap: 2px;
227    }
228    .original-price {
229      text-decoration: line-through;
230      color: var(--color-strikethrough);
231      font-size: 0.9em;
232      font-weight: 500;
233    }
234    .discounted-price {
235      color: var(--color-primary);
236      font-size: 1em;
237      font-weight: 700;
238    }
239    .modal-policy {
240      font-size: 0.8em;
241      color: var(--color-muted);
242      padding: 0px 32px 16px 32px;
243      white-space: initial;
244    }
245    .modal-footer {
246      display: flex;
247      gap: 16px;
248      padding: 24px 32px 32px 32px;
249      border-top: 1px solid var(--color-border);
250    }
251    .secondary-button {
252      background: transparent;
253      color: var(--color-text);
254      border: 2px solid var(--color-border);
255    }
256    .secondary-button:hover {
257      background: var(--color-card-bg);
258      border-color: var(--color-primary);
259    }
260    .success-screen {
261      background: #e8f5e8;
262      border: 2px solid #4caf50;
263      border-radius: 8px;
264      padding: 24px;
265      margin-bottom: 24px;
266      text-align: left;
267    }
268    .success-title {
269      font-size: 1em;
270      font-weight: 700;
271      color: var(--color-text);
272      margin: 0 0 16px 0;
273      text-transform: uppercase;
274      letter-spacing: 0.5px;
275    }
276    .success-message {
277      font-size: 0.9em;
278      color: var(--color-text);
279      margin: 0 0 8px 0;
280      line-height: 1.4;
281    }
282    .success-date {
283      font-weight: 700;
284      color: var(--color-text);
285    }
286    .success-link {
287      color: var(--color-primary);
288      text-decoration: none;
289      font-weight: 700;
290      font-size: 0.9em;
291      margin-top: 16px;
292      display: inline-block;
293      transition: color 0.2s;
294    }
295    .success-link:hover {
296      color: color-mix(in srgb, var(--color-primary) 80%, black);
297      text-decoration: underline;
298    }
299    .sign-in-callout p {
300      margin: 0;
301    }
302    .sign-in-link {
303      color: var(--color-primary);
304      font-weight: 700;
305      text-decoration: underline;
306    }
307    .sign-in-link:hover {
308      color: color-mix(in srgb, var(--color-primary) 80%, black);
309    }
310    .loading-spinner {
311      display: inline-block;
312      width: 16px;
313      height: 16px;
314      border: 2px solid var(--color-accent);
315      border-radius: 50%;
316      border-top-color: transparent;
317      animation: spin 1s linear infinite;
318      margin-right: 8px;
319    }
320    @keyframes spin {
321      to {
322        transform: rotate(360deg);
323      }
324    }
325    .primary-button:disabled {
326      opacity: 0.6;
327      cursor: not-allowed;
328    }
329    .error-message {
330      background: #fee;
331      border: 1px solid #fcc;
332      border-radius: 4px;
333      padding: 12px;
334      margin-bottom: 16px;
335      color: #c33;
336      font-size: 0.9em;
337      text-align: center;
338    }
339    /* Debug / setup mode — matches plan picker's visual language */
340    .setup-mode {
341      padding: 16px;
342      border: 5px solid transparent;
343      border-image: repeating-linear-gradient(
344          -55deg,
345          #000 0,
346          #000 20px,
347          #ffb101 20px,
348          #ffb101 40px
349        )
350        10;
351      margin-bottom: 16px;
352    }
353
354    .setup-mode-label {
355      font-size: 0.75em;
356      font-weight: 700;
357      text-transform: uppercase;
358      letter-spacing: 1px;
359      color: #000;
360      background: #ffb101;
361      padding: 2px 8px;
362      margin-bottom: 12px;
363      display: inline-block;
364    }
365
366    .setup-mode-note {
367      font-size: 0.85em;
368      color: #333;
369      margin: 0 0 12px 0;
370    }
371
372    .debug-mode-toggle {
373      display: flex;
374      gap: 8px;
375      margin-bottom: 16px;
376    }
377
378    .debug-mode-toggle button {
379      font-size: 0.8em;
380      font-weight: 700;
381      padding: 4px 12px;
382      border: 2px solid #000;
383      background: transparent;
384      cursor: pointer;
385      text-transform: uppercase;
386      letter-spacing: 0.5px;
387    }
388
389    .debug-mode-toggle button.active {
390      background: #000;
391      color: #fff;
392    }
393
394    .debug-modal-preview {
395      border: 1px solid var(--skio-group-border-color, #e0e0e0);
396      border-radius: 0;
397      max-width: 500px;
398      background: var(--skio-group-background-color, #fff);
399      margin-top: 8px;
400      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
401      overflow: hidden;
402    }
403
404    @media (max-width: 500px) {
405      .modal-footer {
406        flex-direction: column;
407        gap: 8px;
408        padding: 12px 16px 16px 16px;
409      }
410      .modal-header {
411        padding: 12px 16px 12px 16px;
412      }
413      .modal-body {
414        padding: 12px 16px;
415      }
416      form {
417        padding: 12px 16px;
418      }
419      .modal-policy {
420        padding: 0px 16px 16px 16px;
421      }
422    }
423  `;static properties={isLoading:{type:Boolean},isAuthenticated:{type:Boolean},productId:{type:String},variantId:{type:String,attribute:"variant-id"},product:{type:Object},selectedVariant:{type:Object},upcomingSubscription:{type:Object},inactiveSubscription:{type:Object},failedSubscription:{type:Object},eligibleForSubscription:{type:Boolean},eligibleForOneTimeUpsell:{type:Boolean},selectedOption:{type:String},selectedSellingPlan:{type:Object},showSuccessScreen:{type:Boolean},isSubmitting:{type:Boolean},submitError:{type:String},debug:{type:Boolean},options:{type:String},debugPreviewMode:{type:String},ready:{type:Boolean},successMode:{type:String},successWasPaused:{type:Boolean}};get renderState(){return this.showSuccessScreen?"success":this.ready&&!this.isAuthenticated?"sign-in":this.mode}get signInUrl(){return SkioAddToSubscription.buildSignInUrl(this._options)}get signInPrompt(){return SkioAddToSubscription.getPrompt("sign-in",this._options)}get paymentFailedPrompt(){return SkioAddToSubscription.getPrompt("failed",this._options)}isVariantInUpcomingSubscription(variant){const variantId=variant?.platformId,lines=this.upcomingSubscription?.SubscriptionLines;return!variantId||!Array.isArray(lines)||!lines.length?!1:lines.some(line=>String(line?.ProductVariant?.platformId)===String(variantId))}get _options(){try{return JSON.parse(this.options||"{}")}catch{return{}}}get mode(){return this.upcomingSubscription&&this.eligibleForSubscription&&this.selectedVariant?"add":this.failedSubscription?"failed":this.inactiveSubscription?"reactivate":null}constructor(){super(),this.isLoading=!1,this.isAuthenticated=!1,this.failedSubscription=null,this.eligibleForSubscription=!1,this.eligibleForOneTimeUpsell=!1,this.selectedOption="subscription",this.showSuccessScreen=!1,this.isSubmitting=!1,this.submitError=null,this.debugPreviewMode="add",this.debug=!1,this.isPreviewMode=!1,this.ready=!1,this.successMode="added",this.successWasPaused=!1,this._handleActionBackdropClick=e=>{e.target.tagName==="DIALOG"&&this.closeModal()}}notifySubscriptionContext(){const detail={renderState:this.renderState,upcomingSubscription:this.upcomingSubscription??null};return this.dispatchEvent(new CustomEvent("skio-component:context-updated",{detail})),detail}async connectedCallback(){if(super.connectedCallback(),this.isPreviewMode=!!window?.Shopify?.designMode,this.debug=this.debug||this.hasAttribute("debug")||!!window.__SKIO_ADD_TO_SUBSCRIPTION_DEBUG__,this.isPreviewMode)return;await this.initialize(),this.isLoading=!1,this.bindFormEvents(),this.ready=!0;const context=this.notifySubscriptionContext();this.dispatchEvent(new CustomEvent("skio-component:ready",{detail:context}))}loadSDK(){return SkioAddToSubscription.loadSdk()}async initialize(){if(await this.loadSDK(),await SkioAddToSubscription.waitForStorefrontUser(),this.debug&&console.log("[Skio AddToSub] initialize:start",{productId:this.productId}),this.isAuthenticated=!!window.Skio?.storefrontUser?.id,!this.isAuthenticated){this.debug&&console.log("[Skio AddToSub] initialize:guest");return}this.upcomingSubscription=await this.fetchSubscription("ACTIVE"),this.upcomingSubscription?(this.product=await this.fetchProduct(),this.applyInitialVariantSelection(),this.debug&&console.log("[Skio AddToSub] initialize:active-subscription",{subscriptionId:this.upcomingSubscription.id,hasProduct:!!this.product,defaultVariantPlatformId:this.selectedVariant?.platformId??null,eligibleForSubscription:this.eligibleForSubscription})):(this.inactiveSubscription=await this.fetchSubscription(["CANCELLED","PAUSED"]),this.inactiveSubscription||(this.failedSubscription=await this.fetchSubscription("FAILED")),this.debug&&console.log("[Skio AddToSub] initialize:inactive-subscription-check",{failedStatus:this.failedSubscription?.status??null,failedSubscriptionId:this.failedSubscription?.id??null,status:this.inactiveSubscription?.status??null,subscriptionId:this.inactiveSubscription?.id??null}),this.inactiveSubscription&&this.productId&&(this.product=await this.fetchProduct(),this.applyInitialVariantSelection())),this.debug&&console.log("[
423Skio AddToSub] initialize:end",{hasUpcoming:!!this.upcomingSubscription,hasInactive:!!this.inactiveSubscription,mode:this.mode})}async fetchSubscription(status){return SkioAddToSubscription.fetchSubscription(status,{includeLines:status==="ACTIVE"})}async fetchProduct(){const query=`
424    query Products($where: Product_bool_exp) {
425      Products(where: $where) {
426        title
427        canUpsell
428        oneTimeUpsellPercentageOff
429        ProductVariants {
430          enableOneTimeUpsell
431          platformId
432          price
433          compareAtPrice
434          SellingPlanGroupResources {
435            SellingPlanGroup {
436              customName
437              SellingPlans {
438                id
439                platformId
440                PricingPolicies {
441                  percentageOff
442                  fixedPrice
443                }
444                DeliveryPolicy {
445                  interval
446                  intervalCount
447                }
448              }
449            }
450          }
451        }
452      }
453    }
454  `,variables={where:{platformId:{_eq:`gid://shopify/Product/${this.productId}`}}};try{return(await window.Skio.graphQLRequest({query,variables})).data.Products[0]}catch(error){return console.error("Error fetching product:",error),null}}applyInitialVariantSelection(){this.product?.ProductVariants?.length&&(this.variantId?this.updateSelectedVariant(this.variantId):this.selectedVariant||(this.selectedVariant=this.product.ProductVariants[0]),this.checkVariantEligibility(this.selectedVariant))}bindFormEvents(){if(this.variantId){this.applyInitialVariantSelection(),this.successMode="added",this.showSuccessScreen=this.isVariantInUpcomingSubscription(this.selectedVariant),this.dispatchVariantChanged();return}const variantInput=this.getVariantIdInput();if(!variantInput){this.debug&&console.log("[Skio AddToSub] bindFormEvents:no-variant-input",{selectedVariant:this.selectedVariant?.platformId??null}),this.dispatchVariantChanged();return}this.updateSelectedVariant(variantInput.value),this.checkVariantEligibility(this.selectedVariant),this.successMode="added",this.showSuccessScreen=this.isVariantInUpcomingSubscription(this.selectedVariant),this.debug&&console.log("[Skio AddToSub] bindFormEvents:initial-variant",{variantInputValue:variantInput.value,selectedVariant:this.selectedVariant?.platformId??null,eligibleForSubscription:this.eligibleForSubscription}),this.dispatchVariantChanged(),variantInput.addEventListener("change",e=>{this.updateSelectedVariant(e.target.value),this.checkVariantEligibility(this.selectedVariant),this.successMode="added",this.showSuccessScreen=this.isVariantInUpcomingSubscription(this.selectedVariant),this.submitError=null,this.requestUpdate(),this.dispatchVariantChanged(),this.debug&&console.log("[Skio AddToSub] bindFormEvents:variant-change",{variantInputValue:e.target.value,selectedVariant:this.selectedVariant?.platformId??null,eligibleForSubscription:this.eligibleForSubscription})})}dispatchVariantChanged(){this.dispatchEvent(new CustomEvent("skio-component:variant-changed",{detail:{renderState:this.renderState,selectedVariant:this.selectedVariant??null,eligibleForSubscription:this.eligibleForSubscription,upcomingSubscription:this.upcomingSubscription??null}}))}getVariantIdInput(){const buyboxSelect=(this.closest(".js-buybox")||document).querySelector(".js__buybox-variant-select-element")||document.querySelector(".js__buybox-variant-select-element");if(buyboxSelect)return buyboxSelect;const wrapper=this.closest("[data-product-form-wrapper]"),inColumn=wrapper?.querySelector('[data-product-form] input[name="id"]')||wrapper?.querySelector('input[name="id"]');return inColumn||document.querySelector('[data-product-form] input[name="id"]')||document.querySelector('form[action*="/cart/add"] input[name="id"]')||document.querySelector("[name=id]")}updateSelectedVariant(variantId){const id=variantId!=null?String(variantId).trim():"",variants=this.product?.ProductVariants;if(!variants?.length){this.selectedVariant=void 0;return}if(!id){this.selectedVariant=variants[0];return}this.selectedVariant=variants.find(v=>v.platformId?.includes(id)||v.platformId?.endsWith(id)||String(v.platformId)===id)||variants[0]}checkVariantEligibility(variant){if(!variant)return;const groups=variant.SellingPlanGroupResources,groupCount=Array.isArray(groups)?groups.length:0;
454this.eligibleForOneTimeUpsell=!!(variant.enableOneTimeUpsell&&this.product?.canUpsell),this.eligibleForSubscription=groupCount>0,this.eligibleForOneTimeUpsell||(this.selectedOption="subscription")}isPrepaidSubscription(subscription){return SkioAddToSubscription.isPrepaidSubscription(subscription)}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)}applyPercentageOff(amount,percentageOff){const cents=Math.round(amount*100);return Math.round(cents*(100-percentageOff)/100)/100}getOriginalPrice(){return this.selectedVariant.compareAtPrice?this.formatMoney(this.selectedVariant.compareAtPrice):this.formatMoney(this.selectedVariant.price)}cadenceInDays(deliveryPolicy){const days={DAY:1,WEEK:7,MONTH:30,YEAR:365}[deliveryPolicy?.interval];return days?days*deliveryPolicy.intervalCount:null}getSubscriptionPrice(){const subscriptionPolicy=this.upcomingSubscription.DeliveryPolicy,subscriptionCadence
454=this.cadenceInDays(subscriptionPolicy),plans=this.selectedVariant.SellingPlanGroupResources.flatMap(resource=>resource.SellingPlanGroup.SellingPlans),sellingPlan=plans.find(plan=>plan.DeliveryPolicy.interval===subscriptionPolicy.interval&&plan.DeliveryPolicy.intervalCount===subscriptionPolicy.intervalCount)||plans.find(plan=>subscriptionCadence!==null&&this.cadenceInDays(plan.DeliveryPolicy)===subscriptionCadence);if(!sellingPlan)return{discount:null,price:this.formatMoney(this.selectedVariant.price)};this.selectedSellingPlan=sellingPlan;const{percentageOff,fixedPrice}=sellingPlan.PricingPolicies[0];return percentageOff?{discount:`${percentageOff}%`,price:this.formatMoney(this.applyPercentageOff(this.selectedVariant.price,percentageOff))}:{discount:this.formatMoney(fixedPrice),price:this.formatMoney(this.selectedVariant.price-fixedPrice)}}getOneTimePrice(){const pct=this.product.oneTimeUpsellPercentageOff;if(!pct)return{discount:null,price:this.formatMoney(this.selectedVariant.price)};const percentageOff=pct*100;return{discount:`${percentageOff}%`,price:this.formatMoney(this.applyPercentageOff(this.selectedVariant.price,percentageOff))}}openModal(){const dialog=this.shadowRoot.querySelector("dialog.action-dialog");dialog&&(dialog.showModal(),dialog.addEventListener("click",this._handleActionBackdropClick),document.body.classList.add("skio-modal-open"))}closeModal(){const dialog=this.shadowRoot.querySelector("dialog.action-dialog");dialog&&(dialog.close(),dialog.removeEventListener("click",this._handleActionBackdropClick),document.body.classList.remove("skio-modal-open"))}handleBackdropClick(e){e.target.tagName==="DIALOG"&&this.closeModal()}disconnectedCallback(){super.disconnectedCallback(),document.body.classList.remove("skio-modal-open")}async addToSubscription(){this.submitError=null,this.isSubmitting=!0;const mutation=`
455    mutation storefrontEditSubscriptionLines($input: EditSubscriptionLinesInput!) {
456      editSubscriptionLines(input: $input) {
457        subscriptionId
458      }
459    }
460  `,variables={input:{subscriptionId:this.upcomingSubscription.id,subscriptionLinesToCreate:[{customAttributes:[{key:"add-to-subscription-component",value:"true"}],productVariantPlatformId:this.selectedVariant.platformId,quantity:1,isOneTimeUpsell:this.selectedOption==="one-time"}]}};try{if(await window.Skio.graphQLRequest({query:mutation,variables}),this.closeModal(),this.showSuccessScreen=!0,this.successMode="added",this.upcomingSubscription){const lines=this.upcomingSubscription.SubscriptionLines||[];lines.some(line=>String(line?.ProductVariant?.platformId)===String(this.selectedVariant.platformId))||(this.upcomingSubscription={...this.upcomingSubscription,SubscriptionLines:[...lines,{ProductVariant:{platformId:this.selectedVariant.platformId}}]})}this.dispatchEvent(new CustomEvent("skio:added",{detail:{subscription:this.upcomingSubscription}})),this.notifySubscriptionContext(),this.dispatchVariantChanged()}catch(error){console.error("Error adding to subscription:",error),this.submitError="Error adding to subscription. Please try again."}finally{this.isSubmitting=!1}}async reactivate(){this.submitError=null,this.isSubmitting=!0;const isPaused=this.inactiveSubscription.status==="PAUSED",mutation=isPaused?`mutation storefrontUnpauseSubscription($input: unpauseSubscriptionInput!) {
461        unpauseSubscription(input: $input) { ok }
462      }`:`mutation storefrontReactivateSubscription($input: ReactivateSubscriptionInput!) {
463        reactivateSubscription(input: $input) { ok }
464      }`,variables={input:{subscriptionId:this.inactiveSubscription.id,...isPaused?{}:{newBillingDate:new Date(Date.now()+864e5).toISOString()}}};try{const response=await window.Skio.graphQLRequest({query:mutation,variables});if(response.errors?.length)throw new Error(response.errors[0].message);const reactivated=await this.fetchSubscription("ACTIVE");if(!reactivated)throw new Error("Subscription was reactivated but could not be loaded.");this.upcomingSubscription=reactivated,this.inactiveSubscription=null,this.product||(this.product=await this.fetchProduct());const variantInput=this.getVariantIdInput();this.updateSelectedVariant(variantInput?.value),this.selectedVariant&&this.checkVariantEligibility(this.selectedVariant),this.selectedVariant&&this.eligibleForSubscription&&!this.isVariantInUpcomingSubscription(this.selectedVariant)?await this.addToSubscription():(this.successMode="reactivated",this.successWasPaused=isPaused,this.closeModal(),this.showSuccessScreen=!0),this.notifySubscriptionContext(),this.dispatchVariantChanged()}catch(error){console.error("Error reactivating subscription:",error),this.submitError="Something went wrong. Please try again."}finally{this.isSubmitting=!1}}
464render(){if(this.isPreviewMode)return this.renderDebugMode();if(this.showSuccessScreen)return this.renderSuccessScreen();if(this.ready&&!this.isAuthenticated)return this.renderSignInPrompt();if(!this.mode)return null;if(this.mode==="add")return this.renderAddMode();if(this.mode==="failed")return this.renderPaymentFailedPrompt();if(this.mode==="reactivate")return this.renderReactivateMode()}renderAddMode(){return html`
465      <div class="callout">
466        <h3>${"Add to Upcoming Subscription"}</h3>
467        <small>${"You can easily add this item to your upcoming subscription order."}</small>
468        <p>
469          Next order on:
470          <b>${this.formatDate(this.upcomingSubscription.nextBillingDate)}</b>
471        </p>
472        <button class="callout-button" @click=${()=>this.openModal()}>
473          ${"Add to next order and save"}
474        </button>
475      </div>
476
477      <dialog class="action-dialog">
478        <div class="modal-content">
479          <div class="modal-header">
480            <h2 class="modal-title">
481              Add to Subscription
482              <span class="date-badge"
483                >${this.formatDate(this.upcomingSubscription.nextBillingDate)}</span
484              >
485            </h2>
486            <button class="close-button" @click=${this.closeModal}>
487              &times;
488            </button>
489          </div>
490          <form>
491            ${this.submitError?html`<div class="error-message">${this.submitError}</div>`:null}
492            ${this.eligibleForOneTimeUpsell?this.renderOneTimeOption():null}
493            ${this.eligibleForSubscription?this.renderSubscriptionOption():null}
494          </form>
495          <div class="modal-policy">
496            ${this.selectedOption==="subscription"?html`<small
497                  >By clicking Add to Order, you're adding this item to your
498                  subscription. You'll be charged automatically at the listed
499                  price and frequency until canceled.</small
500                >`:html`<small
501                  >By selecting Add to Order, you agree to a one-time charge for
502                  this item, which will be added to your next subscription
503                  shipment. This item will not renew or be billed again.</small
504                >`}
505          </div>
506          <div class="modal-footer">
507            <button
508              class="primary-button"
509              @click=${this.addToSubscription}
510              ?disabled=${this.isSubmitting}
511            >
512              ${this.isSubmitting?html`<span class="loading-spinner"></span>`:"Add to Order"}
513            </button>
514            <button
515              class="secondary-button"
516              @click=${this.closeModal}
517              type="button"
518              ?disabled=${this.isSubmitting}
519            >
520              Cancel
521            </button>
522          </div>
523        </div>
524      </dialog>
525    `}renderReactivateMode(){const isPaused=this.inactiveSubscription.status==="PAUSED",title=isPaused?"Resume Your Subscription":"Reactivate Your Subscription",description=isPaused?"Your subscription is currently paused. Resume it to receive your next order.":"Your subscription has been cancelled. Reactivate it to continue receiving your orders.",buttonLabel=isPaused?"Resume Subscription":"Reactivate Subscription";return html`
526      <div class="callout">
527        <h3>${title}</h3>
528        <small>${description}</small>
529        <button class="callout-button" @click=${()=>this.openModal()}>
530          ${buttonLabel}
531        </button>
532      </div>
533
534      <dialog class="action-dialog">
535        <div class="modal-content">
536          <div class="modal-header">
537            <h2 class="modal-title">${title}</h2>
538            <button class="close-button" @click=${this.closeModal}>
539              &times;
540            </button>
541          </div>
542          <div class="modal-body">
543            ${this.submitError?html`<div class="error-message">${this.submitError}</div>`:null}
544            <p>
545              ${isPaused?"Your subscription will be resumed and your next order will be scheduled automatically.":"Your subscription will be reactivated and your next order will be scheduled automatically."}
546            </p>
547          </div>
548          <div class="modal-footer">
549            <button
550              class="primary-button"
551              @click=${this.reactivate}
552              ?disabled=${this.isSubmitting}
553            >
554              ${this.isSubmitting?html`<span class="loading-spinner"></span>`:buttonLabel}
555            </button>
556            <button
557              class="secondary-button"
558              @click=${this.closeModal}
559              type="button"
560              ?disabled=${this.isSubmitting}
561            >
562              Cancel
563            </button>
564          </div>
565        </div>
566      </dialog>
567    `}renderOneTimeOption(){const{discount,price}=this.getOneTimePrice(),isSelected=this.selectedOption==="one-time";return html`
568      <label
569        class="option-label ${isSelected?"selected":""}"
570        @click=${()=>this.selectedOption="one-time"}
571      >
572        <input
573          type="radio"
574          name="addType"
575          value="one-time"
576          .checked=${isSelected}
577          @change=${()=>this.selectedOption="one-time"}
578        />
579        <div class="option-content">
580          <span class="option-title">${"Get One-Time"}</span>
581        </div>
582        ${discount?html`<span class="discount-badge">Save ${discount}</span>`:null}
583        <div class="price-container">
584          ${this.getOriginalPrice()!==price?html`<span class="original-price"
585                >${this.getOriginalPrice()}</span
586              >`:null}
587          <span class="discounted-price">${price}</span>
588        </div>
589      </label>
590    `}renderSubscriptionOption(){const{discount,price}=this.getSubscriptionPrice(),isSelected=this.selectedOption==="subscription";return html`
591      <label
592        class="option-label ${isSelected?"selected":""}"
593        @click=${()=>this.selectedOption="subscription"}
594      >
595        <input
596          type="radio"
597          name="addType"
598          value="subscription"
599          .checked=${isSelected}
600          @change=${()=>this.selectedOption="subscription"}
601        />
602        <div class="option-content">
603          <span class="option-title">${"Subscribe"}</span>
604          <div class="delivery-policy">
605            Every ${this.upcomingSubscription.DeliveryPolicy.intervalCount}
606            ${this.upcomingSubscription.DeliveryPolicy.interval.toLowerCase()}
607          </div>
608        </div>
609        ${discount?html`<span class="discount-badge">Save ${discount}</span>`:null}
610        <div class="price-container">
611          ${this.getOriginalPrice()!==price?html`<span class="original-price"
612                >${this.getOriginalPrice()}</span
613              >`:null}
614          <span class="discounted-price">${price}</span>
615        </div>
616      </label>
617    `}renderSignInPrompt(){const{message,linkText,href}=this.signInPrompt;return html`
618      <div class="callout sign-in-callout">
619        <p>
620          ${message}
621          <a class="sign-in-link" href=${href}>${linkText}</a>
622        </p>
623      </div>
624    `}renderPaymentFailedPrompt(){const{linkText,href,message}=this.paymentFailedPrompt;return html`
625      <div class="callout sign-in-callout">
626        <p>
627          <a class="sign-in-link" href=${href}>${linkText}</a>
628          ${message}
629        </p>
630      </div>
631    `}renderSuccessScreen(){const isReactivate=this.successMode==="reactivated",isPaused=this.successWasPaused;return isReactivate?html`
632        <div class="success-screen">
633          <h2 class="success-title">
634            ${isPaused?"Subscription Resumed":"Subscription Reactivated"}
635          </h2>
636          <p class="success-message">
637            Your subscription has been ${isPaused?"resumed":"reactivated"}.
638            Your next order will be processed on the next scheduled billing
639            date.
640          </p>
641          <a href="/a/account/subscriptions" class="success-link"
642            >View and manage my subscriptions -></a
643          >
644        </div>
645      `:html`
646      <div class="success-screen">
647        <h2 class="success-title">Added to Your Next Order</h2>
648        <p class="success-message">
649          This item will be included in your next subscription order shipment on
650          <span class="success-date"
651            >${this.formatDate(this.upcomingSubscription.nextBillingDate)}</span
652          >
653        </p>
654        <a href="/a/account/subscriptions" class="success-link"
655          >View and manage my subscriptions -></a
656        >
657      </div>
658    `}renderDebugMode(){const mockDate=new Date;mockDate.setDate(mockDate.getDate()+30);const formattedDate=this.formatDate(mockDate);return html`
659      <div class="setup-mode">
660        <div class="setup-mode-label">
661          Setup Mode — Skio Add to Subscription
662        </div>
663        <p class="setup-mode-note">
664          UI preview with mock data. Functionality requires an authenticated
665          customer with an active or inactive subscription.
666        </p>
667
668        <div class="debug-mode-toggle">
669          <button
670            class="${this.debugPreviewMode==="add"?"active":""}"
671            @click=${()=>this.debugPreviewMode="add"}
672            type="button"
673          >
674            Add Mode
675          </button>
676          <button
677            class="${this.debugPreviewMode==="reactivate"?"active":""}"
678            @click=${()=>this.debugPreviewMode="reactivate"}
679            type="button"
680          >
681            Reactivate Mode
682          </button>
683        </div>
684
685        ${this.debugPreviewMode==="add"?html`
686              <div class="callout" style="margin-bottom: 16px;">
687                <h3>${"Add to Upcoming Subscription"}</h3>
688                <small>${"You can easily add this item to your upcoming subscription order."}</small>
689                <p>Next order on: <b>${formattedDate}</b></p>
690                <button class="callout-button" type="button">
691                  ${"Add to next order and save"}
692                </button>
693              </div>
694
695              <div class="debug-modal-preview">
696                <div class="modal-content">
697                  <div class="modal-header">
698                    <h2 class="modal-title">
699                      Add to Subscription
700                      <span class="date-badge">${formattedDate}</span>
701                    </h2>
702                    <button class="close-button" type="button">&times;</button>
703                  </div>
704                  <form>
705                    <label class="option-label">
706                      <input type="radio" name="addType" value="one-time" />
707                      <div class="option-content">
708                        <span class="option-title">${"Get One-Time"}</span>
709                      </div>
710                      <span class="discount-badge">Save 10%</span>
711                      <div class="price-container">
712                        <span class="original-price">$34.99</span>
713                        <span class="discounted-price">$31.49</span>
714                      </div>
715                    </label>
716                    <label class="option-label selected">
717                      <input
718                        type="radio"
719                        name="addType"
720                        value="subscription"
721                        checked
722                      />
723                      <div class="option-content">
724                        <span class="option-title">${"Subscribe"}</span>
725                        <div class="delivery-policy">Every 30 days</div>
726                      </div>
727                      <span class="discount-badge">Save 15%</span>
728                      <div class="price-container">
729                        <span class="original-price">$34.99</span>
730                        <span class="discounted-price">$29.74</span>
731                      </div>
732                    </label>
733                  </form>
734                  <div class="modal-policy">
735                    <small
736                      >By clicking Add to Order, you're adding this item to your
737                      subscription. You'll be charged automatically at the
738                      listed price and frequency until canceled.</small
739                    >
740                  </div>
741                  <div class="modal-footer">
742                    <button class="primary-button" type="button">
743                      Add to Order
744                    </button>
745                    <button class="secondary-button" type="button">
746                      Cancel
747                    </button>
748                  </div>
749                </div>
750              </div>
751            `:html`
752              <div class="callout" style="margin-bottom: 16px;">
753                <h3>Resume Your Subscription</h3>
754                <small
755                  >Your subscription is currently paused. Resume it to receive
756                  your next order.</small
757                >
758                <button class="callout-button" type="button">
759                  Resume Subscription
760                </button>
761              </div>
762
763              <div class="debug-modal-preview">
764                <div class="modal-content">
765                  <div class="modal-header">
766                    <h2 class="modal-title">Resume Your Subscription</h2>
767                    <button class="close-button" type="button">&times;</button>
768                  </div>
769                  <div class="modal-body">
770                    <p>
771                      Your subscription will be resumed and your next order will
772                      be scheduled automatically.
773                    </p>
774                  </div>
775                  <div class="modal-footer">
776                    <button class="primary-button" type="button">
777                      RESUME SUBSCRIPTION
778                    </button>
779                    <button class="secondary-button" type="button">
780                      Cancel
781                    </button>
782                  </div>
783                </div>
784              </div>
785            `}
786      </div>
787    `}static PROMPT_DEFAULTS={signInMessage:"Want to add this item to your upcoming subscription?",signInLinkText:"Sign in",paymentFailedLinkText:"Visit your account",paymentFailedMessage:"to update your credit card information."};static THEME_PROMPT_LINK_CLASS="font-semibold text-gray-900 underline decoration-1 underline-offset-2";static parseOptions(optionsJson){try{return JSON.parse(optionsJson||"{}")}catch{return{}}}static buildSignInUrl(options={}){const base=options.signInUrl||`${window.Shopify?.routes?.root||"/"}a/account/login`,returnUrl=encodeURIComponent(`${window.location.pathname}${window.location.search}`),separator=base.includes("?")?"&":"?";return`${base}${separator}return_url=${returnUrl}`}static getPrompt(state,options={}){const defaults=SkioAddToSubscription.PROMPT_DEFAULTS;if(state==="sign-in")return{message:options.signInMessage||defaults.sign
787InMessage,linkText:options.signInLinkText||defaults.signInLinkText,href:SkioAddToSubscription.buildSignInUrl(options)};if(state==="failed"){const root=window.Shopify?.routes?.root||"/";return{linkText:options.paymentFailedLinkText||defaults.paymentFailedLinkText,href:options.paymentFailedUrl||`${root}a/account/subscriptions`,message:options.paymentFailedMessage||defaults.paymentFailedMessage}}return null}static renderThemePrompt(container,state,options={}){if(!container)return;const prompt=SkioAddToSubscription.getPrompt(state,options);if(!prompt){container.classList.add("hidden"),container.innerHTML="";return}const linkClass=SkioAddToSubscription.THEME_PROMPT_LINK_CLASS;container.innerHTML=state==="failed"?`<a href="${prompt.href}" class="${linkClass}">${prompt.linkText}</a> ${prompt.message}`:`${prompt.message} <a href="${prompt.href}" class="${linkClass}">${prompt.linkText}</a>`,container.classList.remove("hidden")}static loadSdk(){return window.Skio?Promise.resolve():new Promise((resolve,reject)=>{const script=document.createElement("script");script.src="https://cdn.skio.com/scripts/shopify/sdk/skio-latest.js",script.onload=resolve,script.onerror=reject,document.head.appendChild(script)})}static isLikelyLoggedInShopifyCustomer(){return typeof window.__nuts_external_id__<"u"}static async waitForStorefrontUser(timeout=8e3){if(window.Skio?.storefrontUser?.id)return!0;if(!SkioAddToSubscription.isLikelyLoggedInShopifyCustomer())return!1;const started=Date.now();for(;Date.now()-started<timeout;){if(window.Skio?.storefrontUser?.id)return!0;await new Promise(resolve=>setTimeout(resolve,100))}return!!window.Skio?.storefrontUser?.id}static async fetchSubscription(status,{includeLines=!1}={}){const statusFilter=Array.isArray(status)?{_in:status}:{_eq:status},isActive=status==="ACTIVE",storefrontUserId=window.Skio?.storefrontUser?.id,storefrontUserFilter=storefrontUserId?`
788        storefrontUserId: { _eq: $storefrontUserId }`:"",query=`
789    query storefrontGetSubscription($storefrontUserId: uuid) {
790      Subscriptions(where: {
791        status: ${JSON.stringify(statusFilter).replace(/"([^"]+)":/g,"$1:")}
792        ${storefrontUserFilter}
793      }, order_by: {
794        ${isActive?"nextBillingDate":"updatedAt"}: asc
795      }, limit: 1) {
796        id
797        status
798        nextBillingDate
799        ${isActive&&includeLines?`
800        SubscriptionLines(
801          where: { removedAt: { _is_null: true } }
802          order_by: { createdAt: asc }
803        ) {
804          ProductVariant {
805            platformId
806          }
807          OriginalSellingPlan {
808            PricingPolicies {
809              percentageOff
810              fixedPrice
811            }
812          }
813        }`:""}
814        DeliveryPolicy {
815          interval
816          intervalCount
817        }
818        BillingPolicy {
819          interval
820          intervalCount
821        }
822      }
823    }
824  `,variables=storefrontUserId?{storefrontUserId}:{};try{const subscription=(await window.Skio.graphQLRequest({query,variables}))?.data?.Subscriptions?.[0];return!subscription||isActive&&SkioAddToSubscription.isPrepaidSubscription(subscription)?null:subscription}catch(error){return console.error("Error fetching subscription:",error),null}}static isPrepaidSubscription({DeliveryPolicy,BillingPolicy}){return DeliveryPolicy.interval!==BillingPolicy.interval||DeliveryPolicy.intervalCount!==BillingPolicy.intervalCount}static async resolveSubscriptionContext(){if(await SkioAddToSubscription.loadSdk(),await SkioAddToSubscription.waitForStorefrontUser(),!window.Skio?.storefrontUser?.id)return{renderState:"sign-in",upcomingSubscription:null};const upcomingSubscription=await SkioAddToSubscription.fetchSubscription("ACTIVE",{includeLines:!0});return upcomingSubscription?{renderState:"add",upcomingSubscription}:await SkioAddToSubscription.fetchSubscription(["CANCELLED","PAUSED"])?{renderState:"reactivate",upcomingSubscription:null}:await SkioAddToSubscription.fetchSubscription("FAILED")?{renderState:"failed",upcomingSubscription:null}:{renderState:null,upcomingSubscription:null}}static async addVariantToSubscription({subscriptionId,variantId,quantity=1}){await SkioAddToSubscription.loadSdk();const mutation=`
825    mutation storefrontEditSubscriptionLines($input: EditSubscriptionLinesInput!) {
826      editSubscriptionLines(input: $input) {
827        subscriptionId
828      }
829    }
830  `,variables={input:{subscriptionId,subscriptionLinesToCreate:[{customAttributes:[{key:"add-to-subscription-component",value:"true"},{key:"added-from-cart",value:"true"}],productVariantPlatformId:`gid://shopify/ProductVariant/${variantId}`,quantity,isOneTimeUpsell:!1}]}},response=await window.Skio.graphQLRequest({query:mutation,variables});if(response?.errors?.length)throw new Error(response.errors[0].message)}}customElements.define("skio-add-to-subscription",SkioAddToSubscription);
831//# sourceMappingURL=/cdn/shop/t/3/assets/skio-add-to-subscription.js.map?v=152525021207288618591786133901

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.