@swift-food-services/catering-widget 0.2.0-beta.3 → 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
  );
@@ -12571,7 +12742,8 @@ function useChatSession(options = {}) {
12571
12742
  { type: "text", text: entry.text },
12572
12743
  ...entry.role === "bot" && entry.chips && entry.chips.length > 0 ? [{ type: "chips", chips: entry.chips }] : [],
12573
12744
  ...restoredParts
12574
- ]
12745
+ ],
12746
+ ...entry.role === "bot" && entry.eventId ? { eventId: entry.eventId } : {}
12575
12747
  };
12576
12748
  }
12577
12749
  );
@@ -12933,6 +13105,19 @@ function useChatSession(options = {}) {
12933
13105
  const getTaxonomyValueFor = react.useCallback((field) => {
12934
13106
  return taxonomyValueFor(field, lastTaxonomyRef.current);
12935
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
+ );
12936
13121
  const latestDraft = react.useMemo(
12937
13122
  () => latestMealSessions[latestActiveMealSessionIndex]?.draft ?? null,
12938
13123
  [latestMealSessions, latestActiveMealSessionIndex]
@@ -12978,7 +13163,8 @@ function useChatSession(options = {}) {
12978
13163
  getTaxonomyValueFor,
12979
13164
  setSelectedMealSessionIndex: (idx) => {
12980
13165
  selectedMealSessionIndexRef.current = idx;
12981
- }
13166
+ },
13167
+ submitFeedback
12982
13168
  };
12983
13169
  }
12984
13170
  function legacyToParts(data) {
@@ -13281,6 +13467,8 @@ function ChatSessionProvider({
13281
13467
  );
13282
13468
  const [activeViewedPreview, setActiveViewedPreview] = react.useState(null);
13283
13469
  const [suggestionsOverlayDismissed, setSuggestionsOverlayDismissed] = react.useState(false);
13470
+ const [feedbackTarget, setFeedbackTarget] = react.useState(null);
13471
+ const [feedbackRating, setFeedbackRating] = react.useState(0);
13284
13472
  react.useEffect(() => {
13285
13473
  if (!chat.sessionId) setActiveViewedPreview(null);
13286
13474
  }, [chat.sessionId]);
@@ -13319,6 +13507,17 @@ function ChatSessionProvider({
13319
13507
  setEditField,
13320
13508
  setEditValue,
13321
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
+ },
13322
13521
  hasResults,
13323
13522
  suggestionsOverlayDismissed,
13324
13523
  dismissSuggestionsOverlay: () => setSuggestionsOverlayDismissed(true),
@@ -13341,21 +13540,23 @@ function useChatSessionContext() {
13341
13540
  }
13342
13541
  return ctx;
13343
13542
  }
13344
- 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;
13345
13545
  function renderBotText(text) {
13546
+ const normalised = text.replace(BULLET_LINE_PATTERN, "\u2022 ");
13346
13547
  const nodes = [];
13347
13548
  let cursor = 0;
13348
13549
  let key = 0;
13349
- for (const match of text.matchAll(BOLD_TAG_PATTERN)) {
13550
+ for (const match of normalised.matchAll(BOLD_PATTERN)) {
13350
13551
  const start = match.index ?? 0;
13351
13552
  if (start > cursor) {
13352
- nodes.push(text.slice(cursor, start));
13553
+ nodes.push(normalised.slice(cursor, start));
13353
13554
  }
13354
- nodes.push(/* @__PURE__ */ jsxRuntime.jsx("strong", { children: match[1] }, key++));
13555
+ nodes.push(/* @__PURE__ */ jsxRuntime.jsx("strong", { children: match[1] ?? match[2] ?? "" }, key++));
13355
13556
  cursor = start + match[0].length;
13356
13557
  }
13357
- if (cursor < text.length) {
13358
- nodes.push(text.slice(cursor));
13558
+ if (cursor < normalised.length) {
13559
+ nodes.push(normalised.slice(cursor));
13359
13560
  }
13360
13561
  return nodes;
13361
13562
  }
@@ -14246,6 +14447,9 @@ function MenuPreviewCard({
14246
14447
  }) {
14247
14448
  const sections = preview.sections.filter((s) => s.items.length > 0);
14248
14449
  const [open, setOpen] = react.useState(defaultOpen);
14450
+ react.useEffect(() => {
14451
+ if (defaultOpen) setOpen(true);
14452
+ }, [preview, defaultOpen]);
14249
14453
  const [activeIdx, setActiveIdx] = react.useState(0);
14250
14454
  if (sections.length === 0) return null;
14251
14455
  const intents = sections.map((s) => s.intent);
@@ -14659,7 +14863,7 @@ function PreviewItemCard({
14659
14863
  },
14660
14864
  children: [
14661
14865
  item.restaurant.name,
14662
- item.restaurant.cuisine ? ` \xB7 ${item.restaurant.cuisine}` : ""
14866
+ item.restaurant.cuisine ? ` \xB7 ${item.restaurant.cuisine.replace(/_/g, " ")}` : ""
14663
14867
  ]
14664
14868
  }
14665
14869
  ),
@@ -15397,6 +15601,22 @@ function formatDay(iso) {
15397
15601
  if (Number.isNaN(d.getTime())) return iso;
15398
15602
  return d.toLocaleDateString(void 0, { weekday: "short" });
15399
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
+ }
15400
15620
  function MessageThread({
15401
15621
  messages,
15402
15622
  sessionId,
@@ -15408,27 +15628,99 @@ function MessageThread({
15408
15628
  onQtyChange,
15409
15629
  liveLatestPreviewKey,
15410
15630
  onViewPreviewInPanel,
15411
- onOpenPreviewItem
15631
+ onOpenPreviewItem,
15632
+ onFeedback,
15633
+ latestBotMessageId,
15634
+ activeFeedbackMessageId
15412
15635
  }) {
15413
- 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 }) => {
15414
- const key = `${msg.id}-${idx}`;
15415
- return /* @__PURE__ */ jsxRuntime.jsx(
15416
- PartRenderer,
15417
- {
15418
- part,
15419
- sender: msg.sender,
15420
- aiMealSessions,
15421
- onChip,
15422
- onSwapItem,
15423
- onRemoveItem,
15424
- onQtyChange,
15425
- isLatestPreview: key === liveLatestPreviewKey,
15426
- onViewPreviewInPanel,
15427
- onOpenPreviewItem
15428
- },
15429
- key
15430
- );
15431
- }) }, 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
+ }) });
15432
15724
  }
