PageSourceSearch

https://www.drinktrade.com/cdn/shop/t/2106/assets/step-template-pl…?v=73957724881088174071789670869

js drinktrade.com collected 2026-09-24 18:20:47 UTC 18,128 bytes, 221 lines download raw bytes

1import{useState,useMemo,useEffect,useRef}from"preact/hooks";import{html}from"htm/preact";import{useStorage}from"onboarding-use-storage";import{dispatchStepEvent}from"onboarding-dispatch-event";import{storeOnboardingData}from"onboarding-storage-utils";import{ONBOARDING_EVENTS,ONBOARDING_STORAGE_KEYS,CTA_GROUPS}from"onboarding-step-constants";import{interpolateAllPlaceholders}from"onboarding-utils";export function App({blockSettings:e,stepId:t,sectionId:i,namespace:n="onboarding",onSelectionChange:o,selectedDropdownBlock:l,isLoading:s=!1}){const[r]=useStorage(n,ONBOARDING_STORAGE_KEYS.STEP_DATA),[a]=useStorage(n,ONBOARDING_STORAGE_KEYS.CHECKOUT_CONTEXT_BASE),[c]=useStorage(n,ONBOARDING_STORAGE_KEYS.ELIGIBLE_DISCOUNTS),[d]=useStorage(n,ONBOARDING_STORAGE_KEYS.ONBOARDING_DISCOUNTS),[p]=useStorage(n,ONBOARDING_STORAGE_KEYS.CURRENT_STEP_STATE),[u,m]=useState(null),f=useRef(null),g=p?.currentStepId===t,x=a?.products,h=useMemo((()=>{const t=e?.filter((e=>"cta"===e._type))||[],i=t.filter((e=>e.cta_group===CTA_GROUPS.PRIMARY));return i.length>0?i:t.slice(0,3)}),[e]);if(0===h.length)throw new Error("Plan selector template requires at least one CTA.");useEffect((()=>{if(void 0!==p&&!g)return;if(0===h.length)return;const e=`${t}_${i}`;if(f.current===e)return;let n=-1;if(r&&r.length>0){let e=0;h.forEach(((t,i)=>{if(!t.recommendations||0===t.recommendations.length)return;const o=t.recommendations.filter((e=>r.some((t=>t.field===e.field&&t.value===e.value)))).length;t.recommendations.every((e=>r.some((t=>t.field===e.field&&t.value===e.value))))&&o>e&&(e=o,n=i)}))}const s=-1===n;s&&(n=h.findIndex((e=>e.is_selected)));const a=-1===n?null:n,c=null!==a?h[a]:null;if(m(a),o?.(a,c?.recommendations||[]),s&&c){const e=[c._id];l?._id&&e.push(l._id),dispatchStepEvent({eventType:ONBOARDING_EVENTS.CTA_SELECTED,selectedBlockIds:e,stepId:t,stepDataKey:ONBOARDING_STORAGE_KEYS.STEP_DATA,sectionId:i})}f.current=e}),[p,g,r,t,i,h,o]),useEffect((()=>{g||(f.current=null)}),[g]);const v=e=>"number"==typeof e?e.toFixed(2):"0.00",_=useMemo((()=>{const e=r?.find((e=>"tier"===e.field));return e?.value?.replace("plan-","")||"good"}),[r]),b=useMemo((()=>e?.find((e=>"description_below_plan_selector"===e.key))),[e]),y=useMemo((()=>e?.find((e=>"text"===e._type&&"bestValueIndex"===e.key))),[e]),S=useMemo((()=>e?.find((e=>"text"===e._type&&"bestValueLabel"===e.key))),[e]),E=useMemo((()=>e?.find((e=>"image_picker"===e._type&&"bestValueImage"===e.key))),[e]),w=useMemo((()=>h.map(((e,t)=>{const i=e.title||`option-${t}`;let o=null,l=null,s=0,c=null;const p=e.recommendations?.find((e=>"quantity"===e.field))?.value||"1",u=e.recommendations?.find((e=>"bag_size"===e.field))?.value,m=u||(i.includes("Large")?"Big Bag":"Standard Bag"),f=x?.find((e=>{if(a?.tiers&&e.id){const t=a.tiers[e.id];if(t)return t===_}const t=e.handle?.toLowerCase();if(t?.includes("premium"))return"better"===_;if(t?.includes("classic"))return"good"===_;throw new Error(`[plan-selector] Unable to determine tier for product ${e.id} (${e.handle}). Handle must contain 'premium' or 'classic'. Checkout context tiers: ${JSON.stringify(a?.tiers)}`)})),g=f?.variants?.find((e=>e.title?.includes(m)||e.bagSize===m));let h=null;if(g?.price){const e=("Big Bag"===m?907:310)/28.3495;e>0&&(h=g.price/100/e)}if(!o){let t=null;if("undefined"!=typeof window&&window.onboardingDiscountSettings&&window.onboardingDiscountSettings[n||"onboarding"]){const i=window.onboardingDiscountSettings[n||"onboarding"];Object.values(i).forEach((i=>{!t&&i.blocks&&i.blocks.forEach((i=>{if(!t&&"plan_discount"===i.blockType){const n=new Set((e.recommendations||[]).map((e=>e.field))),o=[...(r||[]).filter((e=>!n.has(e.field))),...e.recommendations||[]],l=i.recommendations?.every((e=>{const t=o.find((t=>t.field===e.field));return t?.value===e.value}));l&&(t={type:i.discountType,value:parseFloat(i.discountValue),message:i.discountMessage})}}))}))}if(!t&&d&&d.length>0){const i=new Set((e.recommendations||[]).map((e=>e.field))),n=d.filter((t=>{if("plan"!==t.category)return!1;const n=[...(r||[]).filter((e=>!i.has(e.field))),...e.recommendations||[]];return t.requiredRecommendations?.every((e=>{const t=n.find((t=>t.field===e.field));return t?.value===e.value}))})).reduce(((e,t)=>e?"percentage"===t.type&&"percentage"===e.type?t.value>e.value?t:e:"percentage"===t.type&&"percentage"!==e.type?t:"percentage"!==t.type&&"percentage"===e.type?e:t.value>e.value?t:e:t),null);n&&(t={type:n.type,value:n.value,message:n.message||"Discount Applied"})}if(t){let i=e.price;if(!i){const e=x?.find((e=>{if(a?.tiers&&e.id){const t=a.tiers[e.id];if(t)return t===_}const t=e.handle?.toLowerCase();if(t?.includes("premium"))return"better"===_;if(t?.includes("classic"))return"good"===_;throw new Error(`[plan-selector] Unable to determine tier for product ${e.id} (${e.handle}). Handle must contain 'premium' or 'classic'. Checkout context tiers: ${JSON.stringify(a?.tiers)}`)}));if(e?.variants){const t=e.variants.find((e=>e.title?.includes(m)||e.bagSize===m));t?.price&&(i=t.price/100)}}if(i&&t.value){const e=parseInt(p)||1,n=i*e;l=n,"percentage"===t.type?(c=n*(t.value/100),o=n-c):"fixedAmount"===t.type&&(c=t.value*e,o=Math.max(0,n-c)),s=l>0&&null!==c?Math.round(c/l*100):0}
1}}if(!o){let t=null;const i=x?.find((e=>{if(a?.tiers&&e.id){const t=a.tiers[e.id];if(t)return t===_}const t=e.handle?.toLowerCase();if(t?.includes("premium"))return"better"===_;if(t?.includes("classic"))return"good"===_;throw new Error(`[plan-selector] Unable to determine tier for product ${e.id} (${e.handle}). Handle must contain 'premium' or 'classic'. Checkout context tiers: ${JSON.stringify(a?.tiers)}`)}));if(i?.variants){const e=i.variants.find((e=>e.title?.includes(m)||e.bagSize===m));e?.price&&(t=e.price/100)}if(!t&&e.price&&(t=e.price),t){o=t*(parseInt(p)||1)}if(!l&&e.original_price){const t=parseInt(p)||1;l=e.original_price*t}}const v={stepData:r,pricePerOunce:h,pricePerOrder:o};return{title:e.title||i,description:interpolateAllPlaceholders(e.description,v),perUnitPrice:interpolateAllPlaceholders(e.subDescription,v),price:o,originalPrice:l,discountAmount:c,discountPercentage:s,discountMessage:s>0?"Sale Price":null,badge:e.badge,bagType:i,pricingSubtext:interpolateAllPlaceholders(e.pricing_subtext,v)}}))),[h,_,c,x,d,r]),A=useMemo((()=>{const e=y?.value?.toLowerCase().trim();if(e){if("none"===e)return-1;if("auto"===e){let e=0,t=-1;return w.forEach(((i,n)=>{const o=i.discountAmount||0;o>e&&(e=o,t=n)})),t}const t=parseInt(e);if(!isNaN(t)&&t>=1&&t<=h.length)return t-1}let t=0,i=-1;return w.forEach(((e,n)=>{const o=e.discountAmount||0;o>t&&(t=o,i=n)})),i}),[w,y,h.length]),O=useMemo((()=>e?.find((e=>"text"===e._type&&"prepaid_3_banner_message"===e.key))),[e]),T=useMemo((()=>e?.find((e=>"text"===e._type&&"prepaid_6_banner_message"===e.key))),[e]),$=useMemo((()=>e?.find((e=>"text"===e._type&&"banner_background_color"===e.key))),[e]),D=useMemo((()=>e?.find((e=>"text"===e._type&&"banner_text_color"===e.key))),[e]);return useEffect((()=>{if(!O?.value&&!T?.value)return;const e=r?.find((e=>"plan"===e.field)),t=e?.value;let i="",o=!0;if(t){const e=String(t).toLowerCase();e.includes("prepaid-3")&&O?.value?(i=O.value,o=!1):e.includes("prepaid-6")&&T?.value&&(i=T.value,o=!1)}const l={title:i,backgroundColor:$?.value||"#F53C32",textColor:D?.value||"#FFF9F1",hidden:o,isDynamic:!0,hasConfiguration:!0};storeOnboardingData(n,"bannerState",l);const s=new CustomEvent(`${n}:bannerState`,{detail:{bannerState:l}});document.dispatchEvent(s)}),[r,n,O,T,$,D]),html`
2    <!-- Bag Size Options - Responsive: Stacked on Mobile, Horizontal on Desktop -->
3    <div
4      class="w-full flex flex-col md:flex-row gap-4 md:gap-6 mb-6 justify-center md:px-0 justify-self-center"
5    >
6      ${h.map(((e,n)=>{const r=u===n,a=((e,t)=>w[t])(0,n);if(!a)return"";return html`
7          <div class="relative w-full md:w-auto">
8            <!-- Best Value Badge -->
9            ${n===A?html`
10                  <div
11                    class="absolute flex items-center justify-center top-[8px] md:top-[-8px] right-[-2px] w-[100px] h-[24px] z-[2]"
12                  >
13                    ${E?.value?html`<img
14                          src="${E.value}"
15                          alt=""
16                          class="absolute w-[100px] h-[24px]"
17                        />`:html`<svg
18                          width="100"
19                          height="24"
20                          viewBox="0 0 100 24"
21                          fill="none"
22                          xmlns="http://www.w3.org/2000/svg"
23                          style="position: absolute;"
24                        >
25                          <path
26                            d="M0.463047 3.94313C-0.707533 2.28726 0.476616 0 2.50446 0H97.2148C98.5955 0 99.7148 1.11929 99.7148 2.5V21.5C99.7148 22.8807 98.5955 24 97.2148 24H2.50446C0.476614 24 -0.707531 21.7127 0.463048 20.0569L5.13848 13.4431C5.74993 12.5782 5.74993 11.4218 5.13848 10.5569L0.463047 3.94313Z"
27                            fill="#F45048"
28                          />
29                        </svg>`}
30                    <div
31                      class="font-mono uppercase whitespace-nowrap text-white text-eyebrow-xs relative z-[1]"
32                    >
33                      ${S?.value}
34                    </div>
35                  </div>
36                `:""}
37
38            <!-- Card - Horizontal on mobile, vertical on desktop -->
39            <div
40              class="${s?"cursor-default":"cursor-pointer"} transition-all flex flex-row md:flex-col items-stretch md:items-stretch w-full md:w-[204px] gap-0 h-[124px] md:h-[300px] md:max-w-none p-[20px_24px_20px_12px] md:p-6"
41              style="background-color: #FFFFFF; border-radius: 16px; border: 1px solid ${r?"#252525":"rgba(37, 37, 37, 0.25)"}; box-shadow: ${r?"inset 0 0 0 1px #252525":"none"};"
42              onClick=${()=>(e=>{if(s)return;const n=h[e];if(!n)throw new Error(`Plan selector is missing a CTA at index ${e}.`);if(n.advance_on_click){m(e);const o=[n._id];return l?._id&&o.push(l._id),void setTimeout((()=>{dispatchStepEvent({eventType:ONBOARDING_EVENTS.CTA_NAVIGATE,selectedBlockIds:o,triggeringBlockId:n._id,stepId:t,stepDataKey:ONBOARDING_STORAGE_KEYS.STEP_DATA,sectionId:i,nextStepId:n["next-step-id"]})}),250)}const r=!1!==n.allow_toggle;if(r&&u===e)return m(null),dispatchStepEvent({eventType:ONBOARDING_EVENTS.CTA_UNSELECTED,selectedBlockIds:l?._id?[l._id]:[],stepId:t,stepDataKey:ONBOARDING_STORAGE_KEYS.STEP_DATA,sectionId:i}),void(o&&o(null,[]));if(!r&&u===e)return;m(e);const a=[n._id];l?._id&&a.push(l._id),dispatchStepEvent({eventType:ONBOARDING_EVENTS.CTA_SELECTED,selectedBlockIds:a,stepId:t,stepDataKey:ONBOARDING_STORAGE_KEYS.STEP_DATA,sectionId:i}),o&&o(e,n?.recommendations||[])})(n)}
43            >
44              <!-- Mobile: 3-column layout, Desktop: vertical layout -->
45
46              <!-- Column 1: Image (mobile only) -->
47              <div
48                class="relative shrink-0 flex items-center md:hidden mr-[14px]"
49              >
50                ${e.image?html`
51                      <img
52                        src="${e.image}"
53                        alt="${a.title}"
54                        class="object-contain w-[68px] h-[68px]"
55                      />
56                    `:""}
57              </div>
58
59              <!-- Column 2: Title/Description/Discount (mobile), Full content (desktop) -->
60              <div
61                class="flex flex-col flex-1 max-md:min-w-0 md:flex-initial md:w-full justify-between h-[88px] md:h-auto"
62              >
63                <!-- Title and description -->
64                <div
65                  class="flex flex-col gap-1 items-start md:items-center md:mb-4"
66                >
67                  <!-- Title and perUnitPrice inline on mobile -->
68                  <div
69                    class="flex md:flex-col gap-1.5 md:gap-1 items-center whitespace-nowrap"
70                  >
71                    <h3
72                      class="text-[18px] md:text-[20px] font-semibold text-left md:text-center"
73                      style="color: #252525; letter-spacing: -0.54px; md:letter-spacing: -0.6px; line-height: 1.12;"
74                    >
75                      ${a.title}
76                    </h3>
77                    <div class="text-body-14" style="color: #7A7A7F;">
78                      ${a.perUnitPrice}
79                    </div>
80                  </div>
81
82                  <!-- Description below title -->
83                  <p
84                    class="text-[12px] md:hidden font-normal [&_*]:!text-[12px] [&_*]:!leading-normal"
85                    style="color: #252525; line-height: normal;"
86                  >
87                    ${a.description?html`<span>${html([a.description])}</span>`:""}
88                  </p>
89                </div>
90
91                <!-- Discount badge (mobile only, bottom of column 2) -->
92                ${a.discountPercentage>0?html`
93                      <div
94                        class="md:hidden bg-[#EBFF88] flex items-center justify-center px-[6px] pb-[1.016px] self-start"
95                      >
96                        <span
97                          class="font-medium text-[14px]"
98                          style="color: #252525; line-height: 1.6;"
99                          >${a.discountPercentage}% off</span
100                        >
101                      </div>
102                    `:""}
103
104                <!-- Desktop only: Image in middle -->
105                <div class="hidden md:flex relative justify-center">
106                  ${e.image?html`
107                        <img
108                          src="${e.image}"
109                          alt="${a.title}"
110                          class="object-contain w-[86px] h-[86px]"
111                        />
112                      `:""}
113                  <!-- Discount percentage circle overlay -->
114                  ${a.discountPercentage>0?html`
115                        <div
116                          class="absolute right-[26px] -top-3 w-[52px] h-[52px] flex flex-col items-center justify-center rounded-full leading-none"
117                          style="background: #EBFF88; color: #252525;"
118                        >
119                          <div
120                            class="font-serif"
121                            style="font-size: 18px; letter-spacing: -0.72px;"
122                          >
123                            ${a.discountPercentage}%
124                          </div>
125                          <div
126                            class="font-mono uppercase"
127                            style="font-size: 10.4px; letter-spacing: 0.624px; margin-top: 2px;"
128                          >
129                            OFF
130                          </div>
131                        </div>
132                      `:""}
133                </div>
134
135                <!-- Desktop only: Description below image -->
136                <p
137                  class="hidden md:block font-medium text-center md:mb-[16px] [&_*]:!text-[12px] [&_*]:!leading-[1.6]"
138                  style="color: #252525; line-height: 1.6; font-size: 12px;"
139                >
140                  ${a.description?html`<span>${html([a.description])}</span>`:""}
141                </p>
142
143                <!-- Desktop only: Pricing stacked -->
144                <div
145                  class="hidden md:flex flex-col items-center gap-3 text-center"
146                >
147                  <div
148                    class="flex flex-col gap-2 items-center whitespace-nowrap my-[10px]"
149                  >
150                    ${a.originalPrice&&a.originalPrice!==a.price?html`
151                          <div
152                            class="text-[14px] font-normal line-through"
153                            style="color: #4F4F52; line-height: normal;"
154                          >
155                            $${v(a.originalPrice)}
156                          </div>
157                        `:""}
158                    <div
159                      class="text-lg font-bold"
160                      style="color: #252525; letter-spacing: -0.54px; line-height: 1.12;"
161                    >
162                      $${v(a.price)}
163                    </div>
164                  </div>
165                  ${a.pricingSubtext?html`
166                        <p
167                          class="text-sm font-normal"
168                          style="color: #7A7A7F; line-height: 1.6;"
169                        >
170                          ${a.pricingSubtext}
171                        </p>
172                      `:""}
173                </div>
174              </div>
175
176              <!-- Column 3: Pricing (mobile only) -->
177              <div
178                class="md:hidden flex flex-col items-end justify-end md:justify-between md:h-[88px]"
179              >
180                <div class="flex flex-col items-end text-right mt-[25px]">
181                  ${a.originalPrice&&a.originalPrice!==a.price?html`
182                        <div
183                          class="text-[14px] font-normal line-through"
184                          style="color: #4F4F52; line-height: normal;"
185                        >
186                          $${v(a.originalPrice)}
187                        </div>
188                      `:""}
189                  <div
190                    class="text-[16px] font-bold"
191                    style="color: #252525; letter-spacing: -0.32px; line-height: normal;"
192                  >
193                    $${v(a.price)}
194                  </div>
195                </div>
196                ${a.pricingSubtext?html`
197                      <p
198                        class="text-[12px] font-normal whitespace-nowrap"
199                        style="color: #7A7A7F; line-height: 1.6;"
200                      >
201                        ${a.pricingSubtext}
202                      </p>
203                    `:""}
204              </div>
205            </div>
206          </div>
207        `}))}
208    </div>
209
210    <!-- Description below plan selector -->
211    ${b?.value?html`
212          <div class="text-center mb-6">
213            <p
214              class="text-[14px] md:text-[16px] text-trade-black"
215              style="line-height: 140%;"
216            >
217              ${b.value}
218            </p>
219          </div>
220        `:""}
221  `}

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.