kupos-ui-components-lib 10.4.29 → 10.4.31

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.
@@ -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.29",
3
+ "version": "10.4.31",
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
  >
@@ -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>
@@ -134,11 +134,16 @@ const NewUiOfferBanner: React.FC<NewUiOfferBannerProps> = ({
134
134
  </span>{" "}
135
135
  {offerText && ui_type !== 2 ? "| " : ui_type === 2 ? "" : ""}
136
136
  {ui_type === 2 && isPeru ? offerText : "Termina en"}&nbsp;
137
- <span
138
- className="bold-text text-end"
139
- ref={(node) => CommonService.startCountdown(node, 599)}
140
- style={{ fontVariantNumeric: "tabular-nums", display: "inline-block" }}
141
- />
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
+ )}
142
147
  </div>
143
148
  </div>
144
149
  );
@@ -33,6 +33,9 @@ interface PeruOfferBannerProps {
33
33
  coachKey?: number;
34
34
  isExpand?: any;
35
35
  isNewUiEnabled?: boolean;
36
+ // ui_type from the active discount entry: 1 = offer-text-only tier,
37
+ // 2 = full new-UI tier (timer, bomb animation, viewers).
38
+ uiType?: number;
36
39
  }
37
40
 
38
41
  // ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -121,19 +124,13 @@ const OfferBannerContent: React.FC<OfferBannerContentProps> = ({
121
124
  </span>
122
125
  )}{" "}
123
126
  &nbsp;
124
- </span>{" "}
125
- {offerText ? "| " : ""}
126
- Termina en&nbsp;
127
- <span
128
- className="bold-text text-end"
129
- ref={(node) => CommonService.startCountdown(node, 599)}
130
- style={{ fontVariantNumeric: "tabular-nums", display: "inline-block" }}
131
- />
127
+ </span>
132
128
  </div>
133
129
  </div>
134
130
  );
135
131
 
136
- // Shown for legacy promo offers
132
+ // Shown for the offer-text-only tier (ui_type 1): the announcement icon and
133
+ // the offer text, nothing else — no timer, no viewers.
137
134
  interface LegacyOfferBannerProps {
138
135
  offerText: string;
139
136
  getAnimationIcon: PeruOfferBannerProps["getAnimationIcon"];
@@ -207,6 +204,7 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
207
204
  offerGradient,
208
205
  isSoldOut,
209
206
  serviceItem,
207
+ renderIcon,
210
208
  isLoggedIn,
211
209
  showLoginModal,
212
210
  viewersConfig,
@@ -217,28 +215,32 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
217
215
  coachKey,
218
216
  isExpand,
219
217
  isNewUiEnabled,
218
+ uiType,
220
219
  }) => {
221
- const isDiscountBelow20 =
222
- (serviceItem as any)?.discount_value > 0 &&
223
- (serviceItem as any)?.discount_value < 20;
224
220
  const offerText = serviceItem?.offer_text || "";
221
+ const hasDp = hasDpDiscounts(serviceItem);
225
222
 
226
- const isLegacyOffer = !!offerText && !isNewUiEnabled;
223
+ // Tier B: new UI enabled, ui_type 1 — offer text only, no timer, no viewers.
224
+ const isOfferTextOnly = isNewUiEnabled && uiType === 1 && !!offerText;
225
+ // Tier C: new UI enabled, ui_type 2 — full ribbon (timer, bomb, viewers).
226
+ const isFullNewUi = isNewUiEnabled && uiType === 2;
227
227
 
228
- // const showViewers = serviceItem?.is_dp_enabled;
229
- const showViewers = isNewUiEnabled;
228
+ const showViewers = isFullNewUi || hasDp;
230
229
 
231
- const background = isLegacyOffer ? colors?.bottomStripColor : offerGradient;
230
+ const background = isOfferTextOnly
231
+ ? colors?.bottomStripColor || offerGradient
232
+ : offerGradient;
232
233
 
233
234
  const renderLeftContent = () => {
234
- const hasDp = hasDpDiscounts(serviceItem);
235
-
236
235
  if (serviceItem?.is_dp_enabled && !hasDp) {
237
236
  return <PopularServiceBanner getAnimationIcon={getAnimationIcon} />;
238
237
  }
239
238
 
240
- // If new UI is enabled, show offer with registro and countdown
241
- if (isNewUiEnabled || offerText || (serviceItem as any)?.discount_value) {
239
+ if (isOfferTextOnly) {
240
+ return <LegacyOfferBanner offerText={offerText} getAnimationIcon={getAnimationIcon}/>;
241
+ }
242
+
243
+ if (isFullNewUi || hasDp) {
242
244
  return (
243
245
  <OfferBannerContent
244
246
  offerText={offerText}
@@ -252,16 +254,6 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
252
254
  );
253
255
  }
254
256
 
255
- // If new UI is disabled, show offer text only
256
- if (offerText) {
257
- return (
258
- <LegacyOfferBanner
259
- offerText={offerText}
260
- getAnimationIcon={getAnimationIcon}
261
- />
262
- );
263
- }
264
-
265
257
  return null;
266
258
  };
267
259
 
@@ -269,12 +261,7 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
269
261
  <div
270
262
  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]`}
271
263
  style={{
272
- background:
273
- serviceItem?.is_dp_enabled ||
274
- serviceItem?.dp_discount_percents ||
275
- (serviceItem as any)?.discount_value
276
- ? offerGradient
277
- : background,
264
+ background,
278
265
  opacity: isSoldOut ? 0.5 : 1,
279
266
  zIndex: isLinatal ? "-1" : "0",
280
267
  }}