Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
Multi-pin pickup selection (study §3.4) — previously a standalone unused endpoint + TODO comments: - app/services/pickup-locations.server.ts: pure filterPickupLocationIds + I/O resolvePickupLocations (active + has a PICKUP slot template + ProductRule allowedLocationIds + inventory exclusion). - resolveAvailabilityRequest returns pickupLocations[] on method=PICKUP and defaults the active location to the first eligible pickup point. - apps.scheduling.locations.tsx refactored onto the shared resolver (was a second copy of the logic). - Storefront widget: pickup-location chooser (name + address, optional multi-pin Google map) when >1 eligible point and none block-configured; picking one re-requests availability for that location. New styles + widget.choose_pickup_location locale (en + fr). - Checkout extension Checkout.jsx: same chooser before the date list. - tests/unit/pickup-locations.test.ts (5 cases); suite 161 green. checkout-datetime follow-up fixes from the 808a3b7 review: - typescript devDep ^7.0.2 -> ^5.6.3 (there is no typescript@7 on npm). - Deleted dead shopify.d.ts (Preact-global shim, unused after the React rewrite) and dropped it from tsconfig include. Also stages the CLI-written `uid` lines in the checkout-datetime and payment-customization extension tomls. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2 lines
14 KiB
JavaScript
2 lines
14 KiB
JavaScript
"use strict";(()=>{var P=Object.defineProperty;var I=(a,t,e)=>t in a?P(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var c=(a,t,e)=>I(a,typeof t!="symbol"?t+"":t,e);var M="/apps/scheduling";function C(a){let t=Math.floor(a/60),e=a%60,n=t<12?"AM":"PM";return`${t%12===0?12:t%12}:${e.toString().padStart(2,"0")} ${n}`}function m(a){return h(a.slice(0,10))}function w(a){return a.arrivalRangeStart&&a.arrivalRangeEnd?`Arrives ${m(a.arrivalRangeStart)}\u2013${m(a.arrivalRangeEnd)}`:a.arrivalRangeStart?`Arrives from ${m(a.arrivalRangeStart)}`:a.arrivalRangeEnd?`Arrives by ${m(a.arrivalRangeEnd)}`:`${C(a.startMin)}\u2013${C(a.endMin)}`}function h(a){let[t,e,n]=a.split("-").map(Number);return new Date(Date.UTC(t,e-1,n)).toLocaleDateString(void 0,{weekday:"short",month:"short",day:"numeric",timeZone:"UTC"})}function A(a){if(!a||a.indexOf("&")===-1)return a;let t=document.createElement("textarea");return t.innerHTML=a,t.value}function E(a){let t=a.dataset,e=(i,o)=>{let s=t[i];return s?A(s):o},n=[];return t.showShipping==="true"&&n.push({value:"SHIPPING",label:e("labelShipping","Shipping"),attrLabel:e("attrLabelShipping","Shipping date")}),t.showLocalDelivery==="true"&&n.push({value:"LOCAL_DELIVERY",label:e("labelLocalDelivery","Local delivery"),attrLabel:e("attrLabelLocalDelivery","Delivery date")}),t.showPickup==="true"&&n.push({value:"PICKUP",label:e("labelPickup","Pickup"),attrLabel:e("attrLabelPickup","Pickup date")}),{root:a,heading:e("heading",""),locationId:t.locationId||null,googleMapsApiKey:t.googleMapsApiKey||null,mode:t.mode==="preview"?"preview":"full",methods:n,labels:{chooseDate:e("labelChooseDate","Choose a date"),chooseTime:e("labelChooseTime","Choose a time"),noDates:e("labelNoDates","No dates are available right now."),confirmed:e("labelConfirmed","Confirmed for"),change:e("labelChange","Change"),loading:e("labelLoading","Loading available dates\u2026"),error:e("labelError","Couldn't load available dates. Please try again."),postalCodeLabel:e("labelPostalCode","Enter your postal/ZIP code"),postalCodeSubmit:e("labelPostalCodeSubmit","Check availability"),outOfArea:e("labelOutOfArea","Sorry, we don't deliver to this address."),earliestPrefix:e("labelEarliestPrefix","Earliest"),deliveryAtCheckout:e("labelDeliveryAtCheckout","Enter your address at checkout to see local delivery dates."),choosePickupLocation:e("labelChoosePickupLocation","Choose a pickup location")}}}async function v(){var e;let t=await(await fetch("/cart.js",{headers:{Accept:"application/json"}})).json();return{token:t.token,lines:((e=t.items)!=null?e:[]).map(n=>{var i,o,s;return{vendor:(i=n.vendor)!=null?i:"",productType:(o=n.product_type)!=null?o:"",productId:`gid://shopify/Product/${n.product_id}`,variantId:`gid://shopify/ProductVariant/${(s=n.variant_id)!=null?s:n.id}`}})}}async function L(a,t,e,n){let i=new URLSearchParams({method:a,days:"14"});t&&i.set("locationId",t),n&&i.set("postalCode",n),e.lines.length>0&&(i.set("cartLines",JSON.stringify(e.lines.map(r=>({vendor:r.vendor,productType:r.productType})))),i.set("productIds",e.lines.map(r=>r.productId).join(",")),i.set("variantIds",e.lines.map(r=>r.variantId).join(",")));let o=await fetch(`${M}/availability?${i.toString()}`,{headers:{Accept:"application/json"}}),s=await o.json();if(!o.ok)throw new Error(s.error||`Request failed (${o.status})`);return s}async function k(a){let t=await fetch(`${M}/hold`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(a)}),e=await t.json();return!t.ok&&a.intent==="create"?{success:!1,error:e.error||"Slot unavailable"}:e}async function S(a,t,e){await fetch("/cart/update.js",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({attributes:{[a]:e,...t}})})}var f=null;function T(a){return f||(f=new Promise((t,e)=>{let n="__ddMapsReady";window[n]=()=>t();let i=document.createElement("script");i.src=`https://maps.googleapis.com/maps/api/js?key=${encodeURIComponent(a)}&callback=${n}`,i.async=!0,i.onerror=()=>e(new Error("Failed to load Google Maps")),document.head.appendChild(i)}),f)}async function _(a,t,e){if(e.length===0){a.hidden=!0;return}try{await T(t);let n=window.google,i=new n.maps.Map(a,{center:{lat:e[0].lat,lng:e[0].lng},zoom:13});for(let o of e)new n.maps.Marker({position:{lat:o.lat,lng:o.lng},map:i,title:o.title})}catch{a.hidden=!0}}var b=class{constructor(t){c(this,"config");c(this,"el",{heading:document.createElement("h3"),methodRow:document.createElement("div"),postalRow:document.createElement("div"),pickupRow:document.createElement("div"),mapContainer:document.createElement("div"),dateRow:document.createElement("div"),timeRow:document.createElement("div"),status:document.createElement("p"),confirmation:document.createElement("div")});c(this,"selectedMethod",null);c(this,"selectedDate",null);c(this,"selectedPickupLocationId",null);c(this,"availability",null);c(this,"heldSlot",null);this.config=t}mount(){let{root:t,heading:e,methods:n}=this.config;if(t.classList.add("dd-widget--ready"),t.innerHTML="",n.length!==0){if(this.config.mode==="preview"){this.mountPreview();return}e&&(this.el.heading.className="dd-widget__heading",this.el.heading.textContent=e,t.appendChild(this.el.heading)),this.el.methodRow.className="dd-widget__row dd-widget__methods",this.el.postalRow.className="dd-widget__row dd-widget__postal",this.el.postalRow.hidden=!0,this.el.pickupRow.className="dd-widget__row dd-widget__pickup-locations",this.el.pickupRow.hidden=!0,this.el.mapContainer.className="dd-widget__map",this.el.mapContainer.hidden=!0,this.el.dateRow.className="dd-widget__row dd-widget__dates",this.el.timeRow.className="dd-widget__row dd-widget__times",this.el.status.className="dd-widget__status",this.el.confirmation.className="dd-widget__confirmation",this.el.confirmation.hidden=!0,t.append(this.el.confirmation,this.el.methodRow,this.el.postalRow,this.el.pickupRow,this.el.mapContainer,this.el.dateRow,this.el.timeRow,this.el.status),n.length===1?this.selectMethod(n[0]):this.renderMethods()}}async mountPreview(){let{root:t,heading:e,methods:n}=this.config;e&&(this.el.heading.className="dd-widget__heading",this.el.heading.textContent=e,t.appendChild(this.el.heading));let i=document.createElement("ul");i.className="dd-widget__preview",t.appendChild(i);let o=await v().catch(()=>({token:"",lines:[]}));await Promise.all(n.map(async s=>{var l;let r=document.createElement("li");if(r.className="dd-widget__preview-item",s.value==="LOCAL_DELIVERY"){r.textContent=this.config.labels.deliveryAtCheckout,i.appendChild(r);return}r.textContent=this.config.labels.loading,i.appendChild(r);try{let u=await L(s.value,this.config.locationId,o),d=Object.keys((l=u.dates)!=null?l:{}).sort()[0];r.textContent=d?`${this.config.labels.earliestPrefix} ${s.label.toLowerCase()}: ${h(d)}`:`${s.label}: ${this.config.labels.noDates}`}catch{r.textContent=`${s.label}: ${this.config.labels.error}`}}))}renderMethods(){var t;this.el.methodRow.innerHTML="";for(let e of this.config.methods){let n=document.createElement("button");n.type="button",n.className="dd-widget__pill",n.textContent=e.label,n.setAttribute("aria-pressed",String(((t=this.selectedMethod)==null?void 0:t.value)===e.value)),n.addEventListener("click",()=>this.selectMethod(e)),this.el.methodRow.appendChild(n)}}async selectMethod(t){if(this.selectedMethod=t,this.selectedDate=null,this.selectedPickupLocationId=null,this.el.timeRow.innerHTML="",this.el.dateRow.innerHTML="",this.el.pickupRow.hidden=!0,this.el.pickupRow.innerHTML="",this.el.mapContainer.hidden=!0,this.el.confirmation.hidden=!1,this.el.confirmation.hidden=!0,this.config.methods.length>1&&this.renderMethods(),t.value==="LOCAL_DELIVERY"){this.renderPostalCodeInput(t);return}this.el.postalRow.hidden=!0,await this.loadAvailability(t)}renderPostalCodeInput(t){this.el.postalRow.hidden=!1,this.el.postalRow.innerHTML="",this.el.status.textContent="";let e=document.createElement("input");e.type="text",e.className="dd-widget__input",e.placeholder=this.config.labels.postalCodeLabel,e.setAttribute("aria-label",this.config.labels.postalCodeLabel);let n=document.createElement("button");n.type="button",n.className="dd-widget__pill",n.textContent=this.config.labels.postalCodeSubmit,n.addEventListener("click",async()=>{let i=e.value.trim();i&&await this.loadAvailability(t,i)}),e.addEventListener("keydown",i=>{i.key==="Enter"&&n.click()}),this.el.postalRow.append(e,n)}async loadAvailability(t,e,n){var i,o,s;this.el.status.textContent=this.config.labels.loading,this.el.dateRow.innerHTML="";try{let r=await v(),l=(o=(i=this.config.locationId)!=null?i:n)!=null?o:null;this.availability=await L(t.value,l,r,e);let u=(s=this.availability.pickupLocations)!=null?s:[];if(t.value==="PICKUP"&&!this.config.locationId&&!this.selectedPickupLocationId&&u.length>1){this.renderPickupLocations(t,u);return}if(!this.availability.locationId){this.el.status.textContent=this.availability.error||this.config.labels.outOfArea;return}t.value==="PICKUP"&&this.config.googleMapsApiKey&&this.showPickupMap(),this.renderDates()}catch{this.el.status.textContent=this.config.labels.error}}renderPickupLocations(t,e){if(this.el.pickupRow.hidden=!1,this.el.pickupRow.innerHTML="",this.el.status.textContent=this.config.labels.choosePickupLocation,this.config.googleMapsApiKey){let n=e.filter(i=>i.lat!=null&&i.lng!=null).map(i=>({lat:i.lat,lng:i.lng,title:i.name}));n.length>0&&(this.el.mapContainer.hidden=!1,_(this.el.mapContainer,this.config.googleMapsApiKey,n))}for(let n of e){let i=document.createElement("button");i.type="button",i.className="dd-widget__pickup-location",i.setAttribute("aria-pressed",String(this.selectedPickupLocationId===n.id));let o=document.createElement("span");o.className="dd-widget__pickup-location-name",o.textContent=n.name;let s=document.createElement("span");s.className="dd-widget__pickup-location-address",s.textContent=n.address,i.append(o,s),i.addEventListener("click",async()=>{this.selectedPickupLocationId=n.id,this.el.pickupRow.hidden=!0,await this.loadAvailability(t,void 0,n.id)}),this.el.pickupRow.appendChild(i)}}showPickupMap(){var n,i;let t=this.availability;if(!this.config.googleMapsApiKey)return;let e=((n=t==null?void 0:t.pickupLocations)!=null?n:[]).filter(o=>o.lat!=null&&o.lng!=null).map(o=>({lat:o.lat,lng:o.lng,title:o.name}));e.length===0&&(t==null?void 0:t.locationLat)!=null&&(t==null?void 0:t.locationLng)!=null&&e.push({lat:t.locationLat,lng:t.locationLng,title:(i=t.locationName)!=null?i:""}),e.length!==0&&(this.el.mapContainer.hidden=!1,_(this.el.mapContainer,this.config.googleMapsApiKey,e))}renderDates(){var e,n;let t=Object.keys((n=(e=this.availability)==null?void 0:e.dates)!=null?n:{}).sort();if(this.el.dateRow.innerHTML="",t.length===0){this.el.status.textContent=this.config.labels.noDates;return}this.el.status.textContent=this.config.labels.chooseDate;for(let i of t){let o=document.createElement("button");o.type="button",o.className="dd-widget__pill",o.textContent=h(i),o.setAttribute("aria-pressed",String(this.selectedDate===i)),o.addEventListener("click",()=>this.selectDate(i)),this.el.dateRow.appendChild(o)}}selectDate(t){var n,i;this.selectedDate=t;for(let o of Array.from(this.el.dateRow.children))o.setAttribute("aria-pressed",String(o.textContent===h(t)));let e=(i=(n=this.availability)==null?void 0:n.dates[t])!=null?i:[];this.el.timeRow.innerHTML="",this.el.status.textContent=this.config.labels.chooseTime;for(let o of e){let s=document.createElement("button");s.type="button",s.className="dd-widget__pill",s.textContent=w(o),s.addEventListener("click",()=>this.selectSlot(t,o)),this.el.timeRow.appendChild(s)}}async selectSlot(t,e){var r;let n=this.selectedMethod,i=this.availability,o=i.rate?` (${i.rate.label})`:"",s=e.arrivalRangeStart||e.arrivalRangeEnd?`Ships ${h(t)}, ${w(e)}${o}`:`${h(t)}, ${w(e)}${o}`;this.el.status.textContent=this.config.labels.loading;try{let l=await v(),u=await k({intent:"create",locationId:i.locationId,method:n.value,date:t,startMin:e.startMin,cartToken:l.token,cartLines:l.lines.map(g=>({vendor:g.vendor,productType:g.productType})),productIds:l.lines.map(g=>g.productId)});if(!u.success){this.el.status.textContent=u.error||this.config.labels.error,await this.selectMethod(n);return}this.heldSlot={locationId:i.locationId,method:n.value,date:t,startMin:e.startMin,cartToken:l.token,zoneId:(r=i.zoneId)!=null?r:null};let d={dd_method:n.value,dd_date:t,dd_start_min:String(e.startMin),dd_end_min:String(e.endMin),dd_location_id:i.locationId};i.zoneId&&(d.dd_zone_id=i.zoneId),i.rate&&(d.dd_rate_label=i.rate.label),e.arrivalRangeStart&&(d.dd_arrival_range_start=e.arrivalRangeStart),e.arrivalRangeEnd&&(d.dd_arrival_range_end=e.arrivalRangeEnd),await S(n.attrLabel,d,s),this.el.status.textContent="",this.el.methodRow.hidden=!0,this.el.postalRow.hidden=!0,this.el.mapContainer.hidden=!0,this.el.dateRow.hidden=!0,this.el.timeRow.hidden=!0,this.el.confirmation.hidden=!1,this.el.confirmation.innerHTML="";let y=document.createElement("p");y.textContent=`${this.config.labels.confirmed} ${s}`;let p=document.createElement("button");p.type="button",p.className="dd-widget__link",p.textContent=this.config.labels.change,p.addEventListener("click",()=>{this.heldSlot&&(k({intent:"release",...this.heldSlot}),this.heldSlot=null),this.el.methodRow.hidden=!1,this.el.dateRow.hidden=!1,this.el.timeRow.hidden=!1,this.el.confirmation.hidden=!0}),this.el.confirmation.append(y,p)}catch{this.el.status.textContent=this.config.labels.error}}},D=['form[action*="/cart"] button[name="checkout"]','form[action*="/cart"] input[name="checkout"]','[name="checkout"]',"#checkout",'a[href="/checkout"]'];function x(){for(let a of D){let t=document.querySelector(a);if(t)return t}return null}function $(){return window.location.pathname.replace(/\/+$/,"").endsWith("/cart")}function N(){if(!$()||document.querySelector("[data-dd-widget]"))return;let a=document.getElementById("dd-widget-cart-template");if(!a)return;let t=x();if(!t)return;let e=document.createElement("div");e.setAttribute("data-dd-widget",""),e.classList.add("dd-widget--cart-injected");for(let n of Array.from(a.attributes))n.name!=="id"&&e.setAttribute(n.name,n.value);t.insertAdjacentElement("beforebegin",e),new b(E(e)).mount()}function R(){document.querySelectorAll("[data-dd-widget]").forEach(t=>{new b(E(t)).mount()}),N()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",R):R();})();
|