@swift-food-services/catering-widget 0.2.0-beta.4 → 0.2.0-beta.6

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
@@ -33,7 +33,7 @@ function styleInject(css, { insertAt } = {}) {
33
33
  }
34
34
 
35
35
  // src/styles/generated.css
36
- styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
36
+ styleInject(`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
37
37
  @layer properties {
38
38
  @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) {
39
39
  .swift-catering-widget *,
@@ -667,19 +667,17 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
667
667
  --depth:1;
668
668
  --noise:0;
669
669
  }
670
- .swift-catering-widget {
671
- --fx-noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E");
672
- scrollbar-color: currentColor #0000;
673
- }
674
- @supports (color:color-mix(in lab, red, red)) {
675
- .swift-catering-widget {
676
- scrollbar-color: color-mix(in oklch, currentColor 35%, #0000) #0000;
677
- }
678
- }
679
- @property --radialprogress { syntax:"<percentage>";inherits:true;initial-value:0% }
680
670
  .swift-catering-widget :root:not(span) {
681
671
  overflow: var(--page-overflow);
682
672
  }
673
+ .swift-catering-widget,
674
+ .swift-catering-widget [data-theme] {
675
+ background: var(--page-scroll-bg,var(--root-bg));
676
+ color: var(--color-base-content);
677
+ }
678
+ .swift-catering-widget :where(:root, [data-theme]) {
679
+ --root-bg:var(--color-base-100);
680
+ }
683
681
  .swift-catering-widget {
684
682
  background: var(--page-scroll-bg,var(--root-bg));
685
683
  --page-scroll-bg-on:linear-gradient(var(--root-bg,#0000), var(--root-bg,#0000)) var(--root-bg,#0000);
@@ -700,14 +698,16 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
700
698
  --page-has-scroll:1;
701
699
  }
702
700
  }
703
- .swift-catering-widget,
704
- .swift-catering-widget [data-theme] {
705
- background: var(--page-scroll-bg,var(--root-bg));
706
- color: var(--color-base-content);
701
+ .swift-catering-widget {
702
+ --fx-noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E");
703
+ scrollbar-color: currentColor #0000;
707
704
  }
708
- .swift-catering-widget :where(:root, [data-theme]) {
709
- --root-bg:var(--color-base-100);
705
+ @supports (color:color-mix(in lab, red, red)) {
706
+ .swift-catering-widget {
707
+ scrollbar-color: color-mix(in oklch, currentColor 35%, #0000) #0000;
708
+ }
710
709
  }
710
+ @property --radialprogress { syntax:"<percentage>";inherits:true;initial-value:0% }
711
711
  }
712
712
  @layer components {
713
713
  .swift-catering-widget .swift-btn-primary {
@@ -1323,7 +1323,8 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
1323
1323
  transition-property:
1324
1324
  opacity,
1325
1325
  scale,
1326
- display;
1326
+ display,
1327
+ overlay;
1327
1328
  transition-duration: .2s;
1328
1329
  transition-timing-function: cubic-bezier(.4, 0, .2, 1);
1329
1330
  animation: .2s dropdown;
@@ -3543,12 +3544,6 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
3543
3544
  .swift-catering-widget .inset-x-4 {
3544
3545
  inset-inline: calc(var(--spacing) * 4);
3545
3546
  }
3546
- .swift-catering-widget .start {
3547
- inset-inline-start: var(--spacing);
3548
- }
3549
- .swift-catering-widget .end {
3550
- inset-inline-end: var(--spacing);
3551
- }
3552
3547
  .swift-catering-widget .-top-14 {
3553
3548
  top: calc(var(--spacing) * -14);
3554
3549
  }
@@ -3854,6 +3849,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
3854
3849
  .swift-catering-widget .mb-2 {
3855
3850
  margin-bottom: calc(var(--spacing) * 2);
3856
3851
  }
3852
+ .swift-catering-widget .mb-2\\.5 {
3853
+ margin-bottom: calc(var(--spacing) * 2.5);
3854
+ }
3857
3855
  .swift-catering-widget .mb-3 {
3858
3856
  margin-bottom: calc(var(--spacing) * 3);
3859
3857
  }
@@ -4053,8 +4051,8 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
4053
4051
  .swift-catering-widget .min-h-16 {
4054
4052
  min-height: calc(var(--spacing) * 16);
4055
4053
  }
4056
- .swift-catering-widget .min-h-\\[22px\\] {
4057
- min-height: 22px;
4054
+ .swift-catering-widget .min-h-\\[18px\\] {
4055
+ min-height: 18px;
4058
4056
  }
4059
4057
  .swift-catering-widget .min-h-\\[100px\\] {
4060
4058
  min-height: 100px;
@@ -5352,6 +5350,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5352
5350
  .swift-catering-widget .pl-4 {
5353
5351
  padding-left: calc(var(--spacing) * 4);
5354
5352
  }
5353
+ .swift-catering-widget .pl-6 {
5354
+ padding-left: calc(var(--spacing) * 6);
5355
+ }
5355
5356
  .swift-catering-widget .pl-9 {
5356
5357
  padding-left: calc(var(--spacing) * 9);
5357
5358
  }
@@ -6963,10 +6964,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
6963
6964
  transform: translate(100%);
6964
6965
  }
6965
6966
  }
6966
- @keyframes rating {
6967
- 0%, 40% {
6968
- filter: brightness(1.05)contrast(1.05);
6969
- scale: 1.1;
6967
+ @keyframes menu {
6968
+ 0% {
6969
+ opacity: 0;
6970
6970
  }
6971
6971
  }
6972
6972
  @keyframes dropdown {
@@ -6974,12 +6974,23 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
6974
6974
  opacity: 0;
6975
6975
  }
6976
6976
  }
6977
- @keyframes radio {
6977
+ @keyframes skeleton {
6978
6978
  0% {
6979
- padding: 5px;
6979
+ background-position: 150%;
6980
+ }
6981
+ to {
6982
+ background-position: -50%;
6983
+ }
6984
+ }
6985
+ @keyframes rating {
6986
+ 0%, 40% {
6987
+ filter: brightness(1.05)contrast(1.05);
6988
+ scale: 1.1;
6980
6989
  }
6990
+ }
6991
+ @keyframes progress {
6981
6992
  50% {
6982
- padding: 3px;
6993
+ background-position-x: -115%;
6983
6994
  }
6984
6995
  }
6985
6996
  @keyframes toast {
@@ -7003,22 +7014,12 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
7003
7014
  translate: 0 -100%;
7004
7015
  }
7005
7016
  }
7006
- @keyframes skeleton {
7007
- 0% {
7008
- background-position: 150%;
7009
- }
7010
- to {
7011
- background-position: -50%;
7012
- }
7013
- }
7014
- @keyframes menu {
7017
+ @keyframes radio {
7015
7018
  0% {
7016
- opacity: 0;
7019
+ padding: 5px;
7017
7020
  }
7018
- }
7019
- @keyframes progress {
7020
7021
  50% {
7021
- background-position-x: -115%;
7022
+ padding: 3px;
7022
7023
  }
7023
7024
  }
7024
7025
  @property --tw-translate-x { syntax:"*";inherits:false;initial-value:0 }
@@ -7120,6 +7121,9 @@ function useCateringConfig() {
7120
7121
  }
7121
7122
  return ctx;
7122
7123
  }
7124
+ function useApiClient() {
7125
+ return useCateringConfig().api;
7126
+ }
7123
7127
  function useStorage() {
7124
7128
  return useCateringConfig().storage;
7125
7129
  }
@@ -7559,6 +7563,15 @@ function createChatEndpoints(client) {
7559
7563
  if (!res.ok) throw new ChatApiError(`HTTP ${res.status}`, res.status);
7560
7564
  return res.json();
7561
7565
  },
7566
+ detectFields: async (message, signal) => {
7567
+ const res = await client.request("/catering-chat/detect-fields", {
7568
+ method: "POST",
7569
+ body: JSON.stringify({ message }),
7570
+ signal
7571
+ });
7572
+ if (!res.ok) throw new ChatApiError(`HTTP ${res.status}`, res.status);
7573
+ return res.json();
7574
+ },
7562
7575
  submitFeedback: async (sid, body) => {
7563
7576
  const payload = { rating: body.rating };
7564
7577
  if (body.note && body.note.trim()) payload.note = body.note.trim();
@@ -8136,7 +8149,8 @@ var STORAGE_KEY = "catering_filters";
8136
8149
  var defaultFilters = {
8137
8150
  dietaryRestrictions: [],
8138
8151
  allergens: [],
8139
- pricePerPersonRange: null
8152
+ pricePerPersonRange: null,
8153
+ restaurantPriceRange: null
8140
8154
  };
8141
8155
  function CateringFilterProvider({ children }) {
8142
8156
  const storage = useStorage();
@@ -8166,6 +8180,15 @@ function CateringFilterProvider({ children }) {
8166
8180
  const clearFilters = () => setFiltersState(defaultFilters);
8167
8181
  return /* @__PURE__ */ jsxRuntime.jsx(FilterContext.Provider, { value: { filters, setFilters, clearFilters }, children });
8168
8182
  }
8183
+ function useCateringFilters() {
8184
+ const context = react.useContext(FilterContext);
8185
+ if (context === void 0) {
8186
+ throw new Error(
8187
+ "useCateringFilters must be used within a CateringFilterProvider"
8188
+ );
8189
+ }
8190
+ return context;
8191
+ }
8169
8192
  function useSearchParams() {
8170
8193
  return new URLSearchParams();
8171
8194
  }
@@ -9452,7 +9475,7 @@ function MenuItemModal({
9452
9475
  onClick: () => handleSaveAiPick(true),
9453
9476
  disabled: isMinSelectionsUnmet,
9454
9477
  className: `w-full py-3 rounded-lg font-medium transition-all text-sm ${isMinSelectionsUnmet ? "bg-base-300 text-base-content/50 cursor-not-allowed" : "bg-primary hover:opacity-90 text-white"}`,
9455
- children: isPickedInAiMenu ? "Save selections" : "Pick \xB7 Save selections"
9478
+ children: addButtonLabel ?? (isPickedInAiMenu ? "Save selections" : "Pick \xB7 Save selections")
9456
9479
  }
9457
9480
  ),
9458
9481
  isPickedInAiMenu && /* @__PURE__ */ jsxRuntime.jsx(
@@ -11267,7 +11290,8 @@ function SessionEditor({
11267
11290
  onUpdate,
11268
11291
  onClose,
11269
11292
  restaurants,
11270
- existingDates = []
11293
+ existingDates = [],
11294
+ existingSessions = []
11271
11295
  }) {
11272
11296
  const [sessionName, setSessionName] = react.useState(session.sessionName);
11273
11297
  const [sessionDate, setSessionDate] = react.useState(session.sessionDate);
@@ -11347,6 +11371,10 @@ function SessionEditor({
11347
11371
  );
11348
11372
  return;
11349
11373
  }
11374
+ if (existingSessions.some((s) => s.date === sessionDate && s.time === eventTime)) {
11375
+ setValidationError("A session with the same date and time already exists.");
11376
+ return;
11377
+ }
11350
11378
  if (eventStartDate && sessionDate < eventStartDate) {
11351
11379
  setValidationError("Session date is before the event starts.");
11352
11380
  return;
@@ -11460,7 +11488,7 @@ function SessionEditor({
11460
11488
  ] }),
11461
11489
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
11462
11490
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Date" }),
11463
- existingDates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 overflow-x-auto scrollbar-hide pb-2 mb-2", children: existingDates.map((d) => {
11491
+ existingDates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 overflow-x-auto scrollbar-hide pb-2 mb-2", children: [...existingDates].sort((a, b) => a.date.localeCompare(b.date)).map((d) => {
11464
11492
  const isActive = sessionDate === d.date;
11465
11493
  return /* @__PURE__ */ jsxRuntime.jsxs(
11466
11494
  "button",
@@ -13514,6 +13542,10 @@ function ChatSessionProvider({
13514
13542
  setFeedbackTarget({ messageId, up, eventId });
13515
13543
  setFeedbackRating(0);
13516
13544
  },
13545
+ startGeneralFeedback: () => {
13546
+ setFeedbackTarget({ messageId: "__general__", up: false });
13547
+ setFeedbackRating(0);
13548
+ },
13517
13549
  cancelFeedback: () => {
13518
13550
  setFeedbackTarget(null);
13519
13551
  setFeedbackRating(0);
@@ -13735,21 +13767,23 @@ function DraftItemRow({
13735
13767
  }
13736
13768
  ),
13737
13769
  item.reason && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 4 }, children: /* @__PURE__ */ jsxRuntime.jsx(Reason, { children: item.reason }) }),
13738
- (dietary.length > 0 || allergens.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6 }, children: [
13739
- dietary.map((d) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dietary-pill", children: d.replace(/_/g, " ") }, `d-${d}`)),
13740
- allergens.slice(0, 3).map((a) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "allergen-pill", children: a.toLowerCase().replace(/_/g, " ") }, `a-${a}`)),
13741
- allergens.length > 3 && /* @__PURE__ */ jsxRuntime.jsxs(
13742
- "span",
13770
+ dietary.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: 3, marginTop: 6 }, children: dietary.map((d) => {
13771
+ const iconInfo = DIETARY_ICON_MAP[d.toLowerCase()];
13772
+ if (!iconInfo) return null;
13773
+ return /* @__PURE__ */ jsxRuntime.jsx(
13774
+ "img",
13743
13775
  {
13744
- className: "allergen-pill",
13745
- title: allergens.slice(3).map((a) => a.toLowerCase().replace(/_/g, " ")).join(", "),
13746
- style: { cursor: "help" },
13747
- children: [
13748
- "+",
13749
- allergens.length - 3
13750
- ]
13751
- }
13752
- )
13776
+ src: iconInfo.src,
13777
+ alt: iconInfo.label,
13778
+ title: iconInfo.label,
13779
+ style: { width: 16, height: 16, objectFit: "contain" }
13780
+ },
13781
+ d
13782
+ );
13783
+ }) }),
13784
+ allergens.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.65rem", color: "#b0b0b0", margin: "4px 0 0", lineHeight: 1.4 }, children: [
13785
+ "Contains: ",
13786
+ allergens.map((a) => a.toLowerCase().replace(/_/g, " ")).join(", ")
13753
13787
  ] })
13754
13788
  ] }),
13755
13789
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -13819,19 +13853,6 @@ function DraftItemRow({
13819
13853
  children: "Swap"
13820
13854
  }
13821
13855
  ),
13822
- onRemove && /* @__PURE__ */ jsxRuntime.jsx(
13823
- "button",
13824
- {
13825
- type: "button",
13826
- className: "chip",
13827
- onClick: (e) => {
13828
- e.stopPropagation();
13829
- onRemove();
13830
- },
13831
- style: { height: 26, fontSize: "0.75rem" },
13832
- children: "Remove"
13833
- }
13834
- ),
13835
13856
  (addonSummary || addonState) && /* @__PURE__ */ jsxRuntime.jsx(
13836
13857
  "button",
13837
13858
  {
@@ -14902,16 +14923,21 @@ function PreviewItemCard({
14902
14923
  dietary.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
14903
14924
  "div",
14904
14925
  {
14905
- style: { display: "flex", flexWrap: "wrap", gap: 3, marginTop: 4 },
14906
- children: dietary.map((d) => /* @__PURE__ */ jsxRuntime.jsx(
14907
- "span",
14908
- {
14909
- className: "dietary-pill",
14910
- style: { fontSize: "0.62rem" },
14911
- children: d.replace(/_/g, " ")
14912
- },
14913
- d
14914
- ))
14926
+ style: { display: "flex", alignItems: "center", gap: 3, marginTop: 4 },
14927
+ children: dietary.map((d) => {
14928
+ const iconInfo = DIETARY_ICON_MAP[d.toLowerCase()];
14929
+ if (!iconInfo) return null;
14930
+ return /* @__PURE__ */ jsxRuntime.jsx(
14931
+ "img",
14932
+ {
14933
+ src: iconInfo.src,
14934
+ alt: iconInfo.label,
14935
+ title: iconInfo.label,
14936
+ style: { width: 14, height: 14, objectFit: "contain" }
14937
+ },
14938
+ d
14939
+ );
14940
+ })
14915
14941
  }
14916
14942
  )
14917
14943
  ]
@@ -16324,6 +16350,26 @@ function getOverlayRoot() {
16324
16350
  }
16325
16351
  return document.querySelector("[data-swift-overlay-root]") ?? document.body;
16326
16352
  }
16353
+ function swapOptionToMenuItem(o, restaurantId) {
16354
+ return {
16355
+ id: o.menuItemId,
16356
+ menuItemName: o.name,
16357
+ description: o.description ?? void 0,
16358
+ price: String(o.unitPrice),
16359
+ discountPrice: o.discountPrice != null ? String(o.discountPrice) : void 0,
16360
+ isDiscount: o.isDiscount,
16361
+ image: o.imageUrl ?? void 0,
16362
+ allergens: o.allergens,
16363
+ dietaryFilters: o.dietaryFilters,
16364
+ cateringQuantityUnit: o.cateringQuantityUnit,
16365
+ feedsPerUnit: o.feedsPerUnit,
16366
+ minOrderQuantity: o.minOrderQuantity,
16367
+ restaurantId,
16368
+ groupTitle: o.groupTitle ?? void 0,
16369
+ itemDisplayOrder: 0,
16370
+ addons: o.addons ?? []
16371
+ };
16372
+ }
16327
16373
  function SwapModal({
16328
16374
  open,
16329
16375
  sessionId,
@@ -16341,10 +16387,12 @@ function SwapModal({
16341
16387
  const [options, setOptions] = react.useState(null);
16342
16388
  const [loading, setLoading] = react.useState(false);
16343
16389
  const [error, setError] = react.useState(null);
16390
+ const [selected, setSelected] = react.useState(null);
16344
16391
  react.useEffect(() => {
16345
16392
  if (!open || !restaurantId || !category) {
16346
16393
  setOptions(null);
16347
16394
  setError(null);
16395
+ setSelected(null);
16348
16396
  return;
16349
16397
  }
16350
16398
  let cancelled = false;
@@ -16369,6 +16417,24 @@ function SwapModal({
16369
16417
  };
16370
16418
  }, [open, sessionId, restaurantId, category, excludeIds.join(","), intentPhrase]);
16371
16419
  if (typeof document === "undefined") return null;
16420
+ if (selected && restaurantId) {
16421
+ return /* @__PURE__ */ jsxRuntime.jsx(
16422
+ MenuItemModal,
16423
+ {
16424
+ item: swapOptionToMenuItem(selected, restaurantId),
16425
+ isOpen: true,
16426
+ onClose: () => setSelected(null),
16427
+ viewOnly: true,
16428
+ aiAddonMode: true,
16429
+ addButtonLabel: "Confirm Swap",
16430
+ isPickedInAiMenu: false,
16431
+ onSaveAiPickAddons: (selections) => {
16432
+ onPick(selected, selections);
16433
+ setSelected(null);
16434
+ }
16435
+ }
16436
+ );
16437
+ }
16372
16438
  const modal = /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: open && /* @__PURE__ */ jsxRuntime.jsx(
16373
16439
  react$1.motion.div,
16374
16440
  {
@@ -16377,7 +16443,16 @@ function SwapModal({
16377
16443
  exit: { opacity: 0 },
16378
16444
  transition: { duration: 0.18 },
16379
16445
  onClick: onClose,
16380
- style: overlayStyle,
16446
+ style: {
16447
+ position: "fixed",
16448
+ inset: 0,
16449
+ background: "rgba(20, 18, 16, 0.45)",
16450
+ display: "flex",
16451
+ alignItems: "center",
16452
+ justifyContent: "center",
16453
+ padding: 24,
16454
+ zIndex: 60
16455
+ },
16381
16456
  children: /* @__PURE__ */ jsxRuntime.jsxs(
16382
16457
  react$1.motion.div,
16383
16458
  {
@@ -16386,44 +16461,65 @@ function SwapModal({
16386
16461
  exit: prefersReducedMotion ? void 0 : { opacity: 0, y: 6, scale: 0.98 },
16387
16462
  transition: { duration: 0.22, ease: [0.22, 1, 0.36, 1] },
16388
16463
  onClick: (e) => e.stopPropagation(),
16389
- style: panelStyle,
16464
+ style: {
16465
+ background: "#fff",
16466
+ borderRadius: 16,
16467
+ boxShadow: "0 20px 60px rgba(0,0,0,0.15)",
16468
+ border: "1px solid #e5e7eb",
16469
+ width: "100%",
16470
+ maxWidth: 480,
16471
+ maxHeight: "80vh",
16472
+ overflow: "hidden",
16473
+ display: "flex",
16474
+ flexDirection: "column",
16475
+ padding: 24
16476
+ },
16390
16477
  children: [
16391
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "small-caps", style: { marginBottom: 4 }, children: "Swap this for" }),
16392
- /* @__PURE__ */ jsxRuntime.jsx(
16393
- "div",
16394
- {
16395
- className: "display-italic",
16396
- style: { fontSize: "0.92rem", color: "var(--ink-soft)", marginBottom: 14 },
16397
- children: itemName
16398
- }
16399
- ),
16400
- loading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 24, textAlign: "center", color: "var(--ink-faint)", fontSize: "0.85rem" }, children: "Finding alternatives\u2026" }),
16401
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...errorStyle, marginBottom: 12 }, children: error }),
16402
- options && options.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 24, textAlign: "center", color: "var(--ink-faint)", fontSize: "0.85rem" }, children: "No other options available in this category at this restaurant." }),
16403
- options && options.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
16404
- react$1.motion.ul,
16405
- {
16406
- initial: "hidden",
16407
- animate: "visible",
16408
- variants: {
16409
- hidden: {},
16410
- visible: { transition: { staggerChildren: 0.06, delayChildren: 0.04 } }
16411
- },
16412
- style: {
16413
- listStyle: "none",
16414
- margin: 0,
16415
- padding: 0,
16416
- display: "flex",
16417
- flexDirection: "column",
16418
- gap: 8,
16419
- maxHeight: "60vh",
16420
- overflowY: "auto",
16421
- WebkitOverflowScrolling: "touch"
16422
- },
16423
- children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SwapCard, { option: opt, onPick: () => onPick(opt) }, opt.menuItemId))
16424
- }
16425
- ),
16426
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 14 }, children: /* @__PURE__ */ jsxRuntime.jsx(Chip, { onClick: onClose, children: "Keep current" }) })
16478
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 16 }, children: [
16479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
16480
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.06em", color: "#9ca3af", margin: 0 }, children: "Swap this for" }),
16481
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: 16, fontWeight: 600, color: "#1f2937", margin: "4px 0 0" }, children: itemName })
16482
+ ] }),
16483
+ /* @__PURE__ */ jsxRuntime.jsx(
16484
+ "button",
16485
+ {
16486
+ type: "button",
16487
+ onClick: onClose,
16488
+ style: {
16489
+ display: "flex",
16490
+ alignItems: "center",
16491
+ justifyContent: "center",
16492
+ width: 32,
16493
+ height: 32,
16494
+ borderRadius: 8,
16495
+ border: "none",
16496
+ background: "transparent",
16497
+ color: "#9ca3af",
16498
+ cursor: "pointer",
16499
+ flexShrink: 0
16500
+ },
16501
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { style: { width: 16, height: 16 } })
16502
+ }
16503
+ )
16504
+ ] }),
16505
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minHeight: 0, overflowY: "auto", margin: "0 -24px", padding: "0 24px" }, children: [
16506
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "48px 0", textAlign: "center", color: "#9ca3af", fontSize: 14 }, children: "Finding alternatives\u2026" }),
16507
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderRadius: 10, background: "#fef2f2", border: "1px solid #fecaca", padding: "8px 12px", fontSize: 14, color: "#dc2626", marginBottom: 12 }, children: error }),
16508
+ options && options.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "48px 0", textAlign: "center", color: "#9ca3af", fontSize: 14 }, children: "No other options available in this category." }),
16509
+ options && options.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
16510
+ react$1.motion.div,
16511
+ {
16512
+ initial: "hidden",
16513
+ animate: "visible",
16514
+ variants: {
16515
+ hidden: {},
16516
+ visible: { transition: { staggerChildren: 0.04, delayChildren: 0.02 } }
16517
+ },
16518
+ style: { display: "flex", flexDirection: "column" },
16519
+ children: options.map((opt, i) => /* @__PURE__ */ jsxRuntime.jsx(SwapCard, { option: opt, onPick: () => setSelected(opt), showDivider: i < options.length - 1 }, opt.menuItemId))
16520
+ }
16521
+ )
16522
+ ] })
16427
16523
  ]
16428
16524
  }
16429
16525
  )
@@ -16433,112 +16529,118 @@ function SwapModal({
16433
16529
  }
16434
16530
  function SwapCard({
16435
16531
  option,
16436
- onPick
16532
+ onPick,
16533
+ showDivider
16437
16534
  }) {
16438
16535
  const prefersReducedMotion = react$1.useReducedMotion();
16439
16536
  const dietary = (option.dietaryFilters ?? []).filter(Boolean);
16440
- const allergens = (option.allergens ?? []).filter(
16441
- (a) => a && a.toLowerCase() !== "no specific allergens"
16442
- );
16537
+ const description = option.description ?? "";
16538
+ const [hovered, setHovered] = react.useState(false);
16443
16539
  return /* @__PURE__ */ jsxRuntime.jsxs(
16444
- react$1.motion.li,
16540
+ react$1.motion.button,
16445
16541
  {
16542
+ type: "button",
16446
16543
  variants: {
16447
- hidden: { opacity: 0, y: 8 },
16448
- visible: { opacity: 1, y: 0, transition: { duration: 0.22, ease: "easeOut" } }
16544
+ hidden: { opacity: 0, y: 6 },
16545
+ visible: { opacity: 1, y: 0, transition: { duration: 0.2, ease: "easeOut" } }
16449
16546
  },
16450
16547
  whileHover: prefersReducedMotion ? void 0 : { y: -1 },
16451
16548
  whileTap: { scale: 0.99 },
16452
16549
  onClick: onPick,
16550
+ onMouseEnter: () => setHovered(true),
16551
+ onMouseLeave: () => setHovered(false),
16453
16552
  style: {
16454
16553
  display: "flex",
16455
- gap: 10,
16456
- padding: "10px",
16457
- borderRadius: 12,
16458
- border: "1px solid var(--rule)",
16459
- background: "var(--paper)",
16554
+ gap: 12,
16555
+ padding: "12px 4px",
16556
+ borderRadius: 8,
16557
+ border: "none",
16558
+ borderBottom: showDivider ? "1px solid #f0f0f0" : "none",
16559
+ background: hovered ? "#f9fafb" : "transparent",
16460
16560
  cursor: "pointer",
16461
- alignItems: "center"
16561
+ textAlign: "left",
16562
+ width: "100%",
16563
+ transition: "background 0.15s"
16462
16564
  },
16463
16565
  children: [
16464
16566
  /* @__PURE__ */ jsxRuntime.jsx(
16465
16567
  "div",
16466
16568
  {
16467
16569
  style: {
16468
- width: 44,
16469
- height: 44,
16470
- borderRadius: 8,
16471
- background: option.imageUrl ? `url(${option.imageUrl}) center/cover` : "linear-gradient(135deg, var(--paper-deep) 0%, var(--rule) 100%)",
16570
+ width: 80,
16571
+ height: 80,
16572
+ borderRadius: 10,
16573
+ overflow: "hidden",
16472
16574
  flexShrink: 0,
16473
- border: "1px solid var(--rule)",
16474
- position: "relative"
16575
+ background: "#f3f4f6"
16475
16576
  },
16476
- children: !option.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
16477
- "span",
16577
+ children: option.imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
16578
+ "img",
16579
+ {
16580
+ src: option.imageUrl,
16581
+ alt: option.name,
16582
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
16583
+ }
16584
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
16585
+ "div",
16478
16586
  {
16479
- className: "display-italic",
16480
16587
  style: {
16481
- position: "absolute",
16482
- inset: 0,
16588
+ width: "100%",
16589
+ height: "100%",
16483
16590
  display: "flex",
16484
16591
  alignItems: "center",
16485
16592
  justifyContent: "center",
16486
- color: "var(--ink-faint)",
16487
- fontSize: "1.2rem"
16593
+ color: "#d1d5db",
16594
+ fontSize: 22,
16595
+ fontWeight: 600,
16596
+ fontStyle: "italic"
16488
16597
  },
16489
16598
  children: option.name.trim()[0]?.toUpperCase() ?? "?"
16490
16599
  }
16491
16600
  )
16492
16601
  }
16493
16602
  ),
16494
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
16495
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "display", style: { fontSize: "0.9rem", fontWeight: 600, color: "var(--ink)" }, children: option.name }),
16496
- (dietary.length > 0 || allergens.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 3, marginTop: 4 }, children: [
16497
- dietary.slice(0, 2).map((d) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dietary-pill", children: d.replace(/_/g, " ") }, d)),
16498
- allergens.slice(0, 2).map((a) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "allergen-pill", children: a.toLowerCase().replace(/_/g, " ") }, a))
16499
- ] })
16500
- ] }),
16501
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "right", flexShrink: 0 }, children: [
16502
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "display", style: { fontSize: "0.9rem", fontWeight: 600, fontVariantNumeric: "tabular-nums" }, children: [
16503
- "\xA3",
16504
- option.totalPrice.toFixed(2)
16603
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", justifyContent: "center" }, children: [
16604
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: 14, fontWeight: 600, color: "#1f2937", margin: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: option.name }),
16605
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
16606
+ fontSize: 12,
16607
+ color: "#9ca3af",
16608
+ margin: "2px 0 0",
16609
+ lineHeight: 1.5,
16610
+ overflow: "hidden",
16611
+ display: "-webkit-box",
16612
+ WebkitLineClamp: 2,
16613
+ WebkitBoxOrient: "vertical"
16614
+ }, children: description }),
16615
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 }, children: [
16616
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 14, fontWeight: 600, color: "var(--primary, #b91c1c)", fontVariantNumeric: "tabular-nums" }, children: [
16617
+ "\xA3",
16618
+ option.unitPrice.toFixed(2)
16619
+ ] }),
16620
+ option.feedsPerUnit > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 11, color: "#9ca3af" }, children: [
16621
+ "feeds ",
16622
+ option.feedsPerUnit
16623
+ ] })
16505
16624
  ] }),
16506
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "0.66rem", color: "var(--ink-faint)" }, children: [
16507
- "\xD7",
16508
- option.quantity
16509
- ] })
16625
+ dietary.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 6 }, children: dietary.map((d) => {
16626
+ const iconInfo = DIETARY_ICON_MAP[d.toLowerCase()];
16627
+ if (!iconInfo) return null;
16628
+ return /* @__PURE__ */ jsxRuntime.jsx(
16629
+ "img",
16630
+ {
16631
+ src: iconInfo.src,
16632
+ alt: iconInfo.label,
16633
+ title: iconInfo.label,
16634
+ style: { width: 16, height: 16, objectFit: "contain" }
16635
+ },
16636
+ d
16637
+ );
16638
+ }) })
16510
16639
  ] })
16511
16640
  ]
16512
16641
  }
16513
16642
  );
16514
16643
  }
16515
- var overlayStyle = {
16516
- position: "fixed",
16517
- inset: 0,
16518
- background: "rgba(20, 18, 16, 0.45)",
16519
- display: "flex",
16520
- alignItems: "center",
16521
- justifyContent: "center",
16522
- padding: 16,
16523
- zIndex: 60
16524
- };
16525
- var panelStyle = {
16526
- background: "var(--paper)",
16527
- borderRadius: 16,
16528
- padding: 18,
16529
- width: "100%",
16530
- maxWidth: 360,
16531
- boxShadow: "var(--shadow-lift)",
16532
- border: "1px solid var(--rule)"
16533
- };
16534
- var errorStyle = {
16535
- fontSize: "0.85rem",
16536
- color: "var(--ember)",
16537
- background: "var(--ember-soft)",
16538
- border: "1px solid var(--ember)",
16539
- borderRadius: 10,
16540
- padding: "8px 12px"
16541
- };
16542
16644
 
16543
16645
  // src/utils/parsePlaceResult.ts
16544
16646
  function parsePlaceResult(place) {
@@ -16606,6 +16708,15 @@ var GOOGLE_MAPS_CONFIG = {
16606
16708
  "name"
16607
16709
  ]
16608
16710
  };
16711
+ var LONDON_DELIVERY_BOUNDS = {
16712
+ north: 51.75,
16713
+ south: 51.2,
16714
+ east: 0.35,
16715
+ west: -0.55
16716
+ };
16717
+ function isWithinLondonBounds(lat, lng) {
16718
+ return lat >= LONDON_DELIVERY_BOUNDS.south && lat <= LONDON_DELIVERY_BOUNDS.north && lng >= LONDON_DELIVERY_BOUNDS.west && lng <= LONDON_DELIVERY_BOUNDS.east;
16719
+ }
16609
16720
 
16610
16721
  // src/utils/google-maps-loader.ts
16611
16722
  var isLoading = false;
@@ -16650,8 +16761,10 @@ function loadGoogleMapsScript(apiKey) {
16650
16761
  }
16651
16762
 
16652
16763
  // src/hooks/useAddressAutocomplete.ts
16764
+ var OUT_OF_ZONE_MESSAGE = "We only deliver inside London right now \u2014 please pick a London address.";
16653
16765
  function useAddressAutocomplete(onPlaceSelect, options = {}) {
16654
16766
  const countryRestriction = options.countryRestriction === void 0 ? GOOGLE_MAPS_CONFIG.COUNTRY_RESTRICTION : options.countryRestriction;
16767
+ const restrictToDeliveryZone = options.restrictToDeliveryZone ?? true;
16655
16768
  const { googleMapsApiKey } = useCateringConfig();
16656
16769
  const inputRef = react.useRef(null);
16657
16770
  const containerRef = react.useRef(null);
@@ -16663,6 +16776,7 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16663
16776
  const [predictions, setPredictions] = react.useState([]);
16664
16777
  const [open, setOpen] = react.useState(false);
16665
16778
  const [activeIndex, setActiveIndex] = react.useState(-1);
16779
+ const [outOfZoneError, setOutOfZoneError] = react.useState(null);
16666
16780
  react.useEffect(() => {
16667
16781
  loadGoogleMapsScript(googleMapsApiKey).then(() => {
16668
16782
  if (!window.google?.maps?.places) return;
@@ -16673,6 +16787,7 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16673
16787
  }, [googleMapsApiKey]);
16674
16788
  react.useEffect(() => {
16675
16789
  if (debounceRef.current) clearTimeout(debounceRef.current);
16790
+ setOutOfZoneError(null);
16676
16791
  if (justSelectedRef.current) {
16677
16792
  justSelectedRef.current = false;
16678
16793
  return;
@@ -16687,7 +16802,10 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16687
16802
  autocompleteServiceRef.current.getPlacePredictions(
16688
16803
  {
16689
16804
  input: query,
16690
- ...countryRestriction ? { componentRestrictions: { country: countryRestriction } } : {}
16805
+ ...countryRestriction ? { componentRestrictions: { country: countryRestriction } } : {},
16806
+ // Delivery pickers hard-restrict predictions to the London box,
16807
+ // so out-of-zone places (e.g. Edinburgh) never appear.
16808
+ ...restrictToDeliveryZone ? { locationRestriction: LONDON_DELIVERY_BOUNDS } : {}
16691
16809
  },
16692
16810
  (results, status) => {
16693
16811
  if (status === google.maps.places.PlacesServiceStatus.OK && results) {
@@ -16716,6 +16834,13 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16716
16834
  { placeId: prediction.place_id, fields: GOOGLE_MAPS_CONFIG.FIELDS },
16717
16835
  (place, status) => {
16718
16836
  if (status === google.maps.places.PlacesServiceStatus.OK && place?.geometry) {
16837
+ const lat = place.geometry.location?.lat();
16838
+ const lng = place.geometry.location?.lng();
16839
+ if (restrictToDeliveryZone && typeof lat === "number" && typeof lng === "number" && !isWithinLondonBounds(lat, lng)) {
16840
+ setOutOfZoneError(OUT_OF_ZONE_MESSAGE);
16841
+ return;
16842
+ }
16843
+ setOutOfZoneError(null);
16719
16844
  onPlaceSelect(place);
16720
16845
  }
16721
16846
  }
@@ -16753,6 +16878,7 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16753
16878
  setPredictions([]);
16754
16879
  setActiveIndex(-1);
16755
16880
  setOpen(false);
16881
+ setOutOfZoneError(null);
16756
16882
  };
16757
16883
  return {
16758
16884
  inputRef,
@@ -16765,7 +16891,8 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16765
16891
  setActiveIndex,
16766
16892
  handleSelect,
16767
16893
  handleKeyDown,
16768
- clear
16894
+ clear,
16895
+ outOfZoneError
16769
16896
  };
16770
16897
  }
16771
16898
  function ChatAddressInput({
@@ -16787,7 +16914,8 @@ function ChatAddressInput({
16787
16914
  activeIndex,
16788
16915
  setActiveIndex,
16789
16916
  handleSelect,
16790
- handleKeyDown
16917
+ handleKeyDown,
16918
+ outOfZoneError
16791
16919
  } = useAddressAutocomplete(onPlaceSelect);
16792
16920
  react.useEffect(() => {
16793
16921
  if (initialQuery) setQuery(initialQuery);
@@ -16866,6 +16994,7 @@ function ChatAddressInput({
16866
16994
  }
16867
16995
  )
16868
16996
  ] }),
16997
+ outOfZoneError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 pl-6 text-xs text-red-500", children: outOfZoneError }),
16869
16998
  open && predictions.length > 0 && dropdownRect && typeof document !== "undefined" && reactDom.createPortal(
16870
16999
  /* Portal to the widget's shared overlay root so the
16871
17000
  dropdown escapes the bar pill's overflow-hidden +
@@ -17046,6 +17175,32 @@ function useBetaUnlock() {
17046
17175
  }, []);
17047
17176
  return { unlocked, tryUnlock };
17048
17177
  }
17178
+ var FIELDS = [
17179
+ ["guestCount", "Number of guests"],
17180
+ ["sessionDate", "Date"],
17181
+ ["eventTime", "Event time"],
17182
+ ["deliveryLocation", "Delivery location"],
17183
+ ["budget", "Budget"]
17184
+ ];
17185
+ function PreSendChecklist({ ticks }) {
17186
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 py-1", children: FIELDS.map(([field, label]) => /* @__PURE__ */ jsxRuntime.jsxs(
17187
+ "span",
17188
+ {
17189
+ className: `flex items-center gap-1 text-xs transition-colors ${ticks[field] ? "text-gray-700" : "text-gray-400"}`,
17190
+ children: [
17191
+ /* @__PURE__ */ jsxRuntime.jsx(
17192
+ "span",
17193
+ {
17194
+ className: `flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center rounded-full border transition-colors ${ticks[field] ? "border-primary bg-primary" : "border-gray-300 bg-transparent"}`,
17195
+ children: ticks[field] && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-2 w-2 text-white", strokeWidth: 3 })
17196
+ }
17197
+ ),
17198
+ label
17199
+ ]
17200
+ },
17201
+ field
17202
+ )) });
17203
+ }
17049
17204
  var MAX_INPUT_HEIGHT = 140;
17050
17205
  function AIChat() {
17051
17206
  const { unlocked, tryUnlock } = useBetaUnlock();
@@ -17057,6 +17212,7 @@ function AIChat() {
17057
17212
  function AIChatBody() {
17058
17213
  const {
17059
17214
  chat: {
17215
+ messages,
17060
17216
  sending,
17061
17217
  bootstrapping,
17062
17218
  sessionId,
@@ -17081,15 +17237,27 @@ function AIChatBody() {
17081
17237
  feedbackTarget,
17082
17238
  feedbackRating,
17083
17239
  setFeedbackRating,
17240
+ startGeneralFeedback,
17084
17241
  cancelFeedback
17085
17242
  } = useChatSessionContext();
17086
17243
  const buildChatAddress = useChatAddressFromContext();
17087
17244
  const { contactInfo, setContactInfo } = useCateringState();
17245
+ const api = useApiClient();
17088
17246
  const hasAddress = !!(contactInfo?.addressLine1 && contactInfo.latitude !== void 0 && contactInfo.longitude !== void 0);
17089
17247
  const gateActive = !hasAddress;
17090
17248
  const [pillEditing, setPillEditing] = react.useState(false);
17249
+ const [headerMenuOpen, setHeaderMenuOpen] = react.useState(false);
17091
17250
  const textareaRef = react.useRef(null);
17092
17251
  const [input, setInput] = react.useState("");
17252
+ const [fieldTicks, setFieldTicks] = react.useState({
17253
+ guestCount: false,
17254
+ sessionDate: false,
17255
+ eventTime: false,
17256
+ deliveryLocation: false,
17257
+ budget: false
17258
+ });
17259
+ const detectDebounceRef = react.useRef(null);
17260
+ const detectAbortRef = react.useRef(null);
17093
17261
  const [feedbackHover, setFeedbackHover] = react.useState(0);
17094
17262
  const [feedbackState, setFeedbackState] = react.useState("idle");
17095
17263
  const [feedbackNote, setFeedbackNote] = react.useState("");
@@ -17118,7 +17286,7 @@ function AIChatBody() {
17118
17286
  };
17119
17287
  react.useEffect(() => {
17120
17288
  handleInput();
17121
- }, [input, gateActive]);
17289
+ }, [input, feedbackNote, gateActive]);
17122
17290
  react.useEffect(() => {
17123
17291
  if (!pendingInputFocus) return;
17124
17292
  if (gateActive || pillEditing) return;
@@ -17128,6 +17296,10 @@ function AIChatBody() {
17128
17296
  setPendingInputFocus(false);
17129
17297
  }, [pendingInputFocus, gateActive, pillEditing, bootstrapping, sessionId]);
17130
17298
  const inFeedbackMode = feedbackTarget !== null;
17299
+ const hasUserReply = messages.some((m) => m.sender === "user");
17300
+ console.log("[AIChat] messages", messages, "hasUserReply", hasUserReply);
17301
+ const showChecklist = !inFeedbackMode && !gateActive && !hasUserReply;
17302
+ const allTicked = Object.values(fieldTicks).every(Boolean);
17131
17303
  const RATING_LABELS = ["Terrible", "Poor", "Okay", "Good", "Great"];
17132
17304
  react.useEffect(() => {
17133
17305
  if (!feedbackTarget) return;
@@ -17135,6 +17307,25 @@ function AIChatBody() {
17135
17307
  setFeedbackNote("");
17136
17308
  setFeedbackState("idle");
17137
17309
  }, [feedbackTarget]);
17310
+ react.useEffect(() => {
17311
+ if (!showChecklist || !input.trim()) return;
17312
+ if (detectDebounceRef.current) clearTimeout(detectDebounceRef.current);
17313
+ detectDebounceRef.current = setTimeout(async () => {
17314
+ detectAbortRef.current?.abort();
17315
+ detectAbortRef.current = new AbortController();
17316
+ const { signal } = detectAbortRef.current;
17317
+ try {
17318
+ const msg = hasAddress && contactInfo?.addressLine1 ? `${input.trim()} ${contactInfo.addressLine1}` : input.trim();
17319
+ const result = await api.chat.detectFields(msg, signal);
17320
+ if (!signal.aborted) setFieldTicks(result);
17321
+ } catch {
17322
+ }
17323
+ }, 400);
17324
+ return () => {
17325
+ if (detectDebounceRef.current) clearTimeout(detectDebounceRef.current);
17326
+ detectAbortRef.current?.abort();
17327
+ };
17328
+ }, [input, showChecklist]);
17138
17329
  function closeFeedback() {
17139
17330
  cancelFeedback();
17140
17331
  setFeedbackHover(0);
@@ -17205,7 +17396,9 @@ function AIChatBody() {
17205
17396
  }
17206
17397
  if (sending || bootstrapping || !sessionId || !input.trim()) return;
17207
17398
  if (gateActive && !hasAddress) return;
17208
- void sendText(input, buildChatAddress());
17399
+ const addr = buildChatAddress();
17400
+ console.log("[AIChat] submitText address", addr, "contactInfo", contactInfo);
17401
+ void sendText(input, addr);
17209
17402
  setInput("");
17210
17403
  }
17211
17404
  function handleSubmit(e) {
@@ -17230,7 +17423,7 @@ function AIChatBody() {
17230
17423
  setEditingMealSessionIndex(void 0);
17231
17424
  await applyEditField(field, value, idx);
17232
17425
  }
17233
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "swift-chat-design bg-white rounded-xl shadow-sm border border-base-200 flex h-full min-h-0 flex-col overflow-hidden", children: [
17426
+ return /* @__PURE__ */ jsxRuntime.jsx(react$1.LayoutGroup, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "swift-chat-design bg-white rounded-xl shadow-sm border border-base-200 flex h-full min-h-0 flex-col overflow-hidden", children: [
17234
17427
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 px-3 py-2.5 flex items-center gap-3 border-b border-base-200", children: [
17235
17428
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-9 h-9 rounded-full bg-primary flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-4 h-4 text-white" }) }),
17236
17429
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
@@ -17261,16 +17454,55 @@ function AIChatBody() {
17261
17454
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "New chat" })
17262
17455
  ]
17263
17456
  }
17264
- )
17265
- ] }),
17266
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-h-0", children: [
17267
- !gateActive && !inFeedbackMode && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute left-0 right-0 z-10 px-3 pt-1.5 flex justify-center pointer-events-none", children: [
17268
- hasAddress && !pillEditing && /* @__PURE__ */ jsxRuntime.jsxs(
17457
+ ),
17458
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
17459
+ /* @__PURE__ */ jsxRuntime.jsx(
17269
17460
  "button",
17270
17461
  {
17271
17462
  type: "button",
17272
- onClick: () => setPillEditing(true),
17273
- className: "pointer-events-auto inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-black/50 backdrop-blur-sm text-white/90 hover:bg-black/60 transition-colors text-xs max-w-full shadow-sm",
17463
+ onClick: () => setHeaderMenuOpen((v) => !v),
17464
+ className: "flex items-center justify-center w-7 h-7 rounded-md text-gray-400 hover:text-gray-600 hover:bg-base-100 transition-colors",
17465
+ title: "More options",
17466
+ "aria-label": "More options",
17467
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "w-4 h-4" })
17468
+ }
17469
+ ),
17470
+ headerMenuOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17471
+ /* @__PURE__ */ jsxRuntime.jsx(
17472
+ "div",
17473
+ {
17474
+ className: "fixed inset-0 z-40",
17475
+ onClick: () => setHeaderMenuOpen(false)
17476
+ }
17477
+ ),
17478
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 top-full z-50 mt-1 w-44 overflow-hidden rounded-xl border border-base-200 bg-white shadow-lg", children: /* @__PURE__ */ jsxRuntime.jsxs(
17479
+ "button",
17480
+ {
17481
+ type: "button",
17482
+ onClick: () => {
17483
+ setHeaderMenuOpen(false);
17484
+ startGeneralFeedback();
17485
+ },
17486
+ className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm text-gray-700 transition-colors hover:bg-base-100",
17487
+ children: [
17488
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquarePlus, { className: "h-4 w-4" }),
17489
+ "Give Feedback"
17490
+ ]
17491
+ }
17492
+ ) })
17493
+ ] })
17494
+ ] })
17495
+ ] }),
17496
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-h-0", children: [
17497
+ !gateActive && !inFeedbackMode && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute left-0 right-0 z-10 px-3 pt-1.5 flex justify-center pointer-events-none", children: [
17498
+ hasAddress && !pillEditing && /* @__PURE__ */ jsxRuntime.jsxs(
17499
+ react$1.motion.button,
17500
+ {
17501
+ type: "button",
17502
+ layoutId: "desktop-address-region",
17503
+ transition: { duration: 0.35, ease: "easeOut" },
17504
+ onClick: () => setPillEditing(true),
17505
+ className: "pointer-events-auto inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-black/50 backdrop-blur-sm text-white/90 hover:bg-black/60 transition-colors text-xs max-w-full shadow-sm",
17274
17506
  children: [
17275
17507
  /* @__PURE__ */ jsxRuntime.jsx(
17276
17508
  lucideReact.MapPin,
@@ -17304,13 +17536,16 @@ function AIChatBody() {
17304
17536
  }
17305
17537
  )
17306
17538
  ] }),
17307
- /* @__PURE__ */ jsxRuntime.jsx(
17539
+ /* @__PURE__ */ jsxRuntime.jsxs(
17308
17540
  "div",
17309
17541
  {
17310
17542
  ref: messagesScrollRef,
17311
17543
  onScroll: handleMessagesScroll,
17312
17544
  className: "absolute inset-0 overflow-y-auto overscroll-contain p-3",
17313
- children: /* @__PURE__ */ jsxRuntime.jsx(ChatMessagesView, {})
17545
+ children: [
17546
+ !gateActive && !inFeedbackMode && hasAddress && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-7", "aria-hidden": true }),
17547
+ /* @__PURE__ */ jsxRuntime.jsx(ChatMessagesView, {})
17548
+ ]
17314
17549
  }
17315
17550
  ),
17316
17551
  /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: showScrollDown && /* @__PURE__ */ jsxRuntime.jsx(
@@ -17374,7 +17609,10 @@ function AIChatBody() {
17374
17609
  "aria-label": `${n} star${n > 1 ? "s" : ""}`,
17375
17610
  onMouseEnter: () => setFeedbackHover(n),
17376
17611
  onMouseLeave: () => setFeedbackHover(0),
17377
- onClick: () => setFeedbackRating(n),
17612
+ onClick: () => {
17613
+ setFeedbackRating(n);
17614
+ textareaRef.current?.focus();
17615
+ },
17378
17616
  className: "p-0.5 transition-transform hover:scale-110",
17379
17617
  children: /* @__PURE__ */ jsxRuntime.jsx(
17380
17618
  lucideReact.Star,
@@ -17421,9 +17659,11 @@ function AIChatBody() {
17421
17659
  exit: { opacity: 0, y: -8 },
17422
17660
  transition: { duration: 0.18, ease: "easeOut" },
17423
17661
  children: gateActive ? /* @__PURE__ */ jsxRuntime.jsxs(
17424
- "form",
17662
+ react$1.motion.form,
17425
17663
  {
17426
17664
  onSubmit: handleSubmit,
17665
+ layoutId: "desktop-address-region",
17666
+ transition: { duration: 0.35, ease: "easeOut" },
17427
17667
  className: "flex flex-col rounded-xl border border-base-300 bg-white px-3 py-2 focus-within:border-primary transition-colors",
17428
17668
  children: [
17429
17669
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pb-2 border-b border-base-200", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -17465,31 +17705,45 @@ function AIChatBody() {
17465
17705
  "form",
17466
17706
  {
17467
17707
  onSubmit: handleSubmit,
17468
- className: "flex items-end gap-2 rounded-xl border border-base-300 bg-white px-3 py-2 focus-within:border-primary transition-colors",
17708
+ className: "flex flex-col rounded-xl border border-base-300 bg-white px-3 py-2 focus-within:border-primary transition-colors",
17469
17709
  children: [
17470
- /* @__PURE__ */ jsxRuntime.jsx(
17471
- "textarea",
17472
- {
17473
- ref: textareaRef,
17474
- rows: 1,
17475
- value: inFeedbackMode ? feedbackNote : input,
17476
- onChange: (e) => inFeedbackMode ? setFeedbackNote(e.target.value) : setInput(e.target.value),
17477
- onKeyDown: handleKeyDown,
17478
- placeholder: inFeedbackMode ? feedbackRating <= 2 ? "What went wrong? (optional)" : "Add a note (optional)" : "Ask for menu suggestions...",
17479
- disabled: inFeedbackMode ? feedbackState === "sending" : bootstrapping || !sessionId,
17480
- className: "flex-1 bg-transparent text-sm text-gray-800 placeholder:text-gray-400 outline-none resize-none leading-snug py-1 overflow-hidden"
17481
- }
17482
- ),
17483
- /* @__PURE__ */ jsxRuntime.jsx(
17484
- "button",
17710
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: showChecklist && /* @__PURE__ */ jsxRuntime.jsx(
17711
+ react$1.motion.div,
17485
17712
  {
17486
- type: "submit",
17487
- disabled: inFeedbackMode ? feedbackRating < 1 || feedbackState === "sending" : sending || bootstrapping || !input.trim() || !sessionId,
17488
- className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-primary text-white transition-colors hover:bg-primary/90 disabled:opacity-50",
17489
- title: inFeedbackMode ? "Submit feedback" : "Send",
17490
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-3.5 h-3.5" })
17491
- }
17492
- )
17713
+ initial: { opacity: 0, height: 0 },
17714
+ animate: { opacity: 1, height: "auto" },
17715
+ exit: { opacity: 0, height: 0 },
17716
+ transition: { duration: 0.18, ease: "easeOut" },
17717
+ className: "overflow-hidden border-b border-base-200 pb-1.5 mb-1.5",
17718
+ children: /* @__PURE__ */ jsxRuntime.jsx(PreSendChecklist, { ticks: fieldTicks })
17719
+ },
17720
+ "checklist"
17721
+ ) }),
17722
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [
17723
+ /* @__PURE__ */ jsxRuntime.jsx(
17724
+ "textarea",
17725
+ {
17726
+ ref: textareaRef,
17727
+ rows: 1,
17728
+ value: inFeedbackMode ? feedbackNote : input,
17729
+ onChange: (e) => inFeedbackMode ? setFeedbackNote(e.target.value) : setInput(e.target.value),
17730
+ onKeyDown: handleKeyDown,
17731
+ placeholder: inFeedbackMode ? feedbackRating <= 2 ? "What went wrong? (optional)" : "Add a note (optional)" : "Ask for menu suggestions...",
17732
+ disabled: inFeedbackMode ? feedbackState === "sending" : bootstrapping || !sessionId,
17733
+ className: "flex-1 bg-transparent text-sm text-gray-800 placeholder:text-gray-400 outline-none resize-none leading-snug py-1 overflow-hidden"
17734
+ }
17735
+ ),
17736
+ /* @__PURE__ */ jsxRuntime.jsx(
17737
+ "button",
17738
+ {
17739
+ type: "submit",
17740
+ disabled: inFeedbackMode ? feedbackRating < 1 || feedbackState === "sending" : sending || bootstrapping || !input.trim() || !sessionId || showChecklist && !allTicked,
17741
+ className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-primary text-white transition-colors hover:bg-primary/90 disabled:opacity-50",
17742
+ title: inFeedbackMode ? "Submit feedback" : "Send",
17743
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-3.5 h-3.5" })
17744
+ }
17745
+ )
17746
+ ] })
17493
17747
  ]
17494
17748
  }
17495
17749
  ) })
@@ -17509,7 +17763,7 @@ function AIChatBody() {
17509
17763
  intentExcludes: swapTarget?.intentExcludes ?? null,
17510
17764
  itemName: swapTarget?.itemName ?? "",
17511
17765
  onClose: () => setSwapTarget(null),
17512
- onPick: (replacement) => {
17766
+ onPick: (replacement, addonSelections) => {
17513
17767
  const target = swapTarget;
17514
17768
  if (!target) return;
17515
17769
  setSwapTarget(null);
@@ -17526,14 +17780,19 @@ function AIChatBody() {
17526
17780
  allergens: replacement.allergens,
17527
17781
  dietaryFilters: replacement.dietaryFilters,
17528
17782
  feedsPerUnit: replacement.feedsPerUnit,
17529
- cateringQuantityUnit: 1,
17530
- minOrderQuantity: 1,
17531
- addons: []
17783
+ cateringQuantityUnit: replacement.cateringQuantityUnit,
17784
+ minOrderQuantity: replacement.minOrderQuantity,
17785
+ addons: replacement.addons
17532
17786
  });
17787
+ cart.setAddonSelections(
17788
+ target.intentId,
17789
+ replacement.menuItemId,
17790
+ addonSelections
17791
+ );
17533
17792
  }
17534
17793
  }
17535
17794
  )
17536
- ] });
17795
+ ] }) });
17537
17796
  }
17538
17797
  function MobileAddressPickerSurface({
17539
17798
  onPlaceSelect,
@@ -17552,7 +17811,8 @@ function MobileAddressPickerSurface({
17552
17811
  activeIndex,
17553
17812
  setActiveIndex,
17554
17813
  handleSelect,
17555
- handleKeyDown
17814
+ handleKeyDown,
17815
+ outOfZoneError
17556
17816
  } = useAddressAutocomplete(onPlaceSelect);
17557
17817
  react.useEffect(() => {
17558
17818
  if (initialQuery) setQuery(initialQuery);
@@ -17649,7 +17909,7 @@ function MobileAddressPickerSurface({
17649
17909
  "input",
17650
17910
  {
17651
17911
  ref: inputRef,
17652
- type: "search",
17912
+ type: "text",
17653
17913
  value: query,
17654
17914
  onChange: (e) => setQuery(e.target.value),
17655
17915
  onKeyDown: handleSearchKeyDown,
@@ -17678,6 +17938,7 @@ function MobileAddressPickerSurface({
17678
17938
  ] })
17679
17939
  }
17680
17940
  ),
17941
+ outOfZoneError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 flex-shrink-0 text-center text-xs text-red-600", children: outOfZoneError }),
17681
17942
  predictions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
17682
17943
  react$1.motion.div,
17683
17944
  {
@@ -20298,15 +20559,73 @@ function AISuggestionsPanel() {
20298
20559
  mealSessions: widgetMealSessions,
20299
20560
  addMealSession,
20300
20561
  addMenuItem,
20562
+ updateMealSession,
20301
20563
  setActiveSessionIndex
20302
20564
  } = useCateringState();
20565
+ const [mergeConfirm, setMergeConfirm] = react.useState(null);
20303
20566
  function handleSwap(target) {
20304
20567
  setSwapTarget(target);
20305
20568
  }
20569
+ function findConflictingSessions() {
20570
+ const conflicts = [];
20571
+ const seen = /* @__PURE__ */ new Set();
20572
+ for (const part of latestMealSessionParts) {
20573
+ const aiMeal = latestMealSessions[part.mealSessionIndex];
20574
+ const isoDate = aiMeal?.isoSessionDate ?? "";
20575
+ const aiTime = convertAiTime(aiMeal?.isoEventTime ?? "");
20576
+ if (!isoDate) continue;
20577
+ const matchIdx = widgetMealSessions.findIndex(
20578
+ (s) => s.sessionDate === isoDate && s.eventTime === aiTime
20579
+ );
20580
+ if (matchIdx < 0 || seen.has(matchIdx)) continue;
20581
+ seen.add(matchIdx);
20582
+ const existing = widgetMealSessions[matchIdx];
20583
+ if (existing.orderItems.length > 0) {
20584
+ conflicts.push({
20585
+ idx: matchIdx,
20586
+ name: existing.sessionName || `Session ${matchIdx + 1}`,
20587
+ itemCount: existing.orderItems.length
20588
+ });
20589
+ }
20590
+ }
20591
+ return conflicts;
20592
+ }
20306
20593
  function handleAddToBasket() {
20594
+ const conflicts = findConflictingSessions();
20595
+ if (conflicts.length > 0) {
20596
+ setMergeConfirm({ conflictingSessions: conflicts });
20597
+ return;
20598
+ }
20599
+ commitToBasket(false);
20600
+ }
20601
+ function commitToBasket(replace) {
20602
+ setMergeConfirm(null);
20307
20603
  const preBatchCount = widgetMealSessions.length;
20308
20604
  let working = widgetMealSessions;
20309
20605
  let firstTargetIdx = -1;
20606
+ if (replace) {
20607
+ for (const part of latestMealSessionParts) {
20608
+ const aiMeal = latestMealSessions[part.mealSessionIndex];
20609
+ const isoDate = aiMeal?.isoSessionDate ?? "";
20610
+ const aiTime = convertAiTime(aiMeal?.isoEventTime ?? "");
20611
+ if (!isoDate) continue;
20612
+ const matchIdx = widgetMealSessions.findIndex(
20613
+ (s) => s.sessionDate === isoDate && s.eventTime === aiTime
20614
+ );
20615
+ if (matchIdx >= 0) {
20616
+ updateMealSession(matchIdx, { orderItems: [] });
20617
+ }
20618
+ }
20619
+ working = widgetMealSessions.map((s) => {
20620
+ const shouldClear = latestMealSessionParts.some((part) => {
20621
+ const aiMeal = latestMealSessions[part.mealSessionIndex];
20622
+ const isoDate = aiMeal?.isoSessionDate ?? "";
20623
+ const aiTime = convertAiTime(aiMeal?.isoEventTime ?? "");
20624
+ return isoDate && s.sessionDate === isoDate && s.eventTime === aiTime;
20625
+ });
20626
+ return shouldClear ? { ...s, orderItems: [] } : s;
20627
+ });
20628
+ }
20310
20629
  for (const part of latestMealSessionParts) {
20311
20630
  const resolvedItems = resolveDraftItems(
20312
20631
  latestMealSessionParts,
@@ -20338,7 +20657,7 @@ function AISuggestionsPanel() {
20338
20657
  });
20339
20658
  }
20340
20659
  working.forEach((s, idx) => {
20341
- const original = widgetMealSessions[idx];
20660
+ const original = replace ? { orderItems: [] } : widgetMealSessions[idx];
20342
20661
  const originalIds = new Set(
20343
20662
  (original?.orderItems ?? []).map((oi) => oi.item.id)
20344
20663
  );
@@ -20348,7 +20667,6 @@ function AISuggestionsPanel() {
20348
20667
  }
20349
20668
  });
20350
20669
  setActiveSessionIndex(firstTargetIdx);
20351
- dismissSuggestionsOverlay();
20352
20670
  onAddedToBasket();
20353
20671
  }
20354
20672
  const showPreview = activeViewedPreview !== null;
@@ -20458,6 +20776,63 @@ function AISuggestionsPanel() {
20458
20776
  }
20459
20777
  ) })
20460
20778
  }
20779
+ ) }),
20780
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: mergeConfirm && /* @__PURE__ */ jsxRuntime.jsxs(
20781
+ react$1.motion.div,
20782
+ {
20783
+ initial: { opacity: 0 },
20784
+ animate: { opacity: 1 },
20785
+ exit: { opacity: 0 },
20786
+ transition: { duration: 0.15 },
20787
+ className: "fixed inset-0 z-[60] flex items-center justify-center",
20788
+ children: [
20789
+ /* @__PURE__ */ jsxRuntime.jsx(
20790
+ "div",
20791
+ {
20792
+ className: "absolute inset-0 bg-black/40",
20793
+ onClick: () => setMergeConfirm(null)
20794
+ }
20795
+ ),
20796
+ /* @__PURE__ */ jsxRuntime.jsxs(
20797
+ react$1.motion.div,
20798
+ {
20799
+ initial: { scale: 0.95, opacity: 0 },
20800
+ animate: { scale: 1, opacity: 1 },
20801
+ exit: { scale: 0.95, opacity: 0 },
20802
+ transition: { duration: 0.15 },
20803
+ className: "relative w-full max-w-sm rounded-2xl bg-white p-6 shadow-xl mx-4",
20804
+ children: [
20805
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "Session already has items" }),
20806
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 text-sm text-gray-600", children: [
20807
+ mergeConfirm.conflictingSessions.length === 1 ? `"${mergeConfirm.conflictingSessions[0].name}" already has ${mergeConfirm.conflictingSessions[0].itemCount} item${mergeConfirm.conflictingSessions[0].itemCount === 1 ? "" : "s"}.` : `${mergeConfirm.conflictingSessions.length} sessions already have items.`,
20808
+ " ",
20809
+ "Would you like to replace the existing items or add to them?"
20810
+ ] }),
20811
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex gap-3", children: [
20812
+ /* @__PURE__ */ jsxRuntime.jsx(
20813
+ "button",
20814
+ {
20815
+ type: "button",
20816
+ onClick: () => commitToBasket(true),
20817
+ className: "flex-1 rounded-xl border border-base-300 px-4 py-2.5 text-sm font-medium text-gray-700 transition-colors hover:bg-base-100",
20818
+ children: "Replace"
20819
+ }
20820
+ ),
20821
+ /* @__PURE__ */ jsxRuntime.jsx(
20822
+ "button",
20823
+ {
20824
+ type: "button",
20825
+ onClick: () => commitToBasket(false),
20826
+ className: "flex-1 rounded-xl bg-primary px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-primary/90",
20827
+ children: "Add to existing"
20828
+ }
20829
+ )
20830
+ ] })
20831
+ ]
20832
+ }
20833
+ )
20834
+ ]
20835
+ }
20461
20836
  ) })
20462
20837
  ]
20463
20838
  }
@@ -20665,8 +21040,19 @@ function resolveDraftItems(mealSessionParts, aiMealSessions, activeMealSessionIn
20665
21040
  }
20666
21041
  return out;
20667
21042
  }
20668
- function SuggestionsOverlay({ onClose }) {
21043
+ function SuggestionsOverlay({ onClose, flyToCart }) {
20669
21044
  const { stickyTopOffset = 0 } = useCateringConfig();
21045
+ const cardRef = react.useRef(null);
21046
+ const controls = react$1.useAnimation();
21047
+ const flying = !!flyToCart;
21048
+ react.useEffect(() => {
21049
+ if (!flyToCart) return;
21050
+ void controls.start({
21051
+ scale: 0.1,
21052
+ opacity: 0,
21053
+ transition: { duration: 0.4, ease: [0.4, 0, 0.2, 1] }
21054
+ });
21055
+ }, [flyToCart, controls]);
20670
21056
  react.useEffect(() => {
20671
21057
  const body = document.body;
20672
21058
  const scrollY = window.scrollY;
@@ -20703,14 +21089,25 @@ function SuggestionsOverlay({ onClose }) {
20703
21089
  transition: { duration: 0.2, ease: "easeOut" },
20704
21090
  children: [
20705
21091
  /* @__PURE__ */ jsxRuntime.jsx(
20706
- "div",
21092
+ react$1.motion.div,
20707
21093
  {
20708
21094
  className: "absolute inset-0 bg-gray-900/30 backdrop-blur-lg",
20709
21095
  "aria-hidden": "true",
20710
- onClick: onClose
21096
+ onClick: onClose,
21097
+ animate: { opacity: flying ? 0 : 1 },
21098
+ transition: { duration: 0.3, ease: "easeOut" }
20711
21099
  }
20712
21100
  ),
20713
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-full overflow-y-auto overscroll-none pt-6", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-6xl mx-auto w-full px-2 md:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(AISuggestionsPanel, {}) }) })
21101
+ /* @__PURE__ */ jsxRuntime.jsx(
21102
+ react$1.motion.div,
21103
+ {
21104
+ ref: cardRef,
21105
+ animate: controls,
21106
+ className: "relative h-full overflow-hidden pt-6",
21107
+ style: { transformOrigin: "center center" },
21108
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-6xl mx-auto w-full px-2 md:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(AISuggestionsPanel, {}) })
21109
+ }
21110
+ )
20714
21111
  ]
20715
21112
  }
20716
21113
  );
@@ -20850,7 +21247,8 @@ function InlineAddressInput({
20850
21247
  activeIndex,
20851
21248
  setActiveIndex,
20852
21249
  handleSelect,
20853
- handleKeyDown
21250
+ handleKeyDown,
21251
+ outOfZoneError
20854
21252
  } = useAddressAutocomplete(onPlaceSelect);
20855
21253
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative mt-1.5 pt-1.5 border-t border-base-200", children: [
20856
21254
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -20888,7 +21286,8 @@ function InlineAddressInput({
20888
21286
  ]
20889
21287
  },
20890
21288
  p.place_id
20891
- )) })
21289
+ )) }),
21290
+ outOfZoneError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[10px] text-red-500", children: outOfZoneError })
20892
21291
  ] });
20893
21292
  }
20894
21293
  function PricingSummary({
@@ -22088,6 +22487,7 @@ function RestaurantMenuBrowser({
22088
22487
  }) {
22089
22488
  const { addMenuItem, mealSessions, restaurantPromotions, contactInfo } = useCateringState();
22090
22489
  const { stickyTopOffset: hostStickyTopOffset = 0 } = useCateringConfig();
22490
+ const { filters, setFilters } = useCateringFilters();
22091
22491
  const activeSession = mealSessions[sessionIndex];
22092
22492
  const [selectedRestaurantId, setSelectedRestaurantIdState] = react.useState(() => {
22093
22493
  if (typeof window === "undefined") return null;
@@ -22300,6 +22700,8 @@ function RestaurantMenuBrowser({
22300
22700
  const [isMobileViewport, setIsMobileViewport] = react.useState(false);
22301
22701
  const [apiSearchResults, setApiSearchResults] = react.useState(null);
22302
22702
  const [searchLoading, setSearchLoading] = react.useState(false);
22703
+ const [priceFilterOpen, setPriceFilterOpen] = react.useState(false);
22704
+ const priceFilterRef = react.useRef(null);
22303
22705
  const sectionRefs = react.useRef(/* @__PURE__ */ new Map());
22304
22706
  const groupButtonRefs = react.useRef(/* @__PURE__ */ new Map());
22305
22707
  const isProgrammaticScroll = react.useRef(false);
@@ -22499,6 +22901,16 @@ function RestaurantMenuBrowser({
22499
22901
  }, 300);
22500
22902
  return () => clearTimeout(timer);
22501
22903
  }, [isSearchActive, searchQuery, selectedDietaryFilters]);
22904
+ react.useEffect(() => {
22905
+ if (!priceFilterOpen) return;
22906
+ const handleClickOutside = (e) => {
22907
+ if (priceFilterRef.current && !priceFilterRef.current.contains(e.target)) {
22908
+ setPriceFilterOpen(false);
22909
+ }
22910
+ };
22911
+ document.addEventListener("mousedown", handleClickOutside);
22912
+ return () => document.removeEventListener("mousedown", handleClickOutside);
22913
+ }, [priceFilterOpen]);
22502
22914
  const filteredRestaurants = react.useMemo(() => {
22503
22915
  return availableRestaurants.filter((restaurant) => {
22504
22916
  const matchesCategory = !selectedCategoryId || (restaurant.categories ?? []).some(
@@ -22507,12 +22919,14 @@ function RestaurantMenuBrowser({
22507
22919
  const matchesDiet = selectedDietaryFilters.length === 0 || selectedDietaryFilters.every(
22508
22920
  (filter) => (restaurant.dietaryFilters ?? []).includes(filter)
22509
22921
  );
22510
- return matchesCategory && matchesDiet;
22922
+ const matchesPriceRange = !filters.restaurantPriceRange || restaurant.priceRange === filters.restaurantPriceRange;
22923
+ return matchesCategory && matchesDiet && matchesPriceRange;
22511
22924
  }).sort(compareRestaurantsByAvailability);
22512
22925
  }, [
22513
22926
  availableRestaurants,
22514
22927
  selectedCategoryId,
22515
22928
  selectedDietaryFilters,
22929
+ filters.restaurantPriceRange,
22516
22930
  compareRestaurantsByAvailability
22517
22931
  ]);
22518
22932
  const selectedRestaurant = react.useMemo(
@@ -22909,16 +23323,23 @@ function RestaurantMenuBrowser({
22909
23323
  const userLoc = contactInfo?.latitude && contactInfo?.longitude ? { latitude: contactInfo.latitude, longitude: contactInfo.longitude } : null;
22910
23324
  const distance = userLoc && restaurantLoc ? haversineDistanceMiles(userLoc.latitude, userLoc.longitude, restaurantLoc.latitude, restaurantLoc.longitude) : null;
22911
23325
  const rating = restaurant.averageRating && parseFloat(restaurant.averageRating) > 0 ? parseFloat(restaurant.averageRating) : null;
22912
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto pt-2 min-h-[22px] flex items-center gap-2", children: [
22913
- rating !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-0.5 text-[11px] text-gray-500", children: [
22914
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-yellow-400", children: "\u2605" }),
22915
- rating.toFixed(1)
23326
+ const priceRange = restaurant.priceRange;
23327
+ const tags = restaurant.tags?.filter(Boolean) ?? [];
23328
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto pt-2 flex flex-col gap-1.5", children: [
23329
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-[18px] flex items-center gap-2", children: [
23330
+ rating !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-0.5 text-[11px] text-gray-500", children: [
23331
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-yellow-400", children: "\u2605" }),
23332
+ rating.toFixed(1)
23333
+ ] }),
23334
+ rating !== null && distance !== null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300 text-[11px]", children: "\xB7" }),
23335
+ distance !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[11px] text-gray-500", children: [
23336
+ distance.toFixed(1),
23337
+ " mi"
23338
+ ] }),
23339
+ priceRange && (rating !== null || distance !== null) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300 text-[11px]", children: "\xB7" }),
23340
+ priceRange && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-gray-500", children: priceRange })
22916
23341
  ] }),
22917
- rating !== null && distance !== null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300 text-[11px]", children: "\xB7" }),
22918
- distance !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[11px] text-gray-500", children: [
22919
- distance.toFixed(1),
22920
- " mi"
22921
- ] })
23342
+ tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-gray-500 leading-tight", children: tags.slice(0, 3).join(" - ") })
22922
23343
  ] });
22923
23344
  })()
22924
23345
  ] })
@@ -23081,24 +23502,31 @@ function RestaurantMenuBrowser({
23081
23502
  const rating = selectedRestaurant.averageRating && parseFloat(selectedRestaurant.averageRating) > 0 ? parseFloat(selectedRestaurant.averageRating) : null;
23082
23503
  const advanceNoticeText = getRestaurantAdvanceNoticeText(selectedRestaurant);
23083
23504
  const noticeMet = isRestaurantAdvanceNoticeMet(selectedRestaurant, activeSession?.sessionDate, activeSession?.eventTime);
23084
- if (!rating && !distance && !advanceNoticeText) return null;
23085
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 mt-1 mb-1", children: [
23086
- rating !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-sm text-gray-500 whitespace-nowrap", children: [
23087
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-yellow-400", children: "\u2605" }),
23088
- rating.toFixed(1)
23089
- ] }),
23090
- rating !== null && distance !== null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\xB7" }),
23091
- distance !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-500 whitespace-nowrap", children: [
23092
- distance.toFixed(1),
23093
- " mi away"
23094
- ] }),
23095
- advanceNoticeText && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
23096
- (rating !== null || distance !== null) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\xB7" }),
23097
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `flex items-center gap-1 text-sm whitespace-nowrap ${noticeMet ? "text-gray-500" : "text-gray-500"}`, children: [
23098
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock3, { className: `h-3.5 w-3.5 flex-shrink-0 ${noticeMet ? "text-gray-400" : "text-red-500"}` }),
23099
- advanceNoticeText
23505
+ const priceRange = selectedRestaurant.priceRange;
23506
+ const tags = selectedRestaurant.tags?.filter(Boolean).slice(0, 3) ?? [];
23507
+ if (!rating && !distance && !advanceNoticeText && !priceRange && tags.length === 0) return null;
23508
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 mb-1 flex flex-col gap-1", children: [
23509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [
23510
+ rating !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-sm text-gray-500 whitespace-nowrap", children: [
23511
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-yellow-400", children: "\u2605" }),
23512
+ rating.toFixed(1)
23513
+ ] }),
23514
+ rating !== null && distance !== null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\xB7" }),
23515
+ distance !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-500 whitespace-nowrap", children: [
23516
+ distance.toFixed(1),
23517
+ " mi away"
23518
+ ] }),
23519
+ priceRange && (rating !== null || distance !== null) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\xB7" }),
23520
+ priceRange && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500 whitespace-nowrap", children: priceRange }),
23521
+ advanceNoticeText && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
23522
+ (rating !== null || distance !== null || priceRange) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\xB7" }),
23523
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `flex items-center gap-1 text-sm whitespace-nowrap ${noticeMet ? "text-gray-500" : "text-gray-500"}`, children: [
23524
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock3, { className: `h-3.5 w-3.5 flex-shrink-0 ${noticeMet ? "text-gray-400" : "text-red-500"}` }),
23525
+ advanceNoticeText
23526
+ ] })
23100
23527
  ] })
23101
- ] })
23528
+ ] }),
23529
+ tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: tags.join(" - ") })
23102
23530
  ] });
23103
23531
  })(),
23104
23532
  selectedRestaurant.restaurant_description && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -23360,26 +23788,72 @@ function RestaurantMenuBrowser({
23360
23788
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pb-6 md:pb-8", style: { contain: "inline-size" }, children: [
23361
23789
  hoursInfoTooltip,
23362
23790
  mobileHoursInfoSheet,
23363
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mt-2 mb-2 hidden md:block", children: [
23364
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" }),
23365
- /* @__PURE__ */ jsxRuntime.jsx(
23366
- "input",
23367
- {
23368
- type: "text",
23369
- value: searchQuery,
23370
- onChange: (e) => setSearchQuery(e.target.value),
23371
- placeholder: "Search restaurants and menu items...",
23372
- className: "w-full pl-9 pr-9 py-2.5 rounded-xl border border-base-300 bg-white text-base focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors"
23373
- }
23374
- ),
23375
- searchQuery && /* @__PURE__ */ jsxRuntime.jsx(
23376
- "button",
23377
- {
23378
- onClick: () => setSearchQuery(""),
23379
- className: "absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600",
23380
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
23381
- }
23382
- )
23791
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 mb-2 hidden md:flex items-center gap-2", children: [
23792
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1", children: [
23793
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" }),
23794
+ /* @__PURE__ */ jsxRuntime.jsx(
23795
+ "input",
23796
+ {
23797
+ type: "text",
23798
+ value: searchQuery,
23799
+ onChange: (e) => setSearchQuery(e.target.value),
23800
+ placeholder: "Search restaurants and menu items...",
23801
+ className: "w-full pl-9 pr-9 py-2.5 rounded-xl border border-base-300 bg-white text-base focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors"
23802
+ }
23803
+ ),
23804
+ searchQuery && /* @__PURE__ */ jsxRuntime.jsx(
23805
+ "button",
23806
+ {
23807
+ onClick: () => setSearchQuery(""),
23808
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600",
23809
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4" })
23810
+ }
23811
+ )
23812
+ ] }),
23813
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-shrink-0", ref: priceFilterRef, children: [
23814
+ /* @__PURE__ */ jsxRuntime.jsxs(
23815
+ "button",
23816
+ {
23817
+ onClick: () => setPriceFilterOpen((v) => !v),
23818
+ className: `flex items-center gap-2 h-full px-3 py-2.5 rounded-xl border transition-colors ${filters.restaurantPriceRange ? "bg-primary border-primary text-white" : "bg-white border-base-300 text-gray-600 hover:border-primary hover:text-primary"}`,
23819
+ children: [
23820
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SlidersHorizontal, { className: "w-4 h-4 flex-shrink-0" }),
23821
+ filters.restaurantPriceRange ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: filters.restaurantPriceRange }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: "Filters" })
23822
+ ]
23823
+ }
23824
+ ),
23825
+ priceFilterOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 top-full mt-2 bg-white rounded-xl border border-base-300 shadow-lg p-3 z-20 min-w-[180px]", children: [
23826
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2.5", children: [
23827
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-gray-700", children: "Price Range" }),
23828
+ filters.restaurantPriceRange && /* @__PURE__ */ jsxRuntime.jsx(
23829
+ "button",
23830
+ {
23831
+ onClick: () => {
23832
+ setFilters({ ...filters, restaurantPriceRange: null });
23833
+ setPriceFilterOpen(false);
23834
+ },
23835
+ className: "text-xs text-primary hover:text-primary/80 font-medium",
23836
+ children: "Clear"
23837
+ }
23838
+ )
23839
+ ] }),
23840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: ["~\xA3", "\xA3-\xA3\xA3", "\xA3\xA3-\xA3\xA3\xA3", "\xA3\xA3\xA3~"].map((range) => /* @__PURE__ */ jsxRuntime.jsx(
23841
+ "button",
23842
+ {
23843
+ onClick: () => {
23844
+ setFilters({
23845
+ ...filters,
23846
+ restaurantPriceRange: filters.restaurantPriceRange === range ? null : range
23847
+ });
23848
+ setPriceFilterOpen(false);
23849
+ },
23850
+ className: `px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${filters.restaurantPriceRange === range ? "bg-primary text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}`,
23851
+ children: range
23852
+ },
23853
+ range
23854
+ )) })
23855
+ ] })
23856
+ ] })
23383
23857
  ] }),
