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

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
@@ -150,6 +150,7 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
150
150
  --color-orange-800:oklch(47% .157 37.304);
151
151
  --color-amber-50:oklch(98.7% .022 95.277);
152
152
  --color-amber-100:oklch(96.2% .059 95.617);
153
+ --color-amber-400:oklch(82.8% .189 84.429);
153
154
  --color-amber-500:oklch(76.9% .188 70.08);
154
155
  --color-amber-600:oklch(66.6% .179 58.318);
155
156
  --color-amber-700:oklch(55.5% .163 48.998);
@@ -158,6 +159,7 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
158
159
  --color-green-50:oklch(98.2% .018 155.826);
159
160
  --color-green-100:oklch(96.2% .044 156.743);
160
161
  --color-green-200:oklch(92.5% .084 155.995);
162
+ --color-green-300:oklch(87.1% .15 154.449);
161
163
  --color-green-400:oklch(79.2% .209 151.711);
162
164
  --color-green-500:oklch(72.3% .219 149.579);
163
165
  --color-green-600:oklch(62.7% .194 149.214);
@@ -184,6 +186,7 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
184
186
  --color-white:#fff;
185
187
  --spacing:.25rem;
186
188
  --container-xs:20rem;
189
+ --container-sm:24rem;
187
190
  --container-md:28rem;
188
191
  --container-lg:32rem;
189
192
  --container-2xl:42rem;
@@ -2987,6 +2990,49 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
2987
2990
  z-index: 3;
2988
2991
  width: 100%;
2989
2992
  }
2993
+ .swift-catering-widget .hero {
2994
+ background-position: 50%;
2995
+ background-size: cover;
2996
+ place-items: center;
2997
+ width: 100%;
2998
+ display: grid;
2999
+ }
3000
+ .swift-catering-widget .hero > * {
3001
+ grid-row-start: 1;
3002
+ grid-column-start: 1;
3003
+ }
3004
+ .swift-catering-widget .divider {
3005
+ white-space: nowrap;
3006
+ height: 1rem;
3007
+ margin: var(--divider-m,1rem 0);
3008
+ --divider-color:var(--color-base-content);
3009
+ flex-direction: row;
3010
+ align-self: stretch;
3011
+ align-items: center;
3012
+ display: flex;
3013
+ }
3014
+ @supports (color:color-mix(in lab, red, red)) {
3015
+ .swift-catering-widget .divider {
3016
+ --divider-color:color-mix(in oklab, var(--color-base-content) 10%, transparent);
3017
+ }
3018
+ }
3019
+ .swift-catering-widget .divider:before,
3020
+ .swift-catering-widget .divider:after {
3021
+ content: "";
3022
+ background-color: var(--divider-color);
3023
+ flex-grow: 1;
3024
+ width: 100%;
3025
+ height: .125rem;
3026
+ }
3027
+ @media print {
3028
+ .swift-catering-widget .divider:before,
3029
+ .swift-catering-widget .divider:after {
3030
+ border: .5px solid;
3031
+ }
3032
+ }
3033
+ .swift-catering-widget .divider:not(:empty) {
3034
+ gap: 1rem;
3035
+ }
2990
3036
  .swift-catering-widget .filter {
2991
3037
  flex-wrap: wrap;
2992
3038
  display: flex;
@@ -4154,15 +4200,18 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
4154
4200
  .swift-catering-widget .max-w-7xl {
4155
4201
  max-width: var(--container-7xl);
4156
4202
  }
4203
+ .swift-catering-widget .max-w-\\[60\\%\\] {
4204
+ max-width: 60%;
4205
+ }
4157
4206
  .swift-catering-widget .max-w-\\[100vw\\] {
4158
4207
  max-width: 100vw;
4159
4208
  }
4209
+ .swift-catering-widget .max-w-\\[260px\\] {
4210
+ max-width: 260px;
4211
+ }
4160
4212
  .swift-catering-widget .max-w-\\[280px\\] {
4161
4213
  max-width: 280px;
4162
4214
  }
4163
- .swift-catering-widget .max-w-\\[325px\\] {
4164
- max-width: 325px;
4165
- }
4166
4215
  .swift-catering-widget .max-w-\\[350px\\] {
4167
4216
  max-width: 350px;
4168
4217
  }
@@ -4178,12 +4227,18 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
4178
4227
  .swift-catering-widget .max-w-min {
4179
4228
  max-width: min-content;
4180
4229
  }
4230
+ .swift-catering-widget .max-w-sm {
4231
+ max-width: var(--container-sm);
4232
+ }
4181
4233
  .swift-catering-widget .max-w-xs {
4182
4234
  max-width: var(--container-xs);
4183
4235
  }
4184
4236
  .swift-catering-widget .min-w-0 {
4185
4237
  min-width: calc(var(--spacing) * 0);
4186
4238
  }
4239
+ .swift-catering-widget .min-w-\\[3\\.5rem\\] {
4240
+ min-width: 3.5rem;
4241
+ }
4187
4242
  .swift-catering-widget .min-w-\\[44px\\] {
4188
4243
  min-width: 44px;
4189
4244
  }
@@ -4284,9 +4339,15 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
4284
4339
  .swift-catering-widget .cursor-pointer {
4285
4340
  cursor: pointer;
4286
4341
  }
4342
+ .swift-catering-widget .touch-auto {
4343
+ touch-action: auto;
4344
+ }
4287
4345
  .swift-catering-widget .touch-manipulation {
4288
4346
  touch-action: manipulation;
4289
4347
  }
4348
+ .swift-catering-widget .touch-none {
4349
+ touch-action: none;
4350
+ }
4290
4351
  .swift-catering-widget .resize {
4291
4352
  resize: both;
4292
4353
  }
@@ -4763,14 +4824,6 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
4763
4824
  .swift-catering-widget .bg-base-content {
4764
4825
  background-color: var(--color-base-content);
4765
4826
  }
4766
- .swift-catering-widget .bg-black\\/20 {
4767
- background-color: #0003;
4768
- }
4769
- @supports (color:color-mix(in lab, red, red)) {
4770
- .swift-catering-widget .bg-black\\/20 {
4771
- background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
4772
- }
4773
- }
4774
4827
  .swift-catering-widget .bg-black\\/30 {
4775
4828
  background-color: #0000004d;
4776
4829
  }
@@ -4795,6 +4848,14 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
4795
4848
  background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
4796
4849
  }
4797
4850
  }
4851
+ .swift-catering-widget .bg-black\\/60 {
4852
+ background-color: #0009;
4853
+ }
4854
+ @supports (color:color-mix(in lab, red, red)) {
4855
+ .swift-catering-widget .bg-black\\/60 {
4856
+ background-color: color-mix(in oklab, var(--color-black) 60%, transparent);
4857
+ }
4858
+ }
4798
4859
  .swift-catering-widget .bg-black\\/95 {
4799
4860
  background-color: #000000f2;
4800
4861
  }
@@ -4844,6 +4905,14 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
4844
4905
  background-color: color-mix(in oklab, var(--color-gray-900) 30%, transparent);
4845
4906
  }
4846
4907
  }
4908
+ .swift-catering-widget .bg-gray-900\\/40 {
4909
+ background-color: #10182866;
4910
+ }
4911
+ @supports (color:color-mix(in lab, red, red)) {
4912
+ .swift-catering-widget .bg-gray-900\\/40 {
4913
+ background-color: color-mix(in oklab, var(--color-gray-900) 40%, transparent);
4914
+ }
4915
+ }
4847
4916
  .swift-catering-widget .bg-gray-900\\/50 {
4848
4917
  background-color: #10182880;
4849
4918
  }
@@ -5097,6 +5166,12 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5097
5166
  --tw-gradient-to:var(--color-purple-600);
5098
5167
  --tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
5099
5168
  }
5169
+ .swift-catering-widget .fill-amber-400 {
5170
+ fill: var(--color-amber-400);
5171
+ }
5172
+ .swift-catering-widget .fill-transparent {
5173
+ fill: #0000;
5174
+ }
5100
5175
  .swift-catering-widget .object-contain {
5101
5176
  object-fit: contain;
5102
5177
  }
@@ -5124,6 +5199,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5124
5199
  .swift-catering-widget .p-4 {
5125
5200
  padding: calc(var(--spacing) * 4);
5126
5201
  }
5202
+ .swift-catering-widget .p-5 {
5203
+ padding: calc(var(--spacing) * 5);
5204
+ }
5127
5205
  .swift-catering-widget .p-6 {
5128
5206
  padding: calc(var(--spacing) * 6);
5129
5207
  }
@@ -5241,6 +5319,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5241
5319
  .swift-catering-widget .pb-1 {
5242
5320
  padding-bottom: calc(var(--spacing) * 1);
5243
5321
  }
5322
+ .swift-catering-widget .pb-1\\.5 {
5323
+ padding-bottom: calc(var(--spacing) * 1.5);
5324
+ }
5244
5325
  .swift-catering-widget .pb-2 {
5245
5326
  padding-bottom: calc(var(--spacing) * 2);
5246
5327
  }
@@ -5413,6 +5494,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5413
5494
  .swift-catering-widget .whitespace-pre-wrap {
5414
5495
  white-space: pre-wrap;
5415
5496
  }
5497
+ .swift-catering-widget .text-amber-400 {
5498
+ color: var(--color-amber-400);
5499
+ }
5416
5500
  .swift-catering-widget .text-amber-600 {
5417
5501
  color: var(--color-amber-600);
5418
5502
  }
@@ -5509,6 +5593,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5509
5593
  .swift-catering-widget .text-gray-900 {
5510
5594
  color: var(--color-gray-900);
5511
5595
  }
5596
+ .swift-catering-widget .text-green-300 {
5597
+ color: var(--color-green-300);
5598
+ }
5512
5599
  .swift-catering-widget .text-green-500 {
5513
5600
  color: var(--color-green-500);
5514
5601
  }
@@ -5561,6 +5648,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5561
5648
  color: color-mix(in oklab, var(--color-primary) 60%, transparent);
5562
5649
  }
5563
5650
  }
5651
+ .swift-catering-widget .text-red-300 {
5652
+ color: var(--color-red-300);
5653
+ }
5564
5654
  .swift-catering-widget .text-red-400 {
5565
5655
  color: var(--color-red-400);
5566
5656
  }
@@ -5588,6 +5678,30 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5588
5678
  .swift-catering-widget .text-white {
5589
5679
  color: var(--color-white);
5590
5680
  }
5681
+ .swift-catering-widget .text-white\\/40 {
5682
+ color: #fff6;
5683
+ }
5684
+ @supports (color:color-mix(in lab, red, red)) {
5685
+ .swift-catering-widget .text-white\\/40 {
5686
+ color: color-mix(in oklab, var(--color-white) 40%, transparent);
5687
+ }
5688
+ }
5689
+ .swift-catering-widget .text-white\\/50 {
5690
+ color: #ffffff80;
5691
+ }
5692
+ @supports (color:color-mix(in lab, red, red)) {
5693
+ .swift-catering-widget .text-white\\/50 {
5694
+ color: color-mix(in oklab, var(--color-white) 50%, transparent);
5695
+ }
5696
+ }
5697
+ .swift-catering-widget .text-white\\/60 {
5698
+ color: #fff9;
5699
+ }
5700
+ @supports (color:color-mix(in lab, red, red)) {
5701
+ .swift-catering-widget .text-white\\/60 {
5702
+ color: color-mix(in oklab, var(--color-white) 60%, transparent);
5703
+ }
5704
+ }
5591
5705
  .swift-catering-widget .text-white\\/70 {
5592
5706
  color: #ffffffb3;
5593
5707
  }
@@ -5596,6 +5710,14 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5596
5710
  color: color-mix(in oklab, var(--color-white) 70%, transparent);
5597
5711
  }
5598
5712
  }
5713
+ .swift-catering-widget .text-white\\/80 {
5714
+ color: #fffc;
5715
+ }
5716
+ @supports (color:color-mix(in lab, red, red)) {
5717
+ .swift-catering-widget .text-white\\/80 {
5718
+ color: color-mix(in oklab, var(--color-white) 80%, transparent);
5719
+ }
5720
+ }
5599
5721
  .swift-catering-widget .text-white\\/90 {
5600
5722
  color: #ffffffe6;
5601
5723
  }
@@ -5766,6 +5888,11 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5766
5888
  -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
5767
5889
  backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
5768
5890
  }
5891
+ .swift-catering-widget .backdrop-blur-\\[1px\\] {
5892
+ --tw-backdrop-blur:blur(1px);
5893
+ -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
5894
+ backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
5895
+ }
5769
5896
  .swift-catering-widget .backdrop-blur-lg {
5770
5897
  --tw-backdrop-blur:blur(var(--blur-lg));
5771
5898
  -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
@@ -5853,6 +5980,10 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5853
5980
  transition-timing-function: var(--tw-ease,var(--default-transition-timing-function));
5854
5981
  transition-duration: var(--tw-duration,var(--default-transition-duration));
5855
5982
  }
5983
+ .swift-catering-widget .duration-150 {
5984
+ --tw-duration:.15s;
5985
+ transition-duration: .15s;
5986
+ }
5856
5987
  .swift-catering-widget .duration-200 {
5857
5988
  --tw-duration:.2s;
5858
5989
  transition-duration: .2s;
@@ -5957,6 +6088,12 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
5957
6088
  --tw-scale-z:105%;
5958
6089
  scale: var(--tw-scale-x) var(--tw-scale-y);
5959
6090
  }
6091
+ .swift-catering-widget .hover\\:scale-110:hover {
6092
+ --tw-scale-x:110%;
6093
+ --tw-scale-y:110%;
6094
+ --tw-scale-z:110%;
6095
+ scale: var(--tw-scale-x) var(--tw-scale-y);
6096
+ }
5960
6097
  .swift-catering-widget .hover\\:border-base-content\\/20:hover {
5961
6098
  border-color: var(--color-base-content);
5962
6099
  }
@@ -6013,6 +6150,14 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
6013
6150
  background-color: color-mix(in oklab, var(--color-black) 5%, transparent);
6014
6151
  }
6015
6152
  }
6153
+ .swift-catering-widget .hover\\:bg-black\\/60:hover {
6154
+ background-color: #0009;
6155
+ }
6156
+ @supports (color:color-mix(in lab, red, red)) {
6157
+ .swift-catering-widget .hover\\:bg-black\\/60:hover {
6158
+ background-color: color-mix(in oklab, var(--color-black) 60%, transparent);
6159
+ }
6160
+ }
6016
6161
  .swift-catering-widget .hover\\:bg-gray-50:hover {
6017
6162
  background-color: var(--color-gray-50);
6018
6163
  }
@@ -6176,6 +6321,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
6176
6321
  .swift-catering-widget .hover\\:text-gray-300:hover {
6177
6322
  color: var(--color-gray-300);
6178
6323
  }
6324
+ .swift-catering-widget .hover\\:text-gray-500:hover {
6325
+ color: var(--color-gray-500);
6326
+ }
6179
6327
  .swift-catering-widget .hover\\:text-gray-600:hover {
6180
6328
  color: var(--color-gray-600);
6181
6329
  }
@@ -6308,6 +6456,9 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
6308
6456
  --tw-tracking:.08em;
6309
6457
  letter-spacing: .08em;
6310
6458
  }
6459
+ .swift-catering-widget .disabled\\:opacity-40:disabled {
6460
+ opacity: .4;
6461
+ }
6311
6462
  .swift-catering-widget .disabled\\:opacity-50:disabled {
6312
6463
  opacity: .5;
6313
6464
  }