15433
15725
  function partSortKey(part) {
15434
15726
  return part.type === "text" ? 0 : 1;
@@ -15493,9 +15785,6 @@ function PartRenderer({
15493
15785
  if (part.type === "missing_fields_clarifier") {
15494
15786
  return null;
15495
15787
  }
15496
- if (part.type === "feedback") {
15497
- return null;
15498
- }
15499
15788
  return null;
15500
15789
  }
15501
15790
  function StandaloneIntentBlock({
@@ -15534,10 +15823,11 @@ function ChatMessagesView() {
15534
15823
  chat,
15535
15824
  setEditField,
15536
15825
  setEditValue,
15537
- hasResults,
15538
15826
  onViewResults,
15539
15827
  setActiveViewedPreview,
15540
- onOpenPreviewItem
15828
+ onOpenPreviewItem,
15829
+ startFeedback,
15830
+ feedbackTarget
15541
15831
  } = useChatSessionContext();
15542
15832
  const { messages, sending, awaitingReply, bootstrapping, error, sessionId, handleChip, retryLastSend, latestMealSessions } = chat;
15543
15833
  const messagesEndRef = react.useRef(null);
@@ -15545,6 +15835,12 @@ function ChatMessagesView() {
15545
15835
  () => messages.map((m) => ({ ...m, parts: chatPartsOnly(m.parts) })).filter((m) => m.parts.length > 0),
15546
15836
  [messages]
15547
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]);
15548
15844
  react.useEffect(() => {
15549
15845
  messagesEndRef.current?.scrollIntoView({
15550
15846
  behavior: "smooth",
@@ -15582,7 +15878,10 @@ function ChatMessagesView() {
15582
15878
  onEditField: handleEditField,
15583
15879
  liveLatestPreviewKey: chat.liveLatestPreviewKey,
15584
15880
  onViewPreviewInPanel: handleViewPreviewInPanel,
15585
- onOpenPreviewItem
15881
+ onOpenPreviewItem,
15882
+ onFeedback: startFeedback,
15883
+ latestBotMessageId,
15884
+ activeFeedbackMessageId: feedbackTarget?.messageId ?? null
15586
15885
  }
15587
15886
  ),
15588
15887
  awaitingReply && /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, {}),
@@ -15611,15 +15910,6 @@ function ChatMessagesView() {
15611
15910
  ]
15612
15911
  }
15613
15912
  ),
15614
- hasResults && /* @__PURE__ */ jsxRuntime.jsx(
15615
- "button",
15616
- {
15617
- type: "button",
15618
- onClick: onViewResults,
15619
- 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",
15620
- children: "\u2728 View menu suggestions"
15621
- }
15622
- ),
15623
15913
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
15624
15914
  ] });
15625
15915
  }
