kupos-ui-components-lib 9.10.3 → 9.10.5

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.
@@ -391,10 +391,10 @@ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaDa
391
391
  : serviceItem.seat_types || [];
392
392
  const discountedSeats = seats.map((seat) => (Object.assign(Object.assign({}, seat), CommonService.calculateDiscountedPrice(seat.fare, serviceItem))));
393
393
  const hasDiscount = discountedSeats.some((seat) => seat.originalPrice !== seat.discountedPrice);
394
- return (React.createElement("div", { className: `relative hover:z-[150] ${hasOfferText ? "mb-[55px]" : "mb-[10px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
394
+ return (React.createElement("div", { className: `relative hover:z-[150] ${hasOfferText || hasDpEnabled || isNewUiEnabled ? "mb-[65px]" : "mb-[20px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
395
395
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.train_type_label) === "Tren Express (Nuevo)" ||
396
396
  showTopLabel
397
- ? "mt-[24px]"
397
+ ? "mt-[30px]"
398
398
  : "mt-[20px]"} ` },
399
399
  ((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || hasDpEnabled) && !isSoldOut && (React.createElement(OfferBanner, { offerGradient: offerGradient, isSoldOut: isSoldOut, serviceItem: serviceItem, renderIcon: renderIcon, isLoggedIn: isLoggedIn, showLoginModal: showLoginModal, viewersConfig: viewersConfig, getAnimationIcon: getAnimationIcon, showLoginOption: showLoginOption, isNewUiEnabled: isNewUiEnabled, colors: colors })),
400
400
  React.createElement("div", { id: `service-card-${serviceItem.id}`, className: `bg-white mx-auto relative ${(hasOfferText && isNewUiEnabled && !isSoldOut) || hasDpEnabled
@@ -261,7 +261,7 @@ function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, child
261
261
  setIsFeatureDropDownExpand(isFeatureDropDownExpand === serviceItem.id ||
262
262
  isFeatureDropDownExpand === true
263
263
  ? null
264
- : serviceItem.id), selectedTimeSlot: selectedTimeSlot, onTimeSlotChange: onTimeSlotChange, isTimeDropdownOpen: isTimeDropdownOpen, onTimeDropdownToggle: onTimeDropdownToggle, wowDealData: wowDealData })) : (React.createElement("div", { className: `relative hover:z-[150] ${hasOfferText || hasDpEnabled || isNewUiEnabled ? "mb-[55px]" : "mb-[10px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
264
+ : serviceItem.id), selectedTimeSlot: selectedTimeSlot, onTimeSlotChange: onTimeSlotChange, isTimeDropdownOpen: isTimeDropdownOpen, onTimeDropdownToggle: onTimeDropdownToggle, wowDealData: wowDealData })) : (React.createElement("div", { className: `relative hover:z-[150] ${hasOfferText || hasDpEnabled || isNewUiEnabled ? "mb-[65px]" : "mb-[20px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
265
265
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.train_type_label) === "Tren Express (Nuevo)" ||
266
266
  showTopLabel
267
267
  ? "mt-[30px]"
@@ -136,7 +136,10 @@ function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, o
136
136
  setIsExpanded(isItemExpanded ? null : serviceItem.id);
137
137
  }, isPeru: isPeru, femaleAnim: serviceItem.icons.femaleAnim, ladiesBookedSeats: serviceItem.ladies_booked_seats, isDpEnabled: serviceItem.is_dp_enabled })),
138
138
  React.createElement(ServiceBadgesMobile, { showTopLabel: showTopLabel, isSoldOut: isSoldOut, colors: colors, renderIcon: renderIcon, serviceItem: serviceItem, isConexion: isConexion })),
