kupos-ui-components-lib 10.4.75 → 10.4.76

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/styles.css CHANGED
@@ -270,6 +270,9 @@
270
270
  .mt-\[50px\] {
271
271
  margin-top: 50px;
272
272
  }
273
+ .mt-auto {
274
+ margin-top: auto;
275
+ }
273
276
  .-mr-\[12px\] {
274
277
  margin-right: calc(12px * -1);
275
278
  }
@@ -1106,6 +1109,9 @@
1106
1109
  .py-\[2px\] {
1107
1110
  padding-block: 2px;
1108
1111
  }
1112
+ .py-\[3px\] {
1113
+ padding-block: 3px;
1114
+ }
1109
1115
  .py-\[4px\] {
1110
1116
  padding-block: 4px;
1111
1117
  }
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import LottiePlayer from "../../assets/LottiePlayer";
3
3
  import commonService from "../../utils/CommonService";
4
+ import { CARD_BORDER, CARD_SELECTED_BORDER, CARD_SELECTED_SHADOW, CalendarIcon, CrownIcon, DestinationIcon, OriginIcon, formatDepartureTime, formatTravelDate, setNodeText, } from "../FeatureServiceUI/featureServiceShared";
4
5
  const HARDCODED_OPERATORS = [
5
6
  {
6
7
  logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Turbus_logo.svg/320px-Turbus_logo.svg.png",
@@ -21,8 +22,12 @@ const HARDCODED_OPERATORS = [
21
22
  seatsAvailable: "3 disponibles",
22
23
  },
23
24
  ];
25
+ // Selected operator card per serviceItem.id (no hooks — module-level state)
26
+ const selectedCardMap = {};
27
+ // Whether the "select a card" error is visible per serviceItem.id
28
+ const selectErrorMap = {};
24
29
  const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut, cityOrigin, cityDestination, renderIcon, viewersConfig, isFeatureDropDownExpand, onToggleExpand, ticketQuantity = 1, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onBookButtonPress, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData = undefined, }) => {
25
- var _a, _b, _c, _d, _e, _f, _g, _h;
30
+ var _a, _b, _c, _d, _e;
26
31
  // Use wow_deal data if available, otherwise fall back to serviceItem operators or hardcoded
27
32
  const operators = ((_a = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) === null || _a === void 0 ? void 0 : _a.length) > 0
28
33
  ? wowDealData.services.slice(0, 3).map((service) => ({
@@ -69,6 +74,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
69
74
  const activeSlot = availableTimeSlots.find((s) => s.label === selectedTimeSlot) ||
70
75
  availableTimeSlots[0];
71
76
  // Services that fall within the active slot, sorted by final price ascending
77
+ // (only used by the disabled time slot dropdown)
72
78
  const servicesInActiveSlot = activeSlot
73
79
  ? services
74
80
  .filter((s) => {
@@ -77,28 +83,92 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
77
83
  })
78
84
  .sort((a, b) => a.final_price - b.final_price)
79
85
  : services;
80
- // The selected price (final and original) from the cheapest service in the active slot
81
- const selectedSlotService = servicesInActiveSlot === null || servicesInActiveSlot === void 0 ? void 0 : servicesInActiveSlot[0];
82
- const displayFinalPrice = selectedSlotService
83
- ? selectedSlotService.final_price
84
- : finalPrice;
85
- const displayOriginalPrice = selectedSlotService
86
- ? selectedSlotService.original_price
87
- : originalPrice;
88
- const displaySavingsPercent = selectedSlotService && selectedSlotService.original_price
89
- ? Math.round(((selectedSlotService.original_price -
90
- selectedSlotService.final_price) /
91
- selectedSlotService.original_price) *
92
- 100)
93
- : savingsPercent;
86
+ // All services in the deal window (time slot dropdown is disabled),
87
+ // cheapest first, then by departure time.
88
+ const sortedServices = [...services].sort((a, b) => {
89
+ var _a, _b;
90
+ return ((_a = a.final_price) !== null && _a !== void 0 ? _a : 0) - ((_b = b.final_price) !== null && _b !== void 0 ? _b : 0) ||
91
+ commonService.timeToMinutes(a.departure_time) -
92
+ commonService.timeToMinutes(b.departure_time);
93
+ });
94
+ // Cards — 3 visible, the rest scroll horizontally (scrollbar hidden)
95
+ const serviceCards = sortedServices.length > 0
96
+ ? sortedServices.map((s, idx) => {
97
+ var _a, _b;
98
+ return ({
99
+ key: String((_b = (_a = s === null || s === void 0 ? void 0 : s.id) !== null && _a !== void 0 ? _a : s === null || s === void 0 ? void 0 : s.service_id) !== null && _b !== void 0 ? _b : `${s === null || s === void 0 ? void 0 : s.operator_name}-${s === null || s === void 0 ? void 0 : s.departure_time}-${idx}`),
100
+ logo: s.operator_logo_url,
101
+ name: s.operator_name,
102
+ time: s.departure_time,
103
+ price: s.final_price,
104
+ seatsAvailable: `¡Solo quedan ${s.available_seats}!`,
105
+ service: s,
106
+ });
107
+ })
108
+ : operators.map((op, idx) => {
109
+ var _a;
110
+ return (Object.assign(Object.assign({}, op), { key: `fallback-${idx}`, price: (_a = op.price) !== null && _a !== void 0 ? _a : finalPrice, service: null }));
111
+ });
112
+ const hasCardScroll = serviceCards.length > 3;
113
+ // Default to the first (cheapest) card when nothing is selected yet,
114
+ // or when the previous selection is no longer in the list (data refresh)
115
+ if (serviceCards.length > 0 &&
116
+ !serviceCards.some((c) => c.key === selectedCardMap[serviceItem.id])) {
117
+ selectedCardMap[serviceItem.id] = serviceCards[0].key;
118
+ }
119
+ const selectedCard = serviceCards.find((c) => c.key === selectedCardMap[serviceItem.id]);
120
+ const isSelectErrorVisible = !selectedCard && !!selectErrorMap[serviceItem.id];
121
+ const getPriceInfo = (svc) => svc
122
+ ? {
123
+ final: svc.final_price,
124
+ original: svc.original_price,
125
+ savings: svc.original_price
126
+ ? Math.round(((svc.original_price - svc.final_price) / svc.original_price) *
127
+ 100)
128
+ : savingsPercent,
129
+ }
130
+ : { final: finalPrice, original: originalPrice, savings: savingsPercent };
131
+ // Price shown: the selected card's service, otherwise the cheapest service
132
+ const selectedSlotService = (selectedCard === null || selectedCard === void 0 ? void 0 : selectedCard.service) || sortedServices[0];
133
+ const { final: displayFinalPrice, original: displayOriginalPrice, savings: displaySavingsPercent, } = getPriceInfo(selectedSlotService);
134
+ const handleCardSelect = (key) => {
135
+ selectedCardMap[serviceItem.id] = key;
136
+ selectErrorMap[serviceItem.id] = false;
137
+ if (typeof document === "undefined")
138
+ return;
139
+ const container = document.getElementById(`feature-m-cards-${serviceItem.id}`);
140
+ container === null || container === void 0 ? void 0 : container.querySelectorAll("[data-card-key]").forEach((node) => {
141
+ const isSelected = node.dataset.cardKey === key;
142
+ node.style.border = isSelected ? CARD_SELECTED_BORDER : CARD_BORDER;
143
+ node.style.boxShadow = isSelected ? CARD_SELECTED_SHADOW : "none";
144
+ const check = node.querySelector("[data-card-check]");
145
+ if (check)
146
+ check.style.display = isSelected ? "flex" : "none";
147
+ });
148
+ const card = serviceCards.find((c) => c.key === key);
149
+ const price = getPriceInfo((card === null || card === void 0 ? void 0 : card.service) || sortedServices[0]);
150
+ setNodeText(document.getElementById(`feature-m-final-price-${serviceItem.id}`), commonService.currencyRounded(price.final * ticketQuantity));
151
+ setNodeText(document.getElementById(`feature-m-original-price-${serviceItem.id}`), commonService.currencyRounded(price.original * ticketQuantity));
152
+ document
153
+ .querySelectorAll(`[data-feature-m-savings="${serviceItem.id}"]`)
154
+ .forEach((node) => setNodeText(node, String(price.savings)));
155
+ const errorNode = document.getElementById(`feature-m-select-error-${serviceItem.id}`);
156
+ if (errorNode)
157
+ errorNode.style.display = "none";
158
+ };
94
159
  // The label shown on the dropdown button (24h format, e.g. "Entre 16:00 y 20:00")
95
160
  const departureRange = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label) || `Entre ${dealWindowFrom} y ${dealWindowTo}`;
96
- const currentWindowFrom = activeSlot
97
- ? commonService.minutesToTime(activeSlot.start)
98
- : dealWindowFrom;
99
- const currentWindowTo = activeSlot
100
- ? commonService.minutesToTime(activeSlot.end === 1440 ? 1439 : activeSlot.end)
101
- : dealWindowTo;
161
+ // Time slot dropdown is disabled, so use the full deal window
162
+ const currentWindowFrom = dealWindowFrom;
163
+ const currentWindowTo = dealWindowTo;
164
+ // const currentWindowFrom = activeSlot
165
+ // ? commonService.minutesToTime(activeSlot.start)
166
+ // : dealWindowFrom;
167
+ // const currentWindowTo = activeSlot
168
+ // ? commonService.minutesToTime(
169
+ // activeSlot.end === 1440 ? 1439 : activeSlot.end,
170
+ // )
171
+ // : dealWindowTo;
102
172
  const isItemExpanded = serviceItem.id === isFeatureDropDownExpand ||
103
173
  isFeatureDropDownExpand === true;
104
174
  const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
@@ -171,97 +241,70 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
171
241
  fontSize: "10px",
172
242
  } },
173
243
  React.createElement("span", null,
174
- "AHORRAS ",
175
- displaySavingsPercent,
244
+ "AHORRAS",
245
+ " ",
246
+ React.createElement("span", { "data-feature-m-savings": serviceItem.id }, displaySavingsPercent),
176
247
  "%")))),