@@ -16481,9 +16771,11 @@ function useAddressAutocomplete(onPlaceSelect, options = {}) {
16481
16771
  function ChatAddressInput({
16482
16772
  onPlaceSelect,
16483
16773
  onClear,
16774
+ onDismiss,
16484
16775
  placeholder = "Enter Location",
16485
16776
  initialQuery,
16486
- autoFocus
16777
+ autoFocus,
16778
+ dropdownDirection = "up"
16487
16779
  }) {
16488
16780
  const {
16489
16781
  inputRef,
@@ -16503,6 +16795,33 @@ function ChatAddressInput({
16503
16795
  react.useEffect(() => {
16504
16796
  if (autoFocus) inputRef.current?.focus();
16505
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]);
16506
16825
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
16507
16826
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
16508
16827
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -16521,62 +16840,87 @@ function ChatAddressInput({
16521
16840
  onChange: (e) => setQuery(e.target.value),
16522
16841
  onKeyDown: handleKeyDown,
16523
16842
  placeholder,
16524
- autoComplete: "new-password",
16525
- 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"
16526
16848
  }
16527
16849
  ),
16528
- query.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
16850
+ (query.length > 0 || onDismiss) && /* @__PURE__ */ jsxRuntime.jsx(
16529
16851
  "button",
16530
16852
  {
16531
16853
  type: "button",
16532
16854
  onClick: () => {
16533
- setQuery("");
16534
- onClear?.();
16535
- inputRef.current?.focus();
16855
+ if (onDismiss) {
16856
+ onDismiss();
16857
+ } else {
16858
+ setQuery("");
16859
+ onClear?.();
16860
+ inputRef.current?.focus();
16861
+ }
16536
16862
  },
16537
16863
  className: "flex-shrink-0 rounded-full p-0.5 text-gray-400 hover:bg-base-100 hover:text-gray-600 transition-colors",
16538
- "aria-label": "Clear address",
16864
+ "aria-label": onDismiss ? "Close" : "Clear address",
16539
16865
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
16540
16866
  }
16541
16867
  )
16542
16868
  ] }),
16543
- 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(
16544
- "button",
16545
- {
16546
- type: "button",
16547
- onMouseDown: (e) => {
16548
- e.preventDefault();
16549
- handleSelect(p);
16550
- },
16551
- onMouseEnter: () => setActiveIndex(idx),
16552
- 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"}`,
16553
- children: [
16554
- /* @__PURE__ */ jsxRuntime.jsx(
16555
- "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",
16556
16885
  {
16557
- className: "h-3.5 w-3.5 flex-shrink-0 text-base-content/40 mt-0.5",
16558
- fill: "none",
16559
- stroke: "currentColor",
16560
- viewBox: "0 0 24 24",
16561
- children: /* @__PURE__ */ jsxRuntime.jsx(
16562
- "path",
16563
- {
16564
- strokeLinecap: "round",
16565
- strokeLinejoin: "round",
16566
- strokeWidth: 2,
16567
- 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"
16568
- }
16569
- )
16570
- }
16571
- ),
16572
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
16573
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-base-content truncate", children: p.structured_formatting.main_text }),
16574
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-base-content/50 truncate", children: p.structured_formatting.secondary_text })
16575
- ] })
16576
- ]
16577
- },
16578
- p.place_id
16579
- )) })
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
+ )
16580
16924
  ] });
16581
16925
  }
16582
16926
  var GATE_PROMPT = "What is the code to use the AI chat and test our beta version?";
@@ -16722,7 +17066,8 @@ function AIChatBody() {
16722
17066
  applyEditField,
16723
17067
  handleChip,
16724
17068
  editingMealSessionIndex,
16725
- setEditingMealSessionIndex
17069
+ setEditingMealSessionIndex,
17070
+ submitFeedback
16726
17071
  },
16727
17072
  resetEverything,
16728
17073
  editField,
@@ -16732,7 +17077,11 @@ function AIChatBody() {
16732
17077
  setEditValue,
16733
17078
  setSwapTarget,
16734
17079
  cart,
16735
- dismissSuggestionsOverlay
17080
+ dismissSuggestionsOverlay,
17081
+ feedbackTarget,
17082
+ feedbackRating,
17083
+ setFeedbackRating,
17084
+ cancelFeedback
16736
17085
  } = useChatSessionContext();
16737
17086
  const buildChatAddress = useChatAddressFromContext();
16738
17087
  const { contactInfo, setContactInfo } = useCateringState();
@@ -16741,6 +17090,9 @@ function AIChatBody() {
16741
17090
  const [pillEditing, setPillEditing] = react.useState(false);
16742
17091
  const textareaRef = react.useRef(null);
16743
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("");
16744
17096
  const [pendingInputFocus, setPendingInputFocus] = react.useState(false);
16745
17097
  const messagesScrollRef = react.useRef(null);
16746
17098
  const [showScrollDown, setShowScrollDown] = react.useState(false);
@@ -16775,6 +17127,31 @@ function AIChatBody() {
16775
17127
  el.focus();
16776
17128
  setPendingInputFocus(false);
16777
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
+ }
16778
17155
  function handlePlaceSelect(place) {
16779
17156
  const parsed = parsePlaceResult(place);
16780
17157
  if (!parsed) return;
@@ -16822,6 +17199,10 @@ function AIChatBody() {
16822
17199
  void handleChip(chip);
16823
17200
  }
16824
17201
  function submitText() {
17202
+ if (inFeedbackMode) {
17203
+ void handleFeedbackSubmit();
17204
+ return;
17205
+ }
16825
17206
  if (sending || bootstrapping || !sessionId || !input.trim()) return;
16826
17207
  if (gateActive && !hasAddress) return;
16827
17208
  void sendText(input, buildChatAddress());
@@ -16832,6 +17213,10 @@ function AIChatBody() {
16832
17213
  submitText();
16833
17214
  }
16834
17215
  function handleKeyDown(e) {
17216
+ if (e.key === "Escape" && inFeedbackMode) {
17217
+ closeFeedback();
17218
+ return;
17219
+ }
16835
17220
  if (e.key === "Enter" && !e.shiftKey) {
16836
17221
  e.preventDefault();
16837
17222
  submitText();
@@ -16879,12 +17264,52 @@ function AIChatBody() {
16879
17264
  )
16880
17265
  ] }),
16881
17266
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-h-0", children: [
16882
- /* @__PURE__ */ jsxRuntime.jsx(
16883
- "div",
16884
- {
16885
- ref: messagesScrollRef,
16886
- onScroll: handleMessagesScroll,
16887
- className: "absolute inset-0 overflow-y-auto overscroll-contain p-3",
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
+ ] }),
17307
+ /* @__PURE__ */ jsxRuntime.jsx(
17308
+ "div",
17309
+ {
17310
+ ref: messagesScrollRef,
17311
+ onScroll: handleMessagesScroll,
17312
+ className: "absolute inset-0 overflow-y-auto overscroll-contain p-3",
16888
17313
  children: /* @__PURE__ */ jsxRuntime.jsx(ChatMessagesView, {})
16889
17314
  }
16890
17315
  ),
@@ -16919,113 +17344,124 @@ function AIChatBody() {
16919
17344
  }
16920
17345
  }