23384
23858
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
23385
23859
  !isSearchActive && /* @__PURE__ */ jsxRuntime.jsxs(
@@ -24393,8 +24867,10 @@ function AddressAutocomplete({
24393
24867
  setActiveIndex,
24394
24868
  handleSelect,
24395
24869
  handleKeyDown,
24396
- clear
24870
+ clear,
24871
+ outOfZoneError
24397
24872
  } = useAddressAutocomplete(onPlaceSelect);
24873
+ const shownError = error || outOfZoneError || void 0;
24398
24874
  const handleClear = () => {
24399
24875
  clear();
24400
24876
  if (onClearAddress) onClearAddress();
@@ -24415,7 +24891,7 @@ function AddressAutocomplete({
24415
24891
  onKeyDown: handleKeyDown,
24416
24892
  placeholder: "Start typing an address...",
24417
24893
  autoComplete: "new-password",
24418
- className: `address-search-input w-full bg-gray-50 border 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 ${error ? "border-error" : hasValidAddress ? "border-success" : "border-base-300"}`
24894
+ className: `address-search-input w-full bg-gray-50 border 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 ${shownError ? "border-error" : hasValidAddress ? "border-success" : "border-base-300"}`
24419
24895
  }
24420
24896
  ),
24421
24897
  hasValidAddress && /* @__PURE__ */ jsxRuntime.jsx(
@@ -24448,9 +24924,9 @@ function AddressAutocomplete({
24448
24924
  p.place_id
24449
24925
  )) })
24450
24926
  ] }),