139
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) && !isNewUiEnabled && !isSoldOut && (React.createElement("div", { className: "px-[12px] pt-[22px] pb-[8px] relative -z-9 -mt-[15px]", style: {
139
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) &&
140
+ !isNewUiEnabled &&
141
+ !isSoldOut &&
142
+ !(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) && (React.createElement("div", { className: "px-[12px] pt-[22px] pb-[8px] relative -z-9 -mt-[15px]", style: {
140
143
  background: colors === null || colors === void 0 ? void 0 : colors.bottomStripColor,
141
144
  borderRadius: "0 0 14px 14px",
142
145
  zIndex: -1,
@@ -15,9 +15,9 @@ const SurveyDesktop = ({ isOpen, isSubmitted, selectedScore, onScoreChange, feed
15
15
  React.createElement(React.Fragment, null,
16
16
  React.createElement("button", { onClick: onClose, "aria-label": "Close survey", className: "absolute top-[15px] right-[25px] bg-transparent border-none cursor-pointer text-[22px] text-gray-400 flex items-center justify-center p-1 z-10 transition-colors duration-200 hover:text-gray-600" },
17
17
  React.createElement("img", { src: icons.closeIcon, alt: "Close", className: "w-[16px] h-[16px] block" })),
18
- (icons === null || icons === void 0 ? void 0 : icons.surveyIcon) && (React.createElement("div", { className: "flex justify-center mb-3 mt-2" },
18
+ (icons === null || icons === void 0 ? void 0 : icons.surveyIcon) && (React.createElement("div", { className: "flex justify-center mb-2 mt-2" },
19
19
  React.createElement("img", { src: icons.surveyIcon, alt: "Survey Illustration", className: "w-[90px] h-[90px] block" }))),
20
- React.createElement("h2", { className: "text-[18px] bold-text leading-[1.25] text-center mt-4 mb-2" }, "Ay\u00FAdanos a mejorar"),
20
+ React.createElement("h2", { className: "text-[18px] bold-text leading-[1.25] text-center mb-2" }, "Ay\u00FAdanos a mejorar"),
21
21
  React.createElement("p", { className: "text-[13.33px] text-center leading-[1.4] mb-6 max-w-[460px] mx-auto" },
22
22
  "Bas\u00E1ndote en tu experiencia de compra.",
23
23
  React.createElement("br", null),
@@ -15,17 +15,17 @@ const SurveyMobile = ({ isOpen, isSubmitted, selectedScore, onScoreChange, feedb
15
15
  React.createElement(React.Fragment, null,
16
16
  React.createElement("button", { onClick: onClose, "aria-label": "Close survey", className: "absolute top-[15px] right-[25px] bg-transparent border-none cursor-pointer text-[22px] text-gray-400 flex items-center justify-center p-1 z-10 transition-colors duration-200 hover:text-gray-600" },
17
17
  React.createElement("img", { src: icons.closeIcon, alt: "Close", className: "w-[16px] h-[16px] block" })),
18
- (icons === null || icons === void 0 ? void 0 : icons.surveyIcon) && (React.createElement("div", { className: "flex justify-center mb-3 mt-2" },
18
+ (icons === null || icons === void 0 ? void 0 : icons.surveyIcon) && (React.createElement("div", { className: "flex justify-center mb-2 mt-2" },
19
19
  React.createElement("img", { src: icons.surveyIcon, alt: "Survey Illustration", className: "w-[90px] h-[90px] block" }))),
20
- React.createElement("h2", { className: "text-[18px] bold-text leading-[1.25] text-center mt-4 mb-2" }, "Ay\u00FAdanos a mejorar"),
21
- React.createElement("p", { className: "text-[13.33px] text-center leading-[1.4] mb-6 max-w-[460px] mx-auto" },
20
+ React.createElement("h2", { className: "text-[18px] bold-text leading-[1.25] text-center mb-2" }, "Ay\u00FAdanos a mejorar"),
21
+ React.createElement("p", { className: "text-[13.33px] text-center leading-[1.4] mb-8 max-w-[460px] mx-auto" },
22
22
  "Bas\u00E1ndote en tu experiencia de compra.",
23
23
  React.createElement("br", null),
24
24
  "\u00BFNos recomendar\u00EDas a un amigo?"),
25
25
  React.createElement(ScoreButtons, { selectedScore: selectedScore, onScoreChange: onScoreChange, buttonHeight: 44, fontSize: 13, gap: 6, colors: colors }),
26
26
  React.createElement(FeedbackTextarea, { config: config, feedback: feedback, onFeedbackChange: onFeedbackChange }),
27
- React.createElement("div", { className: "flex justify-center mt-[20px]" },
28
- React.createElement("div", { className: "w-[180px]" },
27
+ React.createElement("div", { className: "flex justify-center mt-[50px] mb-[50px]" },
28
+ React.createElement("div", { className: "w-[100px]" },
29
29
  React.createElement(KuposButton, { isSoldOut: selectedScore == null, isLoading: isLoading || false, buttonColor: "#FF8E43", buyLabel: "Enviar", soldOutLabel: "Enviar", onClick: handleSubmit }))))));
30
30
  };
31
31
  export default SurveyMobile;
package/dist/styles.css CHANGED
@@ -246,6 +246,9 @@
246
246
  .mt-\[30px\] {
247
247
  margin-top: 30px;
248
248
  }
249
+ .mt-\[50px\] {
250
+ margin-top: 50px;
251
+ }
249
252
  .-mr-\[12px\] {
250
253
  margin-right: calc(12px * -1);
251
254
  }
@@ -306,6 +309,9 @@
306
309
  .mb-\[55px\] {
307
310
  margin-bottom: 55px;
308
311
  }
312
+ .mb-\[65px\] {
313
+ margin-bottom: 65px;
314
+ }
309
315
  .-ml-\[12px\] {
310
316
  margin-left: calc(12px * -1);
311
317
  }
@@ -1,17 +1,21 @@
1
1
  import React from "react";
2
2
  import { ServiceItemProps } from "../components/ServiceItem/types";
3
+ type ServiceItemSlice = Pick<ServiceItemProps["serviceItem"], "is_dp_enabled" | "offer_text" | "dp_discount_percents" | "dp_discounted_seats">;
4
+ interface OfferBannerColors {
5
+ bottomStripColor?: string;
6
+ }
3
7
  interface OfferBannerProps {
4
8
  offerGradient: string;
5
9
  isSoldOut: boolean;
6
- serviceItem: Pick<ServiceItemProps["serviceItem"], "is_dp_enabled" | "offer_text" | "dp_discount_percents" | "dp_discounted_seats">;
10
+ serviceItem: ServiceItemSlice;
7
11
  renderIcon: (name: string, size: string) => React.ReactNode;
8
- isLoggedIn: any;
9
- showLoginModal: any;
12
+ isLoggedIn: boolean;
13
+ showLoginModal: () => void;
10
14
  viewersConfig: ServiceItemProps["viewersConfig"];
11
- getAnimationIcon: (name: string) => any;
15
+ getAnimationIcon: (name: string) => unknown;
12
16
  showLoginOption?: boolean;
13
17
  isNewUiEnabled?: boolean;
14
- colors: any;
18
+ colors: OfferBannerColors;
15
19
  }
16
20
  declare const OfferBanner: React.FC<OfferBannerProps>;
17
21
  export default OfferBanner;
@@ -1,66 +1,80 @@
1
1
  import React from "react";
2
2
  import LottiePlayer from "../assets/LottiePlayer";
3
3
  import CommonService from "../utils/CommonService";
4
- const OfferBanner = ({ offerGradient, isSoldOut, serviceItem, renderIcon, isLoggedIn, showLoginModal, viewersConfig, getAnimationIcon, showLoginOption, isNewUiEnabled, colors, }) => {
5
- var _a, _b, _c, _d;
6
- return (React.createElement("div", { className: "text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[44px] pt-[50px] rounded-b-[14px] text-[14px] mt-[10px]", style: {
7
- background: (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) && !isNewUiEnabled
8
- ? colors === null || colors === void 0 ? void 0 : colors.bottomStripColor
9
- : offerGradient,
10
- opacity: isSoldOut ? 0.5 : 1,
11
- // zIndex: 0,
12
- } },
13
- React.createElement("div", { className: "flex justify-between items-center w-full" },
14
- React.createElement("div", { className: "flex items-center " }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) &&
15
- Object.keys((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents) !== null && _a !== void 0 ? _a : {}).length === 0 &&
16
- ((_b = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) !== null && _b !== void 0 ? _b : []).length === 0 ? (React.createElement("div", { className: "flex items-center gap-[5px]" },
17
- React.createElement(LottiePlayer, { animationData: getAnimationIcon("starAnimation"), width: "18px", height: "18px" }),
18
- React.createElement("span", null, "Servicio popular entre los usuarios"))) : isNewUiEnabled && (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) ? (React.createElement("div", { className: "flex items-center" },
19
- React.createElement(LottiePlayer, { animationData: getAnimationIcon("bombAnimation"), width: "18px", height: "18px" }),
20
- React.createElement("div", { className: "flex items-center mt-[2px]" },
21
- React.createElement("span", { className: "bold-text", style: {
22
- marginLeft: (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) ? "6px" : "3px",
23
- } },
24
- ((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || "").length > 30
25
- ? ((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || "").slice(0, 30) + "..."
26
- : (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || "",
27
- " ",
28
- isLoggedIn && showLoginOption ? null : (React.createElement("span", { onClick: showLoginModal, className: "cursor-pointer" }, "- registro")),
29
- " ",
30
- "\u00A0"),
4
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
5
+ const OFFER_TEXT_MAX_LENGTH = 30;
6
+ function truncateOfferText(text) {
7
+ return text.length > OFFER_TEXT_MAX_LENGTH
8
+ ? `${text.slice(0, OFFER_TEXT_MAX_LENGTH)}...`
9
+ : text;
10
+ }
11
+ function hasDpDiscounts(serviceItem) {
12
+ var _a, _b;
13
+ return (Object.keys((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents) !== null && _a !== void 0 ? _a : {}).length > 0 ||
14
+ ((_b = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) !== null && _b !== void 0 ? _b : []).length > 0);
15
+ }
16
+ const AnimationIcon = ({ getAnimationIcon, name, width = "18px", height = "18px", }) => (React.createElement(LottiePlayer, { animationData: getAnimationIcon(name), width: width, height: height }));
17
+ // Shown when DP is enabled but no discounts / discounted seats are present
18
+ const PopularServiceBanner = ({ getAnimationIcon }) => (React.createElement("div", { className: "flex items-center gap-[5px]" },
19
+ React.createElement(AnimationIcon, { getAnimationIcon: getAnimationIcon, name: "starAnimation" }),
20
+ React.createElement("span", null, "Servicio popular entre los usuarios")));
21
+ const NewUiOfferBanner = ({ offerText, isLoggedIn, showLoginModal, showLoginOption, getAnimationIcon, hideRegister, }) => (React.createElement("div", { className: "flex items-center" },
22
+ React.createElement(AnimationIcon, { getAnimationIcon: getAnimationIcon, name: "bombAnimation" }),
23
+ React.createElement("div", { className: "flex items-center mt-[2px]" },
24
+ React.createElement("span", { className: "bold-text", style: { marginLeft: offerText ? "6px" : "3px" } },
25
+ truncateOfferText(offerText),
26
+ " ",
27
+ !hideRegister &&
28
+ (isLoggedIn && showLoginOption ? null : (React.createElement("span", { onClick: showLoginModal, className: "cursor-pointer" }, "- registro"))),
29
+ " ",
30
+ "\u00A0"),
31
+ " ",
32
+ offerText ? "| " : "",
33
+ "Termina en\u00A0",
34
+ React.createElement("span", { className: "bold-text text-end", ref: (node) => CommonService.startCountdown(node, 599), style: { fontVariantNumeric: "tabular-nums", display: "inline-block" } }))));
35
+ const LegacyOfferBanner = ({ offerText, getAnimationIcon, }) => (React.createElement("div", { className: "flex items-center" },
36
+ React.createElement(AnimationIcon, { getAnimationIcon: getAnimationIcon, name: "promoAnim" }),
37
+ React.createElement("div", { className: "flex items-center mt-[2px]" },
38
+ React.createElement("span", { className: "bold-text", style: { marginLeft: offerText ? "6px" : "3px" } }, offerText))));
39
+ const ViewersCount = ({ serviceItem, viewersConfig, getAnimationIcon, }) => {
40
+ const showScarcity = hasDpDiscounts(serviceItem);
41
+ return (React.createElement("div", { className: "flex items-center" },
42
+ React.createElement(AnimationIcon, { getAnimationIcon: getAnimationIcon, name: "dotAnimation", width: "12px", height: "12px" }),
43
+ React.createElement("span", { className: "ml-[6px]" },
44
+ React.createElement("span", { className: "bold-text", ref: (node) => CommonService.startViewerCount(node, viewersConfig), style: { fontVariantNumeric: "tabular-nums" } }),
45
+ " ",
46
+ React.createElement("span", null,
47
+ (viewersConfig === null || viewersConfig === void 0 ? void 0 : viewersConfig.label) || " viendo",
48
+ " |",
49
+ " ",
50
+ React.createElement("span", null,
51
+ showScarcity && "Quedan pocos • ",
52
+ React.createElement("span", { className: "bold-text", ref: (node) => CommonService.startComprandoCount(node, 4, 16), style: { fontVariantNumeric: "tabular-nums" } }),
31
53
  " ",
32
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) ? "|" : "",
33
- "Termina en\u00A0",
34
- React.createElement("span", { className: "bold-text text-end", ref: (node) => CommonService.startCountdown(node, 599), style: {
35
- fontVariantNumeric: "tabular-nums",
36
- display: "inline-block",
37
- } })))) : ((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) &&
38
- !isNewUiEnabled && (React.createElement("div", { className: "flex items-center" },
39
- React.createElement(LottiePlayer, { animationData: getAnimationIcon("promoAnim"), width: "18px", height: "18px" }),
40
- React.createElement("div", { className: "flex items-center mt-[2px]" },
41
- React.createElement("span", { className: "bold-text", style: {
42
- marginLeft: (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) ? "6px" : "3px",
43
- } }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || ""),
44
- " "))))),
45
- (isNewUiEnabled || (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled)) && (React.createElement("div", { className: "flex items-center" },
46
- React.createElement(LottiePlayer, { animationData: getAnimationIcon("dotAnimation"), width: "12px", height: "12px" }),
47
- React.createElement("span", { className: "ml-[6px]" },
48
- React.createElement("span", { className: "bold-text", ref: (node) => CommonService.startViewerCount(node, viewersConfig), style: { fontVariantNumeric: "tabular-nums" } }),
49
- " ",
50
- React.createElement("span", null,
51
- " ",
52
- (viewersConfig === null || viewersConfig === void 0 ? void 0 : viewersConfig.label) || " viendo",
53
- " |",
54
- " ",
55
- React.createElement("span", { className: "" },
56
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) &&
57
- Object.keys((_c = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents) !== null && _c !== void 0 ? _c : {})
58
- .length === 0 &&
59
- ((_d = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) !== null && _d !== void 0 ? _d : []).length === 0
60
- ? null
61
- : "Quedan pocos • ",
62
- React.createElement("span", { className: "bold-text", ref: (node) => CommonService.startComprandoCount(node, 4, 16), style: { fontVariantNumeric: "tabular-nums" } }),
63
- " ",
64
- "comprando"))))))));
54
+ "comprando")))));
55
+ };
56
+ // ─── Main Component ───────────────────────────────────────────────────────────
57
+ const OfferBanner = ({ offerGradient, isSoldOut, serviceItem, isLoggedIn, showLoginModal, viewersConfig, getAnimationIcon, showLoginOption, isNewUiEnabled, colors, }) => {
58
+ const isLegacyOffer = !!(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) && !isNewUiEnabled && !(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled);
59
+ const isDpEnabledWithoutDiscounts = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) && !hasDpDiscounts(serviceItem);
60
+ const showViewers = isNewUiEnabled || (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled);
61
+ const background = isLegacyOffer ? colors === null || colors === void 0 ? void 0 : colors.bottomStripColor : offerGradient;
62
+ const renderLeftContent = () => {
63
+ if (isDpEnabledWithoutDiscounts) {
64
+ return React.createElement(PopularServiceBanner, { getAnimationIcon: getAnimationIcon });
65
+ }
66
+ const hasDp = hasDpDiscounts(serviceItem);
67
+ if (hasDp || (isNewUiEnabled && (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text))) {
68
+ return (React.createElement(NewUiOfferBanner, { offerText: serviceItem.offer_text || "", isLoggedIn: isLoggedIn, showLoginModal: showLoginModal, showLoginOption: showLoginOption, getAnimationIcon: getAnimationIcon, hideRegister: hasDp }));
69
+ }
70
+ if (isLegacyOffer) {
71
+ return (React.createElement(LegacyOfferBanner, { offerText: serviceItem.offer_text, getAnimationIcon: getAnimationIcon }));
72
+ }
73
+ return null;
74
+ };
75
+ return (React.createElement("div", { className: "text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[44px] pt-[50px] rounded-b-[14px] text-[14px] mt-[10px]", style: { background, opacity: isSoldOut ? 0.5 : 1 } },
76
+ React.createElement("div", { className: "flex justify-between items-center w-full" },
77
+ React.createElement("div", { className: "flex items-center" }, renderLeftContent()),
78
+ showViewers && (React.createElement(ViewersCount, { serviceItem: serviceItem, viewersConfig: viewersConfig, getAnimationIcon: getAnimationIcon })))));
65
79
  };
66
80
  export default OfferBanner;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.10.3",
3
+ "version": "9.10.5",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -605,11 +605,11 @@ function PeruServiceItemDesktop({
605
605
 
606
606
  return (
607
607
  <div
608
- className={`relative hover:z-[150] ${hasOfferText ? "mb-[55px]" : "mb-[10px]"} ${
608
+ className={`relative hover:z-[150] ${hasOfferText || hasDpEnabled || isNewUiEnabled ? "mb-[65px]" : "mb-[20px]"} ${
609
609
  serviceItem?.is_direct_trip ||
610
610
  serviceItem?.train_type_label === "Tren Express (Nuevo)" ||
611
611
  showTopLabel
612
- ? "mt-[24px]"
612
+ ? "mt-[30px]"
613
613
  : "mt-[20px]"
614
614
  } `}
615
615
  >
@@ -487,7 +487,7 @@ function ServiceItemPB({
487
487
  />
488
488
  ) : (
489
489
  <div
490
- className={`relative hover:z-[150] ${hasOfferText || hasDpEnabled || isNewUiEnabled ? "mb-[55px]" : "mb-[10px]"} ${
490
+ className={`relative hover:z-[150] ${hasOfferText || hasDpEnabled || isNewUiEnabled ? "mb-[65px]" : "mb-[20px]"} ${
491
491
  serviceItem?.is_direct_trip ||
492
492
  serviceItem?.train_type_label === "Tren Express (Nuevo)" ||
493
493
  showTopLabel
@@ -386,46 +386,49 @@ function ServiceItemMobile({
386
386
  </div>
387
387
 
388
388
  {/* 🔹 EXPANDABLE DROPDOWN (below the card) */}
389
- {serviceItem?.offer_text && !isNewUiEnabled && !isSoldOut && (
390
- <div
391
- className="px-[12px] pt-[22px] pb-[8px] relative -z-9 -mt-[15px]"
392
- style={{
393
- background: colors?.bottomStripColor,
394
- borderRadius: "0 0 14px 14px",
395
- zIndex: -1,
396
- }}
397
- >
389
+ {serviceItem?.offer_text &&
390
+ !isNewUiEnabled &&
391
+ !isSoldOut &&
392
+ !serviceItem?.is_dp_enabled && (
398
393
  <div
399
- className="flex flex-col gap-[8px] text-[12px] min-[420px]:text-[12px] text-[#464647]"
400
- style={{ lineHeight: 1.6 }}
394
+ className="px-[12px] pt-[22px] pb-[8px] relative -z-9 -mt-[15px]"
395
+ style={{
396
+ background: colors?.bottomStripColor,
397
+ borderRadius: "0 0 14px 14px",
398
+ zIndex: -1,
399
+ }}
401
400
  >
402
- <div className="flex justify-between items-center">
403
- <div
404
- className={`flex ${(serviceItem?.offer_text || "").length > 10 ? "items-start" : "items-center"}`}
405
- >
406
- <div className={isLongOfferText ? "mt-[2px]" : ""}>
407
- <LottiePlayer
408
- animationData={serviceItem.icons.promoAnim}
409
- width="14px"
410
- height="14px"
411
- />
412
- </div>
401
+ <div
402
+ className="flex flex-col gap-[8px] text-[12px] min-[420px]:text-[12px] text-[#464647]"
403
+ style={{ lineHeight: 1.6 }}
404
+ >
405
+ <div className="flex justify-between items-center">
413
406
  <div
414
- className={`ml-[4px] flex-1 outline-none ${isLongOfferText ? "mt-[2px]" : ""}`}
415
- style={{
416
- color: "#fff",
417
- lineHeight: 1.4,
418
- }}
407
+ className={`flex ${(serviceItem?.offer_text || "").length > 10 ? "items-start" : "items-center"}`}
419
408
  >
420
- <span className="whitespace-nowrap min-[380px]:text-[12px]">
421
- {serviceItem?.offer_text}
422
- </span>
409
+ <div className={isLongOfferText ? "mt-[2px]" : ""}>
410
+ <LottiePlayer
411
+ animationData={serviceItem.icons.promoAnim}
412
+ width="14px"
413
+ height="14px"
414
+ />
415
+ </div>
416
+ <div
417
+ className={`ml-[4px] flex-1 outline-none ${isLongOfferText ? "mt-[2px]" : ""}`}
418
+ style={{
419
+ color: "#fff",
420
+ lineHeight: 1.4,
421
+ }}
422
+ >
423
+ <span className="whitespace-nowrap min-[380px]:text-[12px]">
424
+ {serviceItem?.offer_text}
425
+ </span>
426
+ </div>
423
427
  </div>
424
428
  </div>
425
429
  </div>
426
430
  </div>
427
- </div>
428
- )}
431
+ )}
429
432
 
430
433
  {/* 🔹 EXPANDABLE DROPDOWN (below the card) */}
431
434
  {((serviceItem?.offer_text && isNewUiEnabled) ||
@@ -63,7 +63,7 @@ const SurveyDesktop = ({
63
63
 
64
64
  {/* Centered Illustration */}
65
65
  {icons?.surveyIcon && (
66
- <div className="flex justify-center mb-3 mt-2">
66
+ <div className="flex justify-center mb-2 mt-2">
67
67
  <img
68
68
  src={icons.surveyIcon}
69
69
  alt="Survey Illustration"
@@ -73,7 +73,7 @@ const SurveyDesktop = ({
73
73
  )}
74
74
 
75
75
  {/* Centered Title */}
76
- <h2 className="text-[18px] bold-text leading-[1.25] text-center mt-4 mb-2">
76
+ <h2 className="text-[18px] bold-text leading-[1.25] text-center mb-2">
77
77
  Ayúdanos a mejorar
78
78
  </h2>
79
79
 
@@ -66,7 +66,7 @@ const SurveyMobile = ({
66
66
 
67
67
  {/* Centered Illustration */}
68
68
  {icons?.surveyIcon && (
69
- <div className="flex justify-center mb-3 mt-2">
69
+ <div className="flex justify-center mb-2 mt-2">
70
70
  <img
71
71
  src={icons.surveyIcon}
72
72
  alt="Survey Illustration"
@@ -76,12 +76,12 @@ const SurveyMobile = ({
76
76
  )}
77
77
 
78
78
  {/* Centered Title */}
79
- <h2 className="text-[18px] bold-text leading-[1.25] text-center mt-4 mb-2">
79
+ <h2 className="text-[18px] bold-text leading-[1.25] text-center mb-2">
80
80
  Ayúdanos a mejorar
81
81
  </h2>
82
82
 
83
83
  {/* Centered Subtitle */}
84
- <p className="text-[13.33px] text-center leading-[1.4] mb-6 max-w-[460px] mx-auto">
84
+ <p className="text-[13.33px] text-center leading-[1.4] mb-8 max-w-[460px] mx-auto">
85
85
  Basándote en tu experiencia de compra.
86
86
  <br />
87
87
  ¿Nos recomendarías a un amigo?
@@ -104,8 +104,8 @@ const SurveyMobile = ({
104
104
  onFeedbackChange={onFeedbackChange}
105
105
  />
106
106
 
107
- <div className="flex justify-center mt-[20px]">
108
- <div className="w-[180px]">
107
+ <div className="flex justify-center mt-[50px] mb-[50px]">
108
+ <div className="w-[100px]">
109
109
  <KuposButton
110
110
  isSoldOut={selectedScore == null}
111
111
  isLoading={isLoading || false}
@@ -3,31 +3,202 @@ import LottiePlayer from "../assets/LottiePlayer";
3
3
  import CommonService from "../utils/CommonService";
4
4
  import { ServiceItemProps } from "../components/ServiceItem/types";
5
5
 
6
+ // ─── Types ────────────────────────────────────────────────────────────────────
7
+
8
+ type ServiceItemSlice = Pick<
9
+ ServiceItemProps["serviceItem"],
10
+ | "is_dp_enabled"
11
+ | "offer_text"
12
+ | "dp_discount_percents"
13
+ | "dp_discounted_seats"
14
+ >;
15
+
16
+ interface OfferBannerColors {
17
+ bottomStripColor?: string;
18
+ }
19
+
6
20
  interface OfferBannerProps {
7
21
  offerGradient: string;
8
22
  isSoldOut: boolean;
9
- serviceItem: Pick<
10
- ServiceItemProps["serviceItem"],
11
- | "is_dp_enabled"
12
- | "offer_text"
13
- | "dp_discount_percents"
14
- | "dp_discounted_seats"
15
- >;
23
+ serviceItem: ServiceItemSlice;
16
24
  renderIcon: (name: string, size: string) => React.ReactNode;
17
- isLoggedIn: any;
18
- showLoginModal: any;
25
+ isLoggedIn: boolean;
26
+ showLoginModal: () => void;
19
27
  viewersConfig: ServiceItemProps["viewersConfig"];
20
- getAnimationIcon: (name: string) => any;
28
+ getAnimationIcon: (name: string) => unknown;
21
29
  showLoginOption?: boolean;
22
30
  isNewUiEnabled?: boolean;
23
- colors: any;
31
+ colors: OfferBannerColors;
32
+ }
33
+
34
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
35
+
36
+ const OFFER_TEXT_MAX_LENGTH = 30;
37
+
38
+ function truncateOfferText(text: string): string {
39
+ return text.length > OFFER_TEXT_MAX_LENGTH
40
+ ? `${text.slice(0, OFFER_TEXT_MAX_LENGTH)}...`
41
+ : text;
42
+ }
43
+
44
+ function hasDpDiscounts(serviceItem: ServiceItemSlice): boolean {
45
+ return (
46
+ Object.keys(serviceItem?.dp_discount_percents ?? {}).length > 0 ||
47
+ (serviceItem?.dp_discounted_seats ?? []).length > 0
48
+ );
49
+ }
50
+
51
+ // ─── Sub-components ───────────────────────────────────────────────────────────
52
+
53
+ interface AnimationIconProps {
54
+ getAnimationIcon: OfferBannerProps["getAnimationIcon"];
55
+ name: string;
56
+ width?: string;
57
+ height?: string;
58
+ }
59
+
60
+ const AnimationIcon: React.FC<AnimationIconProps> = ({
61
+ getAnimationIcon,
62
+ name,
63
+ width = "18px",
64
+ height = "18px",
65
+ }) => (
66
+ <LottiePlayer
67
+ animationData={getAnimationIcon(name)}
68
+ width={width}
69
+ height={height}
70
+ />
71
+ );
72
+
73
+ // Shown when DP is enabled but no discounts / discounted seats are present
74
+ const PopularServiceBanner: React.FC<{
75
+ getAnimationIcon: OfferBannerProps["getAnimationIcon"];
76
+ }> = ({ getAnimationIcon }) => (
77
+ <div className="flex items-center gap-[5px]">
78
+ <AnimationIcon getAnimationIcon={getAnimationIcon} name="starAnimation" />
79
+ <span>Servicio popular entre los usuarios</span>
80
+ </div>
81
+ );
82
+
83
+ // Shown when the new UI is enabled and an offer text exists
84
+ interface NewUiOfferBannerProps {
85
+ offerText: string;
86
+ isLoggedIn: boolean;
87
+ showLoginModal: () => void;
88
+ showLoginOption?: boolean;
89
+ getAnimationIcon: OfferBannerProps["getAnimationIcon"];
90
+ hideRegister?: boolean;
24
91
  }
25
92
 
93
+ const NewUiOfferBanner: React.FC<NewUiOfferBannerProps> = ({
94
+ offerText,
95
+ isLoggedIn,
96
+ showLoginModal,
97
+ showLoginOption,
98
+ getAnimationIcon,
99
+ hideRegister,
100
+ }) => (
101
+ <div className="flex items-center">
102
+ <AnimationIcon getAnimationIcon={getAnimationIcon} name="bombAnimation" />
103
+ <div className="flex items-center mt-[2px]">
104
+ <span
105
+ className="bold-text"
106
+ style={{ marginLeft: offerText ? "6px" : "3px" }}
107
+ >
108
+ {truncateOfferText(offerText)}{" "}
109
+ {!hideRegister &&
110
+ (isLoggedIn && showLoginOption ? null : (
111
+ <span onClick={showLoginModal} className="cursor-pointer">
112
+ - registro
113
+ </span>
114
+ ))}{" "}
115
+ &nbsp;
116
+ </span>{" "}
117
+ {offerText ? "| " : ""}
118
+ Termina en&nbsp;
119
+ <span
120
+ className="bold-text text-end"
121
+ ref={(node) => CommonService.startCountdown(node, 599)}
122
+ style={{ fontVariantNumeric: "tabular-nums", display: "inline-block" }}
123
+ />
124
+ </div>
125
+ </div>
126
+ );
127
+
128
+ // Shown for legacy (non-new-UI) promo offers
129
+ interface LegacyOfferBannerProps {
130
+ offerText: string;
131
+ getAnimationIcon: OfferBannerProps["getAnimationIcon"];
132
+ }
133
+
134
+ const LegacyOfferBanner: React.FC<LegacyOfferBannerProps> = ({
135
+ offerText,
136
+ getAnimationIcon,
137
+ }) => (
138
+ <div className="flex items-center">
139
+ <AnimationIcon getAnimationIcon={getAnimationIcon} name="promoAnim" />
140
+ <div className="flex items-center mt-[2px]">
141
+ <span
142
+ className="bold-text"
143
+ style={{ marginLeft: offerText ? "6px" : "3px" }}
144
+ >
145
+ {offerText}
146
+ </span>
147
+ </div>
148
+ </div>
149
+ );
150
+
151
+ // Right-side viewers / buying count
152
+ interface ViewersCountProps {
153
+ serviceItem: ServiceItemSlice;
154
+ viewersConfig: ServiceItemProps["viewersConfig"];
155
+ getAnimationIcon: OfferBannerProps["getAnimationIcon"];
156
+ }
157
+
158
+ const ViewersCount: React.FC<ViewersCountProps> = ({
159
+ serviceItem,
160
+ viewersConfig,
161
+ getAnimationIcon,
162
+ }) => {
163
+ const showScarcity = hasDpDiscounts(serviceItem);
164
+
165
+ return (
166
+ <div className="flex items-center">
167
+ <AnimationIcon
168
+ getAnimationIcon={getAnimationIcon}
169
+ name="dotAnimation"
170
+ width="12px"
171
+ height="12px"
172
+ />
173
+ <span className="ml-[6px]">
174
+ <span
175
+ className="bold-text"
176
+ ref={(node) => CommonService.startViewerCount(node, viewersConfig)}
177
+ style={{ fontVariantNumeric: "tabular-nums" }}
178
+ />{" "}
179
+ <span>
180
+ {viewersConfig?.label || " viendo"} |{" "}
181
+ <span>
182
+ {showScarcity && "Quedan pocos • "}
183
+ <span
184
+ className="bold-text"
185
+ ref={(node) => CommonService.startComprandoCount(node, 4, 16)}
186
+ style={{ fontVariantNumeric: "tabular-nums" }}
187
+ />{" "}
188
+ comprando
189
+ </span>
190
+ </span>
191
+ </span>
192
+ </div>
193
+ );
194
+ };
195
+
196
+ // ─── Main Component ───────────────────────────────────────────────────────────
197
+
26
198
  const OfferBanner: React.FC<OfferBannerProps> = ({
27
199
  offerGradient,
28
200
  isSoldOut,
29
201
  serviceItem,
30
- renderIcon,
31
202
  isLoggedIn,
32
203
  showLoginModal,
33
204
  viewersConfig,
@@ -36,132 +207,59 @@ const OfferBanner: React.FC<OfferBannerProps> = ({
36
207
  isNewUiEnabled,
37
208
  colors,
38
209
  }) => {
210
+ const isLegacyOffer =
211
+ !!serviceItem?.offer_text && !isNewUiEnabled && !serviceItem?.is_dp_enabled;
212
+
213
+ const isDpEnabledWithoutDiscounts =
214
+ serviceItem?.is_dp_enabled && !hasDpDiscounts(serviceItem);
215
+
216
+ const showViewers = isNewUiEnabled || serviceItem?.is_dp_enabled;
217
+
218
+ const background = isLegacyOffer ? colors?.bottomStripColor : offerGradient;
219
+
220
+ const renderLeftContent = () => {
221
+ if (isDpEnabledWithoutDiscounts) {
222
+ return <PopularServiceBanner getAnimationIcon={getAnimationIcon} />;
223
+ }
224
+
225
+ const hasDp = hasDpDiscounts(serviceItem);
226
+
227
+ if (hasDp || (isNewUiEnabled && serviceItem?.offer_text)) {
228
+ return (
229
+ <NewUiOfferBanner
230
+ offerText={serviceItem.offer_text || ""}
231
+ isLoggedIn={isLoggedIn}
232
+ showLoginModal={showLoginModal}
233
+ showLoginOption={showLoginOption}
234
+ getAnimationIcon={getAnimationIcon}
235
+ hideRegister={hasDp}
236
+ />
237
+ );
238
+ }
239
+ if (isLegacyOffer) {
240
+ return (
241
+ <LegacyOfferBanner
242
+ offerText={serviceItem.offer_text!}
243
+ getAnimationIcon={getAnimationIcon}
244
+ />
245
+ );
246
+ }
247
+ return null;
248
+ };
249
+
39
250
  return (
40
251
  <div
41
- className="text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[44px] pt-[50px] rounded-b-[14px] text-[14px] mt-[10px]"
42
- style={{
43
- background:
44
- serviceItem?.offer_text && !isNewUiEnabled
45
- ? colors?.bottomStripColor
46
- : offerGradient,
47
- opacity: isSoldOut ? 0.5 : 1,
48
- // zIndex: 0,
49
- }}
252
+ className="text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[44px] pt-[50px] rounded-b-[14px] text-[14px] mt-[10px]"
253
+ style={{ background, opacity: isSoldOut ? 0.5 : 1 }}
50
254
  >
51
- <div className="flex justify-between items-center w-full">
52
- <div className="flex items-center ">
53
- {serviceItem?.is_dp_enabled &&
54
- Object.keys(serviceItem?.dp_discount_percents ?? {}).length === 0 &&
55
- (serviceItem?.dp_discounted_seats ?? []).length === 0 ? (
56
- <div className="flex items-center gap-[5px]">
57
- {/* {renderIcon("whiteFireIcon", "14px")} */}
58
- <LottiePlayer
59
- animationData={getAnimationIcon("starAnimation")}
60
- width="18px"
61
- height="18px"
62
- />
63
- {/* starAnimation */}
64
- <span>Servicio popular entre los usuarios</span>
65
- </div>
66
- ) : isNewUiEnabled && serviceItem?.offer_text ? (
67
- <div className="flex items-center">
68
- <LottiePlayer
69
- animationData={getAnimationIcon("bombAnimation")}
70
- width="18px"
71
- height="18px"
72
- />
73
- <div className="flex items-center mt-[2px]">
74
- <span
75
- className="bold-text"
76
- style={{
77
- marginLeft: serviceItem?.offer_text ? "6px" : "3px",
78
- }}
79
- >
80
- {(serviceItem?.offer_text || "").length > 30
81
- ? (serviceItem?.offer_text || "").slice(0, 30) + "..."
82
- : serviceItem?.offer_text || ""}{" "}
83
- {isLoggedIn && showLoginOption ? null : (
84
- <span onClick={showLoginModal} className="cursor-pointer">
85
- - registro
86
- </span>
87
- )}{" "}
88
- &nbsp;
89
- </span>{" "}
90
- {serviceItem?.offer_text ? "|" : ""}
91
- Termina en&nbsp;
92
- <span
93
- className="bold-text text-end"
94
- ref={(node) => CommonService.startCountdown(node, 599)}
95
- style={{
96
- fontVariantNumeric: "tabular-nums",
97
- display: "inline-block",
98
- }}
99
- />
100
- </div>
101
- </div>
102
- ) : (
103
- serviceItem?.offer_text &&
104
- !isNewUiEnabled && (
105
- <div className="flex items-center">
106
- <LottiePlayer
107
- animationData={getAnimationIcon("promoAnim")}
108
- width="18px"
109
- height="18px"
110
- />
111
- <div className="flex items-center mt-[2px]">
112
- <span
113
- className="bold-text"
114
- style={{
115
- marginLeft: serviceItem?.offer_text ? "6px" : "3px",
116
- }}
117
- >
118
- {serviceItem?.offer_text || ""}
119
- </span>{" "}
120
- </div>
121
- </div>
122
- )
123
- )}
124
- </div>
125
- {(isNewUiEnabled || serviceItem?.is_dp_enabled) && (
126
- <div className="flex items-center">
127
- {/* {renderIcon("personIcon", "16px")} */}
128
- <LottiePlayer
129
- animationData={getAnimationIcon("dotAnimation")}
130
- width="12px"
131
- height="12px"
132
- />
133
-
134
- <span className="ml-[6px]">
135
- <span
136
- className="bold-text"
137
- ref={(node) =>
138
- CommonService.startViewerCount(node, viewersConfig)
139
- }
140
- style={{ fontVariantNumeric: "tabular-nums" }}
141
- />{" "}
142
- {/* <span className="bold-text">personas</span>{" "} */}
143
- <span>
144
- {" "}
145
- {viewersConfig?.label || " viendo"} |{" "}
146
- <span className="">
147
- {serviceItem?.is_dp_enabled &&
148
- Object.keys(serviceItem?.dp_discount_percents ?? {})
149
- .length === 0 &&
150
- (serviceItem?.dp_discounted_seats ?? []).length === 0
151
- ? null
152
- : "Quedan pocos • "}
153
- <span
154
- className="bold-text"
155
- ref={(node) =>
156
- CommonService.startComprandoCount(node, 4, 16)
157
- }
158
- style={{ fontVariantNumeric: "tabular-nums" }}
159
- />{" "}
160
- comprando
161
- </span>
162
- </span>
163
- </span>
164
- </div>
255
+ <div className="flex justify-between items-center w-full">
256
+ <div className="flex items-center">{renderLeftContent()}</div>
257
+ {showViewers && (
258
+ <ViewersCount
259
+ serviceItem={serviceItem}
260
+ viewersConfig={viewersConfig}
261
+ getAnimationIcon={getAnimationIcon}
262
+ />
165
263
  )}
166
264
  </div>
167
265
  </div>