@@ -6699,10 +6850,16 @@ styleInject(`/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
6699
6850
  .swift-catering-widget .md\\:text-\\[11px\\] {
6700
6851
  font-size: 11px;
6701
6852
  }
6853
+ .swift-catering-widget .md\\:opacity-0 {
6854
+ opacity: 0;
6855
+ }
6702
6856
  @media (hover: hover) {
6703
6857
  .swift-catering-widget .md\\:group-hover\\:block:is(:where(.group):hover *) {
6704
6858
  display: block;
6705
6859
  }
6860
+ .swift-catering-widget .md\\:group-hover\\/msg\\:opacity-100:is(:where(.group\\/msg):hover *) {
6861
+ opacity: 1;
6862
+ }
6706
6863
  }
6707
6864
  }
6708
6865
  @media (min-width: 64rem) {
@@ -7401,6 +7558,19 @@ function createChatEndpoints(client) {
7401
7558
  );
7402
7559
  if (!res.ok) throw new ChatApiError(`HTTP ${res.status}`, res.status);
7403
7560
  return res.json();
7561
+ },
7562
+ submitFeedback: async (sid, body) => {
7563
+ const payload = { rating: body.rating };
7564
+ if (body.note && body.note.trim()) payload.note = body.note.trim();
7565
+ if (typeof body.botReplyEventId === "number") {
7566
+ payload.botReplyEventId = body.botReplyEventId;
7567
+ }
7568
+ const res = await client.request(`/catering-chat/${sid}/feedback`, {
7569
+ method: "POST",
7570
+ body: JSON.stringify(payload)
7571
+ });
7572
+ if (!res.ok) throw new ChatApiError(`HTTP ${res.status}`, res.status);
7573
+ return res.json();
7404
7574
  }
7405
7575
  };
7406
7576
  }
@@ -12497,7 +12667,8 @@ function useChatSession(options = {}) {
12497
12667
  { type: "text", text: entry.text },
12498
12668
  ...entry.role === "bot" && entry.chips && entry.chips.length > 0 ? [{ type: "chips", chips: entry.chips }] : [],
12499
12669
  ...restoredParts
12500
- ]
12670
+ ],
12671
+ ...entry.role === "bot" && entry.eventId ? { eventId: entry.eventId } : {}
12501
12672
  };
12502
12673
  }
12503
12674
  );
@@ -12518,7 +12689,15 @@ function useChatSession(options = {}) {
12518
12689
  };
12519
12690
  return [...historicMessages, newLive];
12520
12691
  }
12521
- return existingLive ? [...historicMessages, existingLive] : historicMessages;
12692
+ if (!existingLive) return historicMessages;
12693
+ const carriedParts = existingLive.parts.filter(
12694
+ (p) => p.type !== "menu_preview" && p.type !== "restaurant_info"
12695
+ );
12696
+ if (carriedParts.length === 0) return historicMessages;
12697
+ return [
12698
+ ...historicMessages,
12699
+ { ...existingLive, parts: carriedParts }
12700
+ ];
12522
12701
  });
12523
12702
  if (markLive && newLiveParts.length > 0) {
12524
12703
  const previewIdx = finalLiveParts.findIndex(
@@ -12563,7 +12742,8 @@ function useChatSession(options = {}) {
12563
12742
  { type: "text", text: entry.text },
12564
12743
  ...entry.role === "bot" && entry.chips && entry.chips.length > 0 ? [{ type: "chips", chips: entry.chips }] : [],
12565
12744
  ...restoredParts
12566
- ]
12745
+ ],
12746
+ ...entry.role === "bot" && entry.eventId ? { eventId: entry.eventId } : {}
12567
12747
  };
12568
12748
  }
12569
12749
  );
@@ -12925,6 +13105,19 @@ function useChatSession(options = {}) {
12925
13105
  const getTaxonomyValueFor = react.useCallback((field) => {
12926
13106
  return taxonomyValueFor(field, lastTaxonomyRef.current);
12927
13107
  }, []);
13108
+ const submitFeedback = react.useCallback(
13109
+ async (rating, note, botReplyEventId) => {
13110
+ const sid = sessionIdRef.current;
13111
+ if (!sid) return false;
13112
+ try {
13113
+ await api.chat.submitFeedback(sid, { rating, note, botReplyEventId });
13114
+ return true;
13115
+ } catch {
13116
+ return false;
13117
+ }
13118
+ },
13119
+ [api]
13120
+ );
12928
13121
  const latestDraft = react.useMemo(
12929
13122
  () => latestMealSessions[latestActiveMealSessionIndex]?.draft ?? null,
12930
13123
  [latestMealSessions, latestActiveMealSessionIndex]
@@ -12970,7 +13163,8 @@ function useChatSession(options = {}) {
12970
13163
  getTaxonomyValueFor,
12971
13164
  setSelectedMealSessionIndex: (idx) => {
12972
13165
  selectedMealSessionIndexRef.current = idx;
12973
- }
13166
+ },
13167
+ submitFeedback
12974
13168
  };
12975
13169
  }
12976
13170
  function legacyToParts(data) {
@@ -12983,7 +13177,6 @@ function mergeAdaptedMealParts(data) {
12983
13177
  for (let i = 0; i < meals.length; i++) {
12984
13178
  const meal = meals[i];
12985
13179
  const blocks = meal.intentBlocks ?? [];
12986
- if (blocks.length === 0) continue;
12987
13180
  synthesized.push({
12988
13181
  type: "meal_session",
12989
13182
  mealSessionIndex: i,
@@ -13057,7 +13250,7 @@ function findLatestMealSessionParts(messages) {
13057
13250
  (a, b) => a.mealSessionIndex - b.mealSessionIndex
13058
13251
  );
13059
13252
  }
13060
- var STORAGE_KEY_PREFIX = "swift-food-cart-v2-";
13253
+ var STORAGE_KEY_PREFIX = "swift-food-cart-v3-";
13061
13254
  var emptyIntent = () => ({
13062
13255
  selectedRestaurantId: null,
13063
13256
  qtyOverrides: {},
@@ -13274,6 +13467,8 @@ function ChatSessionProvider({
13274
13467
  );
13275
13468
  const [activeViewedPreview, setActiveViewedPreview] = react.useState(null);
13276
13469
  const [suggestionsOverlayDismissed, setSuggestionsOverlayDismissed] = react.useState(false);
13470
+ const [feedbackTarget, setFeedbackTarget] = react.useState(null);
13471
+ const [feedbackRating, setFeedbackRating] = react.useState(0);
13277
13472
  react.useEffect(() => {
13278
13473
  if (!chat.sessionId) setActiveViewedPreview(null);
13279
13474
  }, [chat.sessionId]);
@@ -13312,6 +13507,17 @@ function ChatSessionProvider({
13312
13507
  setEditField,
13313
13508
  setEditValue,
13314
13509
  setSwapTarget,
13510
+ feedbackTarget,
13511
+ feedbackRating,
13512
+ setFeedbackRating,
13513
+ startFeedback: (messageId, up, eventId) => {
13514
+ setFeedbackTarget({ messageId, up, eventId });
13515
+ setFeedbackRating(0);
13516
+ },
13517
+ cancelFeedback: () => {
13518
+ setFeedbackTarget(null);
13519
+ setFeedbackRating(0);
13520
+ },
13315
13521
  hasResults,
13316
13522
  suggestionsOverlayDismissed,
13317
13523
  dismissSuggestionsOverlay: () => setSuggestionsOverlayDismissed(true),
@@ -13334,21 +13540,23 @@ function useChatSessionContext() {
13334
13540
  }
13335
13541
  return ctx;
13336
13542
  }
13337
- var BOLD_TAG_PATTERN = /<b>([\s\S]*?)<\/b>/g;
13543
+ var BOLD_PATTERN = /\*\*([\s\S]+?)\*\*|<b>([\s\S]*?)<\/b>/g;
13544
+ var BULLET_LINE_PATTERN = /^[ \t]*[*-][ \t]+/gm;
13338
13545
  function renderBotText(text) {
13546
+ const normalised = text.replace(BULLET_LINE_PATTERN, "\u2022 ");
13339
13547
  const nodes = [];
13340
13548
  let cursor = 0;
13341
13549
  let key = 0;
13342
- for (const match of text.matchAll(BOLD_TAG_PATTERN)) {
13550
+ for (const match of normalised.matchAll(BOLD_PATTERN)) {
13343
13551
  const start = match.index ?? 0;
13344
13552
  if (start > cursor) {
13345
- nodes.push(text.slice(cursor, start));
13553
+ nodes.push(normalised.slice(cursor, start));
13346
13554
  }
13347
- nodes.push(/* @__PURE__ */ jsxRuntime.jsx("strong", { children: match[1] }, key++));
13555
+ nodes.push(/* @__PURE__ */ jsxRuntime.jsx("strong", { children: match[1] ?? match[2] ?? "" }, key++));
13348
13556
  cursor = start + match[0].length;
13349
13557
  }
13350
- if (cursor < text.length) {
13351
- nodes.push(text.slice(cursor));
13558
+ if (cursor < normalised.length) {
13559
+ nodes.push(normalised.slice(cursor));
13352
13560
  }
13353
13561
  return nodes;
13354
13562
  }
@@ -13595,7 +13803,7 @@ function DraftItemRow({
13595
13803
  marginTop: 8,
13596
13804
  alignItems: "center",
13597
13805
  flexWrap: "wrap",
13598
- paddingLeft: showCheckbox ? 102 : 68
13806
+ paddingLeft: showCheckbox ? 168 : 132
13599
13807
  },
13600
13808
  children: [
13601
13809
  onSwap && /* @__PURE__ */ jsxRuntime.jsx(
@@ -13729,9 +13937,9 @@ function ItemPhoto({
13729
13937
  "div",
13730
13938
  {
13731
13939
  style: {
13732
- width: 56,
13733
- height: 56,
13734
- borderRadius: 10,
13940
+ width: 120,
13941
+ height: 120,
13942
+ borderRadius: 14,
13735
13943
  background: src ? `url(${src}) center/cover` : PLACEHOLDER_BG,
13736
13944
  flexShrink: 0,
13737
13945
  position: "relative",
@@ -13749,7 +13957,7 @@ function ItemPhoto({
13749
13957
  display: "flex",
13750
13958
  alignItems: "center",
13751
13959
  justifyContent: "center",
13752
- fontSize: "1.6rem",
13960
+ fontSize: "2.2rem",
13753
13961
  color: "var(--ink-faint)"
13754
13962
  },
13755
13963
  children: initial
@@ -14239,6 +14447,9 @@ function MenuPreviewCard({
14239
14447
  }) {
14240
14448
  const sections = preview.sections.filter((s) => s.items.length > 0);
14241
14449
  const [open, setOpen] = react.useState(defaultOpen);
14450
+ react.useEffect(() => {
14451
+ if (defaultOpen) setOpen(true);
14452
+ }, [preview, defaultOpen]);
14242
14453
  const [activeIdx, setActiveIdx] = react.useState(0);
14243
14454
  if (sections.length === 0) return null;
14244
14455
  const intents = sections.map((s) => s.intent);
@@ -14652,7 +14863,7 @@ function PreviewItemCard({
14652
14863
  },
14653
14864
  children: [
14654
14865
  item.restaurant.name,
14655
- item.restaurant.cuisine ? ` \xB7 ${item.restaurant.cuisine}` : ""
14866
+ item.restaurant.cuisine ? ` \xB7 ${item.restaurant.cuisine.replace(/_/g, " ")}` : ""
14656
14867
  ]
14657
14868
  }
14658
14869
  ),
@@ -15390,6 +15601,22 @@ function formatDay(iso) {
15390
15601
  if (Number.isNaN(d.getTime())) return iso;
15391
15602
  return d.toLocaleDateString(void 0, { weekday: "short" });
15392
15603
  }
15604
+ function fallbackCopy(text) {
15605
+ const ta = document.createElement("textarea");
15606
+ ta.value = text;
15607
+ ta.style.position = "fixed";
15608
+ ta.style.opacity = "0";
15609
+ document.body.appendChild(ta);
15610
+ ta.focus();
15611
+ ta.select();
15612
+ let ok = false;
15613
+ try {
15614
+ ok = document.execCommand("copy");
15615
+ } catch {
15616
+ }
15617
+ document.body.removeChild(ta);
15618
+ return ok;
15619
+ }
15393
15620
  function MessageThread({
15394
15621
  messages,
15395
15622
  sessionId,
@@ -15401,27 +15628,99 @@ function MessageThread({
15401
15628
  onQtyChange,
15402
15629
  liveLatestPreviewKey,
15403
15630
  onViewPreviewInPanel,
15404
- onOpenPreviewItem
15631
+ onOpenPreviewItem,
15632
+ onFeedback,
15633
+ latestBotMessageId,
15634
+ activeFeedbackMessageId
15405
15635
  }) {
15406
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: messages.map((msg) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: msg.parts.map((part, idx) => ({ part, idx })).sort((a, b) => partSortKey(a.part) - partSortKey(b.part)).map(({ part, idx }) => {
15407
- const key = `${msg.id}-${idx}`;
15408
- return /* @__PURE__ */ jsxRuntime.jsx(
15409
- PartRenderer,
15410
- {
15411
- part,
15412
- sender: msg.sender,
15413
- aiMealSessions,
15414
- onChip,
15415
- onSwapItem,
15416
- onRemoveItem,
15417
- onQtyChange,
15418
- isLatestPreview: key === liveLatestPreviewKey,
15419
- onViewPreviewInPanel,
15420
- onOpenPreviewItem
15421
- },
15422
- key
15423
- );
15424
- }) }, msg.id)) });
15636
+ let latestPreviewMsgId = null;
15637
+ let latestPreviewIdx = -1;
15638
+ for (let mi = messages.length - 1; mi >= 0; mi--) {
15639
+ const m = messages[mi];
15640
+ for (let pi = m.parts.length - 1; pi >= 0; pi--) {
15641
+ if (m.parts[pi].type === "menu_preview") {
15642
+ latestPreviewMsgId = m.id;
15643
+ latestPreviewIdx = pi;
15644
+ break;
15645
+ }
15646
+ }
15647
+ if (latestPreviewMsgId !== null) break;
15648
+ }
15649
+ const [copiedId, setCopiedId] = react.useState(null);
15650
+ const copyMessage = react.useCallback((msg) => {
15651
+ const text = msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
15652
+ if (!text) return;
15653
+ const onSuccess = () => {
15654
+ setCopiedId(msg.id);
15655
+ window.setTimeout(() => setCopiedId((prev) => prev === msg.id ? null : prev), 1500);
15656
+ };
15657
+ if (navigator.clipboard?.writeText) {
15658
+ navigator.clipboard.writeText(text).then(onSuccess, () => {
15659
+ fallbackCopy(text) && onSuccess();
15660
+ });
15661
+ } else {
15662
+ fallbackCopy(text) && onSuccess();
15663
+ }
15664
+ }, []);
15665
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: messages.map((msg) => {
15666
+ const isBot = msg.sender === "bot";
15667
+ const isUser = msg.sender === "user";
15668
+ const isLatestBot = isBot && msg.id === latestBotMessageId;
15669
+ const isActiveFeedback = msg.id === activeFeedbackMessageId;
15670
+ const hasText = msg.parts.some((p) => p.type === "text");
15671
+ const justCopied = copiedId === msg.id;
15672
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "group/msg", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
15673
+ msg.parts.map((part, idx) => ({ part, idx })).sort((a, b) => partSortKey(a.part) - partSortKey(b.part)).map(({ part, idx }) => {
15674
+ const key = `${msg.id}-${idx}`;
15675
+ const isLatestPreview = msg.id === latestPreviewMsgId && idx === latestPreviewIdx;
15676
+ return /* @__PURE__ */ jsxRuntime.jsx(
15677
+ PartRenderer,
15678
+ {
15679
+ part,
15680
+ sender: msg.sender,
15681
+ aiMealSessions,
15682
+ onChip,
15683
+ onSwapItem,
15684
+ onRemoveItem,
15685
+ onQtyChange,
15686
+ isLatestPreview,
15687
+ onViewPreviewInPanel,
15688
+ onOpenPreviewItem
15689
+ },
15690
+ key
15691
+ );
15692
+ }),
15693
+ (hasText || isBot && onFeedback) && /* @__PURE__ */ jsxRuntime.jsxs(
15694
+ "div",
15695
+ {
15696
+ className: `flex items-center gap-1 transition-opacity duration-150 ${isUser ? "justify-end" : "justify-start"} ${isLatestBot || isActiveFeedback ? "opacity-100" : "opacity-100 md:opacity-0 md:group-hover/msg:opacity-100"}`,
15697
+ children: [
15698
+ isBot && onFeedback && /* @__PURE__ */ jsxRuntime.jsx(
15699
+ "button",
15700
+ {
15701
+ type: "button",
15702
+ onClick: () => onFeedback(msg.id, false, msg.eventId),
15703
+ className: `p-1 rounded transition-colors ${isActiveFeedback ? "text-primary" : "text-gray-300 hover:text-gray-500"}`,
15704
+ "aria-label": "Send feedback",
15705
+ title: "Send feedback",
15706
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageCircleWarning, { className: "w-3.5 h-3.5" })
15707
+ }
15708
+ ),
15709
+ hasText && /* @__PURE__ */ jsxRuntime.jsx(
15710
+ "button",
15711
+ {
15712
+ type: "button",
15713
+ onClick: () => copyMessage(msg),
15714
+ className: "p-1 rounded transition-colors text-gray-300 hover:text-gray-500",
15715
+ "aria-label": justCopied ? "Copied" : "Copy message",
15716
+ children: justCopied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3.5 h-3.5 text-primary" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "w-3.5 h-3.5" })
15717
+ }
15718
+ )
15719
+ ]
15720
+ }
15721
+ )
15722
+ ] }, msg.id);
15723
+ }) });
15425
15724
  }
15426
15725
  function partSortKey(part) {
15427
15726
  return part.type === "text" ? 0 : 1;
@@ -15486,9 +15785,6 @@ function PartRenderer({
15486
15785
  if (part.type === "missing_fields_clarifier") {
15487
15786
  return null;
15488
15787
  }
15489
- if (part.type === "feedback") {
15490
- return null;
15491
- }
15492
15788
  return null;
15493
15789
  }
15494
15790
  function StandaloneIntentBlock({
@@ -15527,10 +15823,11 @@ function ChatMessagesView() {
15527
15823
  chat,
15528
15824
  setEditField,
15529
15825
  setEditValue,
15530
- hasResults,
15531
15826
  onViewResults,
15532
15827
  setActiveViewedPreview,
15533
- onOpenPreviewItem
15828
+ onOpenPreviewItem,
15829
+ startFeedback,
15830
+ feedbackTarget
15534
15831
  } = useChatSessionContext();
15535
15832
  const { messages, sending, awaitingReply, bootstrapping, error, sessionId, handleChip, retryLastSend, latestMealSessions } = chat;
15536
15833
  const messagesEndRef = react.useRef(null);
@@ -15538,6 +15835,12 @@ function ChatMessagesView() {
15538
15835
  () => messages.map((m) => ({ ...m, parts: chatPartsOnly(m.parts) })).filter((m) => m.parts.length > 0),
15539
15836
  [messages]
15540
15837
  );
15838
+ const latestBotMessageId = react.useMemo(() => {
15839
+ for (let i = chatMessages.length - 1; i >= 0; i--) {
15840
+ if (chatMessages[i].sender === "bot") return chatMessages[i].id;
15841
+ }
15842
+ return null;
15843
+ }, [chatMessages]);
15541
15844
  react.useEffect(() => {
15542
15845
  messagesEndRef.current?.scrollIntoView({
15543
15846
  behavior: "smooth",
@@ -15575,7 +15878,10 @@ function ChatMessagesView() {
15575
15878
  onEditField: handleEditField,
15576
15879
  liveLatestPreviewKey: chat.liveLatestPreviewKey,
15577
15880
  onViewPreviewInPanel: handleViewPreviewInPanel,
15578
- onOpenPreviewItem
15881
+ onOpenPreviewItem,
15882
+ onFeedback: startFeedback,
15883
+ latestBotMessageId,
15884
+ activeFeedbackMessageId: feedbackTarget?.messageId ?? null
15579
15885
  }
15580
15886
  ),
15581
15887
  awaitingReply && /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, {}),
@@ -15604,15 +15910,6 @@ function ChatMessagesView() {
15604
15910
  ]
15605
15911
  }
15606
15912
  ),
15607
- hasResults && /* @__PURE__ */ jsxRuntime.jsx(
15608
- "button",
15609
- {
15610
- type: "button",
15611
- onClick: onViewResults,
15612
- className: "md:hidden flex items-center justify-center gap-1.5 mx-auto mt-2 px-4 py-2 rounded-full bg-primary text-white text-sm font-semibold shadow-sm",
15613
- children: "\u2728 View menu suggestions"
15614
- }
15615
- ),
15616
15913
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
15617
15914
  ] });
15618
15915
  }
@@ -16474,9 +16771,11 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16474
16771
  function ChatAddressInput({
16475
16772
  onPlaceSelect,
16476
16773
  onClear,
16774
+ onDismiss,
16477
16775
  placeholder = "Enter Location",
16478
16776
  initialQuery,
16479
- autoFocus
16777
+ autoFocus,
16778
+ dropdownDirection = "up"
16480
16779
  }) {
16481
16780
  const {
16482
16781
  inputRef,
@@ -16496,6 +16795,33 @@ function ChatAddressInput({
16496
16795
  react.useEffect(() => {
16497
16796
  if (autoFocus) inputRef.current?.focus();
16498
16797
  }, []);
16798
+ const [dropdownRect, setDropdownRect] = react.useState(null);
16799
+ react.useLayoutEffect(() => {
16800
+ if (!open || predictions.length === 0) {
16801
+ setDropdownRect(null);
16802
+ return;
16803
+ }
16804
+ const update = () => {
16805
+ const el = containerRef.current;
16806
+ if (!el) return;
16807
+ const rect = el.getBoundingClientRect();
16808
+ setDropdownRect(
16809
+ dropdownDirection === "down" ? { left: rect.left, top: rect.bottom + 4, width: rect.width } : { left: rect.left, bottom: window.innerHeight - rect.top + 4, width: rect.width }
16810
+ );
16811
+ };
16812
+ update();
16813
+ window.addEventListener("resize", update);
16814
+ window.addEventListener("scroll", update, true);
16815
+ const vv = window.visualViewport;
16816
+ vv?.addEventListener("resize", update);
16817
+ vv?.addEventListener("scroll", update);
16818
+ return () => {
16819
+ window.removeEventListener("resize", update);
16820
+ window.removeEventListener("scroll", update, true);
16821
+ vv?.removeEventListener("resize", update);
16822
+ vv?.removeEventListener("scroll", update);
16823
+ };
16824
+ }, [open, predictions.length]);
16499
16825
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
16500
16826
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
16501
16827
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -16514,62 +16840,87 @@ function ChatAddressInput({
16514
16840
  onChange: (e) => setQuery(e.target.value),
16515
16841
  onKeyDown: handleKeyDown,
16516
16842
  placeholder,
16517
- autoComplete: "new-password",
16518
- className: "flex-1 bg-transparent text-sm text-gray-800 placeholder:text-gray-400 outline-none py-1"
16843
+ autoComplete: "off",
16844
+ autoCorrect: "off",
16845
+ autoCapitalize: "off",
16846
+ spellCheck: false,
16847
+ className: "flex-1 bg-transparent text-base text-gray-800 placeholder:text-gray-400 outline-none py-1 touch-auto"
16519
16848
  }
16520
16849
  ),
16521
- query.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
16850
+ (query.length > 0 || onDismiss) && /* @__PURE__ */ jsxRuntime.jsx(
16522
16851
  "button",
16523
16852
  {
16524
16853
  type: "button",
16525
16854
  onClick: () => {
16526
- setQuery("");
16527
- onClear?.();
16528
- inputRef.current?.focus();
16855
+ if (onDismiss) {
16856
+ onDismiss();
16857
+ } else {
16858
+ setQuery("");
16859
+ onClear?.();
16860
+ inputRef.current?.focus();
16861
+ }
16529
16862
  },
16530
16863
  className: "flex-shrink-0 rounded-full p-0.5 text-gray-400 hover:bg-base-100 hover:text-gray-600 transition-colors",
16531
- "aria-label": "Clear address",
16864
+ "aria-label": onDismiss ? "Close" : "Clear address",
16532
16865
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
16533
16866
  }
16534
16867
  )
16535
16868
  ] }),
16536
- open && predictions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-0 right-0 mb-1 rounded-lg border border-base-200 bg-white shadow-lg overflow-hidden z-50", children: predictions.map((p, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
16537
- "button",
16538
- {
16539
- type: "button",
16540
- onMouseDown: (e) => {
16541
- e.preventDefault();
16542
- handleSelect(p);
16543
- },
16544
- onMouseEnter: () => setActiveIndex(idx),
16545
- className: `flex w-full items-start gap-2 px-3 py-2 text-left transition-colors ${idx === activeIndex ? "bg-base-100" : "hover:bg-base-100"}`,
16546
- children: [
16547
- /* @__PURE__ */ jsxRuntime.jsx(
16548
- "svg",
16869
+ open && predictions.length > 0 && dropdownRect && typeof document !== "undefined" && reactDom.createPortal(
16870
+ /* Portal to the widget's shared overlay root so the
16871
+ dropdown escapes the bar pill's overflow-hidden +
16872
+ rounded-3xl clip (where it was otherwise invisible). */
16873
+ /* @__PURE__ */ jsxRuntime.jsx(
16874
+ "div",
16875
+ {
16876
+ style: {
16877
+ position: "fixed",
16878
+ left: dropdownRect.left,
16879
+ ...dropdownRect.top != null ? { top: dropdownRect.top } : { bottom: dropdownRect.bottom },
16880
+ width: dropdownRect.width
16881
+ },
16882
+ className: "z-[60] rounded-lg border border-base-200 bg-white shadow-lg overflow-hidden",
16883
+ children: predictions.map((p, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
16884
+ "button",
16549
16885
  {
16550
- className: "h-3.5 w-3.5 flex-shrink-0 text-base-content/40 mt-0.5",
16551
- fill: "none",
16552
- stroke: "currentColor",
16553
- viewBox: "0 0 24 24",
16554
- children: /* @__PURE__ */ jsxRuntime.jsx(
16555
- "path",
16556
- {
16557
- strokeLinecap: "round",
16558
- strokeLinejoin: "round",
16559
- strokeWidth: 2,
16560
- d: "M17.657 16.657L13.414 20.9a2 2 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0zM15 11a3 3 0 11-6 0 3 3 0 016 0z"
16561
- }
16562
- )
16563
- }
16564
- ),
16565
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
16566
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-base-content truncate", children: p.structured_formatting.main_text }),
16567
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-base-content/50 truncate", children: p.structured_formatting.secondary_text })
16568
- ] })
16569
- ]
16570
- },
16571
- p.place_id
16572
- )) })
16886
+ type: "button",
16887
+ onMouseDown: (e) => {
16888
+ e.preventDefault();
16889
+ handleSelect(p);
16890
+ },
16891
+ onMouseEnter: () => setActiveIndex(idx),
16892
+ className: `flex w-full items-start gap-2 px-3 py-2 text-left transition-colors ${idx === activeIndex ? "bg-base-100" : "hover:bg-base-100"}`,
16893
+ children: [
16894
+ /* @__PURE__ */ jsxRuntime.jsx(
16895
+ "svg",
16896
+ {
16897
+ className: "h-3.5 w-3.5 flex-shrink-0 text-base-content/40 mt-0.5",
16898
+ fill: "none",
16899
+ stroke: "currentColor",
16900
+ viewBox: "0 0 24 24",
16901
+ children: /* @__PURE__ */ jsxRuntime.jsx(
16902
+ "path",
16903
+ {
16904
+ strokeLinecap: "round",
16905
+ strokeLinejoin: "round",
16906
+ strokeWidth: 2,
16907
+ d: "M17.657 16.657L13.414 20.9a2 2 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0zM15 11a3 3 0 11-6 0 3 3 0 016 0z"
16908
+ }
16909
+ )
16910
+ }
16911
+ ),
16912
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
16913
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-base-content truncate", children: p.structured_formatting.main_text }),
16914
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-base-content/50 truncate", children: p.structured_formatting.secondary_text })
16915
+ ] })
16916
+ ]
16917
+ },
16918
+ p.place_id
16919
+ ))
16920
+ }
16921
+ ),
16922
+ getOverlayRoot()
16923
+ )
16573
16924
  ] });
16574
16925
  }
16575
16926
  var GATE_PROMPT = "What is the code to use the AI chat and test our beta version?";
@@ -16663,8 +17014,25 @@ function readUnlocked() {
16663
17014
  return false;
16664
17015
  }
16665
17016
  }
17017
+ var unlockedValue = readUnlocked();
17018
+ var subscribers = /* @__PURE__ */ new Set();
17019
+ function setUnlocked(next) {
17020
+ if (unlockedValue === next) return;
17021
+ unlockedValue = next;
17022
+ subscribers.forEach((cb) => cb());
17023
+ }
17024
+ function subscribe(cb) {
17025
+ subscribers.add(cb);
17026
+ return () => {
17027
+ subscribers.delete(cb);
17028
+ };
17029
+ }
16666
17030
  function useBetaUnlock() {
16667
- const [unlocked, setUnlocked] = react.useState(readUnlocked);
17031
+ const unlocked = react.useSyncExternalStore(
17032
+ subscribe,
17033
+ () => unlockedValue,
17034
+ () => false
17035
+ );
16668
17036
  const tryUnlock = react.useCallback((code) => {
16669
17037
  if (code.trim() !== BETA_CODE) return false;
16670
17038
  setUnlocked(true);
@@ -16698,7 +17066,8 @@ function AIChatBody() {
16698
17066
  applyEditField,
16699
17067
  handleChip,
16700
17068
  editingMealSessionIndex,
16701
- setEditingMealSessionIndex
17069
+ setEditingMealSessionIndex,
17070
+ submitFeedback
16702
17071
  },
16703
17072
  resetEverything,
16704
17073
  editField,
@@ -16708,7 +17077,11 @@ function AIChatBody() {
16708
17077
  setEditValue,
16709
17078
  setSwapTarget,
16710
17079
  cart,
16711
- dismissSuggestionsOverlay
17080
+ dismissSuggestionsOverlay,
17081
+ feedbackTarget,
17082
+ feedbackRating,
17083
+ setFeedbackRating,
17084
+ cancelFeedback
16712
17085
  } = useChatSessionContext();
16713
17086
  const buildChatAddress = useChatAddressFromContext();
16714
17087
  const { contactInfo, setContactInfo } = useCateringState();
@@ -16717,6 +17090,9 @@ function AIChatBody() {
16717
17090
  const [pillEditing, setPillEditing] = react.useState(false);
16718
17091
  const textareaRef = react.useRef(null);
16719
17092
  const [input, setInput] = react.useState("");
17093
+ const [feedbackHover, setFeedbackHover] = react.useState(0);
17094
+ const [feedbackState, setFeedbackState] = react.useState("idle");
17095
+ const [feedbackNote, setFeedbackNote] = react.useState("");
16720
17096
  const [pendingInputFocus, setPendingInputFocus] = react.useState(false);
16721
17097
  const messagesScrollRef = react.useRef(null);
16722
17098
  const [showScrollDown, setShowScrollDown] = react.useState(false);
@@ -16751,6 +17127,31 @@ function AIChatBody() {
16751
17127
  el.focus();
16752
17128
  setPendingInputFocus(false);
16753
17129
  }, [pendingInputFocus, gateActive, pillEditing, bootstrapping, sessionId]);
17130
+ const inFeedbackMode = feedbackTarget !== null;
17131
+ const RATING_LABELS = ["Terrible", "Poor", "Okay", "Good", "Great"];
17132
+ react.useEffect(() => {
17133
+ if (!feedbackTarget) return;
17134
+ setFeedbackHover(0);
17135
+ setFeedbackNote("");
17136
+ setFeedbackState("idle");
17137
+ }, [feedbackTarget]);
17138
+ function closeFeedback() {
17139
+ cancelFeedback();
17140
+ setFeedbackHover(0);
17141
+ setFeedbackNote("");
17142
+ setFeedbackState("idle");
17143
+ }
17144
+ async function handleFeedbackSubmit() {
17145
+ if (feedbackRating < 1 || !feedbackTarget) return;
17146
+ setFeedbackState("sending");
17147
+ const ok = await submitFeedback(feedbackRating, feedbackNote || void 0, feedbackTarget.eventId);
17148
+ if (ok) {
17149
+ setFeedbackState("done");
17150
+ window.setTimeout(closeFeedback, 900);
17151
+ } else {
17152
+ setFeedbackState("error");
17153
+ }
17154
+ }
16754
17155
  function handlePlaceSelect(place) {
16755
17156
  const parsed = parsePlaceResult(place);
16756
17157
  if (!parsed) return;
@@ -16798,6 +17199,10 @@ function AIChatBody() {
16798
17199
  void handleChip(chip);
16799
17200
  }
16800
17201
  function submitText() {
17202
+ if (inFeedbackMode) {
17203
+ void handleFeedbackSubmit();
17204
+ return;
17205
+ }
16801
17206
  if (sending || bootstrapping || !sessionId || !input.trim()) return;
16802
17207
  if (gateActive && !hasAddress) return;
16803
17208
  void sendText(input, buildChatAddress());
@@ -16808,6 +17213,10 @@ function AIChatBody() {
16808
17213
  submitText();
16809
17214
  }
16810
17215
  function handleKeyDown(e) {
17216
+ if (e.key === "Escape" && inFeedbackMode) {
17217
+ closeFeedback();
17218
+ return;
17219
+ }
16811
17220
  if (e.key === "Enter" && !e.shiftKey) {
16812
17221
  e.preventDefault();
16813
17222
  submitText();
@@ -16855,6 +17264,46 @@ function AIChatBody() {
16855
17264
  )
16856
17265
  ] }),
16857
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(
17269
+ "button",
17270
+ {
17271
+ 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",
17274
+ children: [
17275
+ /* @__PURE__ */ jsxRuntime.jsx(
17276
+ lucideReact.MapPin,
17277
+ {
17278
+ className: "w-3 h-3 flex-shrink-0 text-white/70",
17279
+ "aria-hidden": true
17280
+ }
17281
+ ),
17282
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: contactInfo?.addressLine1 }),
17283
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/50", children: "\xB7" }),
17284
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-white", children: "change" })
17285
+ ]
17286
+ }
17287
+ ),
17288
+ pillEditing && /* @__PURE__ */ jsxRuntime.jsx(
17289
+ "div",
17290
+ {
17291
+ ref: pillEditRef,
17292
+ className: "pointer-events-auto w-full rounded-xl border border-base-300 bg-white px-3 py-2 shadow-sm",
17293
+ children: /* @__PURE__ */ jsxRuntime.jsx(
17294
+ ChatAddressInput,
17295
+ {
17296
+ onPlaceSelect: handlePlaceSelect,
17297
+ onClear: handleClearAddress,
17298
+ onDismiss: () => setPillEditing(false),
17299
+ initialQuery: contactInfo?.addressLine1 ?? "",
17300
+ autoFocus: true,
17301
+ dropdownDirection: "down"
17302
+ }
17303
+ )
17304
+ }
17305
+ )
17306
+ ] }),
16858
17307
  /* @__PURE__ */ jsxRuntime.jsx(
16859
17308
  "div",
16860
17309
  {
@@ -16895,113 +17344,124 @@ function AIChatBody() {
16895
17344
  }
16896
17345
  }
16897
17346
  ) }),
16898
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 border-t border-base-200 p-2.5", children: /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { mode: "wait", initial: false, children: editField !== null ? /* @__PURE__ */ jsxRuntime.jsx(
16899
- react$1.motion.div,
16900
- {
16901
- initial: { opacity: 0, y: 8 },
16902
- animate: { opacity: 1, y: 0 },
16903
- exit: { opacity: 0, y: 8 },
16904
- transition: { duration: 0.18, ease: "easeOut" },
16905
- children: /* @__PURE__ */ jsxRuntime.jsx(
16906
- InlineFieldEditor,
16907
- {
16908
- field: editField,
16909
- initialValue: editValue,
16910
- onSave: handleEditSave,
16911
- onCancel: () => {
16912
- setEditField(null);
16913
- setEditingMealSessionIndex(void 0);
16914
- }
16915
- }
16916
- )
16917
- },
16918
- "editor"
16919
- ) : /* @__PURE__ */ jsxRuntime.jsx(
16920
- react$1.motion.div,
16921
- {
16922
- initial: { opacity: 0, y: -8 },
16923
- animate: { opacity: 1, y: 0 },
16924
- exit: { opacity: 0, y: -8 },
16925
- transition: { duration: 0.18, ease: "easeOut" },
16926
- children: gateActive ? /* @__PURE__ */ jsxRuntime.jsxs(
16927
- "form",
16928
- {
16929
- onSubmit: handleSubmit,
16930
- className: "flex flex-col rounded-xl border border-base-300 bg-white px-3 py-2 focus-within:border-primary transition-colors",
16931
- children: [
16932
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pb-2 border-b border-base-200", children: /* @__PURE__ */ jsxRuntime.jsx(
16933
- ChatAddressInput,
17347
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-shrink-0 border-t border-base-200 p-2.5", children: [
17348
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { children: inFeedbackMode && /* @__PURE__ */ jsxRuntime.jsx(
17349
+ react$1.motion.div,
17350
+ {
17351
+ initial: { opacity: 0, y: 4 },
17352
+ animate: { opacity: 1, y: 0 },
17353
+ exit: { opacity: 0, y: 4 },
17354
+ transition: { duration: 0.15, ease: "easeOut" },
17355
+ className: "absolute left-0 right-0 bottom-full z-10 flex justify-center pb-1.5 pointer-events-none",
17356
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-auto inline-flex items-center gap-2 px-3.5 py-2.5 rounded-full bg-black/50 backdrop-blur-sm shadow-sm", children: [
17357
+ /* @__PURE__ */ jsxRuntime.jsx(
17358
+ "button",
17359
+ {
17360
+ type: "button",
17361
+ onClick: closeFeedback,
17362
+ className: "p-0.5 rounded text-white/60 hover:text-white transition-colors",
17363
+ "aria-label": "Cancel feedback",
17364
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
17365
+ }
17366
+ ),
17367
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-white/70", children: "Rate:" }),
17368
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5", children: [1, 2, 3, 4, 5].map((n) => {
17369
+ const active = feedbackHover || feedbackRating;
17370
+ return /* @__PURE__ */ jsxRuntime.jsx(
17371
+ "button",
16934
17372
  {
16935
- onPlaceSelect: handlePlaceSelect,
16936
- onClear: handleClearAddress,
16937
- autoFocus: true
16938
- }
16939
- ) }),
16940
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2 pt-2", children: [
16941
- /* @__PURE__ */ jsxRuntime.jsx(
16942
- "textarea",
16943
- {
16944
- ref: textareaRef,
16945
- rows: 3,
16946
- value: input,
16947
- onChange: (e) => setInput(e.target.value),
16948
- onKeyDown: handleKeyDown,
16949
- placeholder: "Ask for menu suggestions...",
16950
- disabled: bootstrapping || !sessionId,
16951
- className: "flex-1 bg-transparent text-sm text-gray-800 placeholder:text-gray-400 outline-none resize-none leading-snug py-1 overflow-hidden"
16952
- }
16953
- ),
16954
- /* @__PURE__ */ jsxRuntime.jsx(
16955
- "button",
16956
- {
16957
- type: "submit",
16958
- disabled: sending || bootstrapping || !input.trim() || !sessionId || !hasAddress,
16959
- 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",
16960
- title: hasAddress ? "Send" : "Enter a delivery address first",
16961
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-3.5 h-3.5" })
16962
- }
16963
- )
16964
- ] })
16965
- ]
16966
- }
16967
- ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16968
- hasAddress && !pillEditing && /* @__PURE__ */ jsxRuntime.jsxs(
16969
- "button",
17373
+ type: "button",
17374
+ "aria-label": `${n} star${n > 1 ? "s" : ""}`,
17375
+ onMouseEnter: () => setFeedbackHover(n),
17376
+ onMouseLeave: () => setFeedbackHover(0),
17377
+ onClick: () => setFeedbackRating(n),
17378
+ className: "p-0.5 transition-transform hover:scale-110",
17379
+ children: /* @__PURE__ */ jsxRuntime.jsx(
17380
+ lucideReact.Star,
17381
+ {
17382
+ className: `h-5 w-5 transition-colors ${n <= active ? "fill-amber-400 text-amber-400" : "fill-transparent text-white/40"}`
17383
+ }
17384
+ )
17385
+ },
17386
+ n
17387
+ );
17388
+ }) }),
17389
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-white/80 min-w-[3.5rem]", children: (feedbackHover || feedbackRating) > 0 ? RATING_LABELS[(feedbackHover || feedbackRating) - 1] : "" }),
17390
+ feedbackState === "error" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-red-300", children: "Failed" }),
17391
+ feedbackState === "done" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-green-300 font-medium", children: "Thanks!" })
17392
+ ] })
17393
+ }
17394
+ ) }),
17395
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { mode: "wait", initial: false, children: editField !== null ? /* @__PURE__ */ jsxRuntime.jsx(
17396
+ react$1.motion.div,
17397
+ {
17398
+ initial: { opacity: 0, y: 8 },
17399
+ animate: { opacity: 1, y: 0 },
17400
+ exit: { opacity: 0, y: 8 },
17401
+ transition: { duration: 0.18, ease: "easeOut" },
17402
+ children: /* @__PURE__ */ jsxRuntime.jsx(
17403
+ InlineFieldEditor,
16970
17404
  {
16971
- type: "button",
16972
- onClick: () => setPillEditing(true),
16973
- className: "mb-2 inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-base-100 hover:bg-base-200 transition-colors text-xs text-gray-700 max-w-full",
17405
+ field: editField,
17406
+ initialValue: editValue,
17407
+ onSave: handleEditSave,
17408
+ onCancel: () => {
17409
+ setEditField(null);
17410
+ setEditingMealSessionIndex(void 0);
17411
+ }
17412
+ }
17413
+ )
17414
+ },
17415
+ "editor"
17416
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
17417
+ react$1.motion.div,
17418
+ {
17419
+ initial: { opacity: 0, y: -8 },
17420
+ animate: { opacity: 1, y: 0 },
17421
+ exit: { opacity: 0, y: -8 },
17422
+ transition: { duration: 0.18, ease: "easeOut" },
17423
+ children: gateActive ? /* @__PURE__ */ jsxRuntime.jsxs(
17424
+ "form",
17425
+ {
17426
+ onSubmit: handleSubmit,
17427
+ className: "flex flex-col rounded-xl border border-base-300 bg-white px-3 py-2 focus-within:border-primary transition-colors",
16974
17428
  children: [
16975
- /* @__PURE__ */ jsxRuntime.jsx(
16976
- lucideReact.MapPin,
17429
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pb-2 border-b border-base-200", children: /* @__PURE__ */ jsxRuntime.jsx(
17430
+ ChatAddressInput,
16977
17431
  {
16978
- className: "w-3 h-3 flex-shrink-0 text-gray-500",
16979
- "aria-hidden": true
17432
+ onPlaceSelect: handlePlaceSelect,
17433
+ onClear: handleClearAddress,
17434
+ autoFocus: true
16980
17435
  }
16981
- ),
16982
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: contactInfo?.addressLine1 }),
16983
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400", children: "\xB7" }),
16984
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-primary", children: "change" })
17436
+ ) }),
17437
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2 pt-2", children: [
17438
+ /* @__PURE__ */ jsxRuntime.jsx(
17439
+ "textarea",
17440
+ {
17441
+ ref: textareaRef,
17442
+ rows: 3,
17443
+ value: inFeedbackMode ? feedbackNote : input,
17444
+ onChange: (e) => inFeedbackMode ? setFeedbackNote(e.target.value) : setInput(e.target.value),
17445
+ onKeyDown: handleKeyDown,
17446
+ placeholder: inFeedbackMode ? feedbackRating <= 2 ? "What went wrong? (optional)" : "Add a note (optional)" : "Ask for menu suggestions...",
17447
+ disabled: bootstrapping || !sessionId,
17448
+ className: "flex-1 bg-transparent text-sm text-gray-800 placeholder:text-gray-400 outline-none resize-none leading-snug py-1 overflow-hidden"
17449
+ }
17450
+ ),
17451
+ /* @__PURE__ */ jsxRuntime.jsx(
17452
+ "button",
17453
+ {
17454
+ type: "submit",
17455
+ disabled: inFeedbackMode ? feedbackRating < 1 || feedbackState === "sending" : sending || bootstrapping || !input.trim() || !sessionId || !hasAddress,
17456
+ 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",
17457
+ title: inFeedbackMode ? "Submit feedback" : hasAddress ? "Send" : "Enter a delivery address first",
17458
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-3.5 h-3.5" })
17459
+ }
17460
+ )
17461
+ ] })
16985
17462
  ]
16986
17463
  }
16987
- ),
16988
- pillEditing && /* @__PURE__ */ jsxRuntime.jsx(
16989
- "div",
16990
- {
16991
- ref: pillEditRef,
16992
- className: "mb-2 rounded-xl border border-base-300 bg-white px-3 py-2",
16993
- children: /* @__PURE__ */ jsxRuntime.jsx(
16994
- ChatAddressInput,
16995
- {
16996
- onPlaceSelect: handlePlaceSelect,
16997
- onClear: handleClearAddress,
16998
- initialQuery: contactInfo?.addressLine1 ?? "",
16999
- autoFocus: true
17000
- }
17001
- )
17002
- }
17003
- ),
17004
- /* @__PURE__ */ jsxRuntime.jsxs(
17464
+ ) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(
17005
17465
  "form",
17006
17466
  {
17007
17467
  onSubmit: handleSubmit,
@@ -17012,11 +17472,11 @@ function AIChatBody() {
17012
17472
  {
17013
17473
  ref: textareaRef,
17014
17474
  rows: 1,
17015
- value: input,
17016
- onChange: (e) => setInput(e.target.value),
17475
+ value: inFeedbackMode ? feedbackNote : input,
17476
+ onChange: (e) => inFeedbackMode ? setFeedbackNote(e.target.value) : setInput(e.target.value),
17017
17477
  onKeyDown: handleKeyDown,
17018
- placeholder: "Ask for menu suggestions...",
17019
- disabled: bootstrapping || !sessionId,
17478
+ placeholder: inFeedbackMode ? feedbackRating <= 2 ? "What went wrong? (optional)" : "Add a note (optional)" : "Ask for menu suggestions...",
17479
+ disabled: inFeedbackMode ? feedbackState === "sending" : bootstrapping || !sessionId,
17020
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"
17021
17481
  }
17022
17482
  ),
@@ -17024,19 +17484,19 @@ function AIChatBody() {
17024
17484
  "button",
17025
17485
  {
17026
17486
  type: "submit",
17027
- disabled: sending || bootstrapping || !input.trim() || !sessionId,
17487
+ disabled: inFeedbackMode ? feedbackRating < 1 || feedbackState === "sending" : sending || bootstrapping || !input.trim() || !sessionId,
17028
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",
17029
- title: "Send",
17489
+ title: inFeedbackMode ? "Submit feedback" : "Send",
17030
17490
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-3.5 h-3.5" })
17031
17491
  }
17032
17492
  )
17033
17493
  ]
17034
17494
  }
17035
- )
17036
- ] })
17037
- },
17038
- "input"
17039
- ) }) }),
17495
+ ) })
17496
+ },
17497
+ "input"
17498
+ ) })
17499
+ ] }),
17040
17500
  /* @__PURE__ */ jsxRuntime.jsx(
17041
17501
  SwapModal,
17042
17502
  {
@@ -17075,6 +17535,213 @@ function AIChatBody() {
17075
17535
  )
17076
17536
  ] });
17077
17537
  }
17538
+ function MobileAddressPickerSurface({
17539
+ onPlaceSelect,
17540
+ onBack,
17541
+ onRemove,
17542
+ onClose,
17543
+ hasAddress,
17544
+ initialQuery
17545
+ }) {
17546
+ const {
17547
+ inputRef,
17548
+ containerRef,
17549
+ query,
17550
+ setQuery,
17551
+ predictions,
17552
+ activeIndex,
17553
+ setActiveIndex,
17554
+ handleSelect,
17555
+ handleKeyDown
17556
+ } = useAddressAutocomplete(onPlaceSelect);
17557
+ react.useEffect(() => {
17558
+ if (initialQuery) setQuery(initialQuery);
17559
+ }, []);
17560
+ react.useEffect(() => {
17561
+ inputRef.current?.focus();
17562
+ }, []);
17563
+ const handleSearchKeyDown = (e) => {
17564
+ if (e.key === "Enter" && activeIndex < 0) {
17565
+ e.preventDefault();
17566
+ inputRef.current?.blur();
17567
+ return;
17568
+ }
17569
+ handleKeyDown(e);
17570
+ };
17571
+ const handleSurfacePointerDown = (e) => {
17572
+ const target = e.target;
17573
+ if (!target) return;
17574
+ if (target.closest("[data-keep-keyboard]")) return;
17575
+ if (target.closest("button")) return;
17576
+ inputRef.current?.blur();
17577
+ };
17578
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17579
+ react$1.motion.div,
17580
+ {
17581
+ initial: { opacity: 0 },
17582
+ animate: { opacity: 1 },
17583
+ exit: { opacity: 0 },
17584
+ transition: { duration: 0.18, ease: "easeOut" },
17585
+ onPointerDown: handleSurfacePointerDown,
17586
+ className: "absolute inset-0 flex flex-col bg-white",
17587
+ children: [
17588
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 flex items-center justify-between px-3 pt-3", children: [
17589
+ /* @__PURE__ */ jsxRuntime.jsx(
17590
+ "button",
17591
+ {
17592
+ type: "button",
17593
+ onClick: onBack,
17594
+ "aria-label": "Back to chat",
17595
+ "aria-hidden": !hasAddress,
17596
+ tabIndex: hasAddress ? 0 : -1,
17597
+ className: `flex h-9 w-9 items-center justify-center rounded-full text-gray-700 hover:bg-base-100 transition-opacity ${hasAddress ? "opacity-100" : "pointer-events-none opacity-0"}`,
17598
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-4 w-4" })
17599
+ }
17600
+ ),
17601
+ /* @__PURE__ */ jsxRuntime.jsx(
17602
+ "button",
17603
+ {
17604
+ type: "button",
17605
+ onClick: onClose,
17606
+ "aria-label": "Close AI chat",
17607
+ className: "flex h-9 w-9 items-center justify-center rounded-full text-gray-700 hover:bg-base-100",
17608
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
17609
+ }
17610
+ )
17611
+ ] }),
17612
+ /* @__PURE__ */ jsxRuntime.jsxs(
17613
+ "div",
17614
+ {
17615
+ ref: containerRef,
17616
+ className: "flex min-h-0 flex-1 flex-col px-6 pb-6",
17617
+ children: [
17618
+ /* @__PURE__ */ jsxRuntime.jsxs(
17619
+ react$1.motion.div,
17620
+ {
17621
+ initial: { opacity: 0, y: 6 },
17622
+ animate: { opacity: 1, y: 0 },
17623
+ exit: { opacity: 0, y: 6 },
17624
+ transition: { duration: 0.2, delay: 0.08, ease: "easeOut" },
17625
+ className: "flex flex-shrink-0 flex-col items-center text-center pt-6 pb-6",
17626
+ children: [
17627
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "h-7 w-7 text-primary" }) }),
17628
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mt-4 text-xl font-semibold text-gray-900", children: "Delivery address" }),
17629
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 max-w-[260px] text-sm text-gray-500", children: "Required to find caterers near you." })
17630
+ ]
17631
+ }
17632
+ ),
17633
+ /* @__PURE__ */ jsxRuntime.jsx(
17634
+ react$1.motion.div,
17635
+ {
17636
+ layoutId: "catering-address-pill",
17637
+ transition: { duration: 0.28, ease: "easeOut" },
17638
+ "data-keep-keyboard": true,
17639
+ className: "flex-shrink-0 rounded-xl bg-base-100",
17640
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2.5", children: [
17641
+ /* @__PURE__ */ jsxRuntime.jsx(
17642
+ lucideReact.Search,
17643
+ {
17644
+ className: "h-4 w-4 flex-shrink-0 text-gray-400",
17645
+ "aria-hidden": true
17646
+ }
17647
+ ),
17648
+ /* @__PURE__ */ jsxRuntime.jsx(
17649
+ "input",
17650
+ {
17651
+ ref: inputRef,
17652
+ type: "search",
17653
+ value: query,
17654
+ onChange: (e) => setQuery(e.target.value),
17655
+ onKeyDown: handleSearchKeyDown,
17656
+ placeholder: "Search address\u2026",
17657
+ autoComplete: "off",
17658
+ autoCorrect: "off",
17659
+ autoCapitalize: "off",
17660
+ spellCheck: false,
17661
+ enterKeyHint: "search",
17662
+ className: "flex-1 bg-transparent text-base text-gray-800 placeholder:text-gray-400 outline-none py-1 touch-auto"
17663
+ }
17664
+ ),
17665
+ query.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
17666
+ "button",
17667
+ {
17668
+ type: "button",
17669
+ onClick: () => {
17670
+ setQuery("");
17671
+ inputRef.current?.focus();
17672
+ },
17673
+ "aria-label": "Clear search",
17674
+ className: "flex-shrink-0 rounded-full p-0.5 text-gray-400 hover:bg-base-200 hover:text-gray-600 transition-colors",
17675
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
17676
+ }
17677
+ )
17678
+ ] })
17679
+ }
17680
+ ),
17681
+ predictions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
17682
+ react$1.motion.div,
17683
+ {
17684
+ initial: { opacity: 0 },
17685
+ animate: { opacity: 1 },
17686
+ exit: { opacity: 0 },
17687
+ transition: { duration: 0.18, ease: "easeOut" },
17688
+ "data-allow-touch": true,
17689
+ className: "mt-4 min-h-0 flex-1 overflow-y-auto overscroll-contain flex flex-col gap-2",
17690
+ children: predictions.map((p, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
17691
+ "button",
17692
+ {
17693
+ type: "button",
17694
+ onMouseDown: (e) => {
17695
+ e.preventDefault();
17696
+ handleSelect(p);
17697
+ inputRef.current?.blur();
17698
+ },
17699
+ onMouseEnter: () => setActiveIndex(idx),
17700
+ className: `flex w-full flex-shrink-0 items-start gap-3 rounded-xl border border-base-200 bg-white px-3 py-3 text-left transition-colors ${idx === activeIndex ? "bg-base-100" : "hover:bg-base-100"}`,
17701
+ children: [
17702
+ /* @__PURE__ */ jsxRuntime.jsx(
17703
+ lucideReact.MapPin,
17704
+ {
17705
+ className: "h-4 w-4 flex-shrink-0 text-gray-500 mt-0.5",
17706
+ "aria-hidden": true
17707
+ }
17708
+ ),
17709
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
17710
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold text-gray-900", children: p.structured_formatting.main_text }),
17711
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-gray-500", children: p.structured_formatting.secondary_text })
17712
+ ] })
17713
+ ]
17714
+ },
17715
+ p.place_id
17716
+ ))
17717
+ }
17718
+ ),
17719
+ hasAddress && /* @__PURE__ */ jsxRuntime.jsx(
17720
+ react$1.motion.div,
17721
+ {
17722
+ initial: { opacity: 0 },
17723
+ animate: { opacity: 1 },
17724
+ exit: { opacity: 0 },
17725
+ transition: { duration: 0.18, delay: 0.16, ease: "easeOut" },
17726
+ className: "mt-6 flex flex-shrink-0 justify-center",
17727
+ children: /* @__PURE__ */ jsxRuntime.jsx(
17728
+ "button",
17729
+ {
17730
+ type: "button",
17731
+ onClick: onRemove,
17732
+ className: "text-sm font-medium text-red-600 hover:text-red-700 transition-colors",
17733
+ children: "Remove address"
17734
+ }
17735
+ )
17736
+ }
17737
+ )
17738
+ ]
17739
+ }
17740
+ )
17741
+ ]
17742
+ }
17743
+ );
17744
+ }
17078
17745
  function AISuggestionsBottomButton() {
17079
17746
  const {
17080
17747
  hasResults,
@@ -17592,8 +18259,8 @@ function aiPlanToPricingInput(mealSessionParts, aiMealSessions, cart) {
17592
18259
  if (orderItems.length === 0) continue;
17593
18260
  out.push({
17594
18261
  sessionName: meta?.sessionName ?? `Meal ${ms.mealSessionIndex + 1}`,
17595
- sessionDate: meta?.sessionDate ?? "",
17596
- eventTime: meta?.eventTime ?? "",
18262
+ sessionDate: meta?.isoSessionDate ?? "",
18263
+ eventTime: meta?.isoEventTime ?? "",
17597
18264
  guestCount: meta?.guestCount ?? void 0,
17598
18265
  orderItems
17599
18266
  });
@@ -18065,6 +18732,11 @@ function IntentStepper({
18065
18732
  () => checkMinOrders(orderedMeals, aiMealSessions, cart),
18066
18733
  [orderedMeals, aiMealSessions, cart]
18067
18734
  );
18735
+ const myPos = orderedMeals.findIndex(
18736
+ (m) => m.mealSessionIndex === activeMealSessionIndex
18737
+ );
18738
+ const isFirstMeal = myPos <= 0;
18739
+ const isLastMeal = myPos >= 0 && myPos === orderedMeals.length - 1;
18068
18740
  if (!activeMeal) {
18069
18741
  return /* @__PURE__ */ jsxRuntime.jsxs(
18070
18742
  "div",
@@ -18085,6 +18757,23 @@ function IntentStepper({
18085
18757
  );
18086
18758
  }
18087
18759
  if (blocks.length === 0) {
18760
+ const emptyPrevLabel = !isFirstMeal ? aiMealSessions[orderedMeals[myPos - 1].mealSessionIndex]?.sessionName ?? null : null;
18761
+ const emptyNextLabel = !isLastMeal ? aiMealSessions[orderedMeals[myPos + 1].mealSessionIndex]?.sessionName ?? null : null;
18762
+ const handleEmptyPrev = () => {
18763
+ if (sending || isFirstMeal) return;
18764
+ pendingReviewRef.current = true;
18765
+ onPickMealSession(orderedMeals[myPos - 1].mealSessionIndex);
18766
+ };
18767
+ const handleEmptyNext = () => {
18768
+ if (sending || isLastMeal) return;
18769
+ onPickMealSession(orderedMeals[myPos + 1].mealSessionIndex);
18770
+ };
18771
+ const handleEmptyJumpTo = (targetMealSessionIndex, targetIntentIdx) => {
18772
+ if (sending) return;
18773
+ if (targetMealSessionIndex === activeMealSessionIndex) return;
18774
+ pendingIntentIdxRef.current = targetIntentIdx;
18775
+ onPickMealSession(targetMealSessionIndex);
18776
+ };
18088
18777
  return /* @__PURE__ */ jsxRuntime.jsxs(
18089
18778
  "div",
18090
18779
  {
@@ -18094,11 +18783,34 @@ function IntentStepper({
18094
18783
  flex: 1,
18095
18784
  minHeight: 0,
18096
18785
  marginBottom: 16,
18786
+ // Cluster topSlot + empty card + NavBar as one block centred
18787
+ // vertically — same trick the populated branch uses so the
18788
+ // tabs / nav don't get pinned to the panel's top/bottom
18789
+ // edges when the content above is short.
18097
18790
  justifyContent: "center"
18098
18791
  },
18099
18792
  children: [
18100
18793
  topSlot && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginBottom: 12, flexShrink: 0 }, children: topSlot }),
18101
- /* @__PURE__ */ jsxRuntime.jsx(EmptyStepperState, {})
18794
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flexShrink: 0, marginBottom: 8 }, children: /* @__PURE__ */ jsxRuntime.jsx(EmptyStepperState, { missingFields: activeMealMeta?.missingFields }) }),
18795
+ /* @__PURE__ */ jsxRuntime.jsx(
18796
+ NavBar,
18797
+ {
18798
+ onPrev: handleEmptyPrev,
18799
+ onNext: handleEmptyNext,
18800
+ onJumpTo: handleEmptyJumpTo,
18801
+ prevLabel: emptyPrevLabel,
18802
+ nextLabel: emptyNextLabel,
18803
+ atFinal: false,
18804
+ sending,
18805
+ showPrev: !isFirstMeal,
18806
+ showNext: !isLastMeal,
18807
+ mealSessions: orderedMeals,
18808
+ aiMealSessions,
18809
+ activeMealSessionIndex,
18810
+ currentIntentIndex: 0,
18811
+ cart
18812
+ }
18813
+ )
18102
18814
  ]
18103
18815
  }
18104
18816
  );
@@ -18106,13 +18818,8 @@ function IntentStepper({
18106
18818
  const pickedName = selected?.restaurant.name ?? "\u2014";
18107
18819
  const pickedId = selected?.restaurant.id ?? "";
18108
18820
  const alts = block ? block.restaurantPicks.filter((rp) => rp.restaurant.id !== pickedId) : [];
18109
- const myPos = orderedMeals.findIndex(
18110
- (m) => m.mealSessionIndex === activeMealSessionIndex
18111
- );
18112
18821
  const isFirstIntent = !isReview && safeIntentIdx === 0;
18113
18822
  const isLastIntent = !isReview && safeIntentIdx === blocks.length - 1;
18114
- const isFirstMeal = myPos <= 0;
18115
- const isLastMeal = myPos >= 0 && myPos === orderedMeals.length - 1;
18116
18823
  const atFinal = isReview && isLastMeal;
18117
18824
  const prevLabel = isReview ? capitalize(blocks[blocks.length - 1].intent.phrase) : !isFirstIntent ? capitalize(blocks[safeIntentIdx - 1].intent.phrase) : !isFirstMeal ? aiMealSessions[orderedMeals[myPos - 1].mealSessionIndex]?.sessionName ?? null : null;
18118
18825
  const nextLabel = isReview ? !isLastMeal ? aiMealSessions[orderedMeals[myPos + 1].mealSessionIndex]?.sessionName ?? null : null : !isLastIntent ? capitalize(blocks[safeIntentIdx + 1].intent.phrase) : "Review";
@@ -18218,8 +18925,11 @@ function IntentStepper({
18218
18925
  {
18219
18926
  total: blocks.length + 1,
18220
18927
  currentIdx: safeIntentIdx,
18221
- mealName: activeMealMeta?.sessionName ?? `Meal ${activeMeal.mealSessionIndex + 1}`,
18222
- isReview
18928
+ labels: [
18929
+ ...blocks.map((b) => capitalize(b.intent.phrase)),
18930
+ "Review"
18931
+ ],
18932
+ onStepClick: setIntentIndex
18223
18933
  }
18224
18934
  ),
18225
18935
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "8px 18px 14px" }, children: [
@@ -18496,11 +19206,114 @@ function capitalize(s) {
18496
19206
  if (!s) return s;
18497
19207
  return s.charAt(0).toUpperCase() + s.slice(1);
18498
19208
  }
19209
+ var CHEVRON_DEPTH = 10;
19210
+ var CHEVRON_HEIGHT = 34;
19211
+ var CHEVRON_RADIUS = 8;
19212
+ function chevronPath(w, h, pos) {
19213
+ const d = CHEVRON_DEPTH;
19214
+ const r = CHEVRON_RADIUS;
19215
+ if (pos === "only") {
19216
+ return `M${r},0 H${w - r} Q${w},0 ${w},${r} V${h - r} Q${w},${h} ${w - r},${h} H${r} Q0,${h} 0,${h - r} V${r} Q0,0 ${r},0Z`;
19217
+ }
19218
+ if (pos === "first") {
19219
+ return `M${r},0 H${w - d} L${w},${h / 2} L${w - d},${h} H${r} Q0,${h} 0,${h - r} V${r} Q0,0 ${r},0Z`;
19220
+ }
19221
+ if (pos === "last") {
19222
+ return `M0,0 H${w - r} Q${w},0 ${w},${r} V${h - r} Q${w},${h} ${w - r},${h} H0 L${d},${h / 2}Z`;
19223
+ }
19224
+ return `M0,0 H${w - d} L${w},${h / 2} L${w - d},${h} H0 L${d},${h / 2}Z`;
19225
+ }
19226
+ function ChevronStep({
19227
+ label,
19228
+ status,
19229
+ pos,
19230
+ onClick
19231
+ }) {
19232
+ const ref = react.useRef(null);
19233
+ const [width, setWidth] = react.useState(0);
19234
+ react.useEffect(() => {
19235
+ if (!ref.current) return;
19236
+ const ro = new ResizeObserver(([entry]) => {
19237
+ setWidth(Math.round(entry.contentRect.width));
19238
+ });
19239
+ ro.observe(ref.current);
19240
+ return () => ro.disconnect();
19241
+ }, []);
19242
+ const fill = status === "done" ? "color-mix(in srgb, var(--brand) 80%, var(--paper))" : status === "active" ? "var(--brand)" : "var(--rule)";
19243
+ const textColor = status === "done" || status === "active" ? "var(--paper)" : "var(--ink-faint)";
19244
+ const isNotched = pos === "middle" || pos === "last";
19245
+ return /* @__PURE__ */ jsxRuntime.jsxs(
19246
+ "div",
19247
+ {
19248
+ ref,
19249
+ role: onClick ? "button" : void 0,
19250
+ tabIndex: onClick ? 0 : void 0,
19251
+ onClick,
19252
+ onKeyDown: onClick ? (e) => {
19253
+ if (e.key === "Enter" || e.key === " ") {
19254
+ e.preventDefault();
19255
+ onClick();
19256
+ }
19257
+ } : void 0,
19258
+ style: {
19259
+ flex: 1,
19260
+ minWidth: 0,
19261
+ height: CHEVRON_HEIGHT,
19262
+ position: "relative",
19263
+ marginLeft: isNotched ? -1 : 0,
19264
+ cursor: onClick ? "pointer" : void 0
19265
+ },
19266
+ children: [
19267
+ width > 0 && /* @__PURE__ */ jsxRuntime.jsx(
19268
+ "svg",
19269
+ {
19270
+ width,
19271
+ height: CHEVRON_HEIGHT,
19272
+ viewBox: `0 0 ${width} ${CHEVRON_HEIGHT}`,
19273
+ style: { position: "absolute", inset: 0 },
19274
+ "aria-hidden": "true",
19275
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: chevronPath(width, CHEVRON_HEIGHT, pos), fill })
19276
+ }
19277
+ ),
19278
+ /* @__PURE__ */ jsxRuntime.jsx(
19279
+ "div",
19280
+ {
19281
+ style: {
19282
+ position: "relative",
19283
+ height: "100%",
19284
+ display: "flex",
19285
+ alignItems: "center",
19286
+ justifyContent: "center",
19287
+ paddingLeft: isNotched ? CHEVRON_DEPTH + 4 : 12,
19288
+ paddingRight: pos === "last" || pos === "only" ? 12 : CHEVRON_DEPTH + 4
19289
+ },
19290
+ children: /* @__PURE__ */ jsxRuntime.jsx(
19291
+ "span",
19292
+ {
19293
+ style: {
19294
+ fontSize: "0.72rem",
19295
+ fontWeight: status === "active" ? 600 : 400,
19296
+ color: textColor,
19297
+ whiteSpace: "nowrap",
19298
+ overflow: "hidden",
19299
+ textOverflow: "ellipsis",
19300
+ textTransform: "capitalize",
19301
+ letterSpacing: "0.01em"
19302
+ },
19303
+ children: label
19304
+ }
19305
+ )
19306
+ }
19307
+ )
19308
+ ]
19309
+ }
19310
+ );
19311
+ }
18499
19312
  function ProgressStrip({
18500
19313
  total,
18501
19314
  currentIdx,
18502
- mealName,
18503
- isReview
19315
+ labels,
19316
+ onStepClick
18504
19317
  }) {
18505
19318
  const { dismissSuggestionsOverlay } = useChatSessionContext();
18506
19319
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -18508,40 +19321,25 @@ function ProgressStrip({
18508
19321
  {
18509
19322
  style: {
18510
19323
  display: "flex",
18511
- gap: 6,
18512
19324
  alignItems: "center",
19325
+ gap: 10,
18513
19326
  padding: "16px 18px 8px"
18514
19327
  },
18515
19328
  children: [
18516
- Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(
18517
- "div",
18518
- {
18519
- style: {
18520
- height: 4,
18521
- borderRadius: 2,
18522
- flex: 1,
18523
- background: i < currentIdx ? "var(--ink)" : i === currentIdx ? "var(--brand)" : "var(--rule)",
18524
- transition: "background-color 0.2s ease"
18525
- }
18526
- },
18527
- i
18528
- )),
18529
- /* @__PURE__ */ jsxRuntime.jsxs(
18530
- "span",
18531
- {
18532
- style: {
18533
- color: "var(--ink-faint)",
18534
- fontSize: "0.78rem",
18535
- marginLeft: 8,
18536
- whiteSpace: "nowrap"
19329
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flex: 1, minWidth: 0 }, children: Array.from({ length: total }, (_, i) => {
19330
+ const status = i < currentIdx ? "done" : i === currentIdx ? "active" : "pending";
19331
+ const pos = total === 1 ? "only" : i === 0 ? "first" : i === total - 1 ? "last" : "middle";
19332
+ return /* @__PURE__ */ jsxRuntime.jsx(
19333
+ ChevronStep,
19334
+ {
19335
+ label: labels[i] ?? `Step ${i + 1}`,
19336
+ status,
19337
+ pos,
19338
+ onClick: onStepClick && i !== currentIdx ? () => onStepClick(i) : void 0
18537
19339
  },
18538
- children: [
18539
- isReview ? "Review" : `${currentIdx + 1} of ${total}`,
18540
- " \xB7 ",
18541
- mealName
18542
- ]
18543
- }
18544
- ),
19340
+ i
19341
+ );
19342
+ }) }),
18545
19343
  /* @__PURE__ */ jsxRuntime.jsx(
18546
19344
  "button",
18547
19345
  {
@@ -18553,7 +19351,6 @@ function ProgressStrip({
18553
19351
  style: {
18554
19352
  width: 36,
18555
19353
  height: 36,
18556
- marginLeft: 10,
18557
19354
  borderRadius: 999,
18558
19355
  border: "none",
18559
19356
  background: "var(--ink)",
@@ -18579,6 +19376,7 @@ function NavBar({
18579
19376
  atFinal,
18580
19377
  sending,
18581
19378
  showPrev,
19379
+ showNext = true,
18582
19380
  mealSessions,
18583
19381
  aiMealSessions,
18584
19382
  activeMealSessionIndex,
@@ -18665,7 +19463,7 @@ function NavBar({
18665
19463
  {
18666
19464
  type: "button",
18667
19465
  onClick: onNext,
18668
- disabled: sending,
19466
+ disabled: !showNext || sending,
18669
19467
  className: "chip chip-brand",
18670
19468
  style: {
18671
19469
  height: 40,
@@ -18673,6 +19471,7 @@ function NavBar({
18673
19471
  fontSize: atFinal ? "0.95rem" : "0.85rem",
18674
19472
  fontWeight: 500,
18675
19473
  justifyContent: "center",
19474
+ visibility: showNext ? "visible" : "hidden",
18676
19475
  cursor: sending ? "default" : "pointer",
18677
19476
  opacity: sending ? 0.7 : 1
18678
19477
  },
@@ -18700,10 +19499,11 @@ function TimelinePill({
18700
19499
  const activeMeal = mealSessions[activeMealPos];
18701
19500
  if (!activeMeal) return null;
18702
19501
  const activeMealMeta = aiMealSessions[activeMeal.mealSessionIndex];
18703
- const isReviewStep = currentIntentIndex === activeMeal.intentBlocks.length;
19502
+ const isEmptyMeal = activeMeal.intentBlocks.length === 0;
19503
+ const isReviewStep = !isEmptyMeal && currentIntentIndex === activeMeal.intentBlocks.length;
18704
19504
  const currentIntent = activeMeal.intentBlocks[currentIntentIndex];
18705
- if (!isReviewStep && !currentIntent) return null;
18706
- const labelText = isReviewStep ? "Review" : currentIntent.intent.phrase;
19505
+ if (!isEmptyMeal && !isReviewStep && !currentIntent) return null;
19506
+ const labelText = isEmptyMeal ? null : isReviewStep ? "Review" : currentIntent.intent.phrase;
18707
19507
  return /* @__PURE__ */ jsxRuntime.jsxs(
18708
19508
  "div",
18709
19509
  {
@@ -18735,14 +19535,23 @@ function TimelinePill({
18735
19535
  opacity: hovered ? 1 : 0.85,
18736
19536
  transition: "opacity 150ms ease"
18737
19537
  },
18738
- "aria-label": `Step ${currentIntentIndex + 1} of ${activeMeal.intentBlocks.length + 1}: ${labelText}. Click to see the full itinerary.`,
19538
+ "aria-label": isEmptyMeal ? `${activeMealMeta?.sessionName ?? "Meal"}. Click to see the full itinerary.` : `Step ${currentIntentIndex + 1} of ${activeMeal.intentBlocks.length + 1}: ${labelText}. Click to see the full itinerary.`,
18739
19539
  "aria-expanded": hovered,
18740
19540
  children: [
18741
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "var(--ink-faint)" }, children: [
18742
- activeMealMeta?.sessionName ?? `Meal ${activeMeal.mealSessionIndex + 1}`,
18743
- " \xB7 "
18744
- ] }),
18745
- /* @__PURE__ */ jsxRuntime.jsx(
19541
+ /* @__PURE__ */ jsxRuntime.jsxs(
19542
+ "span",
19543
+ {
19544
+ style: {
19545
+ color: isEmptyMeal ? "var(--ink)" : "var(--ink-faint)",
19546
+ fontWeight: isEmptyMeal ? 500 : 400
19547
+ },
19548
+ children: [
19549
+ activeMealMeta?.sessionName ?? `Meal ${activeMeal.mealSessionIndex + 1}`,
19550
+ !isEmptyMeal && " \xB7 "
19551
+ ]
19552
+ }
19553
+ ),
19554
+ !isEmptyMeal && /* @__PURE__ */ jsxRuntime.jsx(
18746
19555
  "span",
18747
19556
  {
18748
19557
  style: {
@@ -19222,7 +20031,23 @@ function EmptyIntentState({ message }) {
19222
20031
  }
19223
20032
  );
19224
20033
  }
19225
- function EmptyStepperState() {
20034
+ var MISSING_FIELD_LABELS = {
20035
+ guestCount: "guest count",
20036
+ sessionDate: "date",
20037
+ eventTime: "time"
20038
+ };
20039
+ function formatMissingFieldList(fields) {
20040
+ const labels = fields.map((f) => MISSING_FIELD_LABELS[f] ?? f);
20041
+ if (labels.length === 0) return "";
20042
+ if (labels.length === 1) return labels[0];
20043
+ if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
20044
+ return `${labels.slice(0, -1).join(", ")}, and ${labels[labels.length - 1]}`;
20045
+ }
20046
+ function EmptyStepperState({
20047
+ missingFields
20048
+ }) {
20049
+ const hasMissing = !!missingFields && missingFields.length > 0;
20050
+ const label = hasMissing ? formatMissingFieldList(missingFields) : null;
19226
20051
  return /* @__PURE__ */ jsxRuntime.jsx(
19227
20052
  "div",
19228
20053
  {
@@ -19234,7 +20059,11 @@ function EmptyStepperState() {
19234
20059
  textAlign: "center",
19235
20060
  color: "var(--ink-faint)"
19236
20061
  },
19237
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "display-italic", style: { fontSize: "0.95rem" }, children: "No items yet for this meal \u2014 keep chatting on the right." })
20062
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "display-italic", style: { fontSize: "0.95rem" }, children: hasMissing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20063
+ "Tell me the ",
20064
+ label,
20065
+ " for this meal and I'll suggest a menu."
20066
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: "No items yet for this meal \u2014 keep chatting on the right." }) })
19238
20067
  }
19239
20068
  );
19240
20069
  }
@@ -26049,17 +26878,8 @@ function useCateringTutorial({
26049
26878
  const [tutorialStep, setTutorialStep] = react.useState(null);
26050
26879
  const [tutorialPhase, setTutorialPhase] = react.useState("navigation");
26051
26880
  react.useEffect(() => {
26052
- const tutorialCompleted = localStorage.getItem(TUTORIAL_STORAGE_KEY);
26053
- if (tutorialCompleted) {
26054
- setTutorialPhase("completed");
26055
- setTutorialStep(null);
26056
- } else if (mealSessions.length === 1 && mealSessions[0].orderItems.length === 0) {
26057
- setTutorialPhase("navigation");
26058
- setTutorialStep(0);
26059
- } else {
26060
- setTutorialPhase("completed");
26061
- setTutorialStep(null);
26062
- }
26881
+ setTutorialPhase("completed");
26882
+ setTutorialStep(null);
26063
26883
  }, []);
26064
26884
  const getTutorialSteps = react.useCallback(() => {
26065
26885
  switch (tutorialPhase) {
@@ -26481,6 +27301,7 @@ function CateringOrderBuilder() {
26481
27301
  const [removeItemIndex, setRemoveItemIndex] = react.useState(null);
26482
27302
  const [isClearAllConfirmOpen, setIsClearAllConfirmOpen] = react.useState(false);
26483
27303
  const [isMobileCartMenuOpen, setIsMobileCartMenuOpen] = react.useState(false);
27304
+ const [isMobileChatMenuOpen, setIsMobileChatMenuOpen] = react.useState(false);
26484
27305
  const [isDesktopCartMenuOpen, setIsDesktopCartMenuOpen] = react.useState(false);
26485
27306
  const [isAIMobileUnavailableOpen, setIsAIMobileUnavailableOpen] = react.useState(false);
26486
27307
  const storage = useStorage();
@@ -26500,11 +27321,20 @@ function CateringOrderBuilder() {
26500
27321
  "chat"
26501
27322
  );
26502
27323
  react.useEffect(() => {
26503
- if (!isMobileAIChatOpen) setMobileChatView("chat");
27324
+ if (!isMobileAIChatOpen) {
27325
+ setMobileChatView("chat");
27326
+ setMobileAddressPillEditing(false);
27327
+ setIsMobileChatMenuOpen(false);
27328
+ }
26504
27329
  }, [isMobileAIChatOpen]);
26505
27330
  const [mobileAIInput, setMobileAIInput] = react.useState("");
26506
27331
  const [mobileAIInputHeight, setMobileAIInputHeight] = react.useState(44);
26507
27332
  const mobileAIInputRef = react.useRef(null);
27333
+ const [mobileGateError, setMobileGateError] = react.useState(null);
27334
+ const mobileChatScrollRef = react.useRef(null);
27335
+ const [mobileAddressPillEditing, setMobileAddressPillEditing] = react.useState(false);
27336
+ const buildChatAddress = useChatAddressFromContext();
27337
+ const hasChatAddress = !!(contactInfo?.addressLine1 && contactInfo.latitude !== void 0 && contactInfo.longitude !== void 0);
26508
27338
  const handleMobileAIInput = () => {
26509
27339
  const el = mobileAIInputRef.current;
26510
27340
  if (!el) return;
@@ -26523,6 +27353,7 @@ function CateringOrderBuilder() {
26523
27353
  }
26524
27354
  };
26525
27355
  const [isMobileSearchOpen, setIsMobileSearchOpen] = react.useState(false);
27356
+ const [isMobileSearchActive, setIsMobileSearchActive] = react.useState(false);
26526
27357
  const [mobileSearchState, setMobileSearchState] = react.useState({ mode: "list", query: "" });
26527
27358
  const mobileSearchSetterRef = react.useRef(null);
26528
27359
  const mobileSearchInputRef = react.useRef(null);
@@ -26530,12 +27361,17 @@ function CateringOrderBuilder() {
26530
27361
  const suppressMobileSearchScrollCloseRef = react.useRef(false);
26531
27362
  const [mobileSearchCaretVisible, setMobileSearchCaretVisible] = react.useState(false);
26532
27363
  const [keyboardOffset, setKeyboardOffset] = react.useState(0);
27364
+ const [vvOffsetTop, setVvOffsetTop] = react.useState(0);
27365
+ const [vvHeight, setVvHeight] = react.useState(null);
26533
27366
  react.useEffect(() => {
26534
27367
  if (typeof window === "undefined" || !window.visualViewport) return;
26535
27368
  const vv = window.visualViewport;
26536
27369
  const update = () => {
26537
- const offset = Math.max(0, window.innerHeight - vv.height - vv.offsetTop);
26538
- setKeyboardOffset(offset);
27370
+ setKeyboardOffset(
27371
+ Math.max(0, window.innerHeight - vv.height - vv.offsetTop)
27372
+ );
27373
+ setVvOffsetTop(vv.offsetTop);
27374
+ setVvHeight(vv.height);
26539
27375
  };
26540
27376
  update();
26541
27377
  vv.addEventListener("resize", update);
@@ -26545,6 +27381,48 @@ function CateringOrderBuilder() {
26545
27381
  vv.removeEventListener("scroll", update);
26546
27382
  };
26547
27383
  }, []);
27384
+ react.useEffect(() => {
27385
+ if (typeof document === "undefined") return;
27386
+ if (!isMobileAIChatOpen) return;
27387
+ const html = document.documentElement;
27388
+ const body = document.body;
27389
+ const prev = {
27390
+ htmlOverflow: html.style.overflow,
27391
+ bodyOverflow: body.style.overflow,
27392
+ bodyOverscroll: body.style.overscrollBehavior
27393
+ };
27394
+ html.style.overflow = "hidden";
27395
+ body.style.overflow = "hidden";
27396
+ body.style.overscrollBehavior = "none";
27397
+ const preventTouch = (e) => {
27398
+ const target = e.target;
27399
+ if (target?.closest?.("[data-allow-touch]")) return;
27400
+ if (target instanceof HTMLTextAreaElement && target.scrollHeight > target.clientHeight + 1) {
27401
+ return;
27402
+ }
27403
+ e.preventDefault();
27404
+ };
27405
+ document.addEventListener("touchmove", preventTouch, { passive: false });
27406
+ return () => {
27407
+ html.style.overflow = prev.htmlOverflow;
27408
+ body.style.overflow = prev.bodyOverflow;
27409
+ body.style.overscrollBehavior = prev.bodyOverscroll;
27410
+ document.removeEventListener("touchmove", preventTouch);
27411
+ };
27412
+ }, [isMobileAIChatOpen]);
27413
+ const [chatInputFocused, setChatInputFocused] = react.useState(false);
27414
+ react.useEffect(() => {
27415
+ if (!isMobileAIChatOpen) return;
27416
+ if (!chatInputFocused) return;
27417
+ const el = mobileChatScrollRef.current;
27418
+ if (!el) return;
27419
+ el.scrollTop = el.scrollHeight;
27420
+ const id = setTimeout(() => {
27421
+ const node = mobileChatScrollRef.current;
27422
+ if (node) node.scrollTop = node.scrollHeight;
27423
+ }, 350);
27424
+ return () => clearTimeout(id);
27425
+ }, [isMobileAIChatOpen, chatInputFocused]);
26548
27426
  react.useEffect(() => {
26549
27427
  if (!isMobileSearchOpen) {
26550
27428
  setMobileSearchCaretVisible(false);
@@ -26609,6 +27487,7 @@ function CateringOrderBuilder() {
26609
27487
  mobileSearchSetterRef.current?.(value);
26610
27488
  };
26611
27489
  const openMobileSearch = () => {
27490
+ setIsMobileSearchActive(true);
26612
27491
  mobileSearchInputRef.current?.focus({ preventScroll: true });
26613
27492
  setTimeout(() => {
26614
27493
  setIsMobileSearchOpen(true);
@@ -26620,8 +27499,46 @@ function CateringOrderBuilder() {
26620
27499
  mobileSearchInputRef.current?.blur();
26621
27500
  setTimeout(() => {
26622
27501
  setIsMobileSearchOpen(false);
27502
+ setIsMobileSearchActive(false);
26623
27503
  }, 250);
26624
27504
  };
27505
+ const handleMobileAddressPick = (place) => {
27506
+ const parsed = parsePlaceResult(place);
27507
+ if (!parsed) return;
27508
+ setContactInfo({
27509
+ ...contactInfo ?? {},
27510
+ ...parsed.contactInfo
27511
+ });
27512
+ setMobileAddressPillEditing(false);
27513
+ requestAnimationFrame(() => {
27514
+ mobileAIInputRef.current?.focus();
27515
+ });
27516
+ };
27517
+ const handleMobileAddressClear = () => {
27518
+ setContactInfo({
27519
+ ...contactInfo ?? {},
27520
+ addressLine1: "",
27521
+ city: "",
27522
+ zipcode: "",
27523
+ latitude: void 0,
27524
+ longitude: void 0
27525
+ });
27526
+ setMobileAddressPillEditing(false);
27527
+ };
27528
+ const handleMobileGateSubmit = () => {
27529
+ const code = mobileAIInput.trim();
27530
+ if (!code) return;
27531
+ if (tryUnlockAiBeta(code)) {
27532
+ setMobileAIInput("");
27533
+ resetMobileAIInputHeight();
27534
+ setMobileGateError(null);
27535
+ return;
27536
+ }
27537
+ setMobileGateError("Incorrect code. Try again.");
27538
+ setMobileAIInput("");
27539
+ resetMobileAIInputHeight();
27540
+ mobileAIInputRef.current?.focus();
27541
+ };
26625
27542
  const closeMobileAIChat = () => {
26626
27543
  mobileAIInputRef.current?.blur();
26627
27544
  setTimeout(() => {
@@ -26657,7 +27574,6 @@ function CateringOrderBuilder() {
26657
27574
  handleTutorialNext,
26658
27575
  handleSkipTutorial,
26659
27576
  triggerSessionCreated,
26660
- resetTutorial,
26661
27577
  getTutorialSteps
26662
27578
  } = useCateringTutorial({
26663
27579
  mealSessions,
@@ -26719,6 +27635,9 @@ function CateringOrderBuilder() {
26719
27635
  setExpandedItemId(item.id);
26720
27636
  };
26721
27637
  const { stickyTopOffset = 0, publishableKey, aiEnabled = false } = useCateringConfig();
27638
+ const { unlocked: aiBetaUnlocked, tryUnlock: tryUnlockAiBeta } = useBetaUnlock();
27639
+ const mobileAddressGateActive = isMobileAIChatOpen && aiBetaUnlocked && !hasChatAddress;
27640
+ const mobileAddressEditorOpen = mobileAddressGateActive || aiBetaUnlocked && mobileAddressPillEditing;
26722
27641
  const effectiveRightPanelTab = aiEnabled ? rightPanelTab : "cart";
26723
27642
  const [overlayVisible, setOverlayVisible] = react.useState(false);
26724
27643
  const basketColumnRef = react.useRef(null);
@@ -27500,7 +28419,7 @@ function CateringOrderBuilder() {
27500
28419
  }))
27501
28420
  }
27502
28421
  ),
27503
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row px-2", children: [
28422
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col md:flex-row px-2${isMobileAIChatOpen ? " hidden md:flex" : ""}`, children: [
27504
28423
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", children: [
27505
28424
  /* @__PURE__ */ jsxRuntime.jsx(
27506
28425
  SessionPickerDropdown,
@@ -27702,40 +28621,6 @@ function CateringOrderBuilder() {
27702
28621
  ]
27703
28622
  }
27704
28623
  ),
27705
- /* @__PURE__ */ jsxRuntime.jsxs(
27706
- "button",
27707
- {
27708
- onClick: () => {
27709
- setIsDesktopCartMenuOpen(false);
27710
- resetTutorial();
27711
- setNavMode("dates");
27712
- setSelectedDayDate(null);
27713
- },
27714
- 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",
27715
- children: [
27716
- /* @__PURE__ */ jsxRuntime.jsx(
27717
- "svg",
27718
- {
27719
- xmlns: "http://www.w3.org/2000/svg",
27720
- className: "h-4 w-4",
27721
- fill: "none",
27722
- viewBox: "0 0 24 24",
27723
- stroke: "currentColor",
27724
- children: /* @__PURE__ */ jsxRuntime.jsx(
27725
- "path",
27726
- {
27727
- strokeLinecap: "round",
27728
- strokeLinejoin: "round",
27729
- strokeWidth: 2,
27730
- d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
27731
- }
27732
- )
27733
- }
27734
- ),
27735
- "Tutorial"
27736
- ]
27737
- }
27738
- ),
27739
28624
  /* @__PURE__ */ jsxRuntime.jsxs(
27740
28625
  "button",
27741
28626
  {
@@ -27770,45 +28655,143 @@ function CateringOrderBuilder() {
27770
28655
  }
27771
28656
  )
27772
28657
  ] }),