24451
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-error", children: error }),
24452
- hasValidAddress && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-success", children: "Address selected" }),
24453
- !hasValidAddress && !error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-base-content/60", children: "Please select an address from the dropdown" })
24927
+ shownError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-error", children: shownError }),
24928
+ hasValidAddress && !shownError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-success", children: "Address selected" }),
24929
+ !hasValidAddress && !shownError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-base-content/60", children: "Please select an address from the dropdown" })
24454
24930
  ] });
24455
24931
  }
24456
24932
  function DeliveryAddressForm({
@@ -24588,7 +25064,11 @@ function BillingAddressAutocomplete({
24588
25064
  setActiveIndex,
24589
25065
  handleSelect,
24590
25066
  handleKeyDown
24591
- } = useAddressAutocomplete(onPlaceSelect, { countryRestriction: null });
25067
+ } = useAddressAutocomplete(onPlaceSelect, {
25068
+ countryRestriction: null,
25069
+ // Billing can be anywhere — only delivery is London-restricted.
25070
+ restrictToDeliveryZone: false
25071
+ });
24592
25072
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-full", ref: containerRef, children: [
24593
25073
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-[10px] font-bold text-base-content/60 uppercase tracking-widest mb-1.5", children: [
24594
25074
  "Search Address",
@@ -27325,6 +27805,10 @@ function CateringOrderBuilder() {
27325
27805
  setMobileChatView("chat");
27326
27806
  setMobileAddressPillEditing(false);
27327
27807
  setIsMobileChatMenuOpen(false);
27808
+ } else {
27809
+ requestAnimationFrame(() => {
27810
+ mobileAIInputRef.current?.blur();
27811
+ });
27328
27812
  }
27329
27813
  }, [isMobileAIChatOpen]);
