kupos-ui-components-lib 10.4.31 → 10.4.34

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.
Files changed (71) hide show
  1. package/dist/KuposUIComponent.d.ts +1 -1
  2. package/dist/KuposUIComponent.js +3 -0
  3. package/dist/assets/images/discount/blue.png +0 -0
  4. package/dist/assets/images/discount/cash.png +0 -0
  5. package/dist/assets/images/discount/first.png +0 -0
  6. package/dist/assets/images/discount/green.png +0 -0
  7. package/dist/assets/images/discount/instant.png +0 -0
  8. package/dist/assets/images/discount/last.png +0 -0
  9. package/dist/assets/images/discount/promo.png +0 -0
  10. package/dist/assets/images/discount/round.png +0 -0
  11. package/dist/assets/images/discount/seat.png +0 -0
  12. package/dist/assets/images/discount/yellow.png +0 -0
  13. package/dist/assets/images/index.d.ts +12 -0
  14. package/dist/assets/images/index.js +12 -0
  15. package/dist/assets/images/index.ts +14 -0
  16. package/dist/components/DiscountBanners/DiscountBannerDesktop.d.ts +11 -0
  17. package/dist/components/DiscountBanners/DiscountBannerDesktop.js +133 -0
  18. package/dist/components/DiscountBanners/DiscountBannerMobile.d.ts +11 -0
  19. package/dist/components/DiscountBanners/DiscountBannerMobile.js +132 -0
  20. package/dist/components/DiscountBanners/ResponsiveDiscountBanner.d.ts +3 -0
  21. package/dist/components/DiscountBanners/ResponsiveDiscountBanner.js +10 -0
  22. package/dist/components/DiscountBanners/index.d.ts +5 -0
  23. package/dist/components/DiscountBanners/index.js +4 -0
  24. package/dist/components/DiscountBanners/types.d.ts +51 -0
  25. package/dist/components/DiscountBanners/types.js +1 -0
  26. package/dist/components/ServiceItem/ServiceItemDesktop.js +1 -1
  27. package/dist/components/ServiceItem/ServiceItemMobile.js +0 -1
  28. package/dist/index.d.ts +5 -2
  29. package/dist/index.js +6 -1
  30. package/dist/styles.css +89 -0
  31. package/dist/ui/DiscountBanners/DiscountBanners.d.ts +4 -0
  32. package/dist/ui/DiscountBanners/DiscountBanners.js +114 -0
  33. package/dist/ui/DiscountBanners/defaultBackgrounds.d.ts +12 -0
  34. package/dist/ui/DiscountBanners/defaultBackgrounds.js +38 -0
  35. package/dist/ui/DiscountBanners/discountUtils.d.ts +3 -0
  36. package/dist/ui/DiscountBanners/discountUtils.js +126 -0
  37. package/dist/ui/DiscountBanners/index.d.ts +5 -0
  38. package/dist/ui/DiscountBanners/index.js +3 -0
  39. package/dist/ui/DiscountBanners/types.d.ts +49 -0
  40. package/dist/ui/DiscountBanners/types.js +1 -0
  41. package/dist/ui/ServiceBadges/ServiceBadges.js +1 -1
  42. package/dist/ui/mobileweb/ServiceBadgesMobile.js +1 -1
  43. package/package.json +21 -6
  44. package/src/KuposUIComponent.tsx +13 -1
  45. package/src/assets/images/discount/blue.png +0 -0
  46. package/src/assets/images/discount/cash.png +0 -0
  47. package/src/assets/images/discount/first.png +0 -0
  48. package/src/assets/images/discount/green.png +0 -0
  49. package/src/assets/images/discount/instant.png +0 -0
  50. package/src/assets/images/discount/last.png +0 -0
  51. package/src/assets/images/discount/promo.png +0 -0
  52. package/src/assets/images/discount/round.png +0 -0
  53. package/src/assets/images/discount/seat.png +0 -0
  54. package/src/assets/images/discount/yellow.png +0 -0
  55. package/src/assets/images/index.ts +14 -0
  56. package/src/components/DiscountBanners/DiscountBannerDesktop.tsx +240 -0
  57. package/src/components/DiscountBanners/DiscountBannerMobile.tsx +234 -0
  58. package/src/components/DiscountBanners/ResponsiveDiscountBanner.tsx +16 -0
  59. package/src/components/DiscountBanners/index.ts +16 -0
  60. package/src/components/DiscountBanners/types.ts +55 -0
  61. package/src/components/ServiceItem/ServiceItemDesktop.tsx +1 -0
  62. package/src/components/ServiceItem/ServiceItemMobile.tsx +0 -1
  63. package/src/index.ts +34 -3
  64. package/src/types/images.d.ts +12 -0
  65. package/src/ui/DiscountBanners/DiscountBanners.tsx +215 -0
  66. package/src/ui/DiscountBanners/defaultBackgrounds.ts +42 -0
  67. package/src/ui/DiscountBanners/discountUtils.ts +166 -0
  68. package/src/ui/DiscountBanners/index.ts +14 -0
  69. package/src/ui/DiscountBanners/types.ts +53 -0
  70. package/src/ui/ServiceBadges/ServiceBadges.tsx +1 -1
  71. package/src/ui/mobileweb/ServiceBadgesMobile.tsx +1 -1
