@swift-food-services/catering-widget 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6246,7 +6246,10 @@ function createPricingApi(client) {
6246
6246
  }
6247
6247
  ),
6248
6248
  calculateCateringPricingWithMealSessions: async (mealSessions, promoCodes, deliveryLocation, userId) => {
6249
- const mealSessionRequests = mealSessions.filter((s) => s.orderItems.length > 0).map((session, index) => ({
6249
+ const HHMM = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/;
6250
+ const mealSessionRequests = mealSessions.filter(
6251
+ (s) => s.orderItems.length > 0 && !!s.sessionDate?.trim() && HHMM.test(s.eventTime ?? "")
6252
+ ).map((session, index) => ({
6250
6253
  sessionName: session.sessionName || `Session ${index + 1}`,
6251
6254
  sessionDate: session.sessionDate,
6252
6255
  eventTime: session.eventTime,
@@ -6254,6 +6257,14 @@ function createPricingApi(client) {
6254
6257
  specialRequirements: session.specialRequirements,
6255
6258
  orderItems: groupItemsByRestaurant(session.orderItems)
6256
6259
  }));
6260
+ if (mealSessionRequests.length === 0) {
6261
+ return {
6262
+ isValid: false,
6263
+ subtotal: 0,
6264
+ deliveryFee: 0,
6265
+ total: 0
6266
+ };
6267
+ }
6257
6268
  try {
6258
6269
  return await client.requestJson(
6259
6270
  "/pricing/catering-verify-cart",
@@ -11264,7 +11275,8 @@ function loadGoogleMapsScript(apiKey) {
11264
11275
  }
11265
11276
 
11266
11277
  // src/hooks/useAddressAutocomplete.ts
11267
- function useAddressAutocomplete(onPlaceSelect) {
11278
+ function useAddressAutocomplete(onPlaceSelect, options = {}) {
11279
+ const countryRestriction = options.countryRestriction === void 0 ? GOOGLE_MAPS_CONFIG.COUNTRY_RESTRICTION : options.countryRestriction;
11268
11280
  const { googleMapsApiKey } = useCateringConfig();
11269
11281
  const inputRef = react.useRef(null);
11270
11282
  const containerRef = react.useRef(null);
@@ -11298,7 +11310,10 @@ function useAddressAutocomplete(onPlaceSelect) {
11298
11310
  }
11299
11311
  debounceRef.current = setTimeout(() => {
11300
11312
  autocompleteServiceRef.current.getPlacePredictions(
11301
- { input: query, componentRestrictions: { country: GOOGLE_MAPS_CONFIG.COUNTRY_RESTRICTION } },
11313
+ {
11314
+ input: query,
11315
+ ...countryRestriction ? { componentRestrictions: { country: countryRestriction } } : {}
11316
+ },
11302
11317
  (results, status) => {
11303
11318
  if (status === google.maps.places.PlacesServiceStatus.OK && results) {
11304
11319
  setPredictions(results);
@@ -15115,6 +15130,80 @@ function DeliveryAddressForm({
15115
15130
  ] })
15116
15131
  ] });
15117
15132
  }
15133
+ function BillingAddressAutocomplete({
15134
+ onPlaceSelect
15135
+ }) {
15136
+ const {
15137
+ inputRef,
15138
+ containerRef,
15139
+ query,
15140
+ setQuery,
15141
+ predictions,
15142
+ open,
15143
+ activeIndex,
15144
+ setActiveIndex,
15145
+ handleSelect,
15146
+ handleKeyDown
15147
+ } = useAddressAutocomplete(onPlaceSelect, { countryRestriction: null });
15148
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-full", ref: containerRef, children: [
15149
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-[10px] font-bold text-base-content/60 uppercase tracking-widest mb-1.5", children: [
15150
+ "Search Address",
15151
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-base-content/40 ml-2", children: "(Optional)" })
15152
+ ] }),
15153
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
15154
+ /* @__PURE__ */ jsxRuntime.jsx(
15155
+ "input",
15156
+ {
15157
+ ref: inputRef,
15158
+ type: "search",
15159
+ value: query,
15160
+ onChange: (e) => setQuery(e.target.value),
15161
+ onKeyDown: handleKeyDown,
15162
+ placeholder: "Start typing an address...",
15163
+ autoComplete: "new-password",
15164
+ className: "w-full bg-gray-50 border border-base-300 rounded-lg px-4 py-2.5 text-base text-base-content placeholder:text-base-content/50 focus:outline-none focus:ring-2 focus:ring-dark-pink/20 focus:border-dark-pink transition-all"
15165
+ }
15166
+ ),
15167
+ open && predictions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-full left-0 right-0 mt-1 rounded-lg border border-base-200 bg-white shadow-lg overflow-hidden z-50", children: predictions.map((p, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
15168
+ "button",
15169
+ {
15170
+ type: "button",
15171
+ onMouseDown: (e) => {
15172
+ e.preventDefault();
15173
+ handleSelect(p);
15174
+ },
15175
+ onMouseEnter: () => setActiveIndex(idx),
15176
+ className: `flex w-full items-start gap-2 px-3 py-2.5 text-left transition-colors ${idx === activeIndex ? "bg-base-100" : "hover:bg-base-100"}`,
15177
+ children: [
15178
+ /* @__PURE__ */ jsxRuntime.jsx(
15179
+ "svg",
15180
+ {
15181
+ className: "h-3.5 w-3.5 flex-shrink-0 text-base-content/40 mt-0.5",
15182
+ fill: "none",
15183
+ stroke: "currentColor",
15184
+ viewBox: "0 0 24 24",
15185
+ children: /* @__PURE__ */ jsxRuntime.jsx(
15186
+ "path",
15187
+ {
15188
+ strokeLinecap: "round",
15189
+ strokeLinejoin: "round",
15190
+ strokeWidth: 2,
15191
+ d: "M17.657 16.657L13.414 20.9a2 2 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0zM15 11a3 3 0 11-6 0 3 3 0 016 0z"
15192
+ }
15193
+ )
15194
+ }
15195
+ ),
15196
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
15197
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-base-content truncate", children: p.structured_formatting.main_text }),
15198
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-base-content/50 truncate", children: p.structured_formatting.secondary_text })
15199
+ ] })
15200
+ ]
15201
+ },
15202
+ p.place_id
15203
+ )) })
15204
+ ] })
15205
+ ] });
15206
+ }
15118
15207
  var fieldLabelClass = "block text-[10px] font-bold text-base-content/60 uppercase tracking-widest mb-1.5";