27330
27814
  const [mobileAIInput, setMobileAIInput] = react.useState("");
@@ -27352,6 +27836,9 @@ function CateringOrderBuilder() {
27352
27836
  mobileAIInputRef.current.style.overflowY = "hidden";
27353
27837
  }
27354
27838
  };
27839
+ const cartTabRef = react.useRef(null);
27840
+ const [flyToCart, setFlyToCart] = react.useState(null);
27841
+ const [flyGhost, setFlyGhost] = react.useState(null);
27355
27842
  const [isMobileSearchOpen, setIsMobileSearchOpen] = react.useState(false);
27356
27843
  const [isMobileSearchActive, setIsMobileSearchActive] = react.useState(false);
27357
27844
  const [mobileSearchState, setMobileSearchState] = react.useState({ mode: "list", query: "" });
@@ -27510,9 +27997,6 @@ function CateringOrderBuilder() {
27510
27997
  ...parsed.contactInfo
27511
27998
  });
27512
27999
  setMobileAddressPillEditing(false);
27513
- requestAnimationFrame(() => {
27514
- mobileAIInputRef.current?.focus();
27515
- });
27516
28000
  };
27517
28001
  const handleMobileAddressClear = () => {
27518
28002
  setContactInfo({
@@ -27918,37 +28402,45 @@ function CateringOrderBuilder() {
27918
28402
  longitude: void 0
27919
28403
  });
27920
28404
  }, [contactInfo, setContactInfo]);