package/dist/styles.css CHANGED
@@ -216,6 +216,9 @@
216
216
  .mt-\[0px\] {
217
217
  margin-top: 0px;
218
218
  }
219
+ .mt-\[1px\] {
220
+ margin-top: 1px;
221
+ }
219
222
  .mt-\[2px\] {
220
223
  margin-top: 2px;
221
224
  }
@@ -414,6 +417,9 @@
414
417
  .h-\[16px\] {
415
418
  height: 16px;
416
419
  }
420
+ .h-\[17px\] {
421
+ height: 17px;
422
+ }
417
423
  .h-\[18px\] {
418
424
  height: 18px;
419
425
  }
@@ -435,6 +441,12 @@
435
441
  .h-\[58px\] {
436
442
  height: 58px;
437
443
  }
444
+ .h-\[68px\] {
445
+ height: 68px;
446
+ }
447
+ .h-\[80px\] {
448
+ height: 80px;
449
+ }
438
450
  .h-\[90px\] {
439
451
  height: 90px;
440
452
  }
@@ -507,9 +519,15 @@
507
519
  .w-\[65\%\%\] {
508
520
  width: 65%%;
509
521
  }
522
+ .w-\[69px\] {
523
+ width: 69px;
524
+ }
510
525
  .w-\[80\%\] {
511
526
  width: 80%;
512
527
  }
528
+ .w-\[81px\] {
529
+ width: 81px;
530
+ }
513
531
  .w-\[90px\] {
514
532
  width: 90px;
515
533
  }
@@ -528,12 +546,18 @@
528
546
  .w-\[150px\] {
529
547
  width: 150px;
530
548
  }
549
+ .w-\[170px\] {
550
+ width: 170px;
551
+ }
531
552
  .w-\[180px\] {
532
553
  width: 180px;
533
554
  }
534
555
  .w-\[188px\] {
535
556
  width: 188px;
536
557
  }
558
+ .w-\[200px\] {
559
+ width: 200px;
560
+ }
537
561
  .w-\[230px\] {
538
562
  width: 230px;
539
563
  }
@@ -563,6 +587,12 @@
563
587
  .w-px {
564
588
  width: 1px;
565
589
  }
590
+ .max-w-\[99px\] {
591
+ max-width: 99px;
592
+ }
593
+ .max-w-\[116px\] {
594
+ max-width: 116px;
595
+ }
566
596
  .max-w-\[120px\] {
567
597
  max-width: 120px;
568
598
  }
@@ -1084,6 +1114,12 @@
1084
1114
  .pt-\[6px\] {
1085
1115
  padding-top: 6px;
1086
1116
  }
1117
+ .pt-\[7px\] {
1118
+ padding-top: 7px;
1119
+ }
1120
+ .pt-\[8px\] {
1121
+ padding-top: 8px;
1122
+ }
1087
1123
  .pt-\[14px\] {
1088
1124
  padding-top: 14px;
1089
1125
  }
@@ -1105,6 +1141,9 @@
1105
1141
  .pt-\[50px\] {
1106
1142
  padding-top: 50px;
1107
1143
  }
1144
+ .pr-\[7px\] {
1145
+ padding-right: 7px;
1146
+ }
1108
1147
  .pr-\[8px\] {
1109
1148
  padding-right: 8px;
1110
1149
  }
@@ -1150,6 +1189,12 @@
1150
1189
  .pl-\[6px\] {
1151
1190
  padding-left: 6px;
1152
1191
  }
