kupos-ui-components-lib 10.4.28 → 10.4.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ServiceItem/PeruServiceItemDesktop.js +21 -14
- package/dist/components/ServiceItem/PeruServiceItemMobile.js +39 -44
- package/dist/components/ServiceItem/ServiceItemDesktop.js +1 -1
- package/dist/components/ServiceItem/ServiceItemMobile.js +7 -3
- package/dist/ui/OfferBanner.d.ts +1 -0
- package/dist/ui/OfferBanner.js +10 -6
- package/dist/ui/PeruOfferBanner.d.ts +1 -0
- package/dist/ui/PeruOfferBanner.js +16 -24
- package/dist/ui/SeatSection/PeruSeatSection.js +42 -15
- package/dist/ui/SeatSection/SeatSection.js +18 -9
- package/dist/ui/mobileweb/SeatSectionMobile.js +16 -11
- package/package.json +1 -1
- package/src/components/ServiceItem/PeruServiceItemDesktop.tsx +32 -24
- package/src/components/ServiceItem/PeruServiceItemMobile.tsx +62 -79
- package/src/components/ServiceItem/ServiceItemDesktop.tsx +1 -0
- package/src/components/ServiceItem/ServiceItemMobile.tsx +17 -11
- package/src/ui/OfferBanner.tsx +17 -7
- package/src/ui/PeruOfferBanner.tsx +23 -36
- package/src/ui/SeatSection/PeruSeatSection.tsx +64 -17
- package/src/ui/SeatSection/SeatSection.tsx +17 -1
- package/src/ui/mobileweb/SeatSectionMobile.tsx +53 -46
|
@@ -33,6 +33,9 @@ interface PeruOfferBannerProps {
|
|
|
33
33
|
coachKey?: number;
|
|
34
34
|
isExpand?: any;
|
|
35
35
|
isNewUiEnabled?: boolean;
|
|
36
|
+
// ui_type from the active discount entry: 1 = offer-text-only tier,
|
|
37
|
+
// 2 = full new-UI tier (timer, bomb animation, viewers).
|
|
38
|
+
uiType?: number;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -121,19 +124,13 @@ const OfferBannerContent: React.FC<OfferBannerContentProps> = ({
|
|
|
121
124
|
</span>
|
|
122
125
|
)}{" "}
|
|
123
126
|
|
|
124
|
-
</span>
|
|
125
|
-
{offerText ? "| " : ""}
|
|
126
|
-
Termina en
|
|
127
|
-
<span
|
|
128
|
-
className="bold-text text-end"
|
|
129
|
-
ref={(node) => CommonService.startCountdown(node, 599)}
|
|
130
|
-
style={{ fontVariantNumeric: "tabular-nums", display: "inline-block" }}
|
|
131
|
-
/>
|
|
127
|
+
</span>
|
|
132
128
|
</div>
|
|
133
129
|
</div>
|
|
134
130
|
);
|
|
135
131
|
|
|
136
|
-
// Shown for
|
|
132
|
+
// Shown for the offer-text-only tier (ui_type 1): the announcement icon and
|
|
133
|
+
// the offer text, nothing else — no timer, no viewers.
|
|
137
134
|
interface LegacyOfferBannerProps {
|
|
138
135
|
offerText: string;
|
|
139
136
|
getAnimationIcon: PeruOfferBannerProps["getAnimationIcon"];
|
|
@@ -207,6 +204,7 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
|
|
|
207
204
|
offerGradient,
|
|
208
205
|
isSoldOut,
|
|
209
206
|
serviceItem,
|
|
207
|
+
renderIcon,
|
|
210
208
|
isLoggedIn,
|
|
211
209
|
showLoginModal,
|
|
212
210
|
viewersConfig,
|
|
@@ -217,28 +215,32 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
|
|
|
217
215
|
coachKey,
|
|
218
216
|
isExpand,
|
|
219
217
|
isNewUiEnabled,
|
|
218
|
+
uiType,
|
|
220
219
|
}) => {
|
|
221
|
-
const isDiscountBelow20 =
|
|
222
|
-
(serviceItem as any)?.discount_value > 0 &&
|
|
223
|
-
(serviceItem as any)?.discount_value < 20;
|
|
224
220
|
const offerText = serviceItem?.offer_text || "";
|
|
221
|
+
const hasDp = hasDpDiscounts(serviceItem);
|
|
225
222
|
|
|
226
|
-
|
|
223
|
+
// Tier B: new UI enabled, ui_type 1 — offer text only, no timer, no viewers.
|
|
224
|
+
const isOfferTextOnly = isNewUiEnabled && uiType === 1 && !!offerText;
|
|
225
|
+
// Tier C: new UI enabled, ui_type 2 — full ribbon (timer, bomb, viewers).
|
|
226
|
+
const isFullNewUi = isNewUiEnabled && uiType === 2;
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
const showViewers = isNewUiEnabled;
|
|
228
|
+
const showViewers = isFullNewUi || hasDp;
|
|
230
229
|
|
|
231
|
-
const background =
|
|
230
|
+
const background = isOfferTextOnly
|
|
231
|
+
? colors?.bottomStripColor || offerGradient
|
|
232
|
+
: offerGradient;
|
|
232
233
|
|
|
233
234
|
const renderLeftContent = () => {
|
|
234
|
-
const hasDp = hasDpDiscounts(serviceItem);
|
|
235
|
-
|
|
236
235
|
if (serviceItem?.is_dp_enabled && !hasDp) {
|
|
237
236
|
return <PopularServiceBanner getAnimationIcon={getAnimationIcon} />;
|
|
238
237
|
}
|
|
239
238
|
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
if (isOfferTextOnly) {
|
|
240
|
+
return <LegacyOfferBanner offerText={offerText} getAnimationIcon={getAnimationIcon}/>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (isFullNewUi || hasDp) {
|
|
242
244
|
return (
|
|
243
245
|
<OfferBannerContent
|
|
244
246
|
offerText={offerText}
|
|
@@ -252,16 +254,6 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
|
|
|
252
254
|
);
|
|
253
255
|
}
|
|
254
256
|
|
|
255
|
-
// If new UI is disabled, show offer text only
|
|
256
|
-
if (offerText) {
|
|
257
|
-
return (
|
|
258
|
-
<LegacyOfferBanner
|
|
259
|
-
offerText={offerText}
|
|
260
|
-
getAnimationIcon={getAnimationIcon}
|
|
261
|
-
/>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
257
|
return null;
|
|
266
258
|
};
|
|
267
259
|
|
|
@@ -269,12 +261,7 @@ const PeruOfferBanner: React.FC<PeruOfferBannerProps> = ({
|
|
|
269
261
|
<div
|
|
270
262
|
className={`text-white w-full absolute ${coachKey ? "-bottom-[32px]" : isExpand ? "-bottom-[32px]" : "-bottom-[40px]"} rounded-b-[14px] text-[14px] h-[58px] flex items-end pb-[7px]`}
|
|
271
263
|
style={{
|
|
272
|
-
background
|
|
273
|
-
serviceItem?.is_dp_enabled ||
|
|
274
|
-
serviceItem?.dp_discount_percents ||
|
|
275
|
-
(serviceItem as any)?.discount_value
|
|
276
|
-
? offerGradient
|
|
277
|
-
: background,
|
|
264
|
+
background,
|
|
278
265
|
opacity: isSoldOut ? 0.5 : 1,
|
|
279
266
|
zIndex: isLinatal ? "-1" : "0",
|
|
280
267
|
}}
|
|
@@ -236,14 +236,19 @@ function PeruSeatSection({
|
|
|
236
236
|
.filter((seat) => !SEAT_EXCEPTIONS.includes(seat.label))
|
|
237
237
|
.sort((a, b) => a.discountedPrice - b.discountedPrice)[0];
|
|
238
238
|
|
|
239
|
+
// discount_type/discount_value/max_discount live on the service item
|
|
240
|
+
// itself; only ui_type and the ready-made badge text come from discounts[0].
|
|
241
|
+
const peruDiscount = serviceItem?.discounts?.[0];
|
|
242
|
+
const uiType = peruDiscount?.ui_type;
|
|
243
|
+
const discountType = serviceItem?.discount_type;
|
|
244
|
+
const discountValueRaw = serviceItem?.discount_value;
|
|
245
|
+
const maxDiscount = serviceItem?.max_discount;
|
|
246
|
+
|
|
239
247
|
const discountValue = (() => {
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
typeof serviceItem?.discount_value === "number"
|
|
243
|
-
) {
|
|
244
|
-
return Math.round(serviceItem.discount_value);
|
|
248
|
+
if (discountType === "percentage" && typeof discountValueRaw === "number") {
|
|
249
|
+
return Math.round(discountValueRaw);
|
|
245
250
|
}
|
|
246
|
-
if (
|
|
251
|
+
if (discountType === "fixed" && discountSeat) {
|
|
247
252
|
const { originalPrice, discountedPrice } = discountSeat;
|
|
248
253
|
if (originalPrice > 0 && originalPrice !== discountedPrice) {
|
|
249
254
|
return Math.round(
|
|
@@ -255,21 +260,30 @@ function PeruSeatSection({
|
|
|
255
260
|
})();
|
|
256
261
|
|
|
257
262
|
const isMaxDiscountApplied = (() => {
|
|
258
|
-
const { discount_type, discount_value, max_discount } = serviceItem ?? {};
|
|
259
263
|
if (
|
|
260
|
-
|
|
261
|
-
typeof
|
|
262
|
-
|
|
263
|
-
|
|
264
|
+
discountType === "percentage" &&
|
|
265
|
+
typeof discountValueRaw === "number" &&
|
|
266
|
+
maxDiscount != null &&
|
|
267
|
+
maxDiscount > 0 &&
|
|
264
268
|
discountSeat
|
|
265
269
|
) {
|
|
266
270
|
const rawPercentageDiscount =
|
|
267
|
-
(discountSeat.originalPrice *
|
|
268
|
-
return rawPercentageDiscount >
|
|
271
|
+
(discountSeat.originalPrice * discountValueRaw) / 100;
|
|
272
|
+
return rawPercentageDiscount > maxDiscount;
|
|
269
273
|
}
|
|
270
274
|
return false;
|
|
271
275
|
})();
|
|
272
276
|
|
|
277
|
+
// Backend-provided ready-made badge text (e.g. "30% OFF" / "$2.000 OFF")
|
|
278
|
+
// takes precedence over the value computed client-side.
|
|
279
|
+
const discountBadgeText =
|
|
280
|
+
peruDiscount?.discount_display_badge ||
|
|
281
|
+
(isMaxDiscountApplied
|
|
282
|
+
? `${CommonService.currency(maxDiscount)} OFF`
|
|
283
|
+
: discountValue != null
|
|
284
|
+
? `${discountValue}% OFF`
|
|
285
|
+
: null);
|
|
286
|
+
|
|
273
287
|
const renderLabels = () => {
|
|
274
288
|
const isMovilBus =
|
|
275
289
|
serviceItem?.operator_service_name === "MovilBus" ||
|
|
@@ -363,6 +377,41 @@ function PeruSeatSection({
|
|
|
363
377
|
};
|
|
364
378
|
|
|
365
379
|
if (hasDiscount && discountSeat) {
|
|
380
|
+
// Tier B (ui_type 1): offer text is announced elsewhere, but the fare
|
|
381
|
+
// itself must render exactly as it would with no discount — no
|
|
382
|
+
// strike-off, no badge, no fire icon.
|
|
383
|
+
if (uiType === 1) {
|
|
384
|
+
return (
|
|
385
|
+
<div
|
|
386
|
+
className="grid grid-cols-2 items-center text-[13.33px] relative"
|
|
387
|
+
style={{ opacity: isSoldOut ? 0.5 : 1 }}
|
|
388
|
+
>
|
|
389
|
+
<div className="col-start-1 row-start-3 flex h-[20px] items-end">
|
|
390
|
+
<span className="text-[13.33px] font-normal leading-[20px] text-[#464647]">
|
|
391
|
+
{discountSeat.label}
|
|
392
|
+
</span>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<div className="col-start-2 row-start-3 flex h-[30px] items-end justify-center relative">
|
|
396
|
+
<span
|
|
397
|
+
className="flex items-center gap-[6px] bold-text leading-[30px]"
|
|
398
|
+
style={{
|
|
399
|
+
color: isSoldOut ? "#c0c0c0" : "#464647",
|
|
400
|
+
fontSize: "13.33px",
|
|
401
|
+
}}
|
|
402
|
+
>
|
|
403
|
+
{availableSeats <= 0
|
|
404
|
+
? CommonService.currency(0, currencySign)
|
|
405
|
+
: CommonService.discountedCurrency(
|
|
406
|
+
discountSeat.originalPrice,
|
|
407
|
+
currencySign,
|
|
408
|
+
)}
|
|
409
|
+
</span>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
366
415
|
return (
|
|
367
416
|
<div
|
|
368
417
|
className="grid grid-cols-2 items-center text-[13.33px] relative"
|
|
@@ -384,7 +433,7 @@ function PeruSeatSection({
|
|
|
384
433
|
className="col-start-2 row-start-1 flex items-center justify-center absolute"
|
|
385
434
|
style={{ top: "-22px", left: "50%", transform: "translateX(-50%)" }}
|
|
386
435
|
>
|
|
387
|
-
{
|
|
436
|
+
{discountBadgeText ? (
|
|
388
437
|
<span
|
|
389
438
|
className="rounded-[100px] bg-[#ff5964] px-[6px] text-[12px] bold-text leading-[20px] text-white"
|
|
390
439
|
style={{
|
|
@@ -393,9 +442,7 @@ function PeruSeatSection({
|
|
|
393
442
|
backgroundColor: discountSeatPriceColor,
|
|
394
443
|
}}
|
|
395
444
|
>
|
|
396
|
-
{
|
|
397
|
-
? `${CommonService.currency(serviceItem?.max_discount)} OFF`
|
|
398
|
-
: `${discountValue}% OFF`}
|
|
445
|
+
{discountBadgeText}
|
|
399
446
|
</span>
|
|
400
447
|
) : null}
|
|
401
448
|
</div>
|
|
@@ -653,6 +653,7 @@ function SeatSection({
|
|
|
653
653
|
style={{ top: "-22px", left: "50%", transform: "translateX(-50%)" }}
|
|
654
654
|
>
|
|
655
655
|
{serviceItem?.ui_type !== 1 &&
|
|
656
|
+
!isPeru &&
|
|
656
657
|
(discountValue != null || isMaxDiscountApplied) && (
|
|
657
658
|
<span
|
|
658
659
|
className="rounded-[100px] bg-[#ff5964] px-[6px] text-[12px] bold-text leading-[20px] text-white"
|
|
@@ -667,6 +668,21 @@ function SeatSection({
|
|
|
667
668
|
: `${discountValue}% OFF`}
|
|
668
669
|
</span>
|
|
669
670
|
)}
|
|
671
|
+
|
|
672
|
+
{serviceItem?.ui_type !== 1 &&
|
|
673
|
+
isPeru &&
|
|
674
|
+
serviceItem?.discount_display_badge && (
|
|
675
|
+
<span
|
|
676
|
+
className="rounded-[100px] bg-[#ff5964] px-[6px] text-[12px] bold-text leading-[20px] text-white"
|
|
677
|
+
style={{
|
|
678
|
+
animation: "pulse-zoom 2s ease-in-out infinite",
|
|
679
|
+
whiteSpace: "nowrap",
|
|
680
|
+
backgroundColor: discountSeatCrossPriceColor,
|
|
681
|
+
}}
|
|
682
|
+
>
|
|
683
|
+
{serviceItem?.discount_display_badge}
|
|
684
|
+
</span>
|
|
685
|
+
)}
|
|
670
686
|
</div>
|
|
671
687
|
|
|
672
688
|
{serviceItem?.ui_type === 1 ? null : (
|
|
@@ -678,7 +694,7 @@ function SeatSection({
|
|
|
678
694
|
className="text-[13.33px] font-normal leading-[20px] relative whitespace-nowrap"
|
|
679
695
|
style={{
|
|
680
696
|
position: "relative",
|
|
681
|
-
color:
|
|
697
|
+
color: discountSeatCrossPriceColor,
|
|
682
698
|
}}
|
|
683
699
|
>
|
|
684
700
|
{formatPrice(discountSeat.originalPrice)}
|
|
@@ -478,6 +478,16 @@ function SeatSectionMobile({
|
|
|
478
478
|
return false;
|
|
479
479
|
})();
|
|
480
480
|
|
|
481
|
+
// Backend-provided ready-made badge text (e.g. "30% OFF" / "$2.000 OFF")
|
|
482
|
+
// takes precedence over the value computed client-side.
|
|
483
|
+
const discountBadgeText =
|
|
484
|
+
serviceItem?.discounts?.[0]?.discount_display_badge ||
|
|
485
|
+
(isMaxDiscountApplied
|
|
486
|
+
? `${commonService.currency(serviceItem?.max_discount)} OFF`
|
|
487
|
+
: discountValue != null
|
|
488
|
+
? `${discountValue}% OFF`
|
|
489
|
+
: null);
|
|
490
|
+
|
|
481
491
|
const getMinValue = (data: any): number | undefined => {
|
|
482
492
|
const vals = (Array.isArray(data) ? data : Object.values(data || {})).map(
|
|
483
493
|
Number,
|
|
@@ -595,59 +605,56 @@ function SeatSectionMobile({
|
|
|
595
605
|
) : hasDiscount && discountSeat ? (
|
|
596
606
|
<div>
|
|
597
607
|
<div className="relative grid grid-cols-[auto_auto] justify-between gap-x-[8px] ">
|
|
598
|
-
{serviceItem?.ui_type !== 1 &&
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
608
|
+
{serviceItem?.ui_type !== 1 && discountBadgeText && (
|
|
609
|
+
<>
|
|
610
|
+
<div
|
|
611
|
+
className="absolute -top-[18px] right-[0px]"
|
|
612
|
+
style={{
|
|
613
|
+
animation: "pulse-zoom 2s ease-in-out infinite",
|
|
614
|
+
opacity: isSoldOut ? 0.5 : 1,
|
|
615
|
+
}}
|
|
616
|
+
>
|
|
617
|
+
<span
|
|
618
|
+
className="rounded-[100px] px-[8px] text-[12px] bold-text leading-[20px] text-[#fff]"
|
|
603
619
|
style={{
|
|
604
|
-
|
|
605
|
-
opacity: isSoldOut ? 0.5 : 1,
|
|
620
|
+
backgroundColor: tooltipBgColor,
|
|
606
621
|
}}
|
|
607
622
|
>
|
|
608
|
-
|
|
609
|
-
className="rounded-[100px] px-[8px] text-[12px] bold-text leading-[20px] text-[#fff]"
|
|
610
|
-
style={{
|
|
611
|
-
backgroundColor: tooltipBgColor,
|
|
612
|
-
}}
|
|
613
|
-
>
|
|
614
|
-
{isMaxDiscountApplied
|
|
615
|
-
? `${commonService.currency(serviceItem?.max_discount)} OFF`
|
|
616
|
-
: `${discountValue}% OFF`}
|
|
617
|
-
</span>
|
|
618
|
-
</div>
|
|
619
|
-
|
|
620
|
-
<span className="min-[420]:text-[13px] text-[12px] leading-[20px] text-[#c2c2c2]">
|
|
621
|
-
Antes
|
|
623
|
+
{discountBadgeText}
|
|
622
624
|
</span>
|
|
625
|
+
</div>
|
|
626
|
+
|
|
627
|
+
<span className="min-[420]:text-[13px] text-[12px] leading-[20px] text-[#c2c2c2]">
|
|
628
|
+
Antes
|
|
629
|
+
</span>
|
|
630
|
+
<span
|
|
631
|
+
className="min-[420]:text-[13px] text-[12px] leading-[20px] text-right "
|
|
632
|
+
style={{
|
|
633
|
+
position: "relative",
|
|
634
|
+
display: "inline-block",
|
|
635
|
+
overflow: "hidden",
|
|
636
|
+
color: discountSeatPriceColor,
|
|
637
|
+
}}
|
|
638
|
+
>
|
|
639
|
+
{commonService.currency(
|
|
640
|
+
discountSeat.originalPrice,
|
|
641
|
+
currencySign,
|
|
642
|
+
)}
|
|
623
643
|
<span
|
|
624
|
-
className="min-[420]:text-[13px] text-[12px] leading-[20px] text-right"
|
|
625
644
|
style={{
|
|
626
|
-
position: "
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
645
|
+
position: "absolute",
|
|
646
|
+
top: "35%",
|
|
647
|
+
left: "50%",
|
|
648
|
+
width: "80%",
|
|
649
|
+
height: "1px",
|
|
650
|
+
background: discountSeatCrossPriceColor,
|
|
651
|
+
transform: "rotate(-12deg)",
|
|
652
|
+
transformOrigin: "center",
|
|
630
653
|
}}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
)}
|
|
636
|
-
<span
|
|
637
|
-
style={{
|
|
638
|
-
position: "absolute",
|
|
639
|
-
top: "35%",
|
|
640
|
-
left: "50%",
|
|
641
|
-
width: "80%",
|
|
642
|
-
height: "1px",
|
|
643
|
-
background: discountSeatCrossPriceColor,
|
|
644
|
-
transform: "rotate(-12deg)",
|
|
645
|
-
transformOrigin: "center",
|
|
646
|
-
}}
|
|
647
|
-
/>
|
|
648
|
-
</span>
|
|
649
|
-
</>
|
|
650
|
-
)}
|
|
654
|
+
/>
|
|
655
|
+
</span>
|
|
656
|
+
</>
|
|
657
|
+
)}
|
|
651
658
|
|
|
652
659
|
<span
|
|
653
660
|
className="min-[420]:text-[13px] text-[12px] leading-[24px]"
|