15119
15208
  var fieldClass = "w-full bg-gray-50 border border-base-300 rounded-lg px-4 py-2.5 text-base text-base-content placeholder:text-base-content/50 focus:outline-none focus:ring-2 focus:ring-dark-pink/20 focus:border-dark-pink transition-all";
15120
15209
  function ContactInfoForm({
@@ -15122,7 +15211,6 @@ function ContactInfoForm({
15122
15211
  errors,
15123
15212
  onFieldChange,
15124
15213
  onBlur,
15125
- onBillingBlur,
15126
15214
  ccEmails,
15127
15215
  onAddCcEmail,
15128
15216
  onRemoveCcEmail
@@ -15131,6 +15219,14 @@ function ContactInfoForm({
15131
15219
  const [showBillingAddress, setShowBillingAddress] = react.useState(
15132
15220
  !!formData.billingAddress?.line1
15133
15221
  );
15222
+ const [billingFullyOpen, setBillingFullyOpen] = react.useState(showBillingAddress);
15223
+ react.useEffect(() => {
15224
+ if (showBillingAddress) {
15225
+ const t = setTimeout(() => setBillingFullyOpen(true), 300);
15226
+ return () => clearTimeout(t);
15227
+ }
15228
+ setBillingFullyOpen(false);
15229
+ }, [showBillingAddress]);
15134
15230
  const handleBillingFieldChange = (field, value) => {
15135
15231
  const currentBilling = formData.billingAddress || {
15136
15232
  line1: "",
@@ -15147,6 +15243,50 @@ function ContactInfoForm({
15147
15243
  }
15148
15244
  );
15149
15245
  };
15246
+ const handleBillingPlaceSelect = (place) => {
15247
+ if (!place || !place.address_components) return;
15248
+ let streetNumber = "";
15249
+ let route = "";
15250
+ let premise = "";
15251
+ let subpremise = "";
15252
+ let city = "";
15253
+ let postalCode = "";
15254
+ let country = "";
15255
+ place.address_components.forEach((component) => {
15256
+ const types = component.types;
15257
+ if (types.includes("street_number")) streetNumber = component.long_name;
15258
+ if (types.includes("route")) route = component.long_name;
15259
+ if (types.includes("premise")) premise = component.long_name;
15260
+ if (types.includes("subpremise")) subpremise = component.long_name;
15261
+ if (types.includes("postal_town") || types.includes("locality") || types.includes("sublocality") || types.includes("administrative_area_level_2")) {
15262
+ if (!city) city = component.long_name;
15263
+ }
15264
+ if (types.includes("postal_code")) postalCode = component.long_name;
15265
+ if (types.includes("country")) country = component.short_name;
15266
+ });
15267
+ const streetParts = [subpremise, premise, streetNumber, route].map((p) => p.trim()).filter(Boolean);
15268
+ let line1 = streetParts.join(" ").trim();
15269
+ if (!line1) {
15270
+ line1 = place.name?.trim() || place.formatted_address?.split(",")[0]?.trim() || "";
15271
+ }
15272
+ const currentBilling = formData.billingAddress || {
15273
+ line1: "",
15274
+ line2: "",
15275
+ city: "",
15276
+ postalCode: "",
15277
+ country: "GB"
15278
+ };
15279
+ onFieldChange(
15280
+ "billingAddress",
15281
+ {
15282
+ ...currentBilling,
15283
+ line1,
15284
+ city,
15285
+ postalCode,
15286
+ country: country || currentBilling.country
15287
+ }
15288
+ );
15289
+ };
15150
15290
  const handleAddCcEmail = () => {
15151
15291
  const trimmedEmail = ccEmailInput.trim();
15152
15292
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -15353,78 +15493,72 @@ function ContactInfoForm({
15353
15493
  {
15354
15494
  "aria-hidden": !showBillingAddress,
15355
15495
  className: `grid transition-all duration-300 ease-out ${showBillingAddress ? "grid-rows-[1fr] opacity-100 mt-4" : "grid-rows-[0fr] opacity-0 mt-0"}`,
15356
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
15357
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-full", children: [
15358
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: fieldLabelClass, children: [
15359
- "Address Line 1",
15360
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-dark-pink ml-0.5", children: "*" })
15361
- ] }),
15362
- /* @__PURE__ */ jsxRuntime.jsx(
15363
- "input",
15364
- {
15365
- type: "text",
15366
- value: formData.billingAddress?.line1 || "",
15367
- onChange: (e) => handleBillingFieldChange("line1", e.target.value),
15368
- onBlur: () => onBillingBlur("line1"),
15369
- placeholder: "Street address",
15370
- className: `${fieldClass} ${errors.billingAddress?.line1 ? "border-error" : ""}`
15371
- }
15372
- ),
15373
- errors.billingAddress?.line1 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-error", children: errors.billingAddress.line1 })
15374
- ] }),
15375
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-full", children: [
15376
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: fieldLabelClass, children: [
15377
- "Address Line 2",
15378
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-base-content/40 ml-2", children: "(Optional)" })
15379
- ] }),
15380
- /* @__PURE__ */ jsxRuntime.jsx(
15381
- "input",
15382
- {
15383
- type: "text",
15384
- value: formData.billingAddress?.line2 || "",
15385
- onChange: (e) => handleBillingFieldChange("line2", e.target.value),
15386
- placeholder: "Apartment, suite, etc.",
15387
- className: fieldClass
15388
- }
15389
- )
15390
- ] }),
15391
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
15392
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: fieldLabelClass, children: [
15393
- "City",
15394
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-dark-pink ml-0.5", children: "*" })
15395
- ] }),
15396
- /* @__PURE__ */ jsxRuntime.jsx(
15397
- "input",
15398
- {
15399
- type: "text",
15400
- value: formData.billingAddress?.city || "",
15401
- onChange: (e) => handleBillingFieldChange("city", e.target.value),
15402
- onBlur: () => onBillingBlur("city"),
15403
- placeholder: "City",
15404
- className: `${fieldClass} ${errors.billingAddress?.city ? "border-error" : ""}`
15405
- }
15406
- ),
15407
- errors.billingAddress?.city && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-error", children: errors.billingAddress.city })
15408
- ] }),
15409
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
15410
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: fieldLabelClass, children: [
15411
- "Postcode",
15412
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-dark-pink ml-0.5", children: "*" })
15413
- ] }),
15414
- /* @__PURE__ */ jsxRuntime.jsx(
15415
- "input",
15416
- {
15417
- type: "text",
15418
- value: formData.billingAddress?.postalCode || "",
15419
- onChange: (e) => handleBillingFieldChange("postalCode", e.target.value.toUpperCase()),
15420
- onBlur: () => onBillingBlur("postalCode"),
15421
- placeholder: "SW1A 1AA",
15422
- className: `${fieldClass} ${errors.billingAddress?.postalCode ? "border-error" : ""}`
15423
- }
15424
- ),
15425
- errors.billingAddress?.postalCode && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-error", children: errors.billingAddress.postalCode })
15426
- ] })
15427
- ] }) })
15496
+ children: /* @__PURE__ */ jsxRuntime.jsx(
15497
+ "div",
15498
+ {
15499
+ className: `min-h-0 ${billingFullyOpen ? "overflow-visible" : "overflow-hidden"}`,
15500
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
15501
+ /* @__PURE__ */ jsxRuntime.jsx(
15502
+ BillingAddressAutocomplete,
15503
+ {
15504
+ onPlaceSelect: handleBillingPlaceSelect
15505
+ }
15506
+ ),
15507
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-full", children: [
15508
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: fieldLabelClass, children: "Address Line 1" }),
15509
+ /* @__PURE__ */ jsxRuntime.jsx(
15510
+ "input",
15511
+ {
15512
+ type: "text",
15513
+ value: formData.billingAddress?.line1 || "",
15514
+ onChange: (e) => handleBillingFieldChange("line1", e.target.value),
15515
+ placeholder: "Street address",
15516
+ className: fieldClass
15517
+ }
15518
+ )
15519
+ ] }),
15520
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-full", children: [
15521
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: fieldLabelClass, children: "Address Line 2" }),
15522
+ /* @__PURE__ */ jsxRuntime.jsx(
15523
+ "input",
15524
+ {
15525
+ type: "text",
15526
+ value: formData.billingAddress?.line2 || "",
15527
+ onChange: (e) => handleBillingFieldChange("line2", e.target.value),
15528
+ placeholder: "Apartment, suite, etc.",
15529
+ className: fieldClass
15530
+ }
15531
+ )
15532
+ ] }),
15533
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
15534
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: fieldLabelClass, children: "City" }),
15535
+ /* @__PURE__ */ jsxRuntime.jsx(
15536
+ "input",
15537
+ {
15538
+ type: "text",
15539
+ value: formData.billingAddress?.city || "",
15540
+ onChange: (e) => handleBillingFieldChange("city", e.target.value),
15541
+ placeholder: "City",
15542
+ className: fieldClass
15543
+ }
15544
+ )
15545
+ ] }),
15546
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
15547
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: fieldLabelClass, children: "Postcode / ZIP" }),
15548
+ /* @__PURE__ */ jsxRuntime.jsx(
15549
+ "input",
15550
+ {
15551
+ type: "text",
15552
+ value: formData.billingAddress?.postalCode || "",
15553
+ onChange: (e) => handleBillingFieldChange("postalCode", e.target.value),
15554
+ placeholder: "Postcode or ZIP",
15555
+ className: fieldClass
15556
+ }
15557
+ )
15558
+ ] })
15559
+ ] })
15560
+ }
15561
+ )
15428
15562
  }