16921
17346
  ) }),
16922
- /* @__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(
16923
- react$1.motion.div,
16924
- {
16925
- initial: { opacity: 0, y: 8 },
16926
- animate: { opacity: 1, y: 0 },
16927
- exit: { opacity: 0, y: 8 },
16928
- transition: { duration: 0.18, ease: "easeOut" },
16929
- children: /* @__PURE__ */ jsxRuntime.jsx(
16930
- InlineFieldEditor,
16931
- {
16932
- field: editField,
16933
- initialValue: editValue,
16934
- onSave: handleEditSave,
16935
- onCancel: () => {
16936
- setEditField(null);
16937
- setEditingMealSessionIndex(void 0);
16938
- }
16939
- }
16940
- )
16941
- },
16942
- "editor"
16943
- ) : /* @__PURE__ */ jsxRuntime.jsx(
16944
- react$1.motion.div,
16945
- {
16946
- initial: { opacity: 0, y: -8 },
16947
- animate: { opacity: 1, y: 0 },
16948
- exit: { opacity: 0, y: -8 },
16949
- transition: { duration: 0.18, ease: "easeOut" },
16950
- children: gateActive ? /* @__PURE__ */ jsxRuntime.jsxs(
16951
- "form",
16952
- {
16953
- onSubmit: handleSubmit,
16954
- className: "flex flex-col rounded-xl border border-base-300 bg-white px-3 py-2 focus-within:border-primary transition-colors",
16955
- children: [
16956
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pb-2 border-b border-base-200", children: /* @__PURE__ */ jsxRuntime.jsx(
16957
- 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",
16958
17372
  {
16959
- onPlaceSelect: handlePlaceSelect,
16960
- onClear: handleClearAddress,
16961
- autoFocus: true
16962
- }
16963
- ) }),
16964
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2 pt-2", children: [
16965
- /* @__PURE__ */ jsxRuntime.jsx(
16966
- "textarea",
16967
- {
16968
- ref: textareaRef,
16969
- rows: 3,
16970
- value: input,
16971
- onChange: (e) => setInput(e.target.value),
16972
- onKeyDown: handleKeyDown,
16973
- placeholder: "Ask for menu suggestions...",
16974
- disabled: bootstrapping || !sessionId,
16975
- className: "flex-1 bg-transparent text-sm text-gray-800 placeholder:text-gray-400 outline-none resize-none leading-snug py-1 overflow-hidden"
16976
- }
16977
- ),
16978
- /* @__PURE__ */ jsxRuntime.jsx(
16979
- "button",
16980
- {
16981
- type: "submit",
16982
- disabled: sending || bootstrapping || !input.trim() || !sessionId || !hasAddress,
16983
- 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",
16984
- title: hasAddress ? "Send" : "Enter a delivery address first",
16985
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-3.5 h-3.5" })
16986
- }
16987
- )
16988
- ] })
16989
- ]
16990
- }
16991
- ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16992
- hasAddress && !pillEditing && /* @__PURE__ */ jsxRuntime.jsxs(
16993
- "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,
16994
17404
  {
16995
- type: "button",
16996
- onClick: () => setPillEditing(true),
16997
- 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",
16998
17428
  children: [
16999
- /* @__PURE__ */ jsxRuntime.jsx(
17000
- lucideReact.MapPin,
17429
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pb-2 border-b border-base-200", children: /* @__PURE__ */ jsxRuntime.jsx(
17430
+ ChatAddressInput,
17001
17431
  {
17002
- className: "w-3 h-3 flex-shrink-0 text-gray-500",
17003
- "aria-hidden": true
17432
+ onPlaceSelect: handlePlaceSelect,
17433
+ onClear: handleClearAddress,
17434
+ autoFocus: true
17004
17435
  }
17005
- ),
17006
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: contactInfo?.addressLine1 }),
17007
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400", children: "\xB7" }),
17008
- /* @__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
+ ] })
17009
17462
  ]
17010
17463
  }
17011
- ),
17012
- pillEditing && /* @__PURE__ */ jsxRuntime.jsx(
17013
- "div",
17014
- {
17015
- ref: pillEditRef,
17016
- className: "mb-2 rounded-xl border border-base-300 bg-white px-3 py-2",
17017
- children: /* @__PURE__ */ jsxRuntime.jsx(
17018
- ChatAddressInput,
17019
- {
17020
- onPlaceSelect: handlePlaceSelect,
17021
- onClear: handleClearAddress,
17022
- initialQuery: contactInfo?.addressLine1 ?? "",
17023
- autoFocus: true
17024
- }
17025
- )
17026
- }
17027
- ),
17028
- /* @__PURE__ */ jsxRuntime.jsxs(
17464
+ ) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(
17029
17465
  "form",
17030
17466
  {
17031
17467
  onSubmit: handleSubmit,
@@ -17036,11 +17472,11 @@ function AIChatBody() {
17036
17472
  {
17037
17473
  ref: textareaRef,
17038
17474
  rows: 1,
17039
- value: input,
17040
- onChange: (e) => setInput(e.target.value),
17475
+ value: inFeedbackMode ? feedbackNote : input,
17476
+ onChange: (e) => inFeedbackMode ? setFeedbackNote(e.target.value) : setInput(e.target.value),
17041
17477
  onKeyDown: handleKeyDown,
17042
- placeholder: "Ask for menu suggestions...",
17043
- 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,
17044
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"
17045
17481
  }
17046
17482
  ),
@@ -17048,19 +17484,19 @@ function AIChatBody() {
17048
17484
  "button",
17049
17485
  {
17050
17486
  type: "submit",
17051
- disabled: sending || bootstrapping || !input.trim() || !sessionId,
17487
+ disabled: inFeedbackMode ? feedbackRating < 1 || feedbackState === "sending" : sending || bootstrapping || !input.trim() || !sessionId,
17052
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",
17053
- title: "Send",
17489
+ title: inFeedbackMode ? "Submit feedback" : "Send",
17054
17490
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-3.5 h-3.5" })
17055
17491
  }
17056
17492
  )
17057
17493
  ]
17058
17494
  }
17059
- )
17060
- ] })
17061
- },
17062
- "input"
17063
- ) }) }),
17495
+ ) })
17496
+ },
17497
+ "input"
17498
+ ) })
17499
+ ] }),
17064
17500
  /* @__PURE__ */ jsxRuntime.jsx(
17065
17501
  SwapModal,
17066
17502
  {
@@ -17099,6 +17535,213 @@ function AIChatBody() {
17099
17535
  )
17100
17536
  ] });