27773
- isMobileAIChatOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-40 md:hidden swift-chat-design", children: [
27774
- /* @__PURE__ */ jsxRuntime.jsx(
27775
- "div",
27776
- {
27777
- className: "absolute inset-0 bg-black/20 backdrop-blur-sm",
27778
- onClick: closeMobileAIChat,
27779
- "aria-hidden": "true"
27780
- }
27781
- ),
27782
- mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(MobileResetChatButton, {}),
27783
- /* @__PURE__ */ jsxRuntime.jsx(
27784
- "div",
27785
- {
27786
- className: "absolute inset-x-0 top-0 overflow-y-auto bg-white",
27787
- style: { bottom: keyboardOffset + 64 },
27788
- children: mobileChatView === "results" ? /* @__PURE__ */ jsxRuntime.jsx(MobileResultsView, { onBack: () => setMobileChatView("chat") }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-14 pb-4", children: /* @__PURE__ */ jsxRuntime.jsx(ChatMessagesView, {}) })
27789
- }
27790
- )
27791
- ] }),
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
28683
+ },
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,
28696
+ {
28697
+ 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
+ ]
28714
+ }
28715
+ ),
28716
+ mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(
28717
+ "button",
28718
+ {
28719
+ 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" })
28725
+ }
28726
+ ),
28727
+ /* @__PURE__ */ jsxRuntime.jsx(
28728
+ "div",
28729
+ {
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
+ )
28754
+ }
28755
+ ),
28756
+ aiBetaUnlocked && mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(MobileChatFloatingChips, { bottomOffset: 64 + 8 })
28757
+ ]
28758
+ },
28759
+ "chat-surface"
28760
+ ) }) })
28761
+ ]
28762
+ }
28763
+ ),
27792
28764
  /* @__PURE__ */ jsxRuntime.jsxs(
27793
28765
  "div",
27794
28766
  {
27795
- className: `fixed left-0 right-0 md:hidden z-50 ${keyboardOffset > 100 && !isMobileAIChatOpen ? "hidden" : ""}`,
28767
+ className: `fixed left-0 right-0 md:hidden z-50 ${mobileAddressEditorOpen || keyboardOffset > 100 && !isMobileAIChatOpen && !isMobileSearchActive ? "hidden" : ""}`,
27796
28768
  style: { bottom: keyboardOffset },
28769
+ "aria-hidden": mobileAddressEditorOpen,
27797
28770
  children: [
27798
28771
  /* @__PURE__ */ jsxRuntime.jsx(
27799
28772
  "div",
27800
28773
  {
27801
28774
  ref: mobileBarRowRef,
27802
- className: "relative mx-auto max-w-[325px] px-1 pt-1 pb-2",
28775
+ className: "relative px-3 pt-1 pb-2",
27803
28776
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-end gap-2", children: [
27804
28777
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
27805
28778
  /* @__PURE__ */ jsxRuntime.jsx(
27806
28779
  "button",
27807
28780
  {
27808
- onClick: isMobileAIChatOpen ? closeMobileAIChat : () => setIsMobileCartMenuOpen((v) => !v),
28781
+ onMouseDown: (e) => {
28782
+ if (chatInputFocused) e.preventDefault();
28783
+ },
28784
+ onClick: isMobileAIChatOpen ? chatInputFocused ? () => mobileAIInputRef.current?.blur() : () => setIsMobileChatMenuOpen((v) => !v) : () => setIsMobileCartMenuOpen((v) => !v),
27809
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",
27810
- title: isMobileAIChatOpen ? "Close AI chat" : "More actions",
27811
- children: isMobileAIChatOpen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-4 w-4" })
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)
27812
28795
  }
27813
28796
  ),
27814
28797
  isMobileCartMenuOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -27853,40 +28836,6 @@ function CateringOrderBuilder() {
27853
28836
  ]
27854
28837
  }
27855
28838
  ),
27856
- /* @__PURE__ */ jsxRuntime.jsxs(
27857
- "button",
27858
- {
27859
- onClick: () => {
27860
- setIsMobileCartMenuOpen(false);
27861
- resetTutorial();
27862
- setNavMode("dates");
27863
- setSelectedDayDate(null);
27864
- },
27865
- 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",
27866
- children: [
27867
- /* @__PURE__ */ jsxRuntime.jsx(
27868
- "svg",
27869
- {
27870
- xmlns: "http://www.w3.org/2000/svg",
27871
- className: "h-4 w-4",
27872
- fill: "none",
27873
- viewBox: "0 0 24 24",
27874
- stroke: "currentColor",
27875
- children: /* @__PURE__ */ jsxRuntime.jsx(
27876
- "path",
27877
- {
27878
- strokeLinecap: "round",
27879
- strokeLinejoin: "round",
27880
- strokeWidth: 2,
27881
- d: "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
27882
- }
27883
- )
27884
- }
27885
- ),
27886
- "Tutorial"
27887
- ]
27888
- }
27889
- ),
27890
28839
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-3 border-t border-base-200" }),
27891
28840
  /* @__PURE__ */ jsxRuntime.jsxs(
27892
28841
  "button",
@@ -27910,15 +28859,21 @@ function CateringOrderBuilder() {
27910
28859
  "button",
27911
28860
  {
27912
28861
  type: "button",
27913
- onClick: () => setIsAIMobileUnavailableOpen(true),
27914
- title: "Coming Soon",
28862
+ onClick: () => {
28863
+ if (aiEnabled) {
28864
+ setIsMobileAIChatOpen(true);
28865
+ } else {
28866
+ setIsAIMobileUnavailableOpen(true);
28867
+ }
28868
+ },
28869
+ title: aiEnabled ? void 0 : "Coming Soon",
27915
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"}`,
27916
28871
  "aria-hidden": isMobileSearchOpen || isMobileAIChatOpen,
27917
28872
  tabIndex: isMobileSearchOpen || isMobileAIChatOpen ? -1 : 0,
27918
28873
  children: [
27919
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4 text-gray-400" }),
27920
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-gray-400", children: "Ask AI" }),
27921
- /* @__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: "Coming Soon" })
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" })
27922
28877
  ]
27923
28878
  }
27924
28879
  ),
@@ -27933,12 +28888,16 @@ function CateringOrderBuilder() {
27933
28888
  "input",
27934
28889
  {
27935
28890
  ref: mobileSearchInputRef,
27936
- type: "text",
28891
+ type: "search",
27937
28892
  value: mobileSearchState.query,
27938
28893
  onChange: (e) => handleMobileSearchInputChange(e.target.value),
27939
28894
  placeholder: "Search Swift Food",
27940
28895
  tabIndex: isMobileSearchOpen ? 0 : -1,
27941
- className: `h-full w-full rounded-full bg-transparent pl-9 pr-3 text-base placeholder:text-xs focus:outline-none ${mobileSearchCaretVisible ? "" : "caret-transparent"}`
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"}`
27942
28901
  }
27943
28902
  )
27944
28903
  ]
@@ -27957,12 +28916,22 @@ function CateringOrderBuilder() {
27957
28916
  {
27958
28917
  inputRef: mobileAIInputRef,
27959
28918
  value: mobileAIInput,
27960
- onChange: setMobileAIInput,
28919
+ onChange: (v) => {
28920
+ setMobileAIInput(v);
28921
+ if (mobileGateError) setMobileGateError(null);
28922
+ },
27961
28923
  onInputResize: handleMobileAIInput,
27962
28924
  onResetHeight: resetMobileAIInputHeight,
27963
28925
  tabbable: isMobileAIChatOpen,
27964
28926
  isOpen: isMobileAIChatOpen,
27965
- onFocus: () => setMobileChatView("chat")
28927
+ onFocus: () => {
28928
+ setMobileChatView("chat");
28929
+ setChatInputFocused(true);
28930
+ },
28931
+ onBlur: () => setChatInputFocused(false),
28932
+ gateMode: !aiBetaUnlocked,
28933
+ onGateSubmit: handleMobileGateSubmit,
28934
+ buildAddress: buildChatAddress
27966
28935
  }
27967
28936
  )
27968
28937
  }
@@ -27972,7 +28941,7 @@ function CateringOrderBuilder() {
27972
28941
  "button",
27973
28942
  {
27974
28943
  onClick: isMobileSearchOpen ? closeMobileSearch : openMobileSearch,
27975
- 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 opacity-0 shadow-none" : "w-11 border-base-200 opacity-100 shadow-sm"}`,
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"}`,
27976
28945
  title: isMobileSearchOpen ? "Close search" : "Search",
27977
28946
  "aria-label": isMobileSearchOpen ? "Close search" : "Open search",
27978
28947
  "aria-hidden": isMobileAIChatOpen,
@@ -27984,7 +28953,7 @@ function CateringOrderBuilder() {
27984
28953
  ] })
27985
28954
  }
27986
28955
  ),
27987
- mealSessions.some((s) => s.orderItems.length > 0) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 bg-primary", children: /* @__PURE__ */ jsxRuntime.jsxs(
28956
+ mealSessions.some((s) => s.orderItems.length > 0) && !isMobileAIChatOpen && !isMobileSearchOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 bg-primary", children: /* @__PURE__ */ jsxRuntime.jsxs(
27988
28957
  "button",
27989
28958
  {
27990
28959
  onClick: () => setIsViewOrderOpen(true),
@@ -28146,13 +29115,12 @@ function CateringOrderBuilder() {
28146
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" }) }),
28147
29116
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
28148
29117
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-bold text-gray-900", children: "Plan your menu with AI" }),
28149
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "Not yet available on mobile" })
29118
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "Coming soon" })
28150
29119
  ] })
28151
29120
  ] }),