1192
+ .pl-\[8px\] {
1193
+ padding-left: 8px;
1194
+ }
1195
+ .pl-\[10px\] {
1196
+ padding-left: 10px;
1197
+ }
1153
1198
  .pl-\[16px\] {
1154
1199
  padding-left: 16px;
1155
1200
  }
@@ -1180,9 +1225,15 @@
1180
1225
  .font-\[\'DMSans-Regular\'\,sans-serif\] {
1181
1226
  font-family: 'DMSans-Regular',sans-serif;
1182
1227
  }
1228
+ .text-\[8px\] {
1229
+ font-size: 8px;
1230
+ }
1183
1231
  .text-\[9px\] {
1184
1232
  font-size: 9px;
1185
1233
  }
1234
+ .text-\[10\.5px\] {
1235
+ font-size: 10.5px;
1236
+ }
1186
1237
  .text-\[10px\] {
1187
1238
  font-size: 10px;
1188
1239
  }
@@ -1195,6 +1246,9 @@
1195
1246
  .text-\[12px\] {
1196
1247
  font-size: 12px;
1197
1248
  }
1249
+ .text-\[13\.5px\] {
1250
+ font-size: 13.5px;
1251
+ }
1198
1252
  .text-\[13\.33px\] {
1199
1253
  font-size: 13.33px;
1200
1254
  }
@@ -1250,10 +1304,30 @@
1250
1304
  --tw-leading: 1.25;
1251
1305
  line-height: 1.25;
1252
1306
  }
1307
+ .leading-\[12px\] {
1308
+ --tw-leading: 12px;
1309
+ line-height: 12px;
1310
+ }
1311
+ .leading-\[13\.5px\] {
1312
+ --tw-leading: 13.5px;
1313
+ line-height: 13.5px;
1314
+ }
1253
1315
  .leading-\[14px\] {
1254
1316
  --tw-leading: 14px;
1255
1317
  line-height: 14px;
1256
1318
  }