27921
- const fetchPricing = react.useCallback(
27922
- async (sessions) => {
27923
- const hasItems = sessions.some((s) => s.orderItems.length > 0);
27924
- if (!hasItems) {
28405
+ const pricingCancelRef = react.useRef(0);
28406
+ const pricingRetryRef = react.useRef(null);
28407
+ react.useEffect(() => {
28408
+ if (pricingDebounceRef.current) clearTimeout(pricingDebounceRef.current);
28409
+ if (pricingRetryRef.current) clearTimeout(pricingRetryRef.current);
28410
+ const gen = ++pricingCancelRef.current;
28411
+ function fire() {
28412
+ if (gen !== pricingCancelRef.current) return;
28413
+ if (totalItems === 0) {
27925
28414
  setPricing(null);
27926
28415
  return;
27927
28416
  }
27928
28417
  setCalculatingPricing(true);
27929
- try {
27930
- const result = await cateringService.calculateCateringPricingWithMealSessions(
27931
- sessions,
27932
- [],
27933
- deliveryLocation ?? void 0
27934
- );
27935
- if (result.isValid) setPricing(result);
27936
- } catch {
27937
- } finally {
28418
+ cateringService.calculateCateringPricingWithMealSessions(
28419
+ mealSessions,
28420
+ [],
28421
+ deliveryLocation ?? void 0
28422
+ ).then((result) => {
28423
+ if (gen !== pricingCancelRef.current) return;
28424
+ if (result.isValid) {
28425
+ setPricing(result);
28426
+ } else {
28427
+ setPricing(null);
28428
+ pricingRetryRef.current = setTimeout(fire, 2e3);
28429
+ }
28430
+ }).catch(() => {
28431
+ if (gen !== pricingCancelRef.current) return;
28432
+ pricingRetryRef.current = setTimeout(fire, 2e3);
28433
+ }).finally(() => {
28434
+ if (gen !== pricingCancelRef.current) return;
27938
28435
  setCalculatingPricing(false);
27939
- }
27940
- },
27941
- [deliveryLocation]
27942
- );
27943
- react.useEffect(() => {
27944
- if (pricingDebounceRef.current) clearTimeout(pricingDebounceRef.current);
27945
- pricingDebounceRef.current = setTimeout(() => {
27946
- fetchPricing(mealSessions);
27947
- }, 600);
28436
+ });
28437
+ }
28438
+ pricingDebounceRef.current = setTimeout(fire, 600);
27948
28439
  return () => {
27949
28440
  if (pricingDebounceRef.current) clearTimeout(pricingDebounceRef.current);
28441
+ if (pricingRetryRef.current) clearTimeout(pricingRetryRef.current);
27950
28442
  };
27951
- }, [mealSessions, fetchPricing]);
28443
+ }, [totalItems, mealSessions, deliveryLocation]);
27952
28444
  const handleEditorClose = (cancelled) => {
27953
28445
  const sessionIndex = editingSessionIndex;
27954
28446
  const wasNewSession = isNewSession;
@@ -28388,7 +28880,7 @@ function CateringOrderBuilder() {
28388
28880
  if (currentStep === 2) {
28389
28881
  return /* @__PURE__ */ jsxRuntime.jsx(CheckoutScreen, {});
28390
28882
  }
28391
- return /* @__PURE__ */ jsxRuntime.jsx(
28883
+ return /* @__PURE__ */ jsxRuntime.jsxs(
28392
28884
  ChatSessionProvider,
28393
28885
  {
28394
28886
  enabled: aiEnabled && (rightPanelTab === "ai" || isMobileAIChatOpen),
@@ -28400,854 +28892,903 @@ function CateringOrderBuilder() {
28400
28892
  setPendingItem(previewItemToMenuItem(previewItem));
28401
28893
  },
28402
28894
  onAddedToBasket: () => {
28403
- setRightPanelTab("cart");
28404
- setMobileChatView("chat");
28405
- },
28406
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-base-100", children: [
28407
- editingSessionIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
28408
- SessionEditor,
28409
- {
28410
- session: mealSessions[editingSessionIndex],
28411
- sessionIndex: editingSessionIndex,
28412
- onUpdate: updateMealSession,
28413
- onClose: handleEditorClose,
28414
- restaurants,
28415
- existingDates: dayGroups.filter((d) => d.date !== "unscheduled").map((d) => ({
28416
- date: d.date,
28417
- dayName: d.dayName,
28418
- displayDate: d.displayDate
28419
- }))
28895
+ const basketRect = basketColumnRef.current?.getBoundingClientRect();
28896
+ const overlayLeft = 8;
28897
+ const overlayRight = window.innerWidth - 384;
28898
+ const fromX = (overlayLeft + overlayRight) / 2;
28899
+ const fromY = window.innerHeight / 2;
28900
+ setFlyToCart({ x: 0, y: 0 });
28901
+ setTimeout(() => {
28902
+ if (basketRect) {
28903
+ setFlyGhost({
28904
+ fromX,
28905
+ fromY,
28906
+ toX: basketRect.left + basketRect.width / 2,
28907
+ toY: basketRect.top + basketRect.height / 2
28908
+ });
28420
28909
  }
28421
- ),
28422
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col md:flex-row px-2${isMobileAIChatOpen ? " hidden md:flex" : ""}`, children: [
28423
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", children: [
28424
- /* @__PURE__ */ jsxRuntime.jsx(
28425
- SessionPickerDropdown,
28426
- {
28427
- dayGroups,
28428
- expandedSessionIndex: activeSessionIndex,
28429
- onSessionPillClick: handleSessionPillClick,
28430
- onEditSession: (index) => setEditingSessionIndex(index),
28431
- onDeleteSession: (index) => setSessionToRemove(index),
28432
- onAddDay: handleAddDay,
28433
- formatTimeDisplay
28434
- }
28435
- ),
28436
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative max-w-6xl mx-auto w-full px-2 md:px-6", children: [
28910
+ }, 350);
28911
+ setTimeout(() => {
28912
+ setFlyToCart(null);
28913
+ setRightPanelTab("cart");
28914
+ setMobileChatView("chat");
28915
+ }, 550);
28916
+ setTimeout(() => setFlyGhost(null), 1200);
28917
+ },
28918
+ children: [
28919
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-base-100", children: [
28920
+ editingSessionIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
28921
+ SessionEditor,
28922
+ {
28923
+ session: mealSessions[editingSessionIndex],
28924
+ sessionIndex: editingSessionIndex,
28925
+ onUpdate: updateMealSession,
28926
+ onClose: handleEditorClose,
28927
+ restaurants,
28928
+ existingDates: dayGroups.filter((d) => d.date !== "unscheduled").map((d) => ({
28929
+ date: d.date,
28930
+ dayName: d.dayName,
28931
+ displayDate: d.displayDate
28932
+ })),
28933
+ existingSessions: mealSessions.filter((_, i) => i !== editingSessionIndex).filter((s) => s.sessionDate && s.eventTime).map((s) => ({ date: s.sessionDate, time: s.eventTime }))
28934
+ }
28935
+ ),
28936
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col md:flex-row px-2${isMobileAIChatOpen ? " hidden md:flex" : ""}`, children: [
28937
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", children: [
28437
28938
  /* @__PURE__ */ jsxRuntime.jsx(
28438
- MenuBrowserColumn,
28939
+ SessionPickerDropdown,
28439
28940
  {
28440
- showBundleBrowser,
28441
- onToggleBundleBrowser: setShowBundleBrowser,
28442
- sessionIndex: activeSessionIndex,
28443
- defaultGuestCount: mealSessions[activeSessionIndex]?.guestCount ?? 1,
28444
- restaurants,
28445
- restaurantsLoading,
28446
- onAddItem: handleAddItem,
28447
- onUpdateQuantity: handleUpdateQuantity,
28448
- onAddOrderPress: handleAddOrderPress,
28449
- getItemQuantity,
28450
- expandedItemId,
28451
- setExpandedItemId,
28452
- selectedDietaryFilters,
28453
- toggleDietaryFilter,
28454
- restaurantListRef,
28455
- firstMenuItemRef,
28456
- categoriesRowRef,
28941
+ dayGroups,
28457
28942
  expandedSessionIndex: activeSessionIndex,
28458
- onRegisterResetToList: (fn) => {
28459
- resetRestaurantListRef.current = fn;
28460
- },
28461
- onMobileSearchStateChange: setMobileSearchState,
28462
- onRegisterMobileSearchSetter: handleRegisterMobileSearchSetter
28943
+ onSessionPillClick: handleSessionPillClick,
28944
+ onEditSession: (index) => setEditingSessionIndex(index),
28945
+ onDeleteSession: (index) => setSessionToRemove(index),
28946
+ onAddDay: handleAddDay,
28947
+ formatTimeDisplay
28463
28948
  }
28464
28949
  ),
28465
- /* @__PURE__ */ jsxRuntime.jsx(
28466
- SuggestionsOverlayBridge,
28467
- {
28468
- active: effectiveRightPanelTab === "ai",
28469
- onVisibleChange: setOverlayVisible
28470
- }
28471
- )
28472
- ] })
28473
- ] }),
28474
- /* @__PURE__ */ jsxRuntime.jsx(
28475
- "div",
28476
- {
28477
- ref: basketColumnRef,
28478
- className: `hidden md:flex md:w-96 flex-shrink-0 flex-col overflow-hidden py-2 px-1 ${overlayVisible ? "fixed z-50 bg-base-100" : "sticky"}`,
28479
- style: {
28480
- top: stickyTopOffset,
28481
- right: overlayVisible ? 0 : void 0,
28482
- height: overlayVisible ? `calc(100dvh - ${stickyTopOffset}px)` : basketHeight,
28483
- alignSelf: "flex-start"
28484
- },
28485
- children: mealSessions[activeSessionIndex] && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col gap-2 overflow-hidden", children: [
28486
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 flex rounded-xl bg-base-200 p-1", children: [
28487
- /* @__PURE__ */ jsxRuntime.jsx(
28488
- "button",
28489
- {
28490
- type: "button",
28491
- onClick: () => setRightPanelTab("cart"),
28492
- className: `flex-1 rounded-lg px-3 py-1.5 text-sm font-semibold transition-colors ${effectiveRightPanelTab === "cart" ? "bg-white text-gray-800 shadow-sm" : "text-gray-500 hover:text-gray-700"}`,
28493
- children: "Cart"
28494
- }
28495
- ),
28496
- /* @__PURE__ */ jsxRuntime.jsxs(
28497
- "button",
28498
- {
28499
- type: "button",
28500
- onClick: () => {
28501
- if (!aiEnabled) return;
28502
- setRightPanelTab("ai");
28503
- },
28504
- disabled: !aiEnabled,
28505
- "aria-disabled": !aiEnabled,
28506
- title: aiEnabled ? void 0 : "Coming Soon",
28507
- className: `flex-1 rounded-lg px-3 py-1.5 text-sm font-semibold transition-colors flex items-center justify-center gap-1.5 ${!aiEnabled ? "text-gray-400 cursor-not-allowed" : effectiveRightPanelTab === "ai" ? "bg-white text-gray-800 shadow-sm" : "text-gray-500 hover:text-gray-700"}`,
28508
- children: [
28509
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-3.5 h-3.5" }),
28510
- "AI Chat",
28511
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-primary px-1.5 py-0.5 text-[8px] font-bold uppercase tracking-wider text-white shadow-sm whitespace-nowrap", children: aiEnabled ? "Beta" : "Soon" })
28512
- ]
28513
- }
28514
- )
28515
- ] }),
28516
- effectiveRightPanelTab === "cart" ? /* @__PURE__ */ jsxRuntime.jsx(
28517
- ActiveSessionPanel,
28950
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative max-w-6xl mx-auto w-full px-2 md:px-6", children: [
28951
+ /* @__PURE__ */ jsxRuntime.jsx(
28952
+ MenuBrowserColumn,
28518
28953
  {
28519
- session: mealSessions[activeSessionIndex],
28954
+ showBundleBrowser,
28955
+ onToggleBundleBrowser: setShowBundleBrowser,
28520
28956
  sessionIndex: activeSessionIndex,
28521
- sessionTotal: getSessionTotal(activeSessionIndex),
28522
- sessionPromotions: getSessionDiscount(activeSessionIndex).promotions,
28523
- validationError: sessionValidationErrors[activeSessionIndex] || null,
28524
- isUnscheduled: !mealSessions[activeSessionIndex].sessionDate,
28525
- canRemove: true,
28526
- onEditSession: () => setEditingSessionIndex(activeSessionIndex),
28527
- onRemoveSession: (e) => handleRemoveSession(activeSessionIndex, e),
28528
- onEditItem: handleEditItem,
28529
- onRemoveItem: handleRemoveItem,
28530
- onSwapItem: handleSwapItem,
28531
- onRemoveBundle: handleRemoveBundle,
28532
- collapsedCategories,
28533
- onToggleCategory: handleToggleCategory,
28534
- onViewMenu: handleViewMenu,
28535
- isCurrentSessionValid,
28536
- totalPrice: getTotalPrice(),
28537
- onCheckout: handleCheckout,
28538
- showCheckoutButton: false,
28539
- restaurants
28957
+ defaultGuestCount: mealSessions[activeSessionIndex]?.guestCount ?? 1,
28958
+ restaurants,
28959
+ restaurantsLoading,
28960
+ onAddItem: handleAddItem,
28961
+ onUpdateQuantity: handleUpdateQuantity,
28962
+ onAddOrderPress: handleAddOrderPress,
28963
+ getItemQuantity,
28964
+ expandedItemId,
28965
+ setExpandedItemId,
28966
+ selectedDietaryFilters,
28967
+ toggleDietaryFilter,
28968
+ restaurantListRef,
28969
+ firstMenuItemRef,
28970
+ categoriesRowRef,
28971
+ expandedSessionIndex: activeSessionIndex,
28972
+ onRegisterResetToList: (fn) => {
28973
+ resetRestaurantListRef.current = fn;
28974
+ },
28975
+ onMobileSearchStateChange: setMobileSearchState,
28976
+ onRegisterMobileSearchSetter: handleRegisterMobileSearchSetter
28540
28977
  }
28541
- ) : /* @__PURE__ */ jsxRuntime.jsx(AIChat, {}),
28542
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-auto flex-shrink-0 flex flex-col gap-1.5", children: effectiveRightPanelTab === "ai" ? /* @__PURE__ */ jsxRuntime.jsx(AISuggestionsBottomButton, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
28543
- totalItems > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1 border-t border-base-300 pt-3", children: /* @__PURE__ */ jsxRuntime.jsx(
28544
- PricingSummary,
28545
- {
28546
- pricing,
28547
- calculatingPricing,
28548
- compact: true,
28549
- onPlaceSelect: handleDeliveryPlaceSelect,
28550
- onClearAddress: handleClearDeliveryAddress
28551
- }
28552
- ) }),
28553
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-stretch gap-2", children: [
28554
- mealSessions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-shrink-0", children: [
28555
- /* @__PURE__ */ jsxRuntime.jsx(
28556
- "button",
28557
- {
28558
- ref: desktopMenuBtnRef,
28559
- onClick: () => {
28560
- if (!isDesktopCartMenuOpen && desktopMenuBtnRef.current) {
28561
- const rect = desktopMenuBtnRef.current.getBoundingClientRect();
28562
- setDesktopMenuPos({
28563
- bottom: window.innerHeight - rect.top + 8,
28564
- left: rect.left
28565
- });
28566
- }
28567
- setIsDesktopCartMenuOpen((v) => !v);
28568
- },
28569
- className: "flex h-full items-center justify-center rounded-lg border border-base-300 px-3 text-base-content/60 transition-colors hover:bg-base-200",
28570
- title: "More options",
28571
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-4 w-4" })
28572
- }
28573
- ),
28574
- isDesktopCartMenuOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
28575
- /* @__PURE__ */ jsxRuntime.jsx(
28576
- "div",
28577
- {
28578
- className: "fixed inset-0 z-40",
28579
- onClick: () => setIsDesktopCartMenuOpen(false)
28580
- }
28581
- ),
28582
- /* @__PURE__ */ jsxRuntime.jsxs(
28583
- "div",
28584
- {
28585
- className: "fixed z-50 w-44 overflow-hidden rounded-xl border border-base-200 bg-white shadow-lg",
28586
- style: {
28587
- bottom: desktopMenuPos.bottom,
28588
- left: desktopMenuPos.left
28589
- },
28590
- children: [
28591
- /* @__PURE__ */ jsxRuntime.jsxs(
28592
- "button",
28593
- {
28594
- onClick: () => {
28595
- setIsDesktopCartMenuOpen(false);
28596
- handleViewMenu();
28597
- },
28598
- disabled: generatingPdf || totalItems === 0,
28599
- className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm text-gray-700 transition-colors hover:bg-base-100 disabled:cursor-not-allowed disabled:opacity-50",
28600
- children: [
28601
- generatingPdf ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "loading loading-spinner loading-xs" }) : /* @__PURE__ */ jsxRuntime.jsx(
28602
- "svg",
28603
- {
28604
- xmlns: "http://www.w3.org/2000/svg",
28605
- className: "h-4 w-4",
28606
- fill: "none",
28607
- viewBox: "0 0 24 24",
28608
- stroke: "currentColor",
28609
- children: /* @__PURE__ */ jsxRuntime.jsx(
28610
- "path",
28611
- {
28612
- strokeLinecap: "round",
28613
- strokeLinejoin: "round",
28614
- strokeWidth: 2,
28615
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
28616
- }
28617
- )
28618
- }
28619
- ),
28620
- "Download Menu"
28621
- ]
28622
- }
28623
- ),
28624
- /* @__PURE__ */ jsxRuntime.jsxs(
28625
- "button",
28626
- {
28627
- onClick: () => {
28628
- setIsDesktopCartMenuOpen(false);
28629
- setIsClearAllConfirmOpen(true);
28630
- },
28631
- className: "flex w-full items-center gap-2 border-t border-base-200 px-3 py-2.5 text-left text-sm text-red-600 transition-colors hover:bg-red-50",
28632
- children: [
28633
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
28634
- "Clear Cart"
28635
- ]
28636
- }
28637
- )
28638
- ]
28639
- }
28640
- )
28641
- ] })
28642
- ] }),
28643
- /* @__PURE__ */ jsxRuntime.jsx(
28644
- "button",
28645
- {
28646
- onClick: handleCheckout,
28647
- disabled: totalItems === 0,
28648
- className: `flex flex-1 items-center justify-center rounded-lg px-3 py-3 text-sm font-semibold text-white transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${isCurrentSessionValid ? "bg-primary hover:bg-primary/90" : "bg-warning hover:bg-warning/90"}`,
28649
- children: totalItems === 0 ? "Cart Empty" : isCurrentSessionValid ? "Checkout" : "Min. Order Not Met"
28650
- }
28651
- )
28652
- ] })
28653
- ] }) })
28978
+ ),
28979
+ /* @__PURE__ */ jsxRuntime.jsx(
28980
+ SuggestionsOverlayBridge,
28981
+ {
28982
+ active: effectiveRightPanelTab === "ai",
28983
+ onVisibleChange: setOverlayVisible,
28984
+ flyToCart
28985
+ }
28986
+ )
28654
28987
  ] })