28152
29121
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 space-y-2 text-sm text-gray-600", children: [
28153
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." }),
28154
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "It's ready to use on the web. Open this page on a desktop or laptop to try it." }),
28155
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "Mobile support is coming soon." })
29123
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "We're putting the finishing touches on it. Check back soon." })
28156
29124
  ] }),
28157
29125
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: /* @__PURE__ */ jsxRuntime.jsx(
28158
29126
  "button",
@@ -28283,35 +29251,136 @@ function CateringOrderBuilder() {
28283
29251
  }
28284
29252
  );
28285
29253
  }
28286
- function MobileResetChatButton() {
29254
+ function MobileChatActionsMenu({
29255
+ onClose
29256
+ }) {
28287
29257
  const {
28288
29258
  chat: { bootstrapping },
28289
29259
  dismissSuggestionsOverlay,
28290
29260
  resetEverything
28291
29261
  } = useChatSessionContext();
28292
- return /* @__PURE__ */ jsxRuntime.jsxs(
28293
- "button",
29262
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29263
+ /* @__PURE__ */ jsxRuntime.jsx(
29264
+ "div",
29265
+ {
29266
+ className: "fixed inset-0 z-40",
29267
+ onClick: onClose,
29268
+ "aria-hidden": "true"
29269
+ }
29270
+ ),
29271
+ /* @__PURE__ */ jsxRuntime.jsx("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: /* @__PURE__ */ jsxRuntime.jsxs(
29272
+ "button",
29273
+ {
29274
+ type: "button",
29275
+ onClick: () => {
29276
+ if (typeof window !== "undefined" && !window.confirm(
29277
+ "Start a new chat? Your conversation history will be cleared."
29278
+ )) {
29279
+ return;
29280
+ }
29281
+ onClose();
29282
+ dismissSuggestionsOverlay();
29283
+ window.setTimeout(() => {
29284
+ void resetEverything();
29285
+ }, 220);
29286
+ },
29287
+ disabled: bootstrapping,
29288
+ 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",
29289
+ children: [
29290
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "h-4 w-4" }),
29291
+ "Reset chat"
29292
+ ]
29293
+ }
29294
+ ) })
29295
+ ] });
29296
+ }
29297
+ function MobileChatThread({
29298
+ hasAddressTopPill
29299
+ }) {
29300
+ const { hasResults } = useChatSessionContext();
29301
+ const suggestionsH = hasResults ? 36 + 6 : 0;
29302
+ const paddingBottom = suggestionsH > 0 ? suggestionsH + 24 : 16;
29303
+ const paddingTop = hasAddressTopPill ? 68 : 56;
29304
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4", style: { paddingTop, paddingBottom }, children: /* @__PURE__ */ jsxRuntime.jsx(ChatMessagesView, {}) });
29305
+ }
29306
+ function MobileChatFloatingChips({ bottomOffset }) {
29307
+ const {
29308
+ hasResults,
29309
+ onViewResults,
29310
+ feedbackTarget,
29311
+ feedbackRating,
29312
+ setFeedbackRating,
29313
+ cancelFeedback
29314
+ } = useChatSessionContext();
29315
+ const [feedbackHover, setFeedbackHover] = react.useState(0);
29316
+ const inFeedbackMode = feedbackTarget !== null;
29317
+ react.useEffect(() => {
29318
+ if (!feedbackTarget) setFeedbackHover(0);
29319
+ }, [feedbackTarget]);
29320
+ if (!hasResults && !inFeedbackMode) return null;
29321
+ const RATING_LABELS = ["Terrible", "Poor", "Okay", "Good", "Great"];
29322
+ const activeStar = feedbackHover || feedbackRating;
29323
+ return /* @__PURE__ */ jsxRuntime.jsx(
29324
+ "div",
28294
29325
  {
28295
- type: "button",
28296
- onClick: () => {
28297
- if (typeof window !== "undefined" && !window.confirm(
28298
- "Start a new chat? Your conversation history will be cleared."
28299
- )) {
28300
- return;
28301
- }
28302
- dismissSuggestionsOverlay();
28303
- window.setTimeout(() => {
28304
- void resetEverything();
28305
- }, 220);
28306
- },
28307
- disabled: bootstrapping,
28308
- className: "absolute top-3 right-3 z-10 flex items-center gap-1 px-2.5 py-1.5 rounded-full bg-white/90 backdrop-blur-sm text-gray-700 shadow-sm border border-base-200 text-xs font-medium disabled:opacity-50",
28309
- title: "Start a new chat",
28310
- "aria-label": "Start a new chat",
28311
- children: [
28312
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "w-3.5 h-3.5" }),
28313
- "New chat"
28314
- ]
29326
+ style: { bottom: bottomOffset },
29327
+ className: "pointer-events-none absolute inset-x-0 z-10 flex flex-col items-center gap-1.5 px-4 touch-none",
29328
+ children: /* @__PURE__ */ jsxRuntime.jsx(react$1.AnimatePresence, { mode: "wait", children: inFeedbackMode ? /* @__PURE__ */ jsxRuntime.jsxs(
29329
+ react$1.motion.div,
29330
+ {
29331
+ initial: { opacity: 0, scale: 0.95 },
29332
+ animate: { opacity: 1, scale: 1 },
29333
+ exit: { opacity: 0, scale: 0.95 },
29334
+ transition: { duration: 0.15, ease: "easeOut" },
29335
+ className: "pointer-events-auto inline-flex items-center gap-2 px-3.5 py-2.5 rounded-full bg-black/50 backdrop-blur-sm shadow-sm",
29336
+ children: [
29337
+ /* @__PURE__ */ jsxRuntime.jsx(
29338
+ "button",
29339
+ {
29340
+ type: "button",
29341
+ onClick: cancelFeedback,
29342
+ className: "p-0.5 rounded text-white/60 hover:text-white transition-colors",
29343
+ "aria-label": "Cancel feedback",
29344
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
29345
+ }
29346
+ ),
29347
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-white/70", children: "Rate:" }),
29348
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsxRuntime.jsx(
29349
+ "button",
29350
+ {
29351
+ type: "button",
29352
+ "aria-label": `${n} star${n > 1 ? "s" : ""}`,
29353
+ onMouseEnter: () => setFeedbackHover(n),
29354
+ onMouseLeave: () => setFeedbackHover(0),
29355
+ onClick: () => setFeedbackRating(n),
29356
+ className: "p-1 transition-transform hover:scale-110",
29357
+ children: /* @__PURE__ */ jsxRuntime.jsx(
29358
+ lucideReact.Star,
29359
+ {
29360
+ className: `h-7 w-7 transition-colors ${n <= activeStar ? "fill-amber-400 text-amber-400" : "fill-transparent text-white/40"}`
29361
+ }
29362
+ )
29363
+ },
29364
+ n
29365
+ )) }),
29366
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-white/80 min-w-[3.5rem]", children: activeStar > 0 ? RATING_LABELS[activeStar - 1] : "" })
29367
+ ]
29368
+ },
29369
+ "feedback-pill"
29370
+ ) : hasResults ? /* @__PURE__ */ jsxRuntime.jsx(
29371
+ react$1.motion.button,
29372
+ {
29373
+ type: "button",
29374
+ onClick: onViewResults,
29375
+ initial: { opacity: 0, scale: 0.95 },
29376
+ animate: { opacity: 1, scale: 1 },
29377
+ exit: { opacity: 0, scale: 0.95 },
29378
+ transition: { duration: 0.15, ease: "easeOut" },
29379
+ className: "pointer-events-auto flex items-center justify-center gap-1.5 px-4 py-2 rounded-full bg-primary text-white text-sm font-semibold shadow-md whitespace-nowrap",
29380
+ children: "\u2728 View menu suggestions"
29381
+ },
29382
+ "view-suggestions"
29383
+ ) : null })
28315
29384
  }
