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.
- package/dist/KuposUIComponent.d.ts +1 -1
- package/dist/KuposUIComponent.js +3 -0
- package/dist/assets/images/discount/blue.png +0 -0
- package/dist/assets/images/discount/cash.png +0 -0
- package/dist/assets/images/discount/first.png +0 -0
- package/dist/assets/images/discount/green.png +0 -0
- package/dist/assets/images/discount/instant.png +0 -0
- package/dist/assets/images/discount/last.png +0 -0
- package/dist/assets/images/discount/promo.png +0 -0
- package/dist/assets/images/discount/round.png +0 -0
- package/dist/assets/images/discount/seat.png +0 -0
- package/dist/assets/images/discount/yellow.png +0 -0
- package/dist/assets/images/index.d.ts +12 -0
- package/dist/assets/images/index.js +12 -0
- package/dist/assets/images/index.ts +14 -0
- package/dist/components/DiscountBanners/DiscountBannerDesktop.d.ts +11 -0
- package/dist/components/DiscountBanners/DiscountBannerDesktop.js +133 -0
- package/dist/components/DiscountBanners/DiscountBannerMobile.d.ts +11 -0
- package/dist/components/DiscountBanners/DiscountBannerMobile.js +132 -0
- package/dist/components/DiscountBanners/ResponsiveDiscountBanner.d.ts +3 -0
- package/dist/components/DiscountBanners/ResponsiveDiscountBanner.js +10 -0
- package/dist/components/DiscountBanners/index.d.ts +5 -0
- package/dist/components/DiscountBanners/index.js +4 -0
- package/dist/components/DiscountBanners/types.d.ts +51 -0
- package/dist/components/DiscountBanners/types.js +1 -0
- package/dist/components/ServiceItem/ServiceItemDesktop.js +1 -1
- package/dist/components/ServiceItem/ServiceItemMobile.js +0 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -1
- package/dist/styles.css +89 -0
- package/dist/ui/DiscountBanners/DiscountBanners.d.ts +4 -0
- package/dist/ui/DiscountBanners/DiscountBanners.js +114 -0
- package/dist/ui/DiscountBanners/defaultBackgrounds.d.ts +12 -0
- package/dist/ui/DiscountBanners/defaultBackgrounds.js +38 -0
- package/dist/ui/DiscountBanners/discountUtils.d.ts +3 -0
- package/dist/ui/DiscountBanners/discountUtils.js +126 -0
- package/dist/ui/DiscountBanners/index.d.ts +5 -0
- package/dist/ui/DiscountBanners/index.js +3 -0
- package/dist/ui/DiscountBanners/types.d.ts +49 -0
- package/dist/ui/DiscountBanners/types.js +1 -0
- package/dist/ui/ServiceBadges/ServiceBadges.js +1 -1
- package/dist/ui/mobileweb/ServiceBadgesMobile.js +1 -1
- package/package.json +21 -6
- package/src/KuposUIComponent.tsx +13 -1
- package/src/assets/images/discount/blue.png +0 -0
- package/src/assets/images/discount/cash.png +0 -0
- package/src/assets/images/discount/first.png +0 -0
- package/src/assets/images/discount/green.png +0 -0
- package/src/assets/images/discount/instant.png +0 -0
- package/src/assets/images/discount/last.png +0 -0
- package/src/assets/images/discount/promo.png +0 -0
- package/src/assets/images/discount/round.png +0 -0
- package/src/assets/images/discount/seat.png +0 -0
- package/src/assets/images/discount/yellow.png +0 -0
- package/src/assets/images/index.ts +14 -0
- package/src/components/DiscountBanners/DiscountBannerDesktop.tsx +240 -0
- package/src/components/DiscountBanners/DiscountBannerMobile.tsx +234 -0
- package/src/components/DiscountBanners/ResponsiveDiscountBanner.tsx +16 -0
- package/src/components/DiscountBanners/index.ts +16 -0
- package/src/components/DiscountBanners/types.ts +55 -0
- package/src/components/ServiceItem/ServiceItemDesktop.tsx +1 -0
- package/src/components/ServiceItem/ServiceItemMobile.tsx +0 -1
- package/src/index.ts +34 -3
- package/src/types/images.d.ts +12 -0
- package/src/ui/DiscountBanners/DiscountBanners.tsx +215 -0
- package/src/ui/DiscountBanners/defaultBackgrounds.ts +42 -0
- package/src/ui/DiscountBanners/discountUtils.ts +166 -0
- package/src/ui/DiscountBanners/index.ts +14 -0
- package/src/ui/DiscountBanners/types.ts +53 -0
- package/src/ui/ServiceBadges/ServiceBadges.tsx +1 -1
- 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,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,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.
|
|
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
|
-
|
|
38
|
-
|
|
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",
|