kupos-ui-components-lib 10.4.28 → 10.4.30

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.
@@ -329,18 +329,27 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
329
329
  React.createElement("span", { className: "text-[13.33px] font-normal leading-[22px] text-[#ccc]" }, "Antes"))),
330
330
  React.createElement("div", { className: "col-start-1 row-start-3 flex h-[20px] items-end" },
331
331
  React.createElement("span", { className: "text-[13.33px] font-normal leading-[20px] text-[#464647]" }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) === 1 ? discountSeat.label : "Desde")),
332
- React.createElement("div", { className: "col-start-2 row-start-1 flex items-center justify-center absolute", style: { top: "-22px", left: "50%", transform: "translateX(-50%)" } }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 &&
333
- (discountValue != null || isMaxDiscountApplied) && (React.createElement("span", { className: "rounded-[100px] bg-[#ff5964] px-[6px] text-[12px] bold-text leading-[20px] text-white", style: {
334
- animation: "pulse-zoom 2s ease-in-out infinite",
335
- whiteSpace: "nowrap",
336
- backgroundColor: discountSeatPriceColor,
337
- } }, isMaxDiscountApplied
338
- ? `${CommonService.currency(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.max_discount)} OFF`
339
- : `${discountValue}% OFF`))),
332
+ React.createElement("div", { className: "col-start-2 row-start-1 flex items-center justify-center absolute", style: { top: "-22px", left: "50%", transform: "translateX(-50%)" } },
333
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 &&
334
+ !isPeru &&
335
+ (discountValue != null || isMaxDiscountApplied) && (React.createElement("span", { className: "rounded-[100px] bg-[#ff5964] px-[6px] text-[12px] bold-text leading-[20px] text-white", style: {
336
+ animation: "pulse-zoom 2s ease-in-out infinite",
337
+ whiteSpace: "nowrap",
338
+ backgroundColor: discountSeatPriceColor,
339
+ } }, isMaxDiscountApplied
340
+ ? `${CommonService.currency(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.max_discount)} OFF`
341
+ : `${discountValue}% OFF`)),
342
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 &&
343
+ isPeru &&
344
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discount_display_badge) && (React.createElement("span", { className: "rounded-[100px] bg-[#ff5964] px-[6px] text-[12px] bold-text leading-[20px] text-white", style: {
345
+ animation: "pulse-zoom 2s ease-in-out infinite",
346
+ whiteSpace: "nowrap",
347
+ backgroundColor: discountSeatCrossPriceColor,
348
+ } }, serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discount_display_badge))),
340
349
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) === 1 ? null : (React.createElement("div", { className: "col-start-2 row-start-2 flex items-center justify-center ", style: { textAlign: "center" } },
341
350
  React.createElement("span", { className: "text-[13.33px] font-normal leading-[20px] relative whitespace-nowrap", style: {
342
351
  position: "relative",
343
- color: discountSeatPriceColor,
352
+ color: discountSeatCrossPriceColor,
344
353
  } },
345
354
  formatPrice(discountSeat.originalPrice),
346
355
  React.createElement("span", { style: {
@@ -48,7 +48,7 @@ const getUniqueSeats = (data, limit) => {
48
48
  .slice(0, limit);
49
49
  };
50
50
  function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, tooltipBgColor, showLastSeats, discountSeatPriceColor, isTrain, discountSeatCrossPriceColor, }) {
51
- var _a, _b, _c, _d, _e, _f, _g;
51
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
52
52
  const hasMultipleTypes = ((_a = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.length) !== null && _a !== void 0 ? _a : 0) > 2;
53
53
  const getFare = (fare) => {
54
54
  if (removeDuplicateSeats && availableSeats <= 0 && !isPeru) {
@@ -193,6 +193,14 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
193
193
  }
194
194
  return false;
195
195
  })();
196
+ // Backend-provided ready-made badge text (e.g. "30% OFF" / "$2.000 OFF")
197
+ // takes precedence over the value computed client-side.
198
+ const discountBadgeText = ((_d = (_c = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discounts) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.discount_display_badge) ||
199
+ (isMaxDiscountApplied
200
+ ? `${commonService.currency(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.max_discount)} OFF`
201
+ : discountValue != null
202
+ ? `${discountValue}% OFF`
203
+ : null);
196
204
  const getMinValue = (data) => {
197
205
  const vals = (Array.isArray(data) ? data : Object.values(data || {})).map(Number);
198
206
  if (!vals.length)
@@ -202,14 +210,14 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
202
210
  };
203
211
  const originalDpPrice = getMinValue(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.original_dp_price);
204
212
  const dpDiscountPercent = getMinValue(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents);
205
- const firstSeatFare = (_d = (_c = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label) && !EXCEPTIONS.includes(item.label))) === null || _c === void 0 ? void 0 : _c.sort((a, b) => a.fare - b.fare)[0]) === null || _d === void 0 ? void 0 : _d.fare;
213
+ const firstSeatFare = (_f = (_e = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label) && !EXCEPTIONS.includes(item.label))) === null || _e === void 0 ? void 0 : _e.sort((a, b) => a.fare - b.fare)[0]) === null || _f === void 0 ? void 0 : _f.fare;
206
214
  const hasDpDiscount = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) &&