1319
+ .leading-\[15px\] {
1320
+ --tw-leading: 15px;
1321
+ line-height: 15px;
1322
+ }
1323
+ .leading-\[16px\] {
1324
+ --tw-leading: 16px;
1325
+ line-height: 16px;
1326
+ }
1327
+ .leading-\[17px\] {
1328
+ --tw-leading: 17px;
1329
+ line-height: 17px;
1330
+ }
1257
1331
  .leading-\[20px\] {
1258
1332
  --tw-leading: 20px;
1259
1333
  line-height: 20px;
@@ -1354,6 +1428,9 @@
1354
1428
  .text-\[\#fff\] {
1355
1429
  color: #fff;
1356
1430
  }
1431
+ .text-\[\#ffffff\] {
1432
+ color: #ffffff;
1433
+ }
1357
1434
  .text-\[white\] {
1358
1435
  color: white;
1359
1436
  }
@@ -1369,6 +1446,10 @@
1369
1446
  .italic {
1370
1447
  font-style: italic;
1371
1448
  }
1449
+ .ordinal {
1450
+ --tw-ordinal: ordinal;
1451
+ font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
1452
+ }
1372
1453
  .tabular-nums {
1373
1454
  --tw-numeric-spacing: tabular-nums;
1374
1455
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
@@ -1388,6 +1469,9 @@
1388
1469
  .opacity-100 {
1389
1470
  opacity: 100%;
1390
1471
  }
1472
+ .opacity-\[0\.65\] {
1473
+ opacity: 0.65;
1474
+ }
1391
1475
  .shadow-\[0_1px_4px_rgba\(0\,0\,0\,0\.25\)\] {
1392
1476
  --tw-shadow: 0 1px 4px var(--tw-shadow-color, rgba(0,0,0,0.25));
1393
1477
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -1434,6 +1518,11 @@
1434
1518
  transition-timing-function: var(--tw-ease, ease);
1435
1519
  transition-duration: var(--tw-duration, 0s);
1436
1520
  }
1521
+ .transition-opacity {
1522
+ transition-property: opacity;
1523
+ transition-timing-function: var(--tw-ease, ease);
1524
+ transition-duration: var(--tw-duration, 0s);
1525
+ }
1437
1526
  .transition-transform {
1438
1527
  transition-property: transform, translate, scale, rotate;
1439
1528
  transition-timing-function: var(--tw-ease, ease);
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { DiscountBannersProps } from "./types";
3
+ declare const DiscountBanners: React.FC<DiscountBannersProps>;
4
+ export default DiscountBanners;
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ import React, { useMemo, useState } from "react";
3
+ import { deriveDiscountCards } from "./discountUtils";
4
+ import { CAMPAIGN_ART, DEFAULT_BACKGROUND_IMAGES, toImageUrl, } from "./defaultBackgrounds";
5
+ const DEFAULT_LABEL = "Oferta especial";
6
+ const DEFAULT_ACCENT_PALETTE = [
7
+ "#107E23",
8
+ "#FFC000",
9
+ "#0069D1", // blue (descuentos especiales)
10
+ ];
11
+ const SIZE_CLASSES = {
12
+ default: {
13
+ card: "w-[200px] h-[80px] pt-[8px] pb-0 pl-[10px] pr-[8px]",
14
+ chip: "w-[81px] h-[17px] text-[9px] leading-[17px]",
15
+ column: "max-w-[116px] mt-[3px]",
16
+ discount: "text-[15px] leading-[17px]",
17
+ subtitle: "text-[12px] leading-[14px] mt-[1px]",
18
+ flameBadge: "text-[18px] leading-[16px]",
19
+ flamePercent: "text-[10px]",
20
+ },
21
+ compact: {
22
+ card: "w-[170px] h-[68px] pt-[7px] pb-0 pl-[8px] pr-[7px]",
23
+ chip: "w-[69px] h-[15px] text-[8px] leading-[15px]",
24
+ column: "max-w-[99px] mt-[2px]",
25
+ discount: "text-[13px] leading-[15px]",
26
+ subtitle: "text-[10.5px] leading-[12px] mt-[1px]",
27
+ flameBadge: "text-[13.5px] leading-[13.5px]",
28
+ flamePercent: "text-[8px]",
29
+ },
30
+ };
31
+ const FLAME_CENTER = { left: "76.8%", top: "56.4%" };
32
+ const REMATE_KEY = "remate";
33
+ function isRemateCard(card) {
34
+ return card.isWowDeal || card.key === REMATE_KEY;
35
+ }
36
+ const FALLBACK_ART = [
37
+ { image: DEFAULT_BACKGROUND_IMAGES[1], accent: "#FFC000" },
38
+ { image: DEFAULT_BACKGROUND_IMAGES[2], accent: "#0069D1" },
39
+ ];
40
+ const REMATE_ART = {
41
+ image: DEFAULT_BACKGROUND_IMAGES[0],
42
+ accent: "#107E23",
43
+ };
44
+ function resolveArt(cards) {
45
+ let ordinal = 0;
46
+ return cards.map((card) => {
47
+ if (isRemateCard(card))
48
+ return REMATE_ART;
49
+ const campaignArt = card.campaignType
50
+ ? CAMPAIGN_ART[card.campaignType]
51
+ : undefined;
52
+ if (campaignArt)
53
+ return campaignArt;
54
+ const art = FALLBACK_ART[ordinal % FALLBACK_ART.length];
55
+ ordinal += 1;
56
+ return art;
57
+ });
58
+ }
59
+ const DiscountBanners = ({ services = [], wowDealData, backgroundImages = [], artIncludesChipPill, accentColors = [], label = DEFAULT_LABEL, currencySign = "$", size = "default", selectedKey, defaultSelectedKey = null, onDiscountSelect, className = "", style, }) => {
60
+ const cards = useMemo(() => deriveDiscountCards(services, wowDealData, currencySign), [services, wowDealData, currencySign]);
61
+ const [internalSelectedKey, setInternalSelectedKey] = useState(defaultSelectedKey);
62
+ const isControlled = selectedKey !== undefined;
63
+ const activeKey = isControlled ? selectedKey : internalSelectedKey;
64
+ if (!cards.length)
65
+ return null;
66
+ const handleCardClick = (card) => {
67
+ var _a, _b;
68
+ const nextKey = activeKey === card.key ? null : card.key;
69
+ if (!isControlled) {
70
+ setInternalSelectedKey(nextKey);
71
+ }
72
+ const selectedCard = nextKey
73
+ ? ((_a = cards.find((c) => c.key === nextKey)) !== null && _a !== void 0 ? _a : null)
74
+ : null;
75
+ onDiscountSelect === null || onDiscountSelect === void 0 ? void 0 : onDiscountSelect(selectedCard, (_b = selectedCard === null || selectedCard === void 0 ? void 0 : selectedCard.matchedServices) !== null && _b !== void 0 ? _b : []);
76
+ };
77
+ const usingDefaultArt = !backgroundImages.length;
78
+ const defaultArt = resolveArt(cards);
79
+ const chipPillInArt = artIncludesChipPill !== null && artIncludesChipPill !== void 0 ? artIncludesChipPill : usingDefaultArt;
80
+ const sizeClasses = SIZE_CLASSES[size];
81
+ return (React.createElement("div", { className: `kupos-discount-banners flex items-stretch gap-[10px] overflow-x-auto [scrollbar-width:none] ${className}`, style: style }, cards.map((card, index) => {
82
+ const isActive = activeKey === card.key;
83
+ const isDimmed = activeKey != null && !isActive;
84
+ const backgroundImage = usingDefaultArt
85
+ ? defaultArt[index].image
86
+ : toImageUrl(backgroundImages[index % backgroundImages.length]);
87
+ const accentColor = accentColors.length
88
+ ? accentColors[index % accentColors.length]
89
+ : usingDefaultArt
90
+ ? defaultArt[index].accent
91
+ : DEFAULT_ACCENT_PALETTE[index % DEFAULT_ACCENT_PALETTE.length];
92
+ const isFlameArt = backgroundImage === DEFAULT_BACKGROUND_IMAGES[0];
93
+ const showFlamePercent = isRemateCard(card) &&
94
+ isFlameArt &&
95
+ card.discountType === "percentage" &&
96
+ card.amount > 0;
97
+ return (React.createElement("button", { key: card.key, type: "button", "aria-pressed": isActive, onClick: () => handleCardClick(card), className: `relative flex-shrink-0 flex flex-col items-start text-left border-none bg-transparent cursor-pointer bg-no-repeat transition-opacity duration-200 ease-in-out ${sizeClasses.card} ${isDimmed ? "opacity-[0.65]" : "opacity-100"}`, style: {
98
+ backgroundImage: backgroundImage
99
+ ? `url(${backgroundImage})`
100
+ : undefined,
101
+ backgroundSize: "100% 100%",
102
+ } },
103
+ React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: chipPillInArt
104
+ ? undefined
105
+ : { backgroundColor: "rgba(255,255,255,0.55)" } }, label),
106
+ showFlamePercent && (React.createElement("span", { className: `bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`, style: Object.assign(Object.assign({}, FLAME_CENTER), { transform: "translate(-50%, -50%)", textShadow: "0 1px 2px rgba(0,0,0,0.28)", fontWeight: 900, whiteSpace: "nowrap" }) },
107
+ card.amount,
108
+ React.createElement("span", { className: sizeClasses.flamePercent, style: { fontWeight: 700 } }, "%"))),
109
+ React.createElement("span", { className: `flex flex-col ${sizeClasses.column}` },
110
+ React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}` }, card.discountLabel),
111
+ React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: { color: accentColor } }, card.subtitle))));
112
+ })));
113
+ };
114
+ export default DiscountBanners;
@@ -0,0 +1,12 @@
1
+ export type ImageRef = string | {
2
+ src?: string;
3
+ default?: string | {
4
+ src?: string;
5
+ };
6
+ };
7
+ export declare function toImageUrl(art: ImageRef): string;
8
+ export declare const DEFAULT_BACKGROUND_IMAGES: string[];
9
+ export declare const CAMPAIGN_ART: Record<string, {
10
+ image: string;
11
+ accent: string;
12
+ }>;
@@ -0,0 +1,38 @@
1
+ import greenArt from "../../assets/images/discount/green.png";
2
+ import yellowArt from "../../assets/images/discount/yellow.png";
3
+ import blueArt from "../../assets/images/discount/blue.png";
4
+ import lastMinuteArt from "../../assets/images/discount/last.png";
5
+ import firstTimeArt from "../../assets/images/discount/first.png";
6
+ import cashbackArt from "../../assets/images/discount/cash.png";
7
+ import roundTripArt from "../../assets/images/discount/round.png";
8
+ import promoCodeArt from "../../assets/images/discount/promo.png";
9
+ import seatLevelArt from "../../assets/images/discount/seat.png";
10
+ import instantArt from "../../assets/images/discount/instant.png";
11
+ export function toImageUrl(art) {
12
+ if (typeof art === "string")
13
+ return art;
14
+ if (art && typeof art === "object") {
15
+ if (typeof art.default === "string")
16
+ return art.default;
17
+ if (art.default && typeof art.default === "object" && art.default.src) {
18
+ return art.default.src;
19
+ }
20
+ if (art.src)
21
+ return art.src;
22
+ }
23
+ return "";
24
+ }
25
+ export const DEFAULT_BACKGROUND_IMAGES = [
26
+ greenArt,
27
+ yellowArt,
28
+ blueArt,
29
+ ].map(toImageUrl);
30
+ export const CAMPAIGN_ART = {
31
+ last_minute: { image: toImageUrl(lastMinuteArt), accent: "#4457C7" },
32
+ first_time: { image: toImageUrl(firstTimeArt), accent: "#D63384" },
33
+ cashback: { image: toImageUrl(cashbackArt), accent: "#0069D1" },
34
+ round_trip: { image: toImageUrl(roundTripArt), accent: "#5C8A00" },
35
+ promo_code: { image: toImageUrl(promoCodeArt), accent: "#E08A00" },
36
+ seat_level: { image: toImageUrl(seatLevelArt), accent: "#B87500" },
37
+ instant_discount: { image: toImageUrl(instantArt), accent: "#107E23" },
38
+ };
@@ -0,0 +1,3 @@
1
+ import { DiscountBannerCard, DiscountBannerServiceItem, WowDealData } from "./types";
2
+ export declare const CAMPAIGN_TYPE_LABELS: Record<string, string>;
3
+ export declare function deriveDiscountCards(services?: DiscountBannerServiceItem[], wowDealData?: WowDealData, currencySign?: string): DiscountBannerCard[];
@@ -0,0 +1,126 @@
1
+ import CommonService from "../../utils/CommonService";
2
+ const DESCUENTOS_ESPECIALES_KEY = "descuentos_especiales";
3
+ const DESCUENTOS_ESPECIALES_LABEL = "Descuentos especiales";
4
+ const REMATE_KEY = "remate";
5
+ export const CAMPAIGN_TYPE_LABELS = {
6
+ last_minute: "Última hora",
7
+ first_time: "Primera compra",
8
+ cashback: "Cashback",
9
+ return_booking: "Pasaje de vuelta",
10
+ round_trip: "Ida y vuelta",
11
+ early_bird: "Compra anticipada",
12
+ promo_code: "Código promocional",
13
+ seat_level: "Descuento por asiento",
14
+ instant_discount: "Descuento inmediato",
15
+ };
16
+ const ACCENTED_CHARS = {
17
+ á: "a",
18
+ é: "e",
19
+ í: "i",
20
+ ó: "o",
21
+ ú: "u",
22
+ ü: "u",
23
+ ñ: "n",
24
+ };
25
+ function slugify(text) {
26
+ return text
27
+ .trim()
28
+ .toLowerCase()
29
+ .replace(/[áéíóúüñ]/g, (char) => { var _a; return (_a = ACCENTED_CHARS[char]) !== null && _a !== void 0 ? _a : char; })
30
+ .replace(/[^a-z0-9]+/g, "_")
31
+ .replace(/^_+|_+$/g, "");
32
+ }
33
+ // Dynamic-pricing discounts store one percent per seat type; the best (lowest) one is what's advertised elsewhere in this library
34
+ function getDpPercent(service) {
35
+ if (!(service === null || service === void 0 ? void 0 : service.is_dp_enabled) || !(service === null || service === void 0 ? void 0 : service.dp_discount_percents))
36
+ return 0;
37
+ const values = Object.values(service.dp_discount_percents)
38
+ .map(Number)
39
+ .filter((v) => isFinite(v) && v > 0);
40
+ return values.length ? Math.min(...values) : 0;
41
+ }
42
+ function getServiceDiscount(service) {
43
+ const dpPercent = getDpPercent(service);
44
+ if (dpPercent > 0)
45
+ return { type: "percentage", amount: dpPercent };
46
+ if ((service === null || service === void 0 ? void 0 : service.discount_type) === "percentage" &&
47
+ (service === null || service === void 0 ? void 0 : service.discount_value) != null &&
48
+ service.discount_value > 0) {
49
+ return { type: "percentage", amount: Number(service.discount_value) };
50
+ }
51
+ if ((service === null || service === void 0 ? void 0 : service.discount_type) === "fixed" &&
52
+ (service === null || service === void 0 ? void 0 : service.discount_value) != null &&
53
+ service.discount_value > 0) {
54
+ const rawDiscount = Number(service.discount_value);
55
+ const { max_discount } = service;
56
+ const amount = max_discount != null && max_discount > 0
57
+ ? Math.min(rawDiscount, max_discount)
58
+ : rawDiscount;
59
+ return amount > 0 ? { type: "fixed", amount } : null;
60
+ }
61
+ return null;
62
+ }
63
+ function toDiscountLabel(discount, currencySign) {
64
+ if (discount.type === "fixed") {
65
+ return `${CommonService.currencyRounded(discount.amount, currencySign)} de dcto`;
66
+ }
67
+ return `${Math.round(discount.amount)}% de dcto`;
68
+ }
69
+ // Groups services into one card per discount category. Remate/wow-deal is always the first card.
70
+ export function deriveDiscountCards(services = [], wowDealData, currencySign = "$") {
71
+ var _a, _b;
72
+ const cards = new Map();
73
+ const wowServices = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services;
74
+ const hasWowDeal = !!wowDealData &&
75
+ ((Array.isArray(wowServices) && wowServices.length > 0) ||
76
+ !!wowDealData.savings_percent);
77
+ if (hasWowDeal) {
78
+ const remateDiscount = {
79
+ type: "percentage",
80
+ amount: Math.round(wowDealData.savings_percent || 0),
81
+ };
82
+ cards.set(REMATE_KEY, {
83
+ key: REMATE_KEY,
84
+ subtitle: "Remate",
85
+ discountType: remateDiscount.type,
86
+ amount: remateDiscount.amount,
87
+ discountLabel: toDiscountLabel(remateDiscount, currencySign),
88
+ matchedServices: Array.isArray(wowServices) ? wowServices : [],
89
+ isWowDeal: true,
90
+ });
91
+ }
92
+ for (const service of services || []) {
93
+ const discount = getServiceDiscount(service);
94
+ if (!discount)
95
+ continue;
96
+ const campaignType = (_b = (_a = service.discounts) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.campaign_type;
97
+ const campaignLabel = campaignType
98
+ ? CAMPAIGN_TYPE_LABELS[campaignType]
99
+ : undefined;
100
+ const category = campaignType
101
+ ? slugify(campaignType)
102
+ : service.offer_text
103
+ ? slugify(service.offer_text)
104
+ : DESCUENTOS_ESPECIALES_KEY;
105
+ const subtitle = campaignLabel || DESCUENTOS_ESPECIALES_LABEL;
106
+ const roundedAmount = Math.round(discount.amount);
107
+ const key = `${category}_${discount.type}_${roundedAmount}`;
108
+ const existing = cards.get(key);
109
+ if (existing) {
110
+ existing.matchedServices.push(service);
111
+ }
112
+ else {
113
+ cards.set(key, {
114
+ key,
115
+ subtitle,
116
+ campaignType,
117
+ discountType: discount.type,
118
+ amount: roundedAmount,
119
+ discountLabel: toDiscountLabel({ type: discount.type, amount: roundedAmount }, currencySign),
120
+ matchedServices: [service],
121
+ isWowDeal: false,
122
+ });
123
+ }
124
+ }
125
+ return Array.from(cards.values());
126
+ }
@@ -0,0 +1,5 @@
1
+ export { default as DiscountBanners } from "./DiscountBanners";
2
+ export { deriveDiscountCards, CAMPAIGN_TYPE_LABELS } from "./discountUtils";
3
+ export { CAMPAIGN_ART, DEFAULT_BACKGROUND_IMAGES, toImageUrl, } from "./defaultBackgrounds";
4
+ export type { ImageRef } from "./defaultBackgrounds";
5
+ export type { DiscountBannerCard, DiscountBannerServiceItem, DiscountBannersProps, WowDealData, } from "./types";
@@ -0,0 +1,3 @@
1
+ export { default as DiscountBanners } from "./DiscountBanners";
2
+ export { deriveDiscountCards, CAMPAIGN_TYPE_LABELS } from "./discountUtils";
3
+ export { CAMPAIGN_ART, DEFAULT_BACKGROUND_IMAGES, toImageUrl, } from "./defaultBackgrounds";
@@ -0,0 +1,49 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { ImageRef } from "./defaultBackgrounds";
3
+ export interface DiscountBannerServiceItem {
4
+ id?: string | number;
5
+ offer_text?: string;
6
+ is_dp_enabled?: boolean;
7
+ dp_discount_percents?: Record<string, number>;
8
+ dp_discounted_seats?: string[];
9
+ discount_type?: string;
10
+ discount_value?: number;
11
+ max_discount?: number;
12
+ discounts?: Array<{
13
+ new_ui_enabled?: boolean;
14
+ campaign_type?: string;
15
+ }>;
16
+ [key: string]: any;
17
+ }
18
+ export interface WowDealData {
19
+ savings_percent?: number;
20
+ services?: any[];
21
+ final_price?: number;
22
+ original_price?: number;
23
+ [key: string]: any;
24
+ }
25
+ export interface DiscountBannerCard {
26
+ key: string;
27
+ subtitle: string;
28
+ campaignType?: string;
29
+ discountType: "percentage" | "fixed";
30
+ amount: number;
31
+ discountLabel: string;
32
+ matchedServices: DiscountBannerServiceItem[];
33
+ isWowDeal: boolean;
34
+ }
35
+ export interface DiscountBannersProps {
36
+ services?: DiscountBannerServiceItem[];
37
+ wowDealData?: WowDealData;
38
+ backgroundImages?: ImageRef[];
39
+ artIncludesChipPill?: boolean;
40
+ accentColors?: string[];
41
+ label?: string;
42
+ currencySign?: string;
43
+ size?: "default" | "compact";
44
+ selectedKey?: string | null;
45
+ defaultSelectedKey?: string | null;
46
+ onDiscountSelect?: (card: DiscountBannerCard | null, filteredServices: DiscountBannerServiceItem[]) => void;
47
+ className?: string;
48
+ style?: CSSProperties;
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -40,7 +40,7 @@ const ServiceBadges = ({ showTopLabel, isSoldOut, colors, renderIcon, translatio
40
40
  } },
41
41
  renderIcon("directo", "12px"),
42
42
  React.createElement("div", null, "Tren Express"))),
43
- boardingName && isAllinBus && (React.createElement("div", { className: `flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`, style: {
43
+ boardingName && isAllinBus && !showTopLabel && (React.createElement("div", { className: `flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`, style: {
44
44
  backgroundColor: colors.topLabelBgColor || "#fff",
45
45
  border: isSoldOut
46
46
  ? "1px solid #ccc"
@@ -40,7 +40,7 @@ const ServiceBadgesMobile = ({ showTopLabel, isSoldOut, colors, renderIcon, serv
40
40
  } },
41
41
  renderIcon("directo", "12px"),
42
42
  React.createElement("div", null, "Tren Express"))),
43
- boardingName && isAllinBus && (React.createElement("div", { className: `flex items-center gap-[2p x] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] h-[24px] z-20`, style: {
43
+ boardingName && isAllinBus && !showTopLabel && (React.createElement("div", { className: `flex items-center gap-[2p x] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] h-[24px] z-20`, style: {
44
44
  backgroundColor: colors.topLabelBgColor || "#fff",
45
45
  border: isSoldOut
46
46
  ? "1px solid #ccc"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "10.4.31",
3
+ "version": "10.4.34",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -13,7 +13,11 @@
13
13
  "import": "./dist/index.js",
14
14
  "require": "./dist/index.js"
15
15
  },
16
- "./dist/styles.css": "./dist/styles.css"
16
+ "./dist/styles.css": "./dist/styles.css",
17
+ "./assets/images": {
18
+ "types": "./dist/assets/images/index.d.ts",
19
+ "default": "./dist/assets/images/index.js"
20
+ }
17
21
  },
18
22
  "scripts": {
19
23
  "build": "tsc && node build-css.js && node copy-assets.js",
@@ -30,12 +34,21 @@
30
34
  "author": "Zahid Dar",
31
35
  "license": "MIT",
32
36
  "dependencies": {
33
- "kupos-ui-components-lib": "^10.4.15",
34
37
  "lottie-react": "^2.4.1",
35
38
  "lottie-web": "^5.13.0",
36
- "moment": "^2.30.1",
37
- "react": "^17.0.2",
38
- "react-dom": "^17.0.2"
39
+ "moment": "^2.30.1"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "^17.0.2 || ^18.0.0 || ^19.0.0",
43
+ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "react": {
47
+ "optional": true
48
+ },
49
+ "react-dom": {
50
+ "optional": true
51
+ }
39
52
  },
40
53
  "devDependencies": {
41
54
  "@tailwindcss/postcss": "^4.1.11",
@@ -45,6 +58,8 @@
45
58
  "file-loader": "^6.2.0",
46
59
  "postcss": "^8.5.6",
47
60
  "postcss-cli": "^11.0.1",
61
+ "react": "^18.3.1",
62
+ "react-dom": "^18.3.1",
48
63
  "svg-url-loader": "^8.0.0",
49
64
  "tailwindcss": "^4.1.11",
50
65
  "typescript": "^4.4.4",