17101
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
+ }
17102
17745
  function AISuggestionsBottomButton() {
17103
17746
  const {
17104
17747
  hasResults,
@@ -17616,8 +18259,8 @@ function aiPlanToPricingInput(mealSessionParts, aiMealSessions, cart) {
17616
18259
  if (orderItems.length === 0) continue;
17617
18260
  out.push({
17618
18261
  sessionName: meta?.sessionName ?? `Meal ${ms.mealSessionIndex + 1}`,
17619
- sessionDate: meta?.sessionDate ?? "",
17620
- eventTime: meta?.eventTime ?? "",
18262
+ sessionDate: meta?.isoSessionDate ?? "",
18263
+ eventTime: meta?.isoEventTime ?? "",
17621
18264
  guestCount: meta?.guestCount ?? void 0,
17622
18265
  orderItems
17623
18266
  });
@@ -18282,8 +18925,11 @@ function IntentStepper({
18282
18925
  {
18283
18926
  total: blocks.length + 1,
18284
18927
  currentIdx: safeIntentIdx,
18285
- mealName: activeMealMeta?.sessionName ?? `Meal ${activeMeal.mealSessionIndex + 1}`,
18286
- isReview
18928
+ labels: [
18929
+ ...blocks.map((b) => capitalize(b.intent.phrase)),
18930
+ "Review"
18931
+ ],
18932
+ onStepClick: setIntentIndex
18287
18933
  }
18288
18934
  ),
18289
18935
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "8px 18px 14px" }, children: [
@@ -18560,11 +19206,114 @@ function capitalize(s) {
18560
19206
  if (!s) return s;
18561
19207
  return s.charAt(0).toUpperCase() + s.slice(1);
18562
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
+ }
18563
19312
  function ProgressStrip({
18564
19313
  total,
18565
19314
  currentIdx,
18566
- mealName,
18567
- isReview
19315
+ labels,
19316
+ onStepClick
18568
19317
  }) {
18569
19318
  const { dismissSuggestionsOverlay } = useChatSessionContext();
18570
19319
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -18572,40 +19321,25 @@ function ProgressStrip({
18572
19321
  {
18573
19322
  style: {
18574
19323
  display: "flex",
18575
- gap: 6,
18576
19324
  alignItems: "center",
19325
+ gap: 10,
18577
19326
  padding: "16px 18px 8px"
18578
19327
  },
18579
19328
  children: [
18580
- Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(
18581
- "div",
18582
- {
18583
- style: {
18584
- height: 4,
18585
- borderRadius: 2,
18586
- flex: 1,
18587
- background: i < currentIdx ? "var(--ink)" : i === currentIdx ? "var(--brand)" : "var(--rule)",
18588
- transition: "background-color 0.2s ease"
18589
- }
18590
- },
18591
- i
18592
- )),
18593
- /* @__PURE__ */ jsxRuntime.jsxs(
18594
- "span",
18595
- {
18596
- style: {
18597
- color: "var(--ink-faint)",
18598
- fontSize: "0.78rem",
18599
- marginLeft: 8,
18600
- 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
18601
19339
  },
18602
- children: [
18603
- isReview ? "Review" : `${currentIdx + 1} of ${total}`,
18604
- " \xB7 ",
18605
- mealName
18606
- ]
18607
- }
18608
- ),
19340
+ i
19341
+ );
19342
+ }) }),
18609
19343
  /* @__PURE__ */ jsxRuntime.jsx(
18610
19344
  "button",
18611
19345
  {
@@ -18617,7 +19351,6 @@ function ProgressStrip({
18617
19351
  style: {
18618
19352
  width: 36,
18619
19353
  height: 36,
18620
- marginLeft: 10,
18621
19354
  borderRadius: 999,
18622
19355
  border: "none",
18623
19356
  background: "var(--ink)",
@@ -26568,6 +27301,7 @@ function CateringOrderBuilder() {
26568
27301
  const [removeItemIndex, setRemoveItemIndex] = react.useState(null);
26569
27302
  const [isClearAllConfirmOpen, setIsClearAllConfirmOpen] = react.useState(false);
26570
27303
  const [isMobileCartMenuOpen, setIsMobileCartMenuOpen] = react.useState(false);
27304
+ const [isMobileChatMenuOpen, setIsMobileChatMenuOpen] = react.useState(false);
26571
27305
  const [isDesktopCartMenuOpen, setIsDesktopCartMenuOpen] = react.useState(false);
26572
27306
  const [isAIMobileUnavailableOpen, setIsAIMobileUnavailableOpen] = react.useState(false);
26573
27307
  const storage = useStorage();
@@ -26587,11 +27321,20 @@ function CateringOrderBuilder() {
26587
27321
  "chat"
26588
27322
  );
26589
27323
  react.useEffect(() => {
26590
- if (!isMobileAIChatOpen) setMobileChatView("chat");
27324
+ if (!isMobileAIChatOpen) {
27325
+ setMobileChatView("chat");
27326
+ setMobileAddressPillEditing(false);
27327
+ setIsMobileChatMenuOpen(false);
27328
+ }
26591
27329
  }, [isMobileAIChatOpen]);
26592
27330
  const [mobileAIInput, setMobileAIInput] = react.useState("");
26593
27331
  const [mobileAIInputHeight, setMobileAIInputHeight] = react.useState(44);
26594
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);
26595
27338
  const handleMobileAIInput = () => {
26596
27339
  const el = mobileAIInputRef.current;
26597
27340
  if (!el) return;
@@ -26610,6 +27353,7 @@ function CateringOrderBuilder() {
26610
27353
  }
26611
27354
  };
26612
27355
  const [isMobileSearchOpen, setIsMobileSearchOpen] = react.useState(false);
27356
+ const [isMobileSearchActive, setIsMobileSearchActive] = react.useState(false);
26613
27357
  const [mobileSearchState, setMobileSearchState] = react.useState({ mode: "list", query: "" });
26614
27358
  const mobileSearchSetterRef = react.useRef(null);
26615
27359
  const mobileSearchInputRef = react.useRef(null);
@@ -26617,12 +27361,17 @@ function CateringOrderBuilder() {
26617
27361
  const suppressMobileSearchScrollCloseRef = react.useRef(false);
26618
27362
  const [mobileSearchCaretVisible, setMobileSearchCaretVisible] = react.useState(false);
26619
27363
  const [keyboardOffset, setKeyboardOffset] = react.useState(0);
27364
+ const [vvOffsetTop, setVvOffsetTop] = react.useState(0);
27365
+ const [vvHeight, setVvHeight] = react.useState(null);
26620
27366
  react.useEffect(() => {
26621
27367
  if (typeof window === "undefined" || !window.visualViewport) return;
26622
27368
  const vv = window.visualViewport;
26623
27369
  const update = () => {
26624
- const offset = Math.max(0, window.innerHeight - vv.height - vv.offsetTop);
26625
- setKeyboardOffset(offset);
27370
+ setKeyboardOffset(
27371
+ Math.max(0, window.innerHeight - vv.height - vv.offsetTop)
27372
+ );
27373
+ setVvOffsetTop(vv.offsetTop);
27374
+ setVvHeight(vv.height);
26626
27375
  };
26627
27376
  update();
26628
27377
  vv.addEventListener("resize", update);
@@ -26632,6 +27381,48 @@ function CateringOrderBuilder() {
26632
27381
  vv.removeEventListener("scroll", update);
26633
27382
  };
26634
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]);
26635
27426
  react.useEffect(() => {
26636
27427
  if (!isMobileSearchOpen) {
26637
27428
  setMobileSearchCaretVisible(false);
@@ -26696,6 +27487,7 @@ function CateringOrderBuilder() {
26696
27487
  mobileSearchSetterRef.current?.(value);
26697
27488
  };
26698
27489
  const openMobileSearch = () => {
27490
+ setIsMobileSearchActive(true);
26699
27491
  mobileSearchInputRef.current?.focus({ preventScroll: true });
26700
27492
  setTimeout(() => {
26701
27493
  setIsMobileSearchOpen(true);
@@ -26707,8 +27499,46 @@ function CateringOrderBuilder() {
26707
27499
  mobileSearchInputRef.current?.blur();
26708
27500
  setTimeout(() => {
26709
27501
  setIsMobileSearchOpen(false);
27502
+ setIsMobileSearchActive(false);
26710
27503
  }, 250);
26711
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
+ };
26712
27542
  const closeMobileAIChat = () => {
26713
27543
  mobileAIInputRef.current?.blur();
26714
27544
  setTimeout(() => {
@@ -26806,6 +27636,8 @@ function CateringOrderBuilder() {
26806
27636
  };
26807
27637
  const { stickyTopOffset = 0, publishableKey, aiEnabled = false } = useCateringConfig();
26808
27638
  const { unlocked: aiBetaUnlocked, tryUnlock: tryUnlockAiBeta } = useBetaUnlock();
27639
+ const mobileAddressGateActive = isMobileAIChatOpen && aiBetaUnlocked && !hasChatAddress;
27640
+ const mobileAddressEditorOpen = mobileAddressGateActive || aiBetaUnlocked && mobileAddressPillEditing;
26809
27641
  const effectiveRightPanelTab = aiEnabled ? rightPanelTab : "cart";
26810
27642
  const [overlayVisible, setOverlayVisible] = react.useState(false);
26811
27643
  const basketColumnRef = react.useRef(null);
@@ -27587,7 +28419,7 @@ function CateringOrderBuilder() {
27587
28419
  }))
27588
28420
  }
27589
28421
  ),
27590
- /* @__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: [
27591
28423
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", children: [
27592
28424
  /* @__PURE__ */ jsxRuntime.jsx(
27593
28425
  SessionPickerDropdown,
@@ -27823,45 +28655,143 @@ function CateringOrderBuilder() {
27823
28655
  }
27824
28656
  )
27825
28657
  ] }),
27826
- isMobileAIChatOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-40 md:hidden swift-chat-design", children: [
27827
- /* @__PURE__ */ jsxRuntime.jsx(
27828
- "div",
27829
- {
27830
- className: "absolute inset-0 bg-black/20 backdrop-blur-sm",
27831
- onClick: closeMobileAIChat,
27832
- "aria-hidden": "true"
27833
- }
27834
- ),
27835
- mobileChatView === "chat" && /* @__PURE__ */ jsxRuntime.jsx(MobileResetChatButton, {}),
27836
- /* @__PURE__ */ jsxRuntime.jsx(
27837
- "div",
27838
- {
27839
- className: `absolute inset-x-0 top-0 overflow-y-auto ${aiBetaUnlocked ? "bg-white" : ""}`,
27840
- style: { bottom: keyboardOffset + (aiBetaUnlocked ? 64 : 0) },
27841
- children: !aiBetaUnlocked ? /* @__PURE__ */ jsxRuntime.jsx(AIChatBetaGate, { onUnlock: tryUnlockAiBeta }) : 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, {}) })
27842
- }
27843
- )
27844
- ] }),
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
+ ),
27845
28764
  /* @__PURE__ */ jsxRuntime.jsxs(
27846
28765
  "div",
27847
28766
  {
27848
- className: `fixed left-0 right-0 md:hidden z-50 ${keyboardOffset > 100 && !isMobileAIChatOpen || isMobileAIChatOpen && !aiBetaUnlocked ? "hidden" : ""}`,
28767
+ className: `fixed left-0 right-0 md:hidden z-50 ${mobileAddressEditorOpen || keyboardOffset > 100 && !isMobileAIChatOpen && !isMobileSearchActive ? "hidden" : ""}`,
27849
28768
  style: { bottom: keyboardOffset },
28769
+ "aria-hidden": mobileAddressEditorOpen,
27850
28770
  children: [
27851
28771
  /* @__PURE__ */ jsxRuntime.jsx(
27852
28772
  "div",
27853
28773
  {
27854
28774
  ref: mobileBarRowRef,
27855
- className: "relative mx-auto max-w-[325px] px-1 pt-1 pb-2",
28775
+ className: "relative px-3 pt-1 pb-2",
27856
28776
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-end gap-2", children: [
27857
28777
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
27858
28778
  /* @__PURE__ */ jsxRuntime.jsx(
27859
28779
  "button",
27860
28780
  {
27861
- 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),
27862
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",
27863
- title: isMobileAIChatOpen ? "Close AI chat" : "More actions",
27864
- 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)
27865
28795
  }
27866
28796
  ),
27867
28797
  isMobileCartMenuOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -27958,12 +28888,16 @@ function CateringOrderBuilder() {
27958
28888
  "input",
27959
28889
  {
27960
28890
  ref: mobileSearchInputRef,
27961
- type: "text",
28891
+ type: "search",
27962
28892
  value: mobileSearchState.query,
27963
28893
  onChange: (e) => handleMobileSearchInputChange(e.target.value),
27964
28894
  placeholder: "Search Swift Food",
27965
28895
  tabIndex: isMobileSearchOpen ? 0 : -1,
27966
- 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"}`
27967
28901
  }
27968
28902
  )
27969
28903
  ]
@@ -27982,12 +28916,22 @@ function CateringOrderBuilder() {
27982
28916
  {
27983
28917
  inputRef: mobileAIInputRef,
27984
28918
  value: mobileAIInput,
27985
- onChange: setMobileAIInput,
28919
+ onChange: (v) => {
28920
+ setMobileAIInput(v);
28921
+ if (mobileGateError) setMobileGateError(null);
28922
+ },
27986
28923
  onInputResize: handleMobileAIInput,
27987
28924
  onResetHeight: resetMobileAIInputHeight,
27988
28925
  tabbable: isMobileAIChatOpen,
27989
28926
  isOpen: isMobileAIChatOpen,
27990
- 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
27991
28935
  }
27992
28936
  )
27993
28937
  }
@@ -27997,7 +28941,7 @@ function CateringOrderBuilder() {
27997
28941
  "button",
27998
28942
  {
27999
28943
  onClick: isMobileSearchOpen ? closeMobileSearch : openMobileSearch,
28000
- 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"}`,
28001
28945
  title: isMobileSearchOpen ? "Close search" : "Search",
28002
28946
  "aria-label": isMobileSearchOpen ? "Close search" : "Open search",
28003
28947
  "aria-hidden": isMobileAIChatOpen,
@@ -28009,7 +28953,7 @@ function CateringOrderBuilder() {
28009
28953
  ] })
28010
28954
  }
28011
28955
  ),
28012
- 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(
28013
28957
  "button",
28014
28958
  {
28015
28959
  onClick: () => setIsViewOrderOpen(true),
@@ -28307,35 +29251,136 @@ function CateringOrderBuilder() {
28307
29251
  }
28308
29252
  );
28309
29253
  }
28310
- function MobileResetChatButton() {
29254
+ function MobileChatActionsMenu({
29255
+ onClose
29256
+ }) {
28311
29257
  const {
28312
29258
  chat: { bootstrapping },
28313
29259
  dismissSuggestionsOverlay,
28314
29260
  resetEverything
28315
29261
  } = useChatSessionContext();
28316
- return /* @__PURE__ */ jsxRuntime.jsxs(
28317
- "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",
28318
29325
  {
28319
- type: "button",
28320
- onClick: () => {
28321
- if (typeof window !== "undefined" && !window.confirm(
28322
- "Start a new chat? Your conversation history will be cleared."
28323
- )) {
28324
- return;
28325
- }
28326
- dismissSuggestionsOverlay();
28327
- window.setTimeout(() => {
28328
- void resetEverything();
28329
- }, 220);
28330
- },
28331
- disabled: bootstrapping,
28332
- 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",
28333
- title: "Start a new chat",
28334
- "aria-label": "Start a new chat",
28335
- children: [
28336
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "w-3.5 h-3.5" }),
28337
- "New chat"
28338
- ]
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 })
28339
29384
  }
28340
29385
  );
28341
29386
  }