15429
15563
  )
15430
15564
  ] })
@@ -15726,37 +15860,6 @@ function CheckoutScreen() {
15726
15860
  [field]: error
15727
15861
  }));
15728
15862
  };
15729
- const handleBillingBlur = (field) => {
15730
- if (!hasBillingAddressData()) return;
15731
- const billing = formData.billingAddress;
15732
- let error;
15733
- switch (field) {
15734
- case "line1":
15735
- if (!billing?.line1?.trim()) {
15736
- error = "Address line 1 is required";
15737
- }
15738
- break;
15739
- case "city":
15740
- if (!billing?.city?.trim()) {
15741
- error = "City is required";
15742
- }
15743
- break;
15744
- case "postalCode":
15745
- if (!billing?.postalCode?.trim()) {
15746
- error = "Postcode is required";
15747
- } else if (!validateUKPostcode(billing.postalCode)) {
15748
- error = "Please enter a valid UK postcode (e.g., SW1A 1AA)";
15749
- }
15750
- break;
15751
- }
15752
- setErrors((prev) => ({
15753
- ...prev,
15754
- billingAddress: {
15755
- ...prev.billingAddress,
15756
- [field]: error
15757
- }
15758
- }));
15759
- };
15760
15863
  const handleChange = (field, value) => {
15761
15864
  setFormData((prev) => ({ ...prev, [field]: value }));
15762
15865
  if (field in errors) {
@@ -15768,28 +15871,6 @@ function CheckoutScreen() {
15768
15871
  if (!postcode) return false;
15769
15872
  return UK_POSTCODE_REGEX.test(postcode.trim());
15770
15873
  };
15771
- const hasBillingAddressData = () => {
15772
- const billing = formData.billingAddress;
15773
- if (!billing) return false;
15774
- return !!(billing.line1?.trim() || billing.city?.trim() || billing.postalCode?.trim());
15775
- };
15776
- const validateBillingAddress = () => {
15777
- if (!hasBillingAddressData()) return void 0;
15778
- const billing = formData.billingAddress;
15779
- const billingErrors = {};
15780
- if (!billing?.line1?.trim()) {
15781
- billingErrors.line1 = "Address line 1 is required";
15782
- }
15783
- if (!billing?.city?.trim()) {
15784
- billingErrors.city = "City is required";
15785
- }
15786
- if (!billing?.postalCode?.trim()) {
15787
- billingErrors.postalCode = "Postcode is required";
15788
- } else if (!validateUKPostcode(billing.postalCode)) {
15789
- billingErrors.postalCode = "Please enter a valid UK postcode (e.g., SW1A 1AA)";
15790
- }
15791
- return Object.keys(billingErrors).length > 0 ? billingErrors : void 0;
15792
- };
15793
15874
  const validateForm = () => {
15794
15875
  const newErrors = {
15795
15876
  fullName: validateFullName(formData.fullName),
@@ -15812,11 +15893,8 @@ function CheckoutScreen() {
15812
15893
  newErrors.addressLine1 = "Please select an address from the Google autocomplete dropdown";
15813
15894
  }
15814
15895
  }
15815
- newErrors.billingAddress = validateBillingAddress();
15816
15896
  setErrors(newErrors);
15817
- const hasBasicErrors = Object.entries(newErrors).filter(([key]) => key !== "billingAddress").some(([, error]) => error !== void 0);
15818
- const hasBillingErrors = newErrors.billingAddress !== void 0;
15819
- return !hasBasicErrors && !hasBillingErrors;
15897
+ return !Object.values(newErrors).some((error) => error !== void 0);
15820
15898
  };
15821
15899
  const handleAddCcEmail = (email) => {
15822
15900
  setCcEmails([...ccEmails, email]);
@@ -16677,7 +16755,6 @@ function CheckoutScreen() {
16677
16755
  errors,
16678
16756
  onFieldChange: handleChange,
16679
16757
  onBlur: handleBlur,
16680
- onBillingBlur: handleBillingBlur,
16681
16758
  ccEmails,
16682
16759
  onAddCcEmail: handleAddCcEmail,
16683
16760
  onRemoveCcEmail: handleRemoveCcEmail