28316
29385
  );
28317
29386
  }
@@ -28323,17 +29392,56 @@ function MobileAIInput({
28323
29392
  onResetHeight,
28324
29393
  tabbable,
28325
29394
  isOpen,
28326
- onFocus
29395
+ onFocus,
29396
+ onBlur,
29397
+ gateMode = false,
29398
+ onGateSubmit,
29399
+ buildAddress,
29400
+ submitBlocked = false
28327
29401
  }) {
28328
29402
  const {
28329
- chat: { sending, bootstrapping, sessionId, sendText }
29403
+ chat: { sending, bootstrapping, sessionId, sendText, submitFeedback },
29404
+ feedbackTarget,
29405
+ feedbackRating,
29406
+ cancelFeedback
28330
29407
  } = useChatSessionContext();
29408
+ const inFeedbackMode = feedbackTarget !== null;
29409
+ const [feedbackState, setFeedbackState] = react.useState("idle");
29410
+ react.useEffect(() => {
29411
+ if (!feedbackTarget) setFeedbackState("idle");
29412
+ }, [feedbackTarget]);
29413
+ const chatDisabled = sending || bootstrapping || !sessionId;
29414
+ const inputDisabled = inFeedbackMode ? feedbackState === "sending" : bootstrapping || !sessionId;
29415
+ const sendDisabled = inFeedbackMode ? feedbackState === "sending" : submitBlocked || (gateMode ? !value.trim() : chatDisabled || !value.trim());
28331
29416
  function submit() {
28332
- if (sending || bootstrapping || !sessionId || !value.trim()) return;
28333
- void sendText(value);
29417
+ if (inFeedbackMode) {
29418
+ void handleFeedbackSubmit();
29419
+ return;
29420
+ }
29421
+ if (!value.trim()) return;
29422
+ if (submitBlocked) return;
29423
+ if (gateMode) {
29424
+ onGateSubmit?.();
29425
+ return;
29426
+ }
29427
+ if (chatDisabled) return;
29428
+ void sendText(value, buildAddress?.() ?? void 0);
28334
29429
  onChange("");
28335
29430
  onResetHeight();
28336
29431
  }
29432
+ async function handleFeedbackSubmit() {
29433
+ if (!feedbackTarget) return;
29434
+ setFeedbackState("sending");
29435
+ const ok = await submitFeedback(feedbackRating || 1, value || void 0, feedbackTarget.eventId);
29436
+ if (ok) {
29437
+ setFeedbackState("done");
29438
+ onChange("");
29439
+ onResetHeight();
29440
+ window.setTimeout(() => cancelFeedback(), 900);
29441
+ } else {
29442
+ setFeedbackState("error");
29443
+ }
29444
+ }
28337
29445
  function handleKeyDown(e) {
28338
29446
  if (e.key === "Enter" && !e.shiftKey) {
28339
29447
  e.preventDefault();
@@ -28351,10 +29459,15 @@ function MobileAIInput({
28351
29459
  onInput: onInputResize,
28352
29460
  onKeyDown: handleKeyDown,
28353
29461
  onFocus,
28354
- placeholder: "How can I help?",
29462
+ onBlur,
29463
+ placeholder: inFeedbackMode ? "Add a note (optional)" : gateMode ? "Enter access code\u2026" : "How can I help?",
28355
29464
  tabIndex: tabbable ? 0 : -1,
28356
- disabled: sending || bootstrapping || !sessionId,
28357
- className: "block w-full resize-none bg-transparent pl-4 pr-12 py-3 text-sm leading-5 focus:outline-none overflow-hidden disabled:opacity-50"
29465
+ disabled: gateMode ? false : inputDisabled,
29466
+ autoComplete: "off",
29467
+ autoCorrect: "off",
29468
+ autoCapitalize: "off",
29469
+ spellCheck: false,
29470
+ className: "block w-full resize-none bg-transparent pl-4 pr-12 py-3 text-base leading-5 focus:outline-none overflow-hidden disabled:opacity-50 touch-auto"
28358
29471
  }
28359
29472
  ),
28360
29473
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -28363,7 +29476,7 @@ function MobileAIInput({
28363
29476
  type: "button",
28364
29477
  onClick: submit,
28365
29478
  tabIndex: tabbable ? 0 : -1,
28366
- disabled: sending || bootstrapping || !sessionId || !value.trim(),
29479
+ disabled: sendDisabled,
28367
29480
  "aria-hidden": !isOpen,
28368
29481
  className: `absolute right-1.5 bottom-1.5 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-primary text-white transition-all duration-[250ms] ease-out hover:bg-primary/90 disabled:opacity-50 ${isOpen ? "scale-100 opacity-100" : "pointer-events-none scale-0 opacity-0"}`,
28369
29482
  title: "Send",