@@ -28347,17 +29392,56 @@ function MobileAIInput({
28347
29392
  onResetHeight,
28348
29393
  tabbable,
28349
29394
  isOpen,
28350
- onFocus
29395
+ onFocus,
29396
+ onBlur,
29397
+ gateMode = false,
29398
+ onGateSubmit,
29399
+ buildAddress,
29400
+ submitBlocked = false
28351
29401
  }) {
28352
29402
  const {
28353
- chat: { sending, bootstrapping, sessionId, sendText }
29403
+ chat: { sending, bootstrapping, sessionId, sendText, submitFeedback },
29404
+ feedbackTarget,
29405
+ feedbackRating,
29406
+ cancelFeedback
28354
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());
28355
29416
  function submit() {
28356
- if (sending || bootstrapping || !sessionId || !value.trim()) return;
28357
- 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);
28358
29429
  onChange("");
28359
29430
  onResetHeight();
28360
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
+ }
28361
29445
  function handleKeyDown(e) {
28362
29446
  if (e.key === "Enter" && !e.shiftKey) {
28363
29447
  e.preventDefault();
@@ -28375,10 +29459,15 @@ function MobileAIInput({
28375
29459
  onInput: onInputResize,
28376
29460
  onKeyDown: handleKeyDown,
28377
29461
  onFocus,
28378
- placeholder: "How can I help?",
29462
+ onBlur,
29463
+ placeholder: inFeedbackMode ? "Add a note (optional)" : gateMode ? "Enter access code\u2026" : "How can I help?",
28379
29464
  tabIndex: tabbable ? 0 : -1,
28380
- disabled: sending || bootstrapping || !sessionId,
28381
- 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"
28382
29471
  }
28383
29472
  ),
28384
29473
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -28387,7 +29476,7 @@ function MobileAIInput({
28387
29476
  type: "button",
28388
29477
  onClick: submit,
28389
29478
  tabIndex: tabbable ? 0 : -1,
28390
- disabled: sending || bootstrapping || !sessionId || !value.trim(),
29479
+ disabled: sendDisabled,
28391
29480
  "aria-hidden": !isOpen,
28392
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"}`,
28393
29482
  title: "Send",