28655
- }
28656
- )
28657
- ] }),
28658
- isMobileAIChatOpen && /* @__PURE__ */ jsxRuntime.jsxs(
28659
- "div",
28660
- {
28661
- className: "fixed left-0 right-0 z-40 md:hidden swift-chat-design",
28662
- style: {
28663
- top: vvOffsetTop,
28664
- height: vvHeight ?? "100vh"
28665
- },
28666
- children: [
28667
- /* @__PURE__ */ jsxRuntime.jsx(
28668
- "div",
28669
- {
28670
- className: "absolute inset-0 bg-white",
28671
- "aria-hidden": "true"
28672
- }
28673
- ),
28674
- /* @__PURE__ */ jsxRuntime.jsx(react$1.LayoutGroup, { children: /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { initial: false, mode: "popLayout", children: mobileAddressEditorOpen ? /* @__PURE__ */ jsxRuntime.jsx(
28675
- MobileAddressPickerSurface,
28676
- {
28677
- hasAddress: hasChatAddress,
28678
- initialQuery: mobileAddressPillEditing ? contactInfo?.addressLine1 ?? "" : "",
28679
- onPlaceSelect: handleMobileAddressPick,
28680
- onBack: () => setMobileAddressPillEditing(false),
28681
- onRemove: handleMobileAddressClear,
28682
- onClose: closeMobileAIChat
28988
+ ] }),
28989
+ overlayVisible && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:block md:w-96 flex-shrink-0", "aria-hidden": true }),
28990
+ /* @__PURE__ */ jsxRuntime.jsx(
28991
+ "div",
28992
+ {
28993
+ ref: basketColumnRef,
28994
+ className: `hidden md:flex md:w-96 flex-shrink-0 flex-col overflow-hidden py-2 px-1 ${overlayVisible ? "fixed z-50 bg-base-100" : "sticky"}`,
28995
+ style: {
28996
+ top: stickyTopOffset,
28997
+ right: overlayVisible ? 0 : void 0,
28998
+ height: overlayVisible ? `calc(100dvh - ${stickyTopOffset}px)` : basketHeight,
28999
+ alignSelf: "flex-start"
28683
29000
  },
28684
- "address-picker"
28685
- ) : /* @__PURE__ */ jsxRuntime.jsxs(
28686
- react$1.motion.div,
28687
- {
28688
- initial: { opacity: 0 },
28689
- animate: { opacity: 1 },
28690
- exit: { opacity: 0 },
28691
- transition: { duration: 0.18, ease: "easeOut" },
28692
- className: "absolute inset-0",
28693
- children: [
28694
- aiBetaUnlocked && hasChatAddress && mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsxs(
28695
- react$1.motion.button,
29001
+ children: mealSessions[activeSessionIndex] && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col gap-2 overflow-hidden", children: [
29002
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 flex rounded-xl bg-base-200 p-1", children: [
29003
+ /* @__PURE__ */ jsxRuntime.jsx(
29004
+ "button",
28696
29005
  {
29006
+ ref: cartTabRef,
28697
29007
  type: "button",
28698
- layoutId: "catering-address-pill",
28699
- transition: { duration: 0.28, ease: "easeOut" },
28700
- onClick: () => setMobileAddressPillEditing(true),
28701
- className: "absolute top-3 left-3 z-10 inline-flex max-w-[60%] items-center gap-1.5 rounded-xl bg-black/60 backdrop-blur-sm px-3 py-2 text-xs text-white",
28702
- children: [
28703
- /* @__PURE__ */ jsxRuntime.jsx(
28704
- lucideReact.MapPin,
28705
- {
28706
- className: "h-3.5 w-3.5 flex-shrink-0 text-white/80",
28707
- "aria-hidden": true
28708
- }
28709
- ),
28710
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: contactInfo?.addressLine1 }),
28711
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/50", children: "\xB7" }),
28712
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-white", children: "change" })
28713
- ]
29008
+ onClick: () => setRightPanelTab("cart"),
29009
+ className: `flex-1 rounded-lg px-3 py-1.5 text-sm font-semibold transition-colors ${effectiveRightPanelTab === "cart" ? "bg-white text-gray-800 shadow-sm" : "text-gray-500 hover:text-gray-700"}`,
29010
+ children: "Cart"
28714
29011
  }
28715
29012
  ),
28716
- mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(
29013
+ /* @__PURE__ */ jsxRuntime.jsxs(
28717
29014
  "button",
28718
29015
  {
28719
29016
  type: "button",
28720
- onClick: closeMobileAIChat,
28721
- className: "absolute top-3 right-3 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-black/60 backdrop-blur-sm text-white",
28722
- title: "Close AI chat",
28723
- "aria-label": "Close AI chat",
28724
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
29017
+ onClick: () => {
29018
+ if (!aiEnabled) return;
29019
+ setRightPanelTab("ai");
29020
+ },
29021
+ disabled: !aiEnabled,
29022
+ "aria-disabled": !aiEnabled,
29023
+ title: aiEnabled ? void 0 : "Coming Soon",
29024
+ className: `flex-1 rounded-lg px-3 py-1.5 text-sm font-semibold transition-colors flex items-center justify-center gap-1.5 ${!aiEnabled ? "text-gray-400 cursor-not-allowed" : effectiveRightPanelTab === "ai" ? "bg-white text-gray-800 shadow-sm" : "text-gray-500 hover:text-gray-700"}`,
29025
+ children: [
29026
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-3.5 h-3.5" }),
29027
+ "AI Chat",
29028
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-primary px-1.5 py-0.5 text-[8px] font-bold uppercase tracking-wider text-white shadow-sm whitespace-nowrap", children: aiEnabled ? "Beta" : "Soon" })
29029
+ ]
28725
29030
  }
28726
- ),
28727
- /* @__PURE__ */ jsxRuntime.jsx(
28728
- "div",
29031
+ )
29032
+ ] }),
29033
+ effectiveRightPanelTab === "cart" ? /* @__PURE__ */ jsxRuntime.jsx(
29034
+ ActiveSessionPanel,
29035
+ {
29036
+ session: mealSessions[activeSessionIndex],
29037
+ sessionIndex: activeSessionIndex,
29038
+ sessionTotal: getSessionTotal(activeSessionIndex),
29039
+ sessionPromotions: getSessionDiscount(activeSessionIndex).promotions,
29040
+ validationError: sessionValidationErrors[activeSessionIndex] || null,
29041
+ isUnscheduled: !mealSessions[activeSessionIndex].sessionDate,
29042
+ canRemove: true,
29043
+ onEditSession: () => setEditingSessionIndex(activeSessionIndex),
29044
+ onRemoveSession: (e) => handleRemoveSession(activeSessionIndex, e),
29045
+ onEditItem: handleEditItem,
29046
+ onRemoveItem: handleRemoveItem,
29047
+ onSwapItem: handleSwapItem,
29048
+ onRemoveBundle: handleRemoveBundle,
29049
+ collapsedCategories,
29050
+ onToggleCategory: handleToggleCategory,
29051
+ onViewMenu: handleViewMenu,
29052
+ isCurrentSessionValid,
29053
+ totalPrice: getTotalPrice(),
29054
+ onCheckout: handleCheckout,
29055
+ showCheckoutButton: false,
29056
+ restaurants
29057
+ }
29058
+ ) : /* @__PURE__ */ jsxRuntime.jsx(AIChat, {}),
29059
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-auto flex-shrink-0 flex flex-col gap-1.5", children: effectiveRightPanelTab === "ai" ? /* @__PURE__ */ jsxRuntime.jsx(AISuggestionsBottomButton, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29060
+ totalItems > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1 border-t border-base-300 pt-3", children: /* @__PURE__ */ jsxRuntime.jsx(
29061
+ PricingSummary,
28729
29062
  {
28730
- ref: mobileChatScrollRef,
28731
- "data-allow-touch": true,
28732
- className: "absolute inset-x-0 top-0 overflow-y-auto overscroll-contain",
28733
- style: { bottom: 64 },
28734
- children: !aiBetaUnlocked ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 pt-14 pb-4 flex flex-col gap-3", children: [
28735
- /* @__PURE__ */ jsxRuntime.jsx(
28736
- TextBubble,
28737
- {
28738
- sender: "bot",
28739
- text: "What is the code to use the AI chat and test our beta version?"
28740
- }
28741
- ),
28742
- mobileGateError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-xs text-red-600", children: mobileGateError })
28743
- ] }) : mobileChatView === "results" ? /* @__PURE__ */ jsxRuntime.jsx(
28744
- MobileResultsView,
28745
- {
28746
- onBack: () => setMobileChatView("chat")
28747
- }
28748
- ) : /* @__PURE__ */ jsxRuntime.jsx(
28749
- MobileChatThread,
28750
- {
28751
- hasAddressTopPill: hasChatAddress && aiBetaUnlocked
28752
- }
28753
- )
29063
+ pricing,
29064
+ calculatingPricing,
29065
+ compact: true,
29066
+ onPlaceSelect: handleDeliveryPlaceSelect,
29067
+ onClearAddress: handleClearDeliveryAddress
28754
29068
  }
28755
- ),
28756
- aiBetaUnlocked && mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(MobileChatFloatingChips, { bottomOffset: 64 + 8 })
28757
- ]
28758
- },
28759
- "chat-surface"
28760
- ) }) })
28761
- ]
28762
- }
28763
- ),
28764
- /* @__PURE__ */ jsxRuntime.jsxs(
28765
- "div",
28766
- {
28767
- className: `fixed left-0 right-0 md:hidden z-50 ${mobileAddressEditorOpen || keyboardOffset > 100 && !isMobileAIChatOpen && !isMobileSearchActive ? "hidden" : ""}`,
28768
- style: { bottom: keyboardOffset },
28769
- "aria-hidden": mobileAddressEditorOpen,
28770
- children: [
28771
- /* @__PURE__ */ jsxRuntime.jsx(
28772
- "div",
28773
- {
28774
- ref: mobileBarRowRef,
28775
- className: "relative px-3 pt-1 pb-2",
28776
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-end gap-2", children: [
28777
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
28778
- /* @__PURE__ */ jsxRuntime.jsx(
28779
- "button",
28780
- {
28781
- onMouseDown: (e) => {
28782
- if (chatInputFocused) e.preventDefault();
28783
- },
28784
- onClick: isMobileAIChatOpen ? chatInputFocused ? () => mobileAIInputRef.current?.blur() : () => setIsMobileChatMenuOpen((v) => !v) : () => setIsMobileCartMenuOpen((v) => !v),
28785
- className: "flex h-11 w-11 items-center justify-center rounded-full border border-base-200 bg-white/70 text-gray-700 shadow-sm backdrop-blur-sm transition-colors hover:bg-white",
28786
- title: isMobileAIChatOpen ? chatInputFocused ? "Hide keyboard" : "Chat actions" : "More actions",
28787
- "aria-label": isMobileAIChatOpen ? chatInputFocused ? "Hide keyboard" : "Chat actions" : "More actions",
28788
- children: isMobileAIChatOpen && chatInputFocused ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-4 w-4" })
28789
- }
28790
- ),
28791
- isMobileAIChatOpen && isMobileChatMenuOpen && /* @__PURE__ */ jsxRuntime.jsx(
28792
- MobileChatActionsMenu,
28793
- {
28794
- onClose: () => setIsMobileChatMenuOpen(false)
28795
- }
28796
- ),
28797
- isMobileCartMenuOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29069
+ ) }),
29070
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-stretch gap-2", children: [
29071
+ mealSessions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-shrink-0", children: [
28798
29072
  /* @__PURE__ */ jsxRuntime.jsx(
28799
- "div",
29073
+ "button",
28800
29074
  {
28801
- className: "fixed inset-0 z-40",
28802
- onClick: () => setIsMobileCartMenuOpen(false)
29075
+ ref: desktopMenuBtnRef,
29076
+ onClick: () => {
29077
+ if (!isDesktopCartMenuOpen && desktopMenuBtnRef.current) {
29078
+ const rect = desktopMenuBtnRef.current.getBoundingClientRect();
29079
+ setDesktopMenuPos({
29080
+ bottom: window.innerHeight - rect.top + 8,
29081
+ left: rect.left
29082
+ });
29083
+ }
29084
+ setIsDesktopCartMenuOpen((v) => !v);
29085
+ },
29086
+ className: "flex h-full items-center justify-center rounded-lg border border-base-300 px-3 text-base-content/60 transition-colors hover:bg-base-200",
29087
+ title: "More options",
29088
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-4 w-4" })
28803
29089
  }
28804
29090
  ),
28805
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-44 overflow-hidden rounded-xl border border-base-200 bg-white shadow-lg", children: [
28806
- /* @__PURE__ */ jsxRuntime.jsxs(
28807
- "button",
29091
+ isDesktopCartMenuOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29092
+ /* @__PURE__ */ jsxRuntime.jsx(
29093
+ "div",
28808
29094
  {
28809
- onClick: () => {
28810
- setIsMobileCartMenuOpen(false);
28811
- handleViewMenu();
28812
- },
28813
- disabled: generatingPdf || totalItems === 0,
28814
- className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm text-gray-700 transition-colors hover:bg-base-100 disabled:cursor-not-allowed disabled:opacity-50",
28815
- children: [
28816
- /* @__PURE__ */ jsxRuntime.jsx(
28817
- "svg",
28818
- {
28819
- xmlns: "http://www.w3.org/2000/svg",
28820
- className: "h-4 w-4",
28821
- fill: "none",
28822
- viewBox: "0 0 24 24",
28823
- stroke: "currentColor",
28824
- children: /* @__PURE__ */ jsxRuntime.jsx(
28825
- "path",
28826
- {
28827
- strokeLinecap: "round",
28828
- strokeLinejoin: "round",
28829
- strokeWidth: 2,
28830
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
28831
- }
28832
- )
28833
- }
28834
- ),
28835
- "Download Menu"
28836
- ]
29095
+ className: "fixed inset-0 z-40",
29096
+ onClick: () => setIsDesktopCartMenuOpen(false)
28837
29097
  }
28838
29098
  ),
28839
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-3 border-t border-base-200" }),
28840
29099
  /* @__PURE__ */ jsxRuntime.jsxs(
28841
- "button",
29100
+ "div",
28842
29101
  {
28843
- onClick: () => {
28844
- setIsMobileCartMenuOpen(false);
28845
- setIsClearAllConfirmOpen(true);
29102
+ className: "fixed z-50 w-44 overflow-hidden rounded-xl border border-base-200 bg-white shadow-lg",
29103
+ style: {
29104
+ bottom: desktopMenuPos.bottom,
29105
+ left: desktopMenuPos.left
28846
29106
  },
28847
- className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm text-red-600 transition-colors hover:bg-red-50",
28848
29107
  children: [
28849
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
28850
- "Clear Cart"
29108
+ /* @__PURE__ */ jsxRuntime.jsxs(
29109
+ "button",
29110
+ {
29111
+ onClick: () => {
29112
+ setIsDesktopCartMenuOpen(false);
29113
+ handleViewMenu();
29114
+ },
29115
+ disabled: generatingPdf || totalItems === 0,
29116
+ className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm text-gray-700 transition-colors hover:bg-base-100 disabled:cursor-not-allowed disabled:opacity-50",
29117
+ children: [
29118
+ generatingPdf ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "loading loading-spinner loading-xs" }) : /* @__PURE__ */ jsxRuntime.jsx(
29119
+ "svg",
29120
+ {
29121
+ xmlns: "http://www.w3.org/2000/svg",
29122
+ className: "h-4 w-4",
29123
+ fill: "none",
29124
+ viewBox: "0 0 24 24",
29125
+ stroke: "currentColor",
29126
+ children: /* @__PURE__ */ jsxRuntime.jsx(
29127
+ "path",
29128
+ {
29129
+ strokeLinecap: "round",
29130
+ strokeLinejoin: "round",
29131
+ strokeWidth: 2,
29132
+ d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
29133
+ }
29134
+ )
29135
+ }
29136
+ ),
29137
+ "Download Menu"
29138
+ ]
29139
+ }
29140
+ ),
29141
+ /* @__PURE__ */ jsxRuntime.jsxs(
29142
+ "button",
29143
+ {
29144
+ onClick: () => {
29145
+ setIsDesktopCartMenuOpen(false);
29146
+ setIsClearAllConfirmOpen(true);
29147
+ },
29148
+ className: "flex w-full items-center gap-2 border-t border-base-200 px-3 py-2.5 text-left text-sm text-red-600 transition-colors hover:bg-red-50",
29149
+ children: [
29150
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
29151
+ "Clear Cart"
29152
+ ]
29153
+ }
29154
+ )
28851
29155
  ]
28852
29156
  }
28853
29157
  )
28854
29158
  ] })