207
215
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents) &&
208
216
  originalDpPrice != null &&
209
217
  dpDiscountPercent != null &&
210
218
  firstSeatFare != null;
211
219
  return (React.createElement("div", { className: "content-center relative", style: { width: "40%" } }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) &&
212
- !((_e = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) === null || _e === void 0 ? void 0 : _e.length) &&
220
+ !((_g = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) === null || _g === void 0 ? void 0 : _g.length) &&
213
221
  !dpDiscountPercent ? (React.createElement("div", { className: "flex flex-col justify-between h-[2.5rem]", style: { gap: isSoldOut ? "0px" : "5px" } }, renderDpSeats())) : hasDpDiscount ? (React.createElement("div", { className: "relative grid grid-cols-[auto_auto] justify-between gap-x-[8px] " },
214
222
  !isNaN(Number(dpDiscountPercent)) &&
215
223
  Number(dpDiscountPercent) > 0 && (React.createElement("div", { className: "absolute -top-[18px] right-[0px]", style: {
@@ -241,23 +249,20 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
241
249
  React.createElement("span", { className: "flex items-center justify-end gap-[4px] text-[14px] bold-text leading-[24px]", style: {
242
250
  color: isSoldOut ? "#bbb" : discountSeatPriceColor || "#ff5964",
243
251
  } },
244
- ((_f = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _f === void 0 ? void 0 : _f.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
252
+ ((_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-[16px] w-[16px] object-contain", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
245
253
  commonService.discountedCurrency(Number(firstSeatFare), currencySign)),
246
254
  isSoldOut ? (React.createElement("span", { className: "col-span-2 min-[420]:text-[13px] text-right text-[12px] text-[#ccc]" }, "Agotado")) : null)) : hasDiscount && discountSeat ? (React.createElement("div", null,
247
255
  React.createElement("div", { className: "relative grid grid-cols-[auto_auto] justify-between gap-x-[8px] " },
248
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 &&
249
- (discountValue != null || isMaxDiscountApplied) && (React.createElement(React.Fragment, null,
256
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 && discountBadgeText && (React.createElement(React.Fragment, null,
250
257
  React.createElement("div", { className: "absolute -top-[18px] right-[0px]", style: {
251
258
  animation: "pulse-zoom 2s ease-in-out infinite",
252
259
  opacity: isSoldOut ? 0.5 : 1,
253
260
  } },
254
261
  React.createElement("span", { className: "rounded-[100px] px-[8px] text-[12px] bold-text leading-[20px] text-[#fff]", style: {
255
262
  backgroundColor: tooltipBgColor,
256
- } }, isMaxDiscountApplied
257
- ? `${commonService.currency(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.max_discount)} OFF`
258
- : `${discountValue}% OFF`)),
263
+ } }, discountBadgeText)),
259
264
  React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] leading-[20px] text-[#c2c2c2]" }, "Antes"),
260
- React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] leading-[20px] text-right", style: {
265
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] leading-[20px] text-right ", style: {
261
266
  position: "relative",
262
267
  display: "inline-block",
263
268
  overflow: "hidden",
@@ -282,7 +287,7 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
282
287
  ? "#464647"
283
288
  : discountSeatPriceColor || "#ff5964",
284
289
  } },
285
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 && ((_g = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _g === void 0 ? void 0 : _g.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { opacity: isSoldOut ? 0.5 : 1 } })) : null,
290
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) !== 1 && ((_j = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _j === void 0 ? void 0 : _j.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { opacity: isSoldOut ? 0.5 : 1 } })) : null,
286
291
  commonService.discountedCurrency((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.ui_type) === 1
287
292
  ? discountSeat.originalPrice
288
293
  : discountSeat.discountedPrice, currencySign))))) : (React.createElement("div", { className: `flex flex-col justify-between ${isTrain ? "" : "h-[2.5rem]"} `, style: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "10.4.28",
3
+ "version": "10.4.30",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -180,6 +180,8 @@ function PeruServiceItemDesktop({
180
180
 
181
181
  let isSoldOut = serviceItem.available_seats <= 0;
182
182
  const hasDpEnabled = serviceItem?.is_dp_enabled === true;
183
+ const peruDiscount = (serviceItem as any)?.discounts?.[0];
184
+ const uiType = peruDiscount?.ui_type;
183
185
 
184
186
  const renderIcon = (iconKey: string, size: string = "14px") => {
185
187
  const iconValue = serviceItem.icons?.[iconKey];
@@ -609,12 +611,18 @@ function PeruServiceItemDesktop({
609
611
  const isItemExpanded = serviceItem.id === isExpand || isExpand === true;
610
612
  const grayscaleClass = isSoldOut ? "grayscale" : "";
611
613
 
612
- const hasOfferText = Boolean(serviceItem?.offer_text);
614
+ // Offer text is only ever shown when the discount's new UI is enabled
615
+ // (tiers B/C). Tier A must stay unchanged even if the backend still sends
616
+ // an offer_text.
617
+ const hasOfferText = isNewUiEnabled && Boolean(serviceItem?.offer_text);
618
+ // Tier C (ui_type 2) always shows the bottom ribbon — timer, bomb
619
+ // animation and viewers count don't depend on offer_text being present.
620
+ const isFullNewUi = isNewUiEnabled && uiType === 2;
613
621
 
614
622
  const offerGradient = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"} 0%, ${colors.leftGradiantColor || "#ff8842"} 100%)`;
615
623
  const offerGradientWithOpacity = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"}80 0%, ${colors.leftGradiantColor || "#ff8842"}80 100%)`;
616
624
  const serviceCardStyle: React.CSSProperties =
617
- (hasOfferText || isNewUiEnabled || hasDpEnabled || hasDiscount) &&
625
+ (isFullNewUi || hasDpEnabled) &&
618
626
  !isSoldOut
619
627
  ? {
620
628
  borderColor: "transparent",
@@ -628,7 +636,7 @@ function PeruServiceItemDesktop({
628
636
 
629
637
  return (
630
638
  <div
631
- className={`relative hover:z-[150] ${(hasOfferText || hasDpEnabled || isNewUiEnabled) && !isSoldOut ? "mb-[65px]" : "mb-[20px]"} ${
639
+ className={`relative hover:z-[150] ${(hasOfferText || hasDpEnabled || isFullNewUi) && !isSoldOut ? "mb-[65px]" : "mb-[20px]"} ${
632
640
  serviceItem?.is_direct_trip ||
633
641
  serviceItem?.train_type_label === "Tren Express (Nuevo)" ||
634
642
  showTopLabel
@@ -636,28 +644,28 @@ function PeruServiceItemDesktop({
636
644
  : "mt-[20px]"
637
645
  } `}
638
646
  >
639
- {(serviceItem?.offer_text || hasDpEnabled || hasDiscount) &&
640
- !isSoldOut && (
641
- <PeruOfferBanner
642
- offerGradient={offerGradient}
643
- isSoldOut={isSoldOut}
644
- serviceItem={serviceItem}
645
- renderIcon={renderIcon}
646
- isLoggedIn={isLoggedIn}
647
- showLoginModal={showLoginModal}
648
- viewersConfig={viewersConfig}
649
- getAnimationIcon={getAnimationIcon}
650
- showLoginOption={showLoginOption}
651
- colors={colors}
652
- coachKey={coachKey}
653
- isExpand={isExpand}
654
- isNewUiEnabled={isNewUiEnabled}
655
- />
656
- )}
647
+ {(hasOfferText || hasDpEnabled || isFullNewUi) && !isSoldOut && (
648
+ <PeruOfferBanner
649
+ offerGradient={offerGradient}
650
+ isSoldOut={isSoldOut}
651
+ serviceItem={serviceItem}
652
+ renderIcon={renderIcon}
653
+ isLoggedIn={isLoggedIn}
654
+ showLoginModal={showLoginModal}
655
+ viewersConfig={viewersConfig}
656
+ getAnimationIcon={getAnimationIcon}
657
+ showLoginOption={showLoginOption}
658
+ colors={colors}
659
+ coachKey={coachKey}
660
+ isExpand={isExpand}
661
+ isNewUiEnabled={isNewUiEnabled}
662
+ uiType={uiType}
663
+ />
664
+ )}
657
665
  <div
658
666
  id={`service-card-${serviceItem.id}`}
659
667
  className={`bg-white mx-auto relative ${
660
- (hasOfferText || isNewUiEnabled || hasDiscount || hasDpEnabled) &&
668
+ (isFullNewUi || hasDpEnabled) &&
661
669
  !isSoldOut
662
670
  ? "z-[3] rounded-[18px]"
663
671
  : "rounded-[10px] border border-[#ccc]"
@@ -667,13 +675,13 @@ function PeruServiceItemDesktop({
667
675
  <div
668
676
  className=" pt-[20px]"
669
677
  style={{
670
- padding: hasOfferText
678
+ padding: hasOfferText || isFullNewUi
671
679
  ? "20px 15px 10px 15px"
672
680
  : coachKey
673
681
  ? "20px 15px 20px 15px"
674
682
  : "20px 15px 10px 15px",
675
683
 
676
- marginTop: hasDiscount || hasOfferText ? "14px" : "",
684
+ marginTop: hasDiscount || hasOfferText || isFullNewUi ? "14px" : "",
677
685
  }}
678
686
  >
679
687
  {/* <div className="grid grid-cols-[1.5fr_1fr_auto] gap-[3rem] sm:gap-[4rem] md:gap-[5rem] lg:gap-[6rem] xl:gap-[5rem] 2xl:gap-[7rem] text-[#464647]"> */}
@@ -12,6 +12,7 @@ import ServiceBadgesMobile from "../../ui/mobileweb/ServiceBadgesMobile";
12
12
  import FeatureServiceUiMobile from "../../ui/FeaturServiceUiMobile/FeatureServiceUiMobile";
13
13
 
14
14
  const SEAT_EXCEPTIONS = ["Asiento mascota"];
15
+ const OFFER_TEXT_MAX_CHARS = 20;
15
16
 
16
17
  const exceptions = [
17
18
  "gy",
@@ -94,17 +95,33 @@ function PeruServiceItemMobile({
94
95
  );
95
96
 
96
97
  const isLongOfferText = (serviceItem?.offer_text?.length || 0) > 35;
98
+ const offerText = serviceItem?.offer_text || "";
99
+ const displayOfferText =
100
+ offerText.length > OFFER_TEXT_MAX_CHARS
101
+ ? `${offerText.slice(0, OFFER_TEXT_MAX_CHARS).trimEnd()}...`
102
+ : offerText;
97
103
  const hasDpEnabled = serviceItem?.is_dp_enabled === true;
104
+ const hasDpRibbon = hasDpEnabled && Boolean(serviceItem?.dp_discount_percents);
105
+
106
+ // ui_type comes from the active discount entry: 1 = offer-text-only tier,
107
+ // 2 = full new-UI tier (timer, bomb animation, viewers).
108
+ const peruDiscount = (serviceItem as any)?.discounts?.[0];
109
+ const uiType = peruDiscount?.ui_type;
98
110
 
99
111
  const isDiscountBelow20 =
100
112
  (serviceItem as any)?.discount_value > 0 &&
101
113
  (serviceItem as any)?.discount_value < 20;
102
- const hasOfferText = Boolean(serviceItem?.offer_text);
114
+ // Offer text is only ever shown when the discount's new UI is enabled
115
+ // (tiers B/C). Tier A must stay unchanged even if the backend still sends
116
+ // an offer_text.
117
+ const hasOfferText = isNewUiEnabled && Boolean(serviceItem?.offer_text);
118
+ // Tier B: offer text only, no timer/viewers. Tier C: full ribbon.
119
+ const isOfferTextOnly = isNewUiEnabled && uiType === 1 && hasOfferText;
120
+ const isFullNewUi = isNewUiEnabled && uiType === 2;
103
121
  const offerGradient = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"} 0%, ${colors.leftGradiantColor || "#ff8842"} 100%)`;
104
122
  const offerGradientWithOpacity = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"}80 0%, ${colors.leftGradiantColor || "#ff8842"}80 100%)`;
105
123
  const serviceCardStyle: React.CSSProperties =
106
- isNewUiEnabled ||
107
- (serviceItem?.is_dp_enabled && serviceItem?.dp_discount_percents)
124
+ isFullNewUi || hasDpRibbon
108
125
  ? {
109
126
  borderColor: isSoldOut ? "#ccc" : "transparent",
110
127
  borderStyle: "solid",
@@ -220,10 +237,7 @@ function PeruServiceItemMobile({
220
237
  >
221
238
  <div
222
239
  className={`z-1 ${
223
- (isNewUiEnabled ||
224
- (serviceItem?.is_dp_enabled &&
225
- serviceItem?.dp_discount_percents)) &&
226
- !isSoldOut
240
+ (isFullNewUi || hasDpRibbon) && !isSoldOut
227
241
  ? "rounded-[18px]"
228
242
  : "rounded-[10px] border border-[#ccc]"
229
243
  }`}
@@ -398,55 +412,50 @@ function PeruServiceItemMobile({
398
412
  />
399
413
  </div>
400
414
 
401
- {/* 🔹 EXPANDABLE DROPDOWN (below the card) - Simple offer text when new UI is disabled */}
402
- {hasOfferText &&
403
- !isNewUiEnabled &&
404
- !isSoldOut &&
405
- !serviceItem?.is_dp_enabled && (
415
+ {/* 🔹 EXPANDABLE DROPDOWN (below the card) - Tier B: offer text only, no timer/viewers */}
416
+ {isOfferTextOnly && !isSoldOut && !hasDpRibbon && (
417
+ <div
418
+ className="px-[12px] pt-[22px] pb-[8px] relative -z-9 -mt-[15px]"
419
+ style={{
420
+ background: colors?.bottomStripColor,
421
+ borderRadius: "0 0 14px 14px",
422
+ zIndex: -1,
423
+ }}
424
+ >
406
425
  <div
407
- className="px-[12px] pt-[22px] pb-[8px] relative -z-9 -mt-[15px]"
408
- style={{
409
- background: colors?.bottomStripColor,
410
- borderRadius: "0 0 14px 14px",
411
- zIndex: -1,
412
- }}
426
+ className="flex flex-col gap-[8px] text-[12px] min-[420px]:text-[12px] text-[#464647]"
427
+ style={{ lineHeight: 1.6 }}
413
428
  >
414
- <div
415
- className="flex flex-col gap-[8px] text-[12px] min-[420px]:text-[12px] text-[#464647]"
416
- style={{ lineHeight: 1.6 }}
417
- >
418
- <div className="flex justify-between items-center">
429
+ <div className="flex justify-between items-center">
430
+ <div
431
+ className={`flex ${(serviceItem?.offer_text || "").length > 10 ? "items-start" : "items-center"}`}
432
+ >
433
+ <div className={isLongOfferText ? "mt-[2px]" : ""}>
434
+ <LottiePlayer
435
+ animationData={serviceItem?.icons?.promoAnim}
436
+ width="14px"
437
+ height="14px"
438
+ />
439
+ </div>
419
440
  <div
420
- className={`flex ${(serviceItem?.offer_text || "").length > 10 ? "items-start" : "items-center"}`}
441
+ className={`ml-[4px] flex-1 outline-none ${isLongOfferText ? "mt-[2px]" : ""}`}
442
+ style={{
443
+ color: "#fff",
444
+ lineHeight: 1.4,
445
+ }}
421
446
  >
422
- <div className={isLongOfferText ? "mt-[2px]" : ""}>
423
- <LottiePlayer
424
- animationData={serviceItem.icons?.promoAnim}
425
- width="14px"
426
- height="14px"
427
- />
428
- </div>
429
- <div
430
- className={`ml-[4px] flex-1 outline-none ${isLongOfferText ? "mt-[2px]" : ""}`}
431
- style={{
432
- color: "#fff",
433
- lineHeight: 1.4,
434
- }}
435
- >
436
- <span className="whitespace-nowrap min-[380px]:text-[12px]">
437
- {serviceItem?.offer_text}
438
- </span>
439
- </div>
447
+ <span className="whitespace-nowrap min-[380px]:text-[12px]">
448
+ {serviceItem?.offer_text}
449
+ </span>
440
450
  </div>
441
451
  </div>
442
452
  </div>
443
453
  </div>
444
- )}
454
+ </div>
455
+ )}
445
456
 
446
- {/* 🔹 EXPANDABLE DROPDOWN (below the card) - Countdown/registro when new UI is enabled */}
447
- {(isNewUiEnabled ||
448
- (serviceItem?.is_dp_enabled && serviceItem?.dp_discount_percents)) &&
449
- !isSoldOut && (
457
+ {/* 🔹 EXPANDABLE DROPDOWN (below the card) - Tier C / DP: full ribbon with timer, bomb animation, viewers */}
458
+ {(isFullNewUi || hasDpRibbon) && !isSoldOut && (
450
459
  <div
451
460
  className="px-[12px] pt-[22px] pb-[8px] relative -z-9 -mt-[15px]"
452
461
  style={{
@@ -493,25 +502,9 @@ function PeruServiceItemMobile({
493
502
  lineHeight: 1.4,
494
503
  }}
495
504
  >
496
- {!isNewUiEnabled && serviceItem?.offer_text ? (
497
- <span className="whitespace-nowrap min-[380px]:text-[12px]">
498
- {serviceItem?.offer_text}
499
- </span>
500
- ) : (
501
- <span className="whitespace-nowrap min-[380px]:text-[12px]">
502
- Termina en&nbsp;
503
- <span
504
- className="bold-text"
505
- ref={(node) =>
506
- commonService.startCountdown(node, 599)
507
- }
508
- style={{
509
- fontVariantNumeric: "tabular-nums",
510
- display: "inline-block",
511
- }}
512
- />
513
- </span>
514
- )}
505
+ <span className="whitespace-nowrap min-[380px]:text-[12px]">
506
+ {displayOfferText}
507
+ </span>
515
508
  </div>
516
509
  </div>
517
510
  )}
@@ -563,12 +556,7 @@ function PeruServiceItemMobile({
563
556
  transition:
564
557
  "grid-template-rows 0.3s ease-in-out, opacity 0.25s ease-in-out",
565
558
  position: "relative",
566
- zIndex:
567
- hasOfferText ||
568
- (serviceItem?.is_dp_enabled && serviceItem?.dp_discount_percents) ||
569
- isNewUiEnabled
570
- ? -2
571
- : -1,
559
+ zIndex: isOfferTextOnly || isFullNewUi || hasDpRibbon ? -2 : -1,
572
560
  }}
573
561
  >
574
562
  <div
@@ -576,15 +564,10 @@ function PeruServiceItemMobile({
576
564
  overflow: "hidden",
577
565
  minHeight: 0,
578
566
  position:
579
- hasOfferText || isNewUiEnabled || serviceItem?.is_dp_enabled
567
+ isOfferTextOnly || isFullNewUi || hasDpEnabled
580
568
  ? "relative"
581
569
  : undefined,
582
- zIndex:
583
- serviceItem?.offer_text ||
584
- isNewUiEnabled ||
585
- (serviceItem?.is_dp_enabled && serviceItem?.dp_discount_percents)
586
- ? -3
587
- : undefined,
570
+ zIndex: isOfferTextOnly || isFullNewUi || hasDpRibbon ? -3 : undefined,
588
571
  marginTop: hasOfferText ? "-15px" : "-10px",
589
572
  }}
590
573
  >
@@ -579,6 +579,7 @@ function ServiceItemPB({
579
579
  isLinatal={isLinatal}
580
580
  coachKey={coachKey}
581
581
  isExpand={isItemExpanded}
582
+ isPeru={isPeru}
582
583
  />
583
584
  )}
584
585
  <div
@@ -68,6 +68,7 @@ function ServiceItemMobile({
68
68
  isPeruSite,
69
69
  priority_stage_ids,
70
70
  }: MobileServiceItemProps): React.ReactElement {
71
+ console.log("🚀 ~ ServiceItemMobile ~ serviceItem:", serviceItem);
71
72
  const isItemExpanded = serviceItem.id === isExpanded;
72
73
  const isPetSeat = (Object.keys(serviceItem?.pet_seat_info) || []).length > 0;
73
74
  const isNewUiEnabled = serviceItem?.discounts?.[0]?.new_ui_enabled === true;
@@ -569,17 +570,22 @@ function ServiceItemMobile({
569
570
  }}
570
571
  >
571
572
  <span className="whitespace-nowrap min-[380px]:text-[12px]">
572
- Termina en&nbsp;
573
- <span
574
- className="bold-text"
575
- ref={(node) =>
576
- commonService.startCountdown(node, 599)
577
- }
578
- style={{
579
- fontVariantNumeric: "tabular-nums",
580
- display: "inline-block",
581
- }}
582
- />
573
+ {serviceItem?.ui_type === 2 && isPeru
574
+ ? serviceItem?.offer_text
575
+ : "Termina en"}
576
+ &nbsp;
577
+ {isPeru ? null : (
578
+ <span
579
+ className="bold-text"
580
+ ref={(node) =>
581
+ commonService.startCountdown(node, 599)
582
+ }
583
+ style={{
584
+ fontVariantNumeric: "tabular-nums",
585
+ display: "inline-block",
586
+ }}
587
+ />
588
+ )}
583
589
  </span>
584
590
  </div>
585
591
  </div>
@@ -35,6 +35,7 @@ interface OfferBannerProps {
35
35
  coachKey?: number;
36
36
  isExpand?: any;
37
37
  ui_type?: number;
38
+ isPeru?: boolean;
38
39
  }
39
40
 
40
41
  // ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -97,6 +98,7 @@ interface NewUiOfferBannerProps {
97
98
  isNewUiEnabled?: any;
98
99
  showLoginButton?: boolean;
99
100
  ui_type?: number;
101
+ isPeru?: boolean;
100
102
  }
101
103
 
102
104
  const NewUiOfferBanner: React.FC<NewUiOfferBannerProps> = ({
@@ -109,6 +111,7 @@ const NewUiOfferBanner: React.FC<NewUiOfferBannerProps> = ({
109
111
  isNewUiEnabled,
110
112
  showLoginButton,
111
113
  ui_type,
114
+ isPeru,
112
115
  }) => (
113
116
  <div className="flex items-center">
114
117
  <AnimationIcon getAnimationIcon={getAnimationIcon} name="bombAnimation" />
@@ -130,12 +133,17 @@ const NewUiOfferBanner: React.FC<NewUiOfferBannerProps> = ({
130
133
  &nbsp;
131
134
  </span>{" "}
132
135
  {offerText && ui_type !== 2 ? "| " : ui_type === 2 ? "" : ""}
133
- Termina en&nbsp;
134
- <span
135
- className="bold-text text-end"
136
- ref={(node) => CommonService.startCountdown(node, 599)}
137
- style={{ fontVariantNumeric: "tabular-nums", display: "inline-block" }}
138
- />
136
+ {ui_type === 2 && isPeru ? offerText : "Termina en"}&nbsp;
137
+ {isPeru ? null : (
138
+ <span
139
+ className="bold-text text-end"
140
+ ref={(node) => CommonService.startCountdown(node, 599)}
141
+ style={{
142
+ fontVariantNumeric: "tabular-nums",
143
+ display: "inline-block",
144
+ }}
145
+ />
146
+ )}
139
147
  </div>
140
148
  </div>
141
149
  );
@@ -224,6 +232,7 @@ const OfferBanner: React.FC<OfferBannerProps> = ({
224
232
  isLinatal,
225
233
  coachKey,
226
234
  isExpand,
235
+ isPeru,
227
236
  }) => {
228
237
  const isDiscountBelow20 =
229
238
  (serviceItem as any)?.discount_value > 0 &&
@@ -270,6 +279,7 @@ const OfferBanner: React.FC<OfferBannerProps> = ({
270
279
  isNewUiEnabled={isNewUiEnabled}
271
280
  showLoginButton={serviceItem?.show_register_button}
272
281
  ui_type={serviceItem?.ui_type}
282
+ isPeru={isPeru}
273
283
  />
274
284
  );
275
285
  }
@@ -278,7 +288,7 @@ const OfferBanner: React.FC<OfferBannerProps> = ({
278
288
 
279
289
  return (
280
290
  <div
281
- className={`text-white w-full absolute ${coachKey ? "-bottom-[32px]" : isExpand ? "-bottom-[32px]" : "-bottom-[40px]"} rounded-b-[14px] text-[14px] h-[58px] flex items-end pb-[7px]`}
291
+ className={`text-white w-full absolute ${coachKey ? "-bottom-[32px]" : isExpand ? "-bottom-[32px]" : "-bottom-[32px]"} rounded-b-[14px] text-[14px] h-[58px] flex items-end pb-[7px]`}
282
292
  style={{
283
293
  background:
284
294
  (isNewUiEnabled && serviceItem?.ui_type === 2) ||