177
248
  React.createElement("div", { id: `service-card-${serviceItem.id}`, className: "bg-[#0C1421] text-white mx-auto relative rounded-[14px] p-[14px] text-[13.33px]" },
178
249
  React.createElement("div", { className: "flex flex-col gap-[10px]" },
179
250
  React.createElement("div", { className: " text-[white]" },
180
- React.createElement("div", { className: "flex flex-col gap-[6px] relative" },
181
- React.createElement("div", { className: "flex items-start gap-[10px]" },
182
- React.createElement("div", { className: "flex flex-col items-center shrink-0", style: { paddingTop: "2px" } },
183
- React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.whiteOrigin, alt: "origin", className: `w-[14px] h-[14px] shrink-0 ${isSoldOut ? "grayscale" : ""}` }),
184
- React.createElement("div", { style: {
185
- width: "1.5px",
186
- flex: 1,
187
- minHeight: "8px",
188
- backgroundColor: "rgba(255,255,255,0.35)",
189
- margin: "2px 0",
190
- } }),
191
- React.createElement("img", { src: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.whiteDestination, alt: "destination", className: `w-[15px] h-[15px] shrink-0 ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } })),
192
- React.createElement("div", { className: "flex flex-col gap-[6px]" },
193
- React.createElement("div", { className: "flex items-center gap-[6px]" },
194
- React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0]),
195
- React.createElement("span", { className: "text-[14px] bold-text" }, "\u2192"),
196
- React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
197
- React.createElement("div", { className: "kupos-time-dd relative", tabIndex: 0, onBlur: (e) => {
198
- if (!e.currentTarget.contains(e.relatedTarget)) {
199
- onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
200
- }
201
- }, style: { outline: "none", marginTop: "3px" } },
202
- React.createElement("button", { type: "button", onClick: () => onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(isThisTimeDropdownOpen ? null : serviceItem.id), className: "flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]" },
203
- React.createElement("span", null, departureRange),
204
- React.createElement("img", { src: (_f = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _f === void 0 ? void 0 : _f.downArrow, alt: "down arrow", className: `kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`, style: {
205
- width: "12px",
206
- height: "8px",
207
- filter: "brightness(0) invert(1)",
208
- } })),
209
- isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
210
- React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
211
- zIndex: 20,
212
- backgroundColor: "#fff",
213
- borderRadius: "14px",
214
- minWidth: "190px",
215
- boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
216
- overflow: "hidden",
217
- padding: "6px 0",
218
- } }, availableTimeSlots.map((slot) => {
219
- const isActive = slot.label === selectedTimeSlot ||
220
- slot === activeSlot;
221
- // Count services in this slot
222
- const count = services.filter((s) => {
223
- const depMin = commonService.timeToMinutes(s.departure_time);
224
- return (depMin >= slot.start && depMin < slot.end);
225
- }).length;
226
- return (React.createElement("button", { key: slot.label, type: "button", onClick: () => {
227
- onTimeSlotChange === null || onTimeSlotChange === void 0 ? void 0 : onTimeSlotChange(slot.label);
228
- onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
229
- }, className: `flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}` },
230
- React.createElement("span", null, slot.label),
231
- count > 0 && (React.createElement("span", { className: `text-[11px] rounded-full px-[6px] py-[2px] font-bold ${isActive
232
- ? "bg-[white] text-[#FF5C60]"
233
- : "bg-[#FF5C60] text-[white]"}` }, count))));
234
- }))))))))),
251
+ React.createElement("div", { className: "flex flex-col gap-[10px]", style: { filter: isSoldOut ? "grayscale(1)" : undefined } },
252
+ React.createElement("div", { className: "flex items-center gap-[10px]" },
253
+ React.createElement(OriginIcon, { size: 16 }),
254
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0])),
255
+ React.createElement("div", { className: "flex items-center gap-[10px]" },
256
+ React.createElement(DestinationIcon, { size: 16 }),
257
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
258
+ React.createElement("div", { className: "flex items-center gap-[10px] text-[13px]" },
259
+ React.createElement(CalendarIcon, { size: 16 }),
260
+ React.createElement("span", null, formatTravelDate(travelDate)))),
261
+ React.createElement("div", { className: "flex items-center gap-[10px] mt-[14px]" },
262
+ React.createElement("div", { className: "shrink-0" }, renderIcon("sheildIcon", "20px")),
263
+ React.createElement("div", { className: "flex flex-col gap-[2px]" },
264
+ React.createElement("span", { className: "bold-text text-[13px] text-white" }, "Precio Remate"),
265
+ React.createElement("span", { className: "text-[11px] text-[#dcdcdc]" }, "Sin cambios, sin cancelaci\u00F3n")))),
235
266
  React.createElement("div", { className: "border-t border-[#363c48] my-[8px]" }),
236
267
  React.createElement("div", null,
237
- React.createElement("span", { className: "block w-full text-[14px] bold-text text-[white] mb-[10px]", style: { textAlign: "center" } },
238
- servicesInActiveSlot.length > 0
239
- ? servicesInActiveSlot.length
268
+ React.createElement("span", { className: "block w-full text-[14px] bold-text text-[white] mb-[2px]", style: { textAlign: "center" } },
269
+ sortedServices.length > 0
270
+ ? sortedServices.length
240
271
  : operatorsCompetingCount,
241
272
  " ",
242
- "operadores compitiendo ",
273
+ "ofertas compitiendo ",
243
274
  React.createElement("br", null),
244
275
  "por tu compra"),
245
- React.createElement("div", { className: "flex gap-[8px] text-[white]", style: { width: "100%", justifyContent: "center" } }, (() => {
246
- const displayOps = servicesInActiveSlot.length > 0
247
- ? servicesInActiveSlot.slice(0, 3).map((s) => ({
248
- logo: s.operator_logo_url,
249
- name: s.operator_name,
250
- time: s.departure_time,
251
- seatsAvailable: `${s.available_seats} disponibles`,
252
- }))
253
- : operators;
254
- const isSingle = displayOps.length === 1;
255
- return displayOps.map((op, idx) => (React.createElement("div", { key: idx, className: "flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]", style: {
256
- flex: isSingle ? "none" : 1,
257
- width: isSingle ? "32%" : undefined,
258
- margin: isSingle ? "0 auto" : undefined,
259
- minWidth: 0,
260
- height: "100px",
261
- border: "1px solid #363c48",
276
+ React.createElement("div", { id: `feature-m-cards-${serviceItem.id}`, className: "feature-cards-scroll flex w-full items-stretch gap-[8px] overflow-x-auto text-[white]", style: {
277
+ justifyContent: hasCardScroll ? "flex-start" : "center",
278
+ // room for the crown badge that sits above the selected card
279
+ paddingTop: "12px",
280
+ scrollbarWidth: "none",
281
+ } }, serviceCards.map((op) => {
282
+ const isSelected = op.key === (selectedCard === null || selectedCard === void 0 ? void 0 : selectedCard.key);
283
+ return (React.createElement("div", { key: op.key, "data-card-key": op.key, role: "button", tabIndex: 0, "aria-pressed": isSelected, onClick: () => handleCardSelect(op.key), onKeyDown: (e) => {
284
+ if (e.key === "Enter" || e.key === " ") {
285
+ e.preventDefault();
286
+ handleCardSelect(op.key);
287
+ }
288
+ }, className: "relative flex min-w-0 cursor-pointer flex-col items-center justify-center gap-[6px] outline-none", style: {
289
+ flex: "0 0 calc((100% - 16px) / 3)",
290
+ minWidth: "90px",
291
+ border: isSelected ? CARD_SELECTED_BORDER : CARD_BORDER,
292
+ boxShadow: isSelected ? CARD_SELECTED_SHADOW : "none",
262
293
  backgroundColor: "#1a202e",
263
- padding: "14px 6px",
294
+ padding: "16px 6px 12px",
295
+ borderRadius: "10px",
296
+ transition: "border-color 150ms ease, box-shadow 150ms ease",
264
297
  } },
298
+ React.createElement("span", { "data-card-check": "true", className: "absolute items-center justify-center", style: {
299
+ display: isSelected ? "flex" : "none",
300
+ top: "-12px",
301
+ right: "-1px",
302
+ width: "30px",
303
+ height: "20px",
304
+ borderRadius: "6px",
305
+ backgroundColor: "#FF5C60",
306
+ } },
307
+ React.createElement(CrownIcon, { width: 14, height: 11 })),
265
308
  React.createElement("img", { src: op.logo, alt: op.name, onError: (e) => {
266
309
  var _a;
267
310
  e.currentTarget.src =
@@ -272,8 +315,10 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
272
315
  maxWidth: "100%",
273
316
  } }),
274
317
  React.createElement("span", { className: "text-[12px] truncate max-w-full text-center" }, op.name),
275
- React.createElement("span", { className: "text-[11px] whitespace-nowrap" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))));
276
- })()),
318
+ React.createElement("div", { className: "bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[3px] rounded-[6px] bold-text whitespace-nowrap" }, commonService.currencyRounded(op.price)),
319
+ React.createElement("span", { className: "bold-text text-[12px] whitespace-nowrap" }, formatDepartureTime(op === null || op === void 0 ? void 0 : op.time)),
320
+ React.createElement("span", { className: "bold-text text-[11px] whitespace-nowrap" }, op === null || op === void 0 ? void 0 : op.seatsAvailable)));
321
+ })),
277
322
  React.createElement("div", { className: "flex w-full items-center justify-center gap-[6px] text-[12px] mt-[12px]", style: {
278
323
  padding: "4px 14px",
279
324
  } },
@@ -320,7 +365,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
320
365
  } },
321
366
  React.createElement("div", { className: "flex flex-col" },
322
367
  React.createElement("span", { className: "text-[18px] font-normal leading-[20px] text-[#9f9f9f] relative", style: { position: "relative" } },
323
- commonService.currencyRounded(displayOriginalPrice * ticketQuantity),
368
+ React.createElement("span", { id: `feature-m-original-price-${serviceItem.id}` }, commonService.currencyRounded(displayOriginalPrice * ticketQuantity)),
324
369
  React.createElement("span", { style: {
325
370
  position: "absolute",
326
371
  left: "-2px",
@@ -331,12 +376,12 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
331
376
  transform: "rotate(-10deg)",
332
377
  transformOrigin: "center",
333
378
  } })),
334
- React.createElement("span", { className: "text-[white] bold-text text-[24px] leading-none mt-[4px]" }, commonService.currencyRounded(displayFinalPrice * ticketQuantity))),
379
+ React.createElement("span", { id: `feature-m-final-price-${serviceItem.id}`, className: "text-[white] bold-text text-[24px] leading-none mt-[4px]" }, commonService.currencyRounded(displayFinalPrice * ticketQuantity))),
335
380
  React.createElement("span", { className: "text-[#FF8F45] bold-text text-[22px] leading-tight", style: {
336
381
  animation: "pulse-zoom 2s ease-in-out infinite",
337
382
  whiteSpace: "nowrap",
338
383
  } },
339
- displaySavingsPercent,
384
+ React.createElement("span", { "data-feature-m-savings": serviceItem.id }, displaySavingsPercent),
340
385
  "% OFF")),
341
386
  React.createElement("button", { type: "button", ref: (btn) => {
342
387
  if (!btn)
@@ -344,6 +389,23 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
344
389
  btn.onclick = () => {
345
390
  if (btn.disabled)
346
391
  return;
392
+ // Require an operator card to be selected before booking
393
+ const currentCard = serviceCards.find((c) => c.key === selectedCardMap[serviceItem.id]);
394
+ if (!currentCard) {
395
+ selectErrorMap[serviceItem.id] = true;
396
+ const errorNode = document.getElementById(`feature-m-select-error-${serviceItem.id}`);
397
+ if (errorNode)
398
+ errorNode.style.display = "block";
399
+ return;
400
+ }
401
+ const chosenService = currentCard.service;
402
+ // Selected service first so consumers reading [0] get the chosen one
403
+ const orderedServices = chosenService
404
+ ? [
405
+ chosenService,
406
+ ...sortedServices.filter((s) => s !== chosenService),
407
+ ]
408
+ : sortedServices;
347
409
  btn.disabled = true;
348
410
  btn.style.cursor = "not-allowed";
349
411
  btn.style.opacity = "0.85";
@@ -353,7 +415,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
353
415
  normal.style.display = "none";
354
416
  if (loading)
355
417
  loading.style.display = "flex";
356
- onBookButtonPress === null || onBookButtonPress === void 0 ? void 0 : onBookButtonPress(ticketQuantity, serviceItem, servicesInActiveSlot);
418
+ onBookButtonPress === null || onBookButtonPress === void 0 ? void 0 : onBookButtonPress(ticketQuantity, serviceItem, orderedServices, chosenService);
357
419
  };
358
420
  }, className: "flex items-center gap-[6px] px-[20px] py-[10px] rounded-[16px] text-[white] bold-text text-[13px] mt-[10px] justify-center border-none cursor-pointer", style: {
359
421
  backgroundColor: "#FF5C60",
@@ -362,8 +424,11 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
362
424
  } },
363
425
  React.createElement(LottiePlayer, { animationData: serviceItem.icons.thunderAnim, width: "16px", height: "16px" }),
364
426
  React.createElement("span", { className: "whitespace-nowrap" }, "\u00A1Lo quiero!")),
427
+ React.createElement("span", { id: `feature-m-select-error-${serviceItem.id}`, role: "alert", className: "text-[11px] text-[#FF5C60] text-center leading-[14px] mt-[6px]", style: {
428
+ display: isSelectErrorVisible ? "block" : "none",
429
+ } }, "Selecciona un horario para continuar"),
365
430
  React.createElement("div", { className: "flex justify-end mt-[10px]", onClick: onToggleExpand },
366
- React.createElement("img", { src: (_g = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _g === void 0 ? void 0 : _g.downArrow, alt: "down arrow", className: `transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`, style: {
431
+ React.createElement("img", { src: (_d = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _d === void 0 ? void 0 : _d.downArrow, alt: "down arrow", className: `transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`, style: {
367
432
  width: "14px",
368
433
  height: "8px",
369
434
  filter: "brightness(0) invert(1)",
@@ -384,7 +449,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
384
449
  React.createElement("div", { className: "absolute -top-[13px] left-0 w-full flex items-center justify-center gap-[12px] z-10 " },
385
450
  React.createElement("div", { className: "flex items-center gap-[6px] py-[5px] px-[14px] rounded-[38px] text-[12.5px] text-white whitespace-nowrap", style: { backgroundColor: "rgba(255, 222, 223, 0.93)" } },
386
451
  React.createElement("span", { className: "flex items-center" },
387
- ((_h = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _h === void 0 ? void 0 : _h.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[12px] w-[12px] object-contain mr-[5px]", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
452
+ ((_e = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _e === void 0 ? void 0 : _e.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[12px] w-[12px] object-contain mr-[5px]", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
388
453
  React.createElement("span", { className: "bold-text text-[#464647] ml-[3px]" }, "Remate\u00A0"),
389
454
  " ",
390
455
  React.createElement("span", { className: "text-[#464647]" }, "t\u00E9rmina en\u00A0"),