28855
- ] })
28856
- ] }),
28857
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative min-w-0 flex-1 overflow-hidden rounded-3xl border border-base-200 bg-white/70 shadow-sm backdrop-blur-sm focus-within:border-primary", children: [
28858
- /* @__PURE__ */ jsxRuntime.jsxs(
29159
+ ] }),
29160
+ /* @__PURE__ */ jsxRuntime.jsx(
28859
29161
  "button",
29162
+ {
29163
+ onClick: handleCheckout,
29164
+ disabled: totalItems === 0,
29165
+ className: `flex flex-1 items-center justify-center rounded-lg px-3 py-3 text-sm font-semibold text-white transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${isCurrentSessionValid ? "bg-primary hover:bg-primary/90" : "bg-warning hover:bg-warning/90"}`,
29166
+ children: totalItems === 0 ? "Cart Empty" : isCurrentSessionValid ? "Checkout" : "Min. Order Not Met"
29167
+ }
29168
+ )
29169
+ ] })
29170
+ ] }) })
29171
+ ] })
29172
+ }
29173
+ )
29174
+ ] }),
29175
+ isMobileAIChatOpen && /* @__PURE__ */ jsxRuntime.jsxs(
29176
+ "div",
29177
+ {
29178
+ className: "fixed left-0 right-0 z-40 md:hidden swift-chat-design",
29179
+ style: {
29180
+ top: vvOffsetTop,
29181
+ height: vvHeight ?? "100vh"
29182
+ },
29183
+ children: [
29184
+ /* @__PURE__ */ jsxRuntime.jsx(
29185
+ "div",
29186
+ {
29187
+ className: "absolute inset-0 bg-white",
29188
+ "aria-hidden": "true"
29189
+ }
29190
+ ),
29191
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.LayoutGroup, { children: /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { initial: false, mode: "popLayout", children: mobileAddressEditorOpen ? /* @__PURE__ */ jsxRuntime.jsx(
29192
+ MobileAddressPickerSurface,
29193
+ {
29194
+ hasAddress: hasChatAddress,
29195
+ initialQuery: mobileAddressPillEditing ? contactInfo?.addressLine1 ?? "" : "",
29196
+ onPlaceSelect: handleMobileAddressPick,
29197
+ onBack: () => setMobileAddressPillEditing(false),
29198
+ onRemove: handleMobileAddressClear,
29199
+ onClose: closeMobileAIChat
29200
+ },
29201
+ "address-picker"
29202
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
29203
+ react$1.motion.div,
29204
+ {
29205
+ initial: { opacity: 0 },
29206
+ animate: { opacity: 1 },
29207
+ exit: { opacity: 0 },
29208
+ transition: { duration: 0.18, ease: "easeOut" },
29209
+ className: "absolute inset-0",
29210
+ children: [
29211
+ aiBetaUnlocked && hasChatAddress && mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsxs(
29212
+ react$1.motion.button,
28860
29213
  {
28861
29214
  type: "button",
28862
- onClick: () => {
28863
- if (aiEnabled) {
28864
- setIsMobileAIChatOpen(true);
28865
- } else {
28866
- setIsAIMobileUnavailableOpen(true);
28867
- }
28868
- },
28869
- title: aiEnabled ? void 0 : "Coming Soon",
28870
- className: `absolute inset-0 flex items-center justify-center gap-1.5 px-4 py-1 transition duration-[250ms] ease-out ${isMobileSearchOpen || isMobileAIChatOpen ? "pointer-events-none -translate-y-full opacity-0" : "translate-y-0 opacity-100"}`,
28871
- "aria-hidden": isMobileSearchOpen || isMobileAIChatOpen,
28872
- tabIndex: isMobileSearchOpen || isMobileAIChatOpen ? -1 : 0,
29215
+ layoutId: "catering-address-pill",
29216
+ transition: { duration: 0.28, ease: "easeOut" },
29217
+ onClick: () => setMobileAddressPillEditing(true),
29218
+ className: "absolute top-3 left-3 z-10 inline-flex max-w-[60%] items-center gap-1.5 rounded-xl bg-black/60 backdrop-blur-sm px-3 py-2 text-xs text-white",
28873
29219
  children: [
28874
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: `h-4 w-4 ${aiEnabled ? "text-primary" : "text-gray-400"}` }),
28875
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm font-semibold ${aiEnabled ? "text-gray-800" : "text-gray-400"}`, children: "Ask AI" }),
28876
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 rounded-full bg-primary px-1.5 py-0.5 text-[8px] font-bold uppercase tracking-wider text-white shadow-sm whitespace-nowrap", children: aiEnabled ? "Beta" : "Soon" })
29220
+ /* @__PURE__ */ jsxRuntime.jsx(
29221
+ lucideReact.MapPin,
29222
+ {
29223
+ className: "h-3.5 w-3.5 flex-shrink-0 text-white/80",
29224
+ "aria-hidden": true
29225
+ }
29226
+ ),
29227
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: contactInfo?.addressLine1 }),
29228
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/50", children: "\xB7" }),
29229
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-white", children: "change" })
28877
29230
  ]
28878
29231
  }
28879
29232
  ),
28880
- /* @__PURE__ */ jsxRuntime.jsxs(
29233
+ mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(
29234
+ "button",
29235
+ {
29236
+ type: "button",
29237
+ onClick: closeMobileAIChat,
29238
+ className: "absolute top-3 right-3 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-black/60 backdrop-blur-sm text-white",
29239
+ title: "Close AI chat",
29240
+ "aria-label": "Close AI chat",
29241
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
29242
+ }
29243
+ ),
29244
+ /* @__PURE__ */ jsxRuntime.jsx(
28881
29245
  "div",
28882
29246
  {
28883
- className: `absolute inset-0 transition duration-300 ease-out ${isMobileSearchOpen ? "translate-y-0 opacity-100" : "pointer-events-none translate-y-full opacity-0"}`,
28884
- "aria-hidden": !isMobileSearchOpen,
28885
- children: [
28886
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-gray-400" }),
29247
+ ref: mobileChatScrollRef,
29248
+ "data-allow-touch": true,
29249
+ className: "absolute inset-x-0 top-0 overflow-y-auto overscroll-contain",
29250
+ style: { bottom: 64 },
29251
+ children: !aiBetaUnlocked ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 pt-14 pb-4 flex flex-col gap-3", children: [
28887
29252
  /* @__PURE__ */ jsxRuntime.jsx(
28888
- "input",
29253
+ TextBubble,
29254
+ {
29255
+ sender: "bot",
29256
+ text: "What is the code to use the AI chat and test our beta version?"
29257
+ }
29258
+ ),
29259
+ mobileGateError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-xs text-red-600", children: mobileGateError })
29260
+ ] }) : mobileChatView === "results" ? /* @__PURE__ */ jsxRuntime.jsx(
29261
+ MobileResultsView,
29262
+ {
29263
+ onBack: () => setMobileChatView("chat")
29264
+ }
29265
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
29266
+ MobileChatThread,
29267
+ {
29268
+ hasAddressTopPill: hasChatAddress && aiBetaUnlocked
29269
+ }
29270
+ )
29271
+ }
29272
+ ),
29273
+ aiBetaUnlocked && mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(MobileChatFloatingChips, { bottomOffset: 64 + 8, inputRef: mobileAIInputRef })
29274
+ ]
29275
+ },
29276
+ "chat-surface"
29277
+ ) }) })
29278
+ ]
29279
+ }
29280
+ ),
29281
+ /* @__PURE__ */ jsxRuntime.jsxs(
29282
+ "div",
29283
+ {
29284
+ className: `fixed left-0 right-0 md:hidden z-50 ${mobileAddressEditorOpen || keyboardOffset > 100 && !isMobileAIChatOpen && !isMobileSearchActive ? "hidden" : ""}`,
29285
+ style: { bottom: keyboardOffset },
29286
+ "aria-hidden": mobileAddressEditorOpen,
29287
+ children: [
29288
+ /* @__PURE__ */ jsxRuntime.jsx(
29289
+ "div",
29290
+ {
29291
+ ref: mobileBarRowRef,
29292
+ className: "relative px-3 pt-1 pb-2",
29293
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-end gap-2", children: [
29294
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
29295
+ /* @__PURE__ */ jsxRuntime.jsx(
29296
+ "button",
29297
+ {
29298
+ onMouseDown: (e) => {
29299
+ if (chatInputFocused) e.preventDefault();
29300
+ },
29301
+ onClick: isMobileAIChatOpen ? chatInputFocused ? () => mobileAIInputRef.current?.blur() : () => setIsMobileChatMenuOpen((v) => !v) : () => setIsMobileCartMenuOpen((v) => !v),
29302
+ className: "flex h-11 w-11 items-center justify-center rounded-full border border-base-200 bg-white/70 text-gray-700 shadow-sm backdrop-blur-sm transition-colors hover:bg-white",
29303
+ title: isMobileAIChatOpen ? chatInputFocused ? "Hide keyboard" : "Chat actions" : "More actions",
29304
+ "aria-label": isMobileAIChatOpen ? chatInputFocused ? "Hide keyboard" : "Chat actions" : "More actions",
29305
+ children: isMobileAIChatOpen && chatInputFocused ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-4 w-4" })
29306
+ }
29307
+ ),
29308
+ isMobileAIChatOpen && isMobileChatMenuOpen && /* @__PURE__ */ jsxRuntime.jsx(
29309
+ MobileChatActionsMenu,
29310
+ {
29311
+ onClose: () => setIsMobileChatMenuOpen(false)
29312
+ }
29313
+ ),
29314
+ isMobileCartMenuOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29315
+ /* @__PURE__ */ jsxRuntime.jsx(
29316
+ "div",
29317
+ {
29318
+ className: "fixed inset-0 z-40",
29319
+ onClick: () => setIsMobileCartMenuOpen(false)
29320
+ }
29321
+ ),
29322
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-44 overflow-hidden rounded-xl border border-base-200 bg-white shadow-lg", children: [
29323
+ /* @__PURE__ */ jsxRuntime.jsxs(
29324
+ "button",
29325
+ {
29326
+ onClick: () => {
29327
+ setIsMobileCartMenuOpen(false);
29328
+ handleViewMenu();
29329
+ },
29330
+ disabled: generatingPdf || totalItems === 0,
29331
+ className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm text-gray-700 transition-colors hover:bg-base-100 disabled:cursor-not-allowed disabled:opacity-50",
29332
+ children: [
29333
+ /* @__PURE__ */ jsxRuntime.jsx(
29334
+ "svg",
29335
+ {
29336
+ xmlns: "http://www.w3.org/2000/svg",
29337
+ className: "h-4 w-4",
29338
+ fill: "none",
29339
+ viewBox: "0 0 24 24",
29340
+ stroke: "currentColor",
29341
+ children: /* @__PURE__ */ jsxRuntime.jsx(
29342
+ "path",
29343
+ {
29344
+ strokeLinecap: "round",
29345
+ strokeLinejoin: "round",
29346
+ strokeWidth: 2,
29347
+ d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
29348
+ }
29349
+ )
29350
+ }
29351
+ ),
29352
+ "Download Menu"
29353
+ ]
29354
+ }
29355
+ ),
29356
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-3 border-t border-base-200" }),
29357
+ /* @__PURE__ */ jsxRuntime.jsxs(
29358
+ "button",
29359
+ {
29360
+ onClick: () => {
29361
+ setIsMobileCartMenuOpen(false);
29362
+ setIsClearAllConfirmOpen(true);
29363
+ },
29364
+ className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm text-red-600 transition-colors hover:bg-red-50",
29365
+ children: [
29366
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
29367
+ "Clear Cart"
29368
+ ]
29369
+ }
29370
+ )
29371
+ ] })
29372
+ ] })
29373
+ ] }),
29374
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative min-w-0 flex-1 overflow-hidden rounded-3xl border border-base-200 bg-white/70 shadow-sm backdrop-blur-sm focus-within:border-primary", children: [
29375
+ /* @__PURE__ */ jsxRuntime.jsxs(
29376
+ "button",
29377
+ {
29378
+ type: "button",
29379
+ onClick: () => {
29380
+ if (aiEnabled) {
29381
+ setIsMobileAIChatOpen(true);
29382
+ } else {
29383
+ setIsAIMobileUnavailableOpen(true);
29384
+ }
29385
+ },
29386
+ title: aiEnabled ? void 0 : "Coming Soon",
29387
+ className: `absolute inset-0 flex items-center justify-center gap-1.5 px-4 py-1 transition duration-[250ms] ease-out ${isMobileSearchOpen || isMobileAIChatOpen ? "pointer-events-none -translate-y-full opacity-0" : "translate-y-0 opacity-100"}`,
29388
+ "aria-hidden": isMobileSearchOpen || isMobileAIChatOpen,
29389
+ tabIndex: isMobileSearchOpen || isMobileAIChatOpen ? -1 : 0,
29390
+ children: [
29391
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: `h-4 w-4 ${aiEnabled ? "text-primary" : "text-gray-400"}` }),
29392
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm font-semibold ${aiEnabled ? "text-gray-800" : "text-gray-400"}`, children: "Ask AI" }),
29393
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 rounded-full bg-primary px-1.5 py-0.5 text-[8px] font-bold uppercase tracking-wider text-white shadow-sm whitespace-nowrap", children: aiEnabled ? "Beta" : "Soon" })
29394
+ ]
29395
+ }
29396
+ ),
29397
+ /* @__PURE__ */ jsxRuntime.jsxs(
29398
+ "div",
29399
+ {
29400
+ className: `absolute inset-0 transition duration-300 ease-out ${isMobileSearchOpen ? "translate-y-0 opacity-100" : "pointer-events-none translate-y-full opacity-0"}`,
29401
+ "aria-hidden": !isMobileSearchOpen,
29402
+ children: [
29403
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-gray-400" }),
29404
+ /* @__PURE__ */ jsxRuntime.jsx(
29405
+ "input",
29406
+ {
29407
+ ref: mobileSearchInputRef,
29408
+ type: "search",
29409
+ value: mobileSearchState.query,
29410
+ onChange: (e) => handleMobileSearchInputChange(e.target.value),
29411
+ placeholder: "Search Swift Food",
29412
+ tabIndex: isMobileSearchOpen ? 0 : -1,
29413
+ autoComplete: "off",
29414
+ autoCorrect: "off",
29415
+ autoCapitalize: "off",
29416
+ spellCheck: false,
29417
+ className: `h-full w-full rounded-full bg-transparent pl-9 pr-3 text-base placeholder:text-xs focus:outline-none touch-auto ${mobileSearchCaretVisible ? "" : "caret-transparent"}`
29418
+ }
29419
+ )
29420
+ ]
29421
+ }
29422
+ ),
29423
+ /* @__PURE__ */ jsxRuntime.jsx(
29424
+ "div",
29425
+ {
29426
+ className: `relative w-full transition duration-[250ms] ease-out ${isMobileAIChatOpen ? "translate-y-0 opacity-100" : "pointer-events-none translate-y-full opacity-0"}`,
29427
+ style: {
29428
+ height: isMobileAIChatOpen ? mobileAIInputHeight : 44
29429
+ },
29430
+ "aria-hidden": !isMobileAIChatOpen,
29431
+ children: /* @__PURE__ */ jsxRuntime.jsx(
29432
+ MobileAIInput,
28889
29433
  {
28890
- ref: mobileSearchInputRef,
28891
- type: "search",
28892
- value: mobileSearchState.query,
28893
- onChange: (e) => handleMobileSearchInputChange(e.target.value),
28894
- placeholder: "Search Swift Food",
28895
- tabIndex: isMobileSearchOpen ? 0 : -1,
28896
- autoComplete: "off",
28897
- autoCorrect: "off",
28898
- autoCapitalize: "off",
28899
- spellCheck: false,
28900
- className: `h-full w-full rounded-full bg-transparent pl-9 pr-3 text-base placeholder:text-xs focus:outline-none touch-auto ${mobileSearchCaretVisible ? "" : "caret-transparent"}`
29434
+ inputRef: mobileAIInputRef,
29435
+ value: mobileAIInput,
29436
+ onChange: (v) => {
29437
+ setMobileAIInput(v);
29438
+ if (mobileGateError) setMobileGateError(null);
29439
+ },
29440
+ onInputResize: handleMobileAIInput,
29441
+ onResetHeight: resetMobileAIInputHeight,
29442
+ tabbable: isMobileAIChatOpen,
29443
+ isOpen: isMobileAIChatOpen,
29444
+ onFocus: () => {
29445
+ setMobileChatView("chat");
29446
+ setChatInputFocused(true);
29447
+ },
29448
+ onBlur: () => setChatInputFocused(false),
29449
+ gateMode: !aiBetaUnlocked,
29450
+ onGateSubmit: handleMobileGateSubmit,
29451
+ buildAddress: buildChatAddress
28901
29452
  }
28902
29453
  )
28903
- ]
28904
- }
28905
- ),
29454
+ }
29455
+ )
29456
+ ] }),
28906
29457
  /* @__PURE__ */ jsxRuntime.jsx(
28907
- "div",
29458
+ "button",
28908
29459
  {
28909
- className: `relative w-full transition duration-[250ms] ease-out ${isMobileAIChatOpen ? "translate-y-0 opacity-100" : "pointer-events-none translate-y-full opacity-0"}`,
28910
- style: {
28911
- height: isMobileAIChatOpen ? mobileAIInputHeight : 44
28912
- },
28913
- "aria-hidden": !isMobileAIChatOpen,
28914
- children: /* @__PURE__ */ jsxRuntime.jsx(
28915
- MobileAIInput,
28916
- {
28917
- inputRef: mobileAIInputRef,
28918
- value: mobileAIInput,
28919
- onChange: (v) => {
28920
- setMobileAIInput(v);
28921
- if (mobileGateError) setMobileGateError(null);
28922
- },
28923
- onInputResize: handleMobileAIInput,
28924
- onResetHeight: resetMobileAIInputHeight,
28925
- tabbable: isMobileAIChatOpen,
28926
- isOpen: isMobileAIChatOpen,
28927
- onFocus: () => {
28928
- setMobileChatView("chat");
28929
- setChatInputFocused(true);
28930
- },
28931
- onBlur: () => setChatInputFocused(false),
28932
- gateMode: !aiBetaUnlocked,
28933
- onGateSubmit: handleMobileGateSubmit,
28934
- buildAddress: buildChatAddress
28935
- }
28936
- )
29460
+ onClick: isMobileSearchOpen ? closeMobileSearch : openMobileSearch,
29461
+ className: `flex h-11 flex-shrink-0 items-center justify-center rounded-full border bg-white/70 text-gray-700 backdrop-blur-sm transition-all duration-[250ms] ease-out hover:bg-white ${isMobileAIChatOpen ? "pointer-events-none w-0 -ml-2 border-transparent bg-transparent opacity-0 shadow-none" : "w-11 border-base-200 opacity-100 shadow-sm"}`,
29462
+ title: isMobileSearchOpen ? "Close search" : "Search",
29463
+ "aria-label": isMobileSearchOpen ? "Close search" : "Open search",
29464
+ "aria-hidden": isMobileAIChatOpen,
29465
+ tabIndex: isMobileAIChatOpen ? -1 : 0,
29466
+ type: "button",
29467
+ children: isMobileSearchOpen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "h-4 w-4" })
28937
29468
  }
28938
29469
  )
28939
- ] }),
28940
- /* @__PURE__ */ jsxRuntime.jsx(
28941
- "button",
28942
- {
28943
- onClick: isMobileSearchOpen ? closeMobileSearch : openMobileSearch,
28944
- className: `flex h-11 flex-shrink-0 items-center justify-center rounded-full border bg-white/70 text-gray-700 backdrop-blur-sm transition-all duration-[250ms] ease-out hover:bg-white ${isMobileAIChatOpen ? "pointer-events-none w-0 -ml-2 border-transparent bg-transparent opacity-0 shadow-none" : "w-11 border-base-200 opacity-100 shadow-sm"}`,
28945
- title: isMobileSearchOpen ? "Close search" : "Search",
28946
- "aria-label": isMobileSearchOpen ? "Close search" : "Open search",
28947
- "aria-hidden": isMobileAIChatOpen,
28948
- tabIndex: isMobileAIChatOpen ? -1 : 0,
28949
- type: "button",
28950
- children: isMobileSearchOpen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "h-4 w-4" })
28951
- }
28952
- )
28953
- ] })
29470
+ ] })
29471
+ }
29472
+ ),
29473
+ mealSessions.some((s) => s.orderItems.length > 0) && !isMobileAIChatOpen && !isMobileSearchOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 bg-primary", children: /* @__PURE__ */ jsxRuntime.jsxs(
29474
+ "button",
29475
+ {
29476
+ onClick: () => setIsViewOrderOpen(true),
29477
+ className: "w-full flex items-center justify-between text-white",
29478
+ children: [
29479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
29480
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShoppingBag, { className: "w-5 h-5" }),
29481
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: "View Order" })
29482
+ ] }),
29483
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
29484
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-lg font-bold", children: [
29485
+ "\xA3",
29486
+ getTotalPrice().toFixed(2)
29487
+ ] }),
29488
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm opacity-80", children: [
29489
+ totalItems,
29490
+ " items"
29491
+ ] })
29492
+ ] })
29493
+ ]
29494
+ }
29495
+ ) })
29496
+ ]
29497
+ }
29498
+ ),
29499
+ /* @__PURE__ */ jsxRuntime.jsx(
29500
+ ViewOrderModal,
29501
+ {
29502
+ isOpen: isViewOrderOpen,
29503
+ onClose: () => setIsViewOrderOpen(false),
29504
+ mealSessions,
29505
+ activeSessionIndex,
29506
+ onSessionChange: setActiveSessionIndex,
29507
+ getSessionTotal,
29508
+ getSessionDiscount,
29509
+ validationErrors: sessionValidationErrors,
29510
+ onEditSession: (index) => {
29511
+ setIsViewOrderOpen(false);
29512
+ setEditingSessionIndex(index);
29513
+ },
29514
+ onRemoveSession: (index, e) => handleRemoveSession(index, e),
29515
+ onEditItem: handleEditItem,
29516
+ onRemoveItem: handleRemoveItem,
29517
+ onSwapItem: handleSwapItem,
29518
+ onRemoveBundle: handleRemoveBundle,
29519
+ collapsedCategories,
29520
+ onToggleCategory: handleToggleCategory,
29521
+ onViewMenu: handleViewMenu,
29522
+ generatingPdf,
29523
+ isCurrentSessionValid,
29524
+ totalPrice: getTotalPrice(),
29525
+ onCheckout: handleCheckout,
29526
+ canRemoveSession: () => true,
29527
+ formatTimeDisplay,
29528
+ navMode,
29529
+ dayGroups,
29530
+ selectedDayDate,
29531
+ currentDayGroup,
29532
+ onDateClick: handleDateClick,
29533
+ onBackToDates: handleBackToDates,
29534
+ onAddDay: handleAddDay,
29535
+ onAddSessionToDay: handleAddSessionToDay,
29536
+ restaurants,
29537
+ pricing,
29538
+ calculatingPricing,
29539
+ onPlaceSelect: handleDeliveryPlaceSelect,
29540
+ onClearAddress: handleClearDeliveryAddress
29541
+ }
29542
+ ),
29543
+ isEditModalOpen && editingItemIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
29544
+ MenuItemModal,
29545
+ {
29546
+ item: mealSessions[activeSessionIndex].orderItems[editingItemIndex].item,
29547
+ isOpen: isEditModalOpen,
29548
+ onClose: () => {
29549
+ setIsEditModalOpen(false);
29550
+ setEditingItemIndex(null);
29551
+ },
29552
+ quantity: mealSessions[activeSessionIndex].orderItems[editingItemIndex].quantity,
29553
+ isEditMode: true,
29554
+ editingIndex: editingItemIndex,
29555
+ onAddItem: handleSaveEditedItem,
29556
+ onRemoveItem: (index) => {
29557
+ removeMenuItemByIndex(activeSessionIndex, index);
29558
+ setIsEditModalOpen(false);
29559
+ setEditingItemIndex(null);
29560
+ }
29561
+ }
29562
+ ),
29563
+ pendingItem && /* @__PURE__ */ jsxRuntime.jsx(
29564
+ MenuItemModal,
29565
+ {
29566
+ item: pendingItem,
29567
+ isOpen: true,
29568
+ onClose: () => setPendingItem(null),
29569
+ quantity: 0,
29570
+ onAddItem: (item) => {
29571
+ handleAddItem(item);
29572
+ setPendingItem(null);
29573
+ }
29574
+ }
29575
+ ),
29576
+ emptySessionIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
29577
+ EmptySessionWarningModal,
29578
+ {
29579
+ sessionName: mealSessions[emptySessionIndex]?.sessionName || "Session",
29580
+ onRemove: handleRemoveEmptySession,
29581
+ onAddItems: handleAddItemsToEmptySession
29582
+ }
29583
+ ),
29584
+ removeItemIndex !== null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-[100] p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-2xl max-w-md w-full p-6 shadow-xl", children: [
29585
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
29586
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-full bg-red-100 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-6 w-6 text-red-600" }) }),
29587
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
29588
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold text-gray-900", children: "Remove Item" }),
29589
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: mealSessions[activeSessionIndex]?.orderItems[removeItemIndex]?.item.menuItemName || "Item" })
29590
+ ] })
29591
+ ] }),
29592
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-6", children: "Are you sure you want to remove this item from your order?" }),
29593
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
29594
+ /* @__PURE__ */ jsxRuntime.jsx(
29595
+ "button",
29596
+ {
29597
+ onClick: () => setRemoveItemIndex(null),
29598
+ className: "flex-1 px-4 py-3 border border-base-300 text-gray-600 rounded-xl hover:bg-base-100 transition-colors font-medium",
29599
+ children: "Cancel"
28954
29600
  }
28955
29601
  ),
28956
- mealSessions.some((s) => s.orderItems.length > 0) && !isMobileAIChatOpen && !isMobileSearchOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 bg-primary", children: /* @__PURE__ */ jsxRuntime.jsxs(
29602
+ /* @__PURE__ */ jsxRuntime.jsx(
28957
29603
  "button",
28958
29604
  {
28959
- onClick: () => setIsViewOrderOpen(true),
28960
- className: "w-full flex items-center justify-between text-white",
29605
+ onClick: confirmRemoveItem,
29606
+ className: "flex-1 px-4 py-3 bg-red-500 text-white rounded-xl hover:bg-red-600 transition-colors font-medium",
29607
+ children: "Remove"
29608
+ }
29609
+ )
29610
+ ] })
29611
+ ] }) }),
29612
+ sessionToRemove !== null && /* @__PURE__ */ jsxRuntime.jsx(
29613
+ RemoveSessionConfirmModal,
29614
+ {
29615
+ sessionName: mealSessions[sessionToRemove]?.sessionName || "Session",
29616
+ onConfirm: confirmRemoveSession,
29617
+ onCancel: () => setSessionToRemove(null)
29618
+ }
29619
+ ),
29620
+ isAIMobileUnavailableOpen && /* @__PURE__ */ jsxRuntime.jsx(
29621
+ "div",
29622
+ {
29623
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4",
29624
+ onClick: () => setIsAIMobileUnavailableOpen(false),
29625
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
29626
+ "div",
29627
+ {
29628
+ className: "w-full max-w-md rounded-2xl bg-white p-6 shadow-xl",
29629
+ onClick: (e) => e.stopPropagation(),
28961
29630
  children: [
28962
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
28963
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShoppingBag, { className: "w-5 h-5" }),
28964
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: "View Order" })
28965
- ] }),
28966
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
28967
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-lg font-bold", children: [
28968
- "\xA3",
28969
- getTotalPrice().toFixed(2)
28970
- ] }),
28971
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm opacity-80", children: [
28972
- totalItems,
28973
- " items"
29631
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-center gap-3", children: [
29632
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-5 w-5 text-primary" }) }),
29633
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
29634
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-bold text-gray-900", children: "Plan your menu with AI" }),
29635
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "Coming soon" })
28974
29636
  ] })
