kupos-ui-components-lib 10.4.75 → 10.4.77

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,90 @@ 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
+ // Crown stays on the lowest fare card — selection only moves the border
145
+ });
146
+ const card = serviceCards.find((c) => c.key === key);
147
+ const price = getPriceInfo((card === null || card === void 0 ? void 0 : card.service) || sortedServices[0]);
148
+ setNodeText(document.getElementById(`feature-m-final-price-${serviceItem.id}`), commonService.currencyRounded(price.final * ticketQuantity));
149
+ setNodeText(document.getElementById(`feature-m-original-price-${serviceItem.id}`), commonService.currencyRounded(price.original * ticketQuantity));
150
+ document
151
+ .querySelectorAll(`[data-feature-m-savings="${serviceItem.id}"]`)
152
+ .forEach((node) => setNodeText(node, String(price.savings)));
153
+ const errorNode = document.getElementById(`feature-m-select-error-${serviceItem.id}`);
154
+ if (errorNode)
155
+ errorNode.style.display = "none";
156
+ };
94
157
  // The label shown on the dropdown button (24h format, e.g. "Entre 16:00 y 20:00")
95
158
  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;
159
+ // Time slot dropdown is disabled, so use the full deal window
160
+ const currentWindowFrom = dealWindowFrom;
161
+ const currentWindowTo = dealWindowTo;
162
+ // const currentWindowFrom = activeSlot
163
+ // ? commonService.minutesToTime(activeSlot.start)
164
+ // : dealWindowFrom;
165
+ // const currentWindowTo = activeSlot
166
+ // ? commonService.minutesToTime(
167
+ // activeSlot.end === 1440 ? 1439 : activeSlot.end,
168
+ // )
169
+ // : dealWindowTo;
102
170
  const isItemExpanded = serviceItem.id === isFeatureDropDownExpand ||
103
171
  isFeatureDropDownExpand === true;
104
172
  const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
@@ -171,97 +239,72 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
171
239
  fontSize: "10px",
172
240
  } },
173
241
  React.createElement("span", null,
174
- "AHORRAS ",
175
- displaySavingsPercent,
242
+ "AHORRAS",
243
+ " ",
244
+ React.createElement("span", { "data-feature-m-savings": serviceItem.id }, displaySavingsPercent),
176
245
  "%")))),
177
246
  React.createElement("div", { id: `service-card-${serviceItem.id}`, className: "bg-[#0C1421] text-white mx-auto relative rounded-[14px] p-[14px] text-[13.33px]" },
178
247
  React.createElement("div", { className: "flex flex-col gap-[10px]" },
179
248
  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
- }))))))))),
249
+ React.createElement("div", { className: "flex flex-col gap-[10px]", style: { filter: isSoldOut ? "grayscale(1)" : undefined } },
250
+ React.createElement("div", { className: "flex items-center gap-[10px]" },
251
+ React.createElement(OriginIcon, { size: 16 }),
252
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0])),
253
+ React.createElement("div", { className: "flex items-center gap-[10px]" },
254
+ React.createElement(DestinationIcon, { size: 16 }),
255
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
256
+ React.createElement("div", { className: "flex items-center gap-[10px] text-[13px]" },
257
+ React.createElement(CalendarIcon, { size: 16 }),
258
+ React.createElement("span", null, formatTravelDate(travelDate)))),
259
+ React.createElement("div", { className: "flex items-center gap-[10px] mt-[14px]" },
260
+ React.createElement("div", { className: "shrink-0" }, renderIcon("sheildIcon", "20px")),
261
+ React.createElement("div", { className: "flex flex-col gap-[2px]" },
262
+ React.createElement("span", { className: "bold-text text-[13px] text-white" }, "Precio Remate"),
263
+ React.createElement("span", { className: "text-[11px] text-[#dcdcdc]" }, "Sin cambios, sin cancelaci\u00F3n")))),
235
264
  React.createElement("div", { className: "border-t border-[#363c48] my-[8px]" }),
236
265
  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
266
+ React.createElement("span", { className: "block w-full text-[14px] bold-text text-[white] mb-[2px]", style: { textAlign: "center" } },
267
+ sortedServices.length > 0
268
+ ? sortedServices.length
240
269
  : operatorsCompetingCount,
241
270
  " ",
242
- "operadores compitiendo ",
271
+ "ofertas compitiendo ",
243
272
  React.createElement("br", null),
244
273
  "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",
274
+ 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: {
275
+ justifyContent: hasCardScroll ? "flex-start" : "center",
276
+ // room for the crown badge that sits above the selected card
277
+ paddingTop: "12px",
278
+ scrollbarWidth: "none",
279
+ } }, serviceCards.map((op, idx) => {
280
+ const isSelected = op.key === (selectedCard === null || selectedCard === void 0 ? void 0 : selectedCard.key);
281
+ // Cards are sorted cheapest first, so index 0 is the lowest fare
282
+ const isLowestFare = idx === 0;
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", { className: "absolute items-center justify-center", style: {
299
+ display: isLowestFare ? "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"),