28975
- ] })
29637
+ ] }),
29638
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 space-y-2 text-sm text-gray-600", children: [
29639
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Chat with our AI to plan the perfect menu \u2014 tell it your guest count, dietary needs, and budget." }),
29640
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "We're putting the finishing touches on it. Check back soon." })
29641
+ ] }),
29642
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: /* @__PURE__ */ jsxRuntime.jsx(
29643
+ "button",
29644
+ {
29645
+ onClick: () => setIsAIMobileUnavailableOpen(false),
29646
+ className: "flex-1 rounded-xl bg-primary px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-primary/90",
29647
+ children: "Got it"
29648
+ }
29649
+ ) })
28976
29650
  ]
28977
29651
  }
28978
- ) })
28979
- ]
28980
- }
28981
- ),
28982
- /* @__PURE__ */ jsxRuntime.jsx(
28983
- ViewOrderModal,
28984
- {
28985
- isOpen: isViewOrderOpen,
28986
- onClose: () => setIsViewOrderOpen(false),
28987
- mealSessions,
28988
- activeSessionIndex,
28989
- onSessionChange: setActiveSessionIndex,
28990
- getSessionTotal,
28991
- getSessionDiscount,
28992
- validationErrors: sessionValidationErrors,
28993
- onEditSession: (index) => {
28994
- setIsViewOrderOpen(false);
28995
- setEditingSessionIndex(index);
28996
- },
28997
- onRemoveSession: (index, e) => handleRemoveSession(index, e),
28998
- onEditItem: handleEditItem,
28999
- onRemoveItem: handleRemoveItem,
29000
- onSwapItem: handleSwapItem,
29001
- onRemoveBundle: handleRemoveBundle,
29002
- collapsedCategories,
29003
- onToggleCategory: handleToggleCategory,
29004
- onViewMenu: handleViewMenu,
29005
- generatingPdf,
29006
- isCurrentSessionValid,
29007
- totalPrice: getTotalPrice(),
29008
- onCheckout: handleCheckout,
29009
- canRemoveSession: () => true,
29010
- formatTimeDisplay,
29011
- navMode,
29012
- dayGroups,
29013
- selectedDayDate,
29014
- currentDayGroup,
29015
- onDateClick: handleDateClick,
29016
- onBackToDates: handleBackToDates,
29017
- onAddDay: handleAddDay,
29018
- onAddSessionToDay: handleAddSessionToDay,
29019
- restaurants,
29020
- pricing,
29021
- calculatingPricing,
29022
- onPlaceSelect: handleDeliveryPlaceSelect,
29023
- onClearAddress: handleClearDeliveryAddress
29024
- }
29025
- ),
29026
- isEditModalOpen && editingItemIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
29027
- MenuItemModal,
29028
- {
29029
- item: mealSessions[activeSessionIndex].orderItems[editingItemIndex].item,
29030
- isOpen: isEditModalOpen,
29031
- onClose: () => {
29032
- setIsEditModalOpen(false);
29033
- setEditingItemIndex(null);
29034
- },
29035
- quantity: mealSessions[activeSessionIndex].orderItems[editingItemIndex].quantity,
29036
- isEditMode: true,
29037
- editingIndex: editingItemIndex,
29038
- onAddItem: handleSaveEditedItem,
29039
- onRemoveItem: (index) => {
29040
- removeMenuItemByIndex(activeSessionIndex, index);
29041
- setIsEditModalOpen(false);
29042
- setEditingItemIndex(null);
29043
- }
29044
- }
29045
- ),
29046
- pendingItem && /* @__PURE__ */ jsxRuntime.jsx(
29047
- MenuItemModal,
29048
- {
29049
- item: pendingItem,
29050
- isOpen: true,
29051
- onClose: () => setPendingItem(null),
29052
- quantity: 0,
29053
- onAddItem: (item) => {
29054
- handleAddItem(item);
29055
- setPendingItem(null);
29652
+ )
29056
29653
  }
29057
- }
29058
- ),
29059
- emptySessionIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
29060
- EmptySessionWarningModal,
29061
- {
29062
- sessionName: mealSessions[emptySessionIndex]?.sessionName || "Session",
29063
- onRemove: handleRemoveEmptySession,
29064
- onAddItems: handleAddItemsToEmptySession
29065
- }
29066
- ),
29067
- removeItemIndex !== null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-[100] p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-2xl max-w-md w-full p-6 shadow-xl", children: [
29068
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
29069
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-full bg-red-100 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-6 w-6 text-red-600" }) }),
29070
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
29071
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold text-gray-900", children: "Remove Item" }),
29072
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: mealSessions[activeSessionIndex]?.orderItems[removeItemIndex]?.item.menuItemName || "Item" })
29073
- ] })
29074
- ] }),
29075
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-6", children: "Are you sure you want to remove this item from your order?" }),
29076
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
29077
- /* @__PURE__ */ jsxRuntime.jsx(
29078
- "button",
29079
- {
29080
- onClick: () => setRemoveItemIndex(null),
29081
- className: "flex-1 px-4 py-3 border border-base-300 text-gray-600 rounded-xl hover:bg-base-100 transition-colors font-medium",
29082
- children: "Cancel"
29083
- }
29084
- ),
29085
- /* @__PURE__ */ jsxRuntime.jsx(
29086
- "button",
29087
- {
29088
- onClick: confirmRemoveItem,
29089
- className: "flex-1 px-4 py-3 bg-red-500 text-white rounded-xl hover:bg-red-600 transition-colors font-medium",
29090
- children: "Remove"
29091
- }
29092
- )
29093
- ] })
29094
- ] }) }),
29095
- sessionToRemove !== null && /* @__PURE__ */ jsxRuntime.jsx(
29096
- RemoveSessionConfirmModal,
29097
- {
29098
- sessionName: mealSessions[sessionToRemove]?.sessionName || "Session",
29099
- onConfirm: confirmRemoveSession,
29100
- onCancel: () => setSessionToRemove(null)
29101
- }
29102
- ),
29103
- isAIMobileUnavailableOpen && /* @__PURE__ */ jsxRuntime.jsx(
29104
- "div",
29105
- {
29106
- className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4",
29107
- onClick: () => setIsAIMobileUnavailableOpen(false),
29108
- children: /* @__PURE__ */ jsxRuntime.jsxs(
29109
- "div",
29110
- {
29111
- className: "w-full max-w-md rounded-2xl bg-white p-6 shadow-xl",
29112
- onClick: (e) => e.stopPropagation(),
29113
- children: [
29114
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-center gap-3", children: [
29115
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-5 w-5 text-primary" }) }),
29116
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
29117
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-bold text-gray-900", children: "Plan your menu with AI" }),
29118
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "Coming soon" })
29119
- ] })
29120
- ] }),
29121
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 space-y-2 text-sm text-gray-600", children: [
29122
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Chat with our AI to plan the perfect menu \u2014 tell it your guest count, dietary needs, and budget." }),
29123
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "We're putting the finishing touches on it. Check back soon." })
29124
- ] }),
29125
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: /* @__PURE__ */ jsxRuntime.jsx(
29126
- "button",
29127
- {
29128
- onClick: () => setIsAIMobileUnavailableOpen(false),
29129
- className: "flex-1 rounded-xl bg-primary px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-primary/90",
29130
- children: "Got it"
29131
- }
29132
- ) })
29133
- ]
29134
- }
29135
- )
29136
- }
29137
- ),
29138
- isClearAllConfirmOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-md rounded-2xl bg-white p-6 shadow-xl", children: [
29139
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-center gap-3", children: [
29140
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-red-100", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-6 w-6 text-red-600" }) }),
29141
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
29142
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold text-gray-900", children: "Clear Cart" }),
29143
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "Remove all items and sessions" })
29144
- ] })
29145
- ] }),
29146
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-6 text-gray-600", children: "This will delete every session and remove all items you've added, returning your order to a single empty session. This action cannot be undone." }),
29147
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
29148
- /* @__PURE__ */ jsxRuntime.jsx(
29149
- "button",
29150
- {
29151
- onClick: () => setIsClearAllConfirmOpen(false),
29152
- className: "flex-1 rounded-xl border border-base-300 px-4 py-3 font-medium text-gray-600 transition-colors hover:bg-base-100",
29153
- children: "Cancel"
29154
- }
29155
- ),
29156
- /* @__PURE__ */ jsxRuntime.jsx(
29157
- "button",
29158
- {
29159
- onClick: handleClearAllItems,
29160
- className: "flex-1 rounded-xl bg-red-500 px-4 py-3 font-medium text-white transition-colors hover:bg-red-600",
29161
- children: "Clear All"
29162
- }
29163
- )
29164
- ] })
29165
- ] }) }),
29166
- /* @__PURE__ */ jsxRuntime.jsx(
29167
- ValidationAlertModal,
29168
- {
29169
- isOpen: checkoutConflict !== null,
29170
- onClose: () => setCheckoutConflict(null),
29171
- title: "Can't check out yet",
29172
- message: checkoutConflict ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29173
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: checkoutConflict.restaurantName }),
29174
- checkoutConflict.message.slice(
29175
- checkoutConflict.restaurantName.length
29654
+ ),
29655
+ isClearAllConfirmOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-md rounded-2xl bg-white p-6 shadow-xl", children: [
29656
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-center gap-3", children: [
29657
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-red-100", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-6 w-6 text-red-600" }) }),
29658
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
29659
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold text-gray-900", children: "Clear Cart" }),
29660
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "Remove all items and sessions" })
29661
+ ] })
29662
+ ] }),
29663
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-6 text-gray-600", children: "This will delete every session and remove all items you've added, returning your order to a single empty session. This action cannot be undone." }),
29664
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
29665
+ /* @__PURE__ */ jsxRuntime.jsx(
29666
+ "button",
29667
+ {
29668
+ onClick: () => setIsClearAllConfirmOpen(false),
29669
+ className: "flex-1 rounded-xl border border-base-300 px-4 py-3 font-medium text-gray-600 transition-colors hover:bg-base-100",
29670
+ children: "Cancel"
29671
+ }
29672
+ ),
29673
+ /* @__PURE__ */ jsxRuntime.jsx(
29674
+ "button",
29675
+ {
29676
+ onClick: handleClearAllItems,
29677
+ className: "flex-1 rounded-xl bg-red-500 px-4 py-3 font-medium text-white transition-colors hover:bg-red-600",
29678
+ children: "Clear All"
29679
+ }
29176
29680
  )
29177
- ] }) : "",
29178
- primaryAction: checkoutConflict ? {
29179
- label: `Remove all ${checkoutConflict.restaurantName} items from this session`,
29180
- onClick: () => {
29181
- const { sessionIndex, restaurantId } = checkoutConflict;
29182
- const session = mealSessions[sessionIndex];
29183
- if (session) {
29184
- const indices = session.orderItems.map(
29185
- (oi, idx) => oi.item.restaurantId === restaurantId ? idx : -1
29186
- ).filter((idx) => idx !== -1).sort((a, b) => b - a);
29187
- indices.forEach(
29188
- (idx) => removeMenuItemByIndex(sessionIndex, idx)
29189
- );
29681
+ ] })
29682
+ ] }) }),
29683
+ /* @__PURE__ */ jsxRuntime.jsx(
29684
+ ValidationAlertModal,
29685
+ {
29686
+ isOpen: checkoutConflict !== null,
29687
+ onClose: () => setCheckoutConflict(null),
29688
+ title: "Can't check out yet",
29689
+ message: checkoutConflict ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29690
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: checkoutConflict.restaurantName }),
29691
+ checkoutConflict.message.slice(
29692
+ checkoutConflict.restaurantName.length
29693
+ )
29694
+ ] }) : "",
29695
+ primaryAction: checkoutConflict ? {
29696
+ label: `Remove all ${checkoutConflict.restaurantName} items from this session`,
29697
+ onClick: () => {
29698
+ const { sessionIndex, restaurantId } = checkoutConflict;
29699
+ const session = mealSessions[sessionIndex];
29700
+ if (session) {
29701
+ const indices = session.orderItems.map(
29702
+ (oi, idx) => oi.item.restaurantId === restaurantId ? idx : -1
29703
+ ).filter((idx) => idx !== -1).sort((a, b) => b - a);
29704
+ indices.forEach(
29705
+ (idx) => removeMenuItemByIndex(sessionIndex, idx)
29706
+ );
29707
+ }
29708
+ setSessionValidationErrors((prev) => {
29709
+ const next = { ...prev };
29710
+ delete next[sessionIndex];
29711
+ return next;
29712
+ });
29713
+ setCheckoutConflict(null);
29190
29714
  }
29191
- setSessionValidationErrors((prev) => {
29192
- const next = { ...prev };
29193
- delete next[sessionIndex];
29194
- return next;
29195
- });
29196
- setCheckoutConflict(null);
29197
- }
29198
- } : void 0,
29199
- secondaryAction: checkoutConflict ? {
29200
- label: "Change session date & time",
29201
- onClick: () => {
29202
- setEditingSessionIndex(checkoutConflict.sessionIndex);
29203
- setCheckoutConflict(null);
29204
- }
29205
- } : void 0
29206
- }
29207
- ),
29208
- minOrderModalSession !== null && /* @__PURE__ */ jsxRuntime.jsx(
29209
- MinOrderModal,
29210
- {
29211
- sessionName: mealSessions[minOrderModalSession.index]?.sessionName || "Session",
29212
- validationStatus: minOrderModalSession.validation,
29213
- onClose: () => setMinOrderModalSession(null),
29214
- onNavigateToSection: handleMinOrderNavigate
29215
- }
29216
- ),
29217
- showHalkinModal && /* @__PURE__ */ jsxRuntime.jsx(HalkinVenueModal, { onClose: () => setShowHalkinModal(false) }),
29218
- showPdfModal && /* @__PURE__ */ jsxRuntime.jsx(
29219
- PdfDownloadModal,
29220
- {
29221
- onDownload: handlePdfDownload,
29222
- onClose: () => setShowPdfModal(false),
29223
- isGenerating: generatingPdf
29224
- }
29225
- ),
29226
- swapItemIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
29227
- SwapItemModal,
29715
+ } : void 0,
29716
+ secondaryAction: checkoutConflict ? {
29717
+ label: "Change session date & time",
29718
+ onClick: () => {
29719
+ setEditingSessionIndex(checkoutConflict.sessionIndex);
29720
+ setCheckoutConflict(null);
29721
+ }
29722
+ } : void 0
29723
+ }
29724
+ ),
29725
+ minOrderModalSession !== null && /* @__PURE__ */ jsxRuntime.jsx(
29726
+ MinOrderModal,
29727
+ {
29728
+ sessionName: mealSessions[minOrderModalSession.index]?.sessionName || "Session",
29729
+ validationStatus: minOrderModalSession.validation,
29730
+ onClose: () => setMinOrderModalSession(null),
29731
+ onNavigateToSection: handleMinOrderNavigate
29732
+ }
29733
+ ),
29734
+ showHalkinModal && /* @__PURE__ */ jsxRuntime.jsx(HalkinVenueModal, { onClose: () => setShowHalkinModal(false) }),
29735
+ showPdfModal && /* @__PURE__ */ jsxRuntime.jsx(
29736
+ PdfDownloadModal,
29737
+ {
29738
+ onDownload: handlePdfDownload,
29739
+ onClose: () => setShowPdfModal(false),
29740
+ isGenerating: generatingPdf
29741
+ }
29742
+ ),
29743
+ swapItemIndex !== null && /* @__PURE__ */ jsxRuntime.jsx(
29744
+ SwapItemModal,
29745
+ {
29746
+ currentItem: mealSessions[activeSessionIndex]?.orderItems[swapItemIndex]?.item,
29747
+ currentQuantity: mealSessions[activeSessionIndex]?.orderItems[swapItemIndex]?.quantity ?? 0,
29748
+ alternatives: swapAlternatives,
29749
+ isOpen: true,
29750
+ onClose: () => {
29751
+ setSwapItemIndex(null);
29752
+ setSwapAlternatives([]);
29753
+ },
29754
+ onSwap: handleConfirmSwap
29755
+ }
29756
+ ),
29757
+ /* @__PURE__ */ jsxRuntime.jsx(
29758
+ TutorialTooltip,
29759
+ {
29760
+ step: currentTutorialStep,
29761
+ onNext: handleTutorialNext,
29762
+ onSkip: handleSkipTutorial,
29763
+ currentStepIndex: tutorialStep ?? 0,
29764
+ totalSteps: getTutorialSteps().length
29765
+ }
29766
+ )
29767
+ ] }),
29768
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: flyGhost && /* @__PURE__ */ jsxRuntime.jsx(
29769
+ react$1.motion.div,
29228
29770
  {
29229
- currentItem: mealSessions[activeSessionIndex]?.orderItems[swapItemIndex]?.item,
29230
- currentQuantity: mealSessions[activeSessionIndex]?.orderItems[swapItemIndex]?.quantity ?? 0,
29231
- alternatives: swapAlternatives,
29232
- isOpen: true,
29233
- onClose: () => {
29234
- setSwapItemIndex(null);
29235
- setSwapAlternatives([]);
29771
+ initial: {
29772
+ position: "fixed",
29773
+ left: flyGhost.fromX - 24,
29774
+ top: flyGhost.fromY - 24,
29775
+ scale: 1,
29776
+ opacity: 1,
29777
+ zIndex: 9999
29236
29778
  },
29237
- onSwap: handleConfirmSwap
29238
- }
29239
- ),
29240
- /* @__PURE__ */ jsxRuntime.jsx(
29241
- TutorialTooltip,
29242
- {
29243
- step: currentTutorialStep,
29244
- onNext: handleTutorialNext,
29245
- onSkip: handleSkipTutorial,
29246
- currentStepIndex: tutorialStep ?? 0,
29247
- totalSteps: getTutorialSteps().length
29248
- }
29249
- )
29250
- ] })
29779
+ animate: {
29780
+ left: flyGhost.toX - 16,
29781
+ top: flyGhost.toY - 16,
29782
+ scale: 0.5,
29783
+ opacity: 0
29784
+ },
29785
+ transition: { duration: 0.85, ease: [0.22, 1, 0.36, 1] },
29786
+ className: "pointer-events-none",
29787
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-primary text-white shadow-2xl", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShoppingBag, { className: "h-5 w-5" }) })
29788
+ },
29789
+ "fly-ghost"
29790
+ ) })
29791
+ ]
29251
29792
  }
29252
29793
  );
29253
29794
  }
@@ -29303,7 +29844,7 @@ function MobileChatThread({
29303
29844
  const paddingTop = hasAddressTopPill ? 68 : 56;
29304
29845
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4", style: { paddingTop, paddingBottom }, children: /* @__PURE__ */ jsxRuntime.jsx(ChatMessagesView, {}) });
29305
29846
  }
29306
- function MobileChatFloatingChips({ bottomOffset }) {
29847
+ function MobileChatFloatingChips({ bottomOffset, inputRef }) {
29307
29848
  const {
29308
29849
  hasResults,
29309
29850
  onViewResults,
@@ -29352,7 +29893,10 @@ function MobileChatFloatingChips({ bottomOffset }) {
29352
29893
  "aria-label": `${n} star${n > 1 ? "s" : ""}`,
29353
29894
  onMouseEnter: () => setFeedbackHover(n),
29354
29895
  onMouseLeave: () => setFeedbackHover(0),
29355
- onClick: () => setFeedbackRating(n),
29896
+ onClick: () => {
29897
+ setFeedbackRating(n);
29898
+ inputRef?.current?.focus();
29899
+ },
29356
29900
  className: "p-1 transition-transform hover:scale-110",
29357
29901
  children: /* @__PURE__ */ jsxRuntime.jsx(
29358
29902
  lucideReact.Star,
@@ -29488,7 +30032,8 @@ function MobileAIInput({
29488
30032
  }
29489
30033
  function SuggestionsOverlayBridge({
29490
30034
  active,
29491
- onVisibleChange
30035
+ onVisibleChange,
30036
+ flyToCart
29492
30037
  }) {
29493
30038
  const {
29494
30039
  hasResults,
@@ -29504,7 +30049,8 @@ function SuggestionsOverlayBridge({
29504
30049
  return /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: visible && /* @__PURE__ */ jsxRuntime.jsx(
29505
30050
  SuggestionsOverlay,
29506
30051
  {
29507
- onClose: dismissSuggestionsOverlay
30052
+ onClose: dismissSuggestionsOverlay,
30053
+ flyToCart
29508
30054
  },
29509
30055
  "suggestions-overlay"
29510
30056
  ) });