kupos-ui-components-lib 9.6.9 → 9.6.10

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.
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { ServiceItemProps } from "./types";
3
- declare function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isSeatIcon, showAvailableSeats, isPeru, siteType, isAllinBus, t, }: ServiceItemProps & {
3
+ declare function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isSeatIcon, showAvailableSeats, isPeru, siteType, isAllinBus, viewersConfig, isExpand, setIsExpand, coachKey, isLoggedIn, showLoginModal, t, }: ServiceItemProps & {
4
4
  currencySign?: string;
5
5
  }): React.ReactElement;
6
6
  export default PeruServiceItemDesktop;
@@ -14,73 +14,65 @@ import pullmanFlexibleAnimation from "../../assets/images/anims/service_list/pul
14
14
  import pullmanPetFriendlyAnimation from "../../assets/images/anims/service_list/pullmanPetFriendly.json";
15
15
  import pullmanLocationAnimation from "../../assets/images/anims/service_list/pullmanLocation.json";
16
16
  import pullmanPriorityStageAnimation from "../../assets/images/anims/service_list/pullmanPriorityStage.json";
17
- import pullmanPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
18
- import pullmanDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
19
17
  import opsitesFlexibleAnimation from "../../assets/images/anims/service_list/opsitesFlexible.json";
20
18
  import opsitesPetFriendlyAnimation from "../../assets/images/anims/service_list/opsitesPetFriendly.json";
21
19
  import opsitesLocationAnimation from "../../assets/images/anims/service_list/opsitesLocation.json";
22
20
  import opsitesPriorityStageAnimation from "../../assets/images/anims/service_list/opsitesPriorityStage.json";
23
- import opsitesPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
24
- import opsitesDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
25
- import linatalFlexibleAnimation from "../../assets/images/anims/service_list/flexible.json";
26
- import linatalPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
27
- import linatalDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
28
- import linatalPriorityStageAnimation from "../../assets/images/anims/service_list/priority_stage.json";
29
- import linatalPetFriendlyAnimation from "../../assets/images/anims/service_list/pet_friendly.json";
30
- import linatalLocationAnimation from "../../assets/images/anims/service_list/location.json";
21
+ import bombAnimation from "../../assets/images/anims/service_list/bomb.json";
22
+ import dotAnimation from "../../assets/images/anims/service_list/dot_animation.json";
31
23
  import StageTooltip from "../../ui/StagesTooltip";
32
24
  import RatingBlock from "../../ui/RatingBlock";
33
25
  import DurationBlock from "../../ui/DurationBlock";
34
26
  import PetBlock from "../../ui/PetBlock";
35
27
  import FlexibleBlock from "../../ui/FlexibleBlock";
36
28
  import AmenitiesBlock from "../../ui/AmenitiesBlock";
29
+ import SeatSection from "../../ui/SeatSection/SeatSection";
30
+ import KuposButton from "../../ui/KuposButton/KuposButton";
31
+ import BottomAmenities from "../../ui/BottomAmenities/BottomAmenities";
37
32
  const SEAT_EXCEPTIONS = ["Asiento mascota"];
38
- function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isSeatIcon, showAvailableSeats, isPeru, siteType, isAllinBus, t = (key) => key, }) {
39
- var _a, _b, _c, _d;
40
- const animationMap = {
41
- promoAnim: {
42
- kupos: promoAnimation,
43
- pullman: pullmanPromoAnimation,
44
- opsites: opsitesPromoAnimation,
45
- linatal: linatalPromoAnimation,
46
- },
47
- locationAnim: {
48
- kupos: locationAnimation,
49
- pullman: pullmanLocationAnimation,
50
- opsites: opsitesLocationAnimation,
51
- linatal: linatalLocationAnimation,
52
- },
53
- directoAnim: {
54
- kupos: directoAnimation,
55
- pullman: pullmanDirectoAnimation,
56
- opsites: opsitesDirectoAnimation,
57
- linatal: linatalDirectoAnimation,
58
- },
59
- petFriendlyAnim: {
60
- kupos: petFriendlyAnimation,
61
- pullman: pullmanPetFriendlyAnimation,
62
- opsites: opsitesPetFriendlyAnimation,
63
- linatal: linatalPetFriendlyAnimation,
64
- },
65
- priorityStageAnim: {
66
- kupos: priorityStageAnimation,
67
- pullman: pullmanPriorityStageAnimation,
68
- opsites: opsitesPriorityStageAnimation,
69
- linatal: linatalPriorityStageAnimation,
70
- },
71
- flexibleIcon: {
72
- kupos: flexibleAnimation,
73
- pullman: pullmanFlexibleAnimation,
74
- opsites: opsitesFlexibleAnimation,
75
- linatal: linatalFlexibleAnimation,
76
- },
77
- };
33
+ const ANIMATION_MAP = {
34
+ promoAnim: {
35
+ kupos: promoAnimation,
36
+ },
37
+ locationAnim: {
38
+ kupos: locationAnimation,
39
+ pullman: pullmanLocationAnimation,
40
+ opsites: opsitesLocationAnimation,
41
+ },
42
+ directoAnim: {
43
+ kupos: directoAnimation,
44
+ },
45
+ petFriendlyAnim: {
46
+ kupos: petFriendlyAnimation,
47
+ pullman: pullmanPetFriendlyAnimation,
48
+ opsites: opsitesPetFriendlyAnimation,
49
+ },
50
+ priorityStageAnim: {
51
+ kupos: priorityStageAnimation,
52
+ pullman: pullmanPriorityStageAnimation,
53
+ opsites: opsitesPriorityStageAnimation,
54
+ },
55
+ flexibleIcon: {
56
+ kupos: flexibleAnimation,
57
+ pullman: pullmanFlexibleAnimation,
58
+ opsites: opsitesFlexibleAnimation,
59
+ },
60
+ bombAnimation: {
61
+ kupos: bombAnimation,
62
+ },
63
+ dotAnimation: {
64
+ kupos: dotAnimation,
65
+ opsites: dotAnimation,
66
+ },
67
+ };
68
+ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isSeatIcon, showAvailableSeats, isPeru, siteType, isAllinBus, viewersConfig, isExpand, setIsExpand, coachKey, isLoggedIn, showLoginModal, t = (key) => key, }) {
69
+ var _a, _b, _c, _d, _e;
78
70
  const getAnimationIcon = (icon) => {
79
- const animation = animationMap[icon];
71
+ var _a;
72
+ const animation = ANIMATION_MAP[icon];
80
73
  if (!animation)
81
74
  return null;
82
- const currentSiteType = siteType || "kupos";
83
- return animation[currentSiteType];
75
+ return (_a = animation[siteType]) !== null && _a !== void 0 ? _a : animation.kupos;
84
76
  };
85
77
  const SvgAmenities = ({ moreAnemities, name, color, }) => {
86
78
  var _a;
@@ -224,6 +216,7 @@ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaDa
224
216
  };
225
217
  const checkMidnight = () => {
226
218
  var _a, _b;
219
+ setIsExpand === null || setIsExpand === void 0 ? void 0 : setIsExpand(null);
227
220
  if ((cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label) &&
228
221
  (cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label) &&
229
222
  ((cityOrigin.label.toLowerCase().includes("argentina") &&
@@ -312,18 +305,55 @@ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaDa
312
305
  const dropoffName = ((_b = serviceItem.stage_details_arr) === null || _b === void 0 ? void 0 : _b.length)
313
306
  ? extractStage(serviceItem.stage_details_arr, 1)
314
307
  : null;
308
+ const countdownSeconds = 599;
309
+ const startCountdown = (node) => {
310
+ if (!node)
311
+ return;
312
+ const prevId = node.dataset.countdownId;
313
+ if (prevId)
314
+ clearInterval(Number(prevId));
315
+ let remaining = countdownSeconds;
316
+ const formatTime = (totalSecs) => {
317
+ const m = Math.floor(totalSecs / 60);
318
+ const s = totalSecs % 60;
319
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
320
+ };
321
+ node.textContent = formatTime(remaining);
322
+ const id = setInterval(() => {
323
+ remaining -= 1;
324
+ if (remaining <= 0) {
325
+ remaining = countdownSeconds;
326
+ }
327
+ node.textContent = formatTime(remaining);
328
+ }, 1000);
329
+ node.dataset.countdownId = String(id);
330
+ };
315
331
  const items = [
316
332
  {
317
- key: "rating",
318
- width: "30%",
319
- render: (React.createElement(RatingBlock, { showRating: showRating, serviceItem: serviceItem, isSoldOut: isSoldOut, colors: colors, t: t, translation: translation, isPeru: isPeru })),
333
+ key: "amenities",
334
+ width: "20%",
335
+ condition: true,
336
+ render: (React.createElement(AmenitiesBlock, { serviceItem: serviceItem, metaData: metaData, isSoldOut: isSoldOut, colors: colors, getAnimationIcon: getAnimationIcon, getAmenityName: CommonService.getAmenityName, SvgAmenities: SvgAmenities, isPeru: isPeru })),
320
337
  },
321
338
  {
322
339
  key: "duration",
323
- width: "20%",
340
+ width: "12%",
324
341
  condition: serviceItem.duration,
325
342
  render: (React.createElement(DurationBlock, { serviceItem: serviceItem, translation: translation, renderIcon: renderIcon, isSoldOut: isSoldOut, colors: colors })),
326
343
  },
344
+ // {
345
+ // key: "directo",
346
+ // width: "12%",
347
+ // condition: serviceItem?.is_direct_trip === true,
348
+ // render: (
349
+ // <DirectoBlock
350
+ // translation={translation}
351
+ // getAnimationIcon={getAnimationIcon}
352
+ // colors={colors}
353
+ // isSoldOut={isSoldOut}
354
+ // />
355
+ // ),
356
+ // },
327
357
  {
328
358
  key: "pet",
329
359
  width: "20%",
@@ -334,48 +364,82 @@ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaDa
334
364
  {
335
365
  key: "flexible",
336
366
  width: "20%",
337
- condition: false,
367
+ condition: serviceItem.is_change_ticket === true,
338
368
  render: (React.createElement(FlexibleBlock, { translation: translation, getAnimationIcon: getAnimationIcon, colors: colors, serviceItem: serviceItem, isSoldOut: isSoldOut })),
339
369
  },
340
- {
341
- key: "amenities",
342
- width: "20%",
343
- render: (React.createElement(AmenitiesBlock, { serviceItem: serviceItem, metaData: metaData, isSoldOut: isSoldOut, colors: colors, isPeru: isPeru, getAnimationIcon: getAnimationIcon, getAmenityName: CommonService.getAmenityName, SvgAmenities: SvgAmenities })),
344
- },
345
370
  ];
346
- const amenitiesItem = items.find((i) => i.key === "amenities");
347
- const otherItems = items.filter((i) => i.key !== "amenities" && i.condition !== false);
348
- return (React.createElement("div", { className: `relative ${serviceItem.offer_text ? "mb-[55px]" : "mb-[10px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
371
+ const otherItems = items.filter((i) => i.key !== "pet" && i.key !== "flexible" && !!i.condition);
372
+ const isItemExpanded = serviceItem.id === isExpand || isExpand === true;
373
+ const grayscaleClass = isSoldOut ? "grayscale" : "";
374
+ const hasOfferText = Boolean(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text);
375
+ const offerGradient = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"} 0%, ${colors.leftGradiantColor || "#ff8842"} 100%)`;
376
+ const offerGradientWithOpacity = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"}80 0%, ${colors.leftGradiantColor || "#ff8842"}80 100%)`;
377
+ const serviceCardStyle = hasOfferText
378
+ ? {
379
+ borderColor: "transparent",
380
+ borderStyle: "solid",
381
+ borderWidth: "3px 3px 0 3px",
382
+ borderRadius: isItemExpanded || coachKey ? "18px 18px 0 0" : "18px",
383
+ background: `linear-gradient(#fff, #fff) padding-box, ${isSoldOut ? offerGradientWithOpacity : offerGradient} border-box`,
384
+ // zIndex: 1,
385
+ }
386
+ : {};
387
+ const seats = removeDuplicateSeats
388
+ ? ((_c = serviceItem.seat_types) === null || _c === void 0 ? void 0 : _c.filter((seat, index, self) => index === self.findIndex((s) => s.label === seat.label))) || []
389
+ : serviceItem.seat_types || [];
390
+ const discountedSeats = seats.map((seat) => (Object.assign(Object.assign({}, seat), CommonService.calculateDiscountedPrice(seat.fare, serviceItem))));
391
+ const hasDiscount = discountedSeats.some((seat) => seat.originalPrice !== seat.discountedPrice);
392
+ 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) ||
349
393
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.train_type_label) === "Tren Express (Nuevo)" ||
350
394
  showTopLabel
351
395
  ? "mt-[24px]"
352
396
  : "mt-[20px]"} ` },
353
- React.createElement("div", { className: "bg-white rounded-[20px] shadow-service mx-auto relative" },
354
- React.createElement("div", { className: "p-[15px] pt-[20px]" },
355
- React.createElement("div", { className: "grid text-[#464647] w-full [grid-template-columns:14%_40%_0.5%_24%_13.5%] gap-x-[2%] items-center", style: { marginTop: showTopLabel ? "8px" : "" } },
356
- React.createElement("div", { className: "flex items-center justify-center m-[auto]" },
357
- React.createElement("div", { className: " " },
358
- React.createElement("img", { src: serviceItem.operator_details[0], alt: "service logo", className: ` h-auto object-contain ${isSoldOut ? "grayscale" : ""}` })),
359
- isCiva ? (React.createElement("div", { className: "text-[13.33px] black-text ml-2" }, serviceItem.operator_details[2])) : null),
397
+ React.createElement("div", { id: `service-card-${serviceItem.id}`, className: `bg-white mx-auto relative ${hasOfferText && !isSoldOut
398
+ ? "z-[3] rounded-[18px]"
399
+ : "rounded-[10px] border border-[#ccc]"}`, style: serviceCardStyle },
400
+ React.createElement("div", { className: " pt-[20px]", style: {
401
+ padding: hasOfferText
402
+ ? "20px 15px 10px 15px"
403
+ : coachKey
404
+ ? "20px 15px 20px 15px"
405
+ : "20px 15px 10px 15px",
406
+ marginTop: hasDiscount || hasOfferText ? "14px" : "",
407
+ } },
408
+ React.createElement("div", { className: "grid text-[#464647] w-full [grid-template-columns:14%_40%_0.5%_24%_13.5%] gap-x-[2%] items-center",
409
+ // style={{ marginTop: showTopLabel ? "8px" : "" }}
410
+ style: {
411
+ marginTop: showTopLabel || (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ? "8px" : "",
412
+ } },
413
+ React.createElement("div", { style: {
414
+ display: "flex",
415
+ flexDirection: "column",
416
+ // gap: "5px",
417
+ } },
418
+ React.createElement("div", {
419
+ // className="flex items-center justify-center m-[auto]"
420
+ className: "" },
421
+ React.createElement("img", { src: serviceItem.operator_details[0], alt: "service logo", className: `h-[30px] w-[auto] ${isSoldOut ? "grayscale" : ""}` }),
422
+ isCiva ? (React.createElement("div", { className: "text-[13.33px] black-text ml-2" }, serviceItem.operator_details[2])) : null),
423
+ React.createElement(RatingBlock, { showRating: showRating, serviceItem: serviceItem, isSoldOut: isSoldOut, colors: colors, t: t, translation: translation, isPeru: isPeru })),
360
424
  React.createElement("div", { className: `min-h-[2.5rem] grid grid-cols-[0.8fr_auto_26%_1fr] gap-x-4 items-center text-[13.33px] ${isSoldOut ? "text-[#c0c0c0]" : ""}`, style: {
361
425
  gridTemplateRows: "1fr",
362
426
  } },
363
- React.createElement("div", { className: "flex flex-col gap-[10px]" },
427
+ React.createElement("div", { className: "flex flex-col gap-[4px]" },
364
428
  orignLabel ? (React.createElement("div", { className: "w-[60px] h-[20px] flex items-center bold-text" }, orignLabel)) : (React.createElement("div", { className: "h-[20px] flex items-center" },
365
- React.createElement("img", { src: (_c = serviceItem.icons) === null || _c === void 0 ? void 0 : _c.origin, alt: "origin", className: `w-[18px] h-auto mr-[8px] ${isSoldOut ? "grayscale" : ""}` }))),
429
+ React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.origin, alt: "origin", className: `w-[18px] h-auto mr-[8px] ${isSoldOut ? "grayscale" : ""}` }))),
366
430
  !isCiva &&
367
431
  (destinationLabel ? (React.createElement("div", { className: "w-[60px] h-[20px] flex items-center bold-text" }, destinationLabel)) : (React.createElement("div", { className: "h-[20px] flex items-center" },
368
- React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.destination, className: `w-[18px] h-auto mr-[8px] ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } }))))),
369
- React.createElement("div", { className: "flex flex-col gap-[10px]" },
432
+ React.createElement("img", { src: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.destination, className: `w-[18px] h-auto mr-[8px] ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } }))))),
433
+ React.createElement("div", { className: "flex flex-col gap-[4px]" },
370
434
  React.createElement(StageTooltip, { stageData: serviceItem.boarding_stages, direction: 1, terminals: busStage, serviceItem: serviceItem, metaData: metaData, colors: colors },
371
435
  React.createElement("span", { className: "cursor-pointer bold-text capitalize" }, DateService.getServiceItemDate(serviceItem.travel_date))),
372
436
  !isCiva && (React.createElement(StageTooltip, { stageData: serviceItem.dropoff_stages, direction: 2, terminals: busStage, serviceItem: serviceItem, metaData: metaData, colors: colors },
373
437
  React.createElement("span", { className: "cursor-pointer bold-text capitalize" }, DateService.getServiceItemDate(serviceItem.arrival_date))))),
374
- React.createElement("div", { className: "flex flex-col gap-[10px] items-center" },
438
+ React.createElement("div", { className: "flex flex-col gap-[4px] items-center" },
375
439
  React.createElement("div", { className: "h-[20px] flex items-center justify-center" },
376
440
  React.createElement("div", null, "\u2022")),
377
441
  !isCiva && (React.createElement("div", { className: "h-[20px] flex items-center justify-center" }, removeArrivalTime ? null : serviceItem.arr_time ? (React.createElement("div", null, "\u2022")) : null))),
378
- React.createElement("div", { className: "flex flex-col gap-[10px]" },
442
+ React.createElement("div", { className: "flex flex-col gap-[4px]" },
379
443
  React.createElement(StageTooltip, { stageData: serviceItem.boarding_stages, direction: 1, terminals: busStage, serviceItem: serviceItem, metaData: metaData, colors: colors },
380
444
  React.createElement("div", { className: "font-[900] bold-text" }, DateService.formatTime(serviceItem.dep_time))),
381
445
  !isCiva && (React.createElement(StageTooltip, { stageData: serviceItem.dropoff_stages, direction: 2, terminals: busStage, serviceItem: serviceItem, metaData: metaData, colors: colors },
@@ -391,63 +455,55 @@ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaDa
391
455
  margin: "auto",
392
456
  } }),
393
457
  React.createElement("div", { className: "content-center" },
394
- React.createElement("div", { className: `relative flex gap-[10px] text-[13.33px] justify-between min-h-[2.5rem] ${getNumberOfSeats() < 3 ? "" : ""}`, style: getNumberOfSeats() < 2
395
- ? { alignItems: "center" }
396
- : { alignItems: "center" } },
397
- React.createElement("div", { className: "flex flex-col justify-between" }, getSeatNames()),
398
- React.createElement("div", { className: "flex flex-col justify-between absolute inset-y-0 right-0 left-1/2 h-full", style: {
399
- color: isSoldOut ? "#c0c0c0" : colors.priceColor,
400
- top: 0,
401
- bottom: 0,
402
- left: "68%",
403
- right: 0,
404
- justifyContent: getNumberOfSeats() < 2 ? "center" : "center",
405
- gap: "5px",
406
- } },
407
- React.createElement("span", { style: {
408
- position: "absolute",
409
- top: getNumberOfSeats() > 1 ? -10 : -5,
410
- fontWeight: "initial",
411
- fontSize: "12px",
412
- left: 0,
413
- color: "#6a6a6a",
414
- } }, "Desde"),
415
- getSeatPrice()))),
458
+ React.createElement(SeatSection, { seatTypes: serviceItem.seat_types, serviceItem: serviceItem, availableSeats: serviceItem.available_seats, isSoldOut: isSoldOut, priceColor: colors.priceColor, currencySign: currencySign, removeDuplicateSeats: removeDuplicateSeats, isPeru: isPeru, renderIcon: renderIcon, discountSeatPriceColor: colors.discountSeatPriceColor })),
416
459
  React.createElement("div", null,
417
- React.createElement("button", { onClick: () => (!isSoldOut ? checkMidnight() : null), disabled: serviceDetailsLoading, className: `w-full ${serviceDetailsLoading || isSoldOut ? "py-[12px]" : "py-[12px]"} text-[13.33px] font-bold text-white rounded-[10px] border-none px-[20px] flex items-center justify-center`, style: {
418
- backgroundColor: serviceDetailsLoading || isSoldOut
419
- ? "lightgray"
420
- : colors.kuposButtonColor,
421
- cursor: serviceDetailsLoading || isSoldOut
422
- ? "not-allowed"
423
- : "pointer",
424
- } },
425
- React.createElement("span", { className: "min-w-[75px] flex justify-center items-center bold-text uppercase" },
426
- isSoldOut ? renderIcon("soldOutIcon", "14px") : null,
427
- serviceDetailsLoading ? (React.createElement("span", { className: "loader-circle" })) : !isSoldOut ? (translation === null || translation === void 0 ? void 0 : translation.buyButton) : (translation === null || translation === void 0 ? void 0 : translation.soldOutButton))))),
428
- showLastSeats ? (React.createElement("div", { className: "flex justify-end mr-[11px]" }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) < 10 &&
429
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) > 0 && (React.createElement("div", { className: "text-[12px] text-[red] mt-1 text-center" }, "\u00A1 \u00DAltimos Asientos!")))) : null,
430
- React.createElement("div", { className: "flex items-center mt-[15px] border-t border-[#eee] pt-[10px]" },
431
- React.createElement("div", { className: "grid items-center gap-[2%] flex-1", style: {
432
- gridTemplateColumns: "30% 18% 23% 23%",
433
- // otherItems
434
- // .map((i) => i.width)
435
- // .join(" "),
436
- } }, otherItems.map((item) => (React.createElement("div", { key: item.key, className: "flex items-center " }, item.render)))),
437
- React.createElement("div", { className: "flex items-center ml-[12px] shrink-0 w-[130px] justify-end" }, amenitiesItem === null || amenitiesItem === void 0 ? void 0 : amenitiesItem.render)))),
460
+ showLastSeats ? (React.createElement("div", { className: "flex justify-end mr-[11px] ", style: {
461
+ position: "absolute",
462
+ top: serviceDetailsLoading ? "7px" : "5px",
463
+ right: "16px",
464
+ } }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) < 10 &&
465
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) > 0 && (React.createElement("div", { className: "text-[12px] text-[#464647] mt-1 text-center" }, "\u00A1\u00DAltimos Asientos!")))) : null,
466
+ React.createElement(KuposButton, { isSoldOut: isSoldOut, isLoading: serviceDetailsLoading, buttonColor: colors.kuposButtonColor, buyLabel: translation === null || translation === void 0 ? void 0 : translation.buyButton, soldOutLabel: translation === null || translation === void 0 ? void 0 : translation.soldOutButton, soldOutIcon: renderIcon("soldOutIcon", "14px"), onClick: checkMidnight }))),
467
+ React.createElement(BottomAmenities, { otherItems: otherItems, serviceItem: serviceItem, grayscaleClass: grayscaleClass, isSoldOut: isSoldOut, isItemExpanded: isItemExpanded, colors: colors, translation: translation, getAnimationIcon: getAnimationIcon, downArrowIcon: renderIcon("downArrow", "10px"), onToggleExpand: () => setIsExpand && setIsExpand(isItemExpanded ? null : serviceItem.id), isPeru: isPeru }))),
438
468
  children,
439
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) && (React.createElement("div", { className: ` text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[36px] pt-[50px] -z-10 rounded-b-[14px] text-[14px]`, style: {
440
- backgroundColor: isSoldOut
441
- ? colors === null || colors === void 0 ? void 0 : colors.bottomStripColor
442
- : colors === null || colors === void 0 ? void 0 : colors.bottomStripColor,
469
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) && (React.createElement("div", { className: "text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[36px] pt-[50px] rounded-b-[14px] text-[14px]", style: {
470
+ background: offerGradient,
443
471
  opacity: isSoldOut ? 0.5 : 1,
444
472
  } },
445
- React.createElement(LottiePlayer
446
- // animationData={serviceItem.icons.promoAnim}
447
- , {
448
- // animationData={serviceItem.icons.promoAnim}
449
- animationData: getAnimationIcon("promoAnim"), width: "18px", height: "18px" }),
450
- React.createElement("span", { className: "ml-[10px]" }, serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text))),
473
+ React.createElement("div", { className: "flex justify-between items-center w-full" },
474
+ React.createElement("div", { className: "flex items-center " },
475
+ React.createElement("div", { className: "flex items-center" },
476
+ React.createElement(LottiePlayer, { animationData: getAnimationIcon("bombAnimation"), width: "18px", height: "18px" }),
477
+ React.createElement("div", { className: "flex items-center mt-[2px]" },
478
+ React.createElement("span", { className: "bold-text ml-[6px]" },
479
+ ((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || "").length > 30
480
+ ? ((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || "").slice(0, 30) + "..."
481
+ : (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || "",
482
+ " ",
483
+ isLoggedIn ? null : (React.createElement("span", { onClick: showLoginModal, className: "cursor-pointer" }, "- registro")),
484
+ " ",
485
+ "\u00A0"),
486
+ " ",
487
+ "| Termina en\u00A0",
488
+ React.createElement("span", { className: "bold-text text-end", ref: (node) => CommonService.startCountdown(node, 599), style: {
489
+ fontVariantNumeric: "tabular-nums",
490
+ display: "inline-block",
491
+ } })))),
492
+ React.createElement("div", { className: "flex items-center" },
493
+ React.createElement(LottiePlayer, { animationData: getAnimationIcon("dotAnimation"), width: "12px", height: "12px" }),
494
+ React.createElement("span", { className: "ml-[6px]" },
495
+ React.createElement("span", { className: "bold-text", ref: (node) => CommonService.startViewerCount(node, viewersConfig), style: { fontVariantNumeric: "tabular-nums" } }),
496
+ " ",
497
+ React.createElement("span", null,
498
+ " ",
499
+ (viewersConfig === null || viewersConfig === void 0 ? void 0 : viewersConfig.label) || " viendo",
500
+ " |",
501
+ " ",
502
+ React.createElement("span", { className: "" },
503
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) ? null : "Quedan pocos • ",
504
+ React.createElement("span", { className: "bold-text", ref: (node) => CommonService.startComprandoCount(node, 4, 16), style: { fontVariantNumeric: "tabular-nums" } }),
505
+ " ",
506
+ "comprando"))))))),
451
507
  React.createElement("div", { className: "absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[15px] z-10 " },
452
508
  showTopLabel && (React.createElement("div", { className: `flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`, style: {
453
509
  backgroundColor: isSoldOut ? "#ddd" : colors.ratingBottomColor,
@@ -472,7 +528,7 @@ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaDa
472
528
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_transpordo) && (React.createElement("div", { className: `flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20`, style: {
473
529
  backgroundColor: isSoldOut ? "#ddd" : colors.tooltipColor,
474
530
  } },
475
- React.createElement(LottiePlayer, { animationData: serviceItem.icons.connectingServiceIcon, width: "14px", height: "14px" }),
531
+ renderIcon("connectingServiceIcon", "12px"),
476
532
  React.createElement("div", null, "Conexión"))),
477
533
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) && (React.createElement("div", { className: `flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20 `, style: {
478
534
  backgroundColor: isSoldOut ? "#ddd" : colors.tooltipColor,
@@ -243,7 +243,7 @@ function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, child
243
243
  },
244
244
  ];
245
245
  const otherItems = items.filter((i) => i.key !== "pet" && i.key !== "flexible" && !!i.condition);
246
- return (React.createElement(React.Fragment, null, isPeruSites ? (React.createElement(PeruServiceItemDesktop, { serviceItem: serviceItem, onBookButtonPress: onBookButtonPress, colors: colors, metaData: metaData, children: children, busStage: busStage, serviceDetailsLoading: serviceDetailsLoading, cityOrigin: cityOrigin, cityDestination: cityDestination, translation: translation, orignLabel: orignLabel, destinationLabel: destinationLabel, currencySign: currencySign, isCiva: isCiva, showRating: showRating, showLastSeats: showLastSeats, removeArrivalTime: removeArrivalTime, removeDuplicateSeats: removeDuplicateSeats, isPeruSites: isPeruSites, t: (key) => t(key), showAvailableSeats: showAvailableSeats, isSeatIcon: isSeatIcon, isPeru: isPeru, siteType: siteType, isAllinBus: isAllinBus })) : (React.createElement("div", { className: `relative hover:z-[150] ${hasOfferText || hasDpEnabled ? "mb-[55px]" : "mb-[10px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
246
+ return (React.createElement(React.Fragment, null, isPeruSites ? (React.createElement(PeruServiceItemDesktop, { serviceItem: serviceItem, onBookButtonPress: onBookButtonPress, colors: colors, metaData: metaData, children: children, busStage: busStage, serviceDetailsLoading: serviceDetailsLoading, cityOrigin: cityOrigin, cityDestination: cityDestination, translation: translation, orignLabel: orignLabel, destinationLabel: destinationLabel, currencySign: currencySign, isCiva: isCiva, showRating: showRating, showLastSeats: showLastSeats, removeArrivalTime: removeArrivalTime, removeDuplicateSeats: removeDuplicateSeats, isPeruSites: isPeruSites, t: (key) => t(key), showAvailableSeats: showAvailableSeats, isSeatIcon: isSeatIcon, isPeru: isPeru, siteType: siteType, isAllinBus: isAllinBus, viewersConfig: viewersConfig, isLoggedIn: isLoggedIn, showLoginModal: showLoginModal, isExpand: isExpand, setIsExpand: setIsExpand, coachKey: coachKey })) : (React.createElement("div", { className: `relative hover:z-[150] ${hasOfferText || hasDpEnabled ? "mb-[55px]" : "mb-[10px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
247
247
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.train_type_label) === "Tren Express (Nuevo)" ||
248
248
  showTopLabel
249
249
  ? "mt-[24px]"
package/dist/styles.css CHANGED
@@ -279,9 +279,6 @@
279
279
  .ml-\[10px\] {
280
280
  margin-left: 10px;
281
281
  }
282
- .ml-\[12px\] {
283
- margin-left: 12px;
284
- }
285
282
  .ml-\[50px\] {
286
283
  margin-left: 50px;
287
284
  }
@@ -399,12 +396,6 @@
399
396
  .w-\[115px\] {
400
397
  width: 115px;
401
398
  }
402
- .w-\[120px\] {
403
- width: 120px;
404
- }
405
- .w-\[130px\] {
406
- width: 130px;
407
- }
408
399
  .w-\[150px\] {
409
400
  width: 150px;
410
401
  }
@@ -794,9 +785,6 @@
794
785
  .pt-\[6px\] {
795
786
  padding-top: 6px;
796
787
  }
797
- .pt-\[10px\] {
798
- padding-top: 10px;
799
- }
800
788
  .pt-\[20px\] {
801
789
  padding-top: 20px;
802
790
  }
@@ -956,9 +944,6 @@
956
944
  .text-\[\#fff\] {
957
945
  color: #fff;
958
946
  }
959
- .text-\[red\] {
960
- color: red;
961
- }
962
947
  .capitalize {
963
948
  text-transform: capitalize;
964
949
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.6.9",
3
+ "version": "9.6.10",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -18,31 +18,63 @@ import pullmanFlexibleAnimation from "../../assets/images/anims/service_list/pul
18
18
  import pullmanPetFriendlyAnimation from "../../assets/images/anims/service_list/pullmanPetFriendly.json";
19
19
  import pullmanLocationAnimation from "../../assets/images/anims/service_list/pullmanLocation.json";
20
20
  import pullmanPriorityStageAnimation from "../../assets/images/anims/service_list/pullmanPriorityStage.json";
21
- import pullmanPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
22
- import pullmanDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
23
21
 
24
22
  import opsitesFlexibleAnimation from "../../assets/images/anims/service_list/opsitesFlexible.json";
25
23
  import opsitesPetFriendlyAnimation from "../../assets/images/anims/service_list/opsitesPetFriendly.json";
26
24
  import opsitesLocationAnimation from "../../assets/images/anims/service_list/opsitesLocation.json";
27
25
  import opsitesPriorityStageAnimation from "../../assets/images/anims/service_list/opsitesPriorityStage.json";
28
- import opsitesPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
29
- import opsitesDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
30
-
31
- import linatalFlexibleAnimation from "../../assets/images/anims/service_list/flexible.json";
32
- import linatalPromoAnimation from "../../assets/images/anims/service_list/promocion.json";
33
- import linatalDirectoAnimation from "../../assets/images/anims/service_list/directo.json";
34
- import linatalPriorityStageAnimation from "../../assets/images/anims/service_list/priority_stage.json";
35
- import linatalPetFriendlyAnimation from "../../assets/images/anims/service_list/pet_friendly.json";
36
- import linatalLocationAnimation from "../../assets/images/anims/service_list/location.json";
26
+
27
+ import bombAnimation from "../../assets/images/anims/service_list/bomb.json";
28
+ import dotAnimation from "../../assets/images/anims/service_list/dot_animation.json";
29
+
37
30
  import StageTooltip from "../../ui/StagesTooltip";
38
31
  import RatingBlock from "../../ui/RatingBlock";
39
32
  import DurationBlock from "../../ui/DurationBlock";
40
33
  import PetBlock from "../../ui/PetBlock";
41
34
  import FlexibleBlock from "../../ui/FlexibleBlock";
42
35
  import AmenitiesBlock from "../../ui/AmenitiesBlock";
36
+ import SeatSection from "../../ui/SeatSection/SeatSection";
37
+ import KuposButton from "../../ui/KuposButton/KuposButton";
38
+ import BottomAmenities from "../../ui/BottomAmenities/BottomAmenities";
43
39
 
44
40
  const SEAT_EXCEPTIONS = ["Asiento mascota"];
45
41
 
42
+ const ANIMATION_MAP: Record<string, Record<string, any>> = {
43
+ promoAnim: {
44
+ kupos: promoAnimation,
45
+ },
46
+ locationAnim: {
47
+ kupos: locationAnimation,
48
+ pullman: pullmanLocationAnimation,
49
+ opsites: opsitesLocationAnimation,
50
+ },
51
+ directoAnim: {
52
+ kupos: directoAnimation,
53
+ },
54
+ petFriendlyAnim: {
55
+ kupos: petFriendlyAnimation,
56
+ pullman: pullmanPetFriendlyAnimation,
57
+ opsites: opsitesPetFriendlyAnimation,
58
+ },
59
+ priorityStageAnim: {
60
+ kupos: priorityStageAnimation,
61
+ pullman: pullmanPriorityStageAnimation,
62
+ opsites: opsitesPriorityStageAnimation,
63
+ },
64
+ flexibleIcon: {
65
+ kupos: flexibleAnimation,
66
+ pullman: pullmanFlexibleAnimation,
67
+ opsites: opsitesFlexibleAnimation,
68
+ },
69
+ bombAnimation: {
70
+ kupos: bombAnimation,
71
+ },
72
+ dotAnimation: {
73
+ kupos: dotAnimation,
74
+ opsites: dotAnimation,
75
+ },
76
+ };
77
+
46
78
  function PeruServiceItemDesktop({
47
79
  serviceItem,
48
80
  onBookButtonPress,
@@ -67,52 +99,18 @@ function PeruServiceItemDesktop({
67
99
  isPeru,
68
100
  siteType,
69
101
  isAllinBus,
102
+ viewersConfig,
103
+ isExpand,
104
+ setIsExpand,
105
+ coachKey,
106
+ isLoggedIn,
107
+ showLoginModal,
70
108
  t = (key: string) => key,
71
109
  }: ServiceItemProps & { currencySign?: string }): React.ReactElement {
72
- const animationMap: Record<string, Record<string, any>> = {
73
- promoAnim: {
74
- kupos: promoAnimation,
75
- pullman: pullmanPromoAnimation,
76
- opsites: opsitesPromoAnimation,
77
- linatal: linatalPromoAnimation,
78
- },
79
- locationAnim: {
80
- kupos: locationAnimation,
81
- pullman: pullmanLocationAnimation,
82
- opsites: opsitesLocationAnimation,
83
- linatal: linatalLocationAnimation,
84
- },
85
- directoAnim: {
86
- kupos: directoAnimation,
87
- pullman: pullmanDirectoAnimation,
88
- opsites: opsitesDirectoAnimation,
89
- linatal: linatalDirectoAnimation,
90
- },
91
- petFriendlyAnim: {
92
- kupos: petFriendlyAnimation,
93
- pullman: pullmanPetFriendlyAnimation,
94
- opsites: opsitesPetFriendlyAnimation,
95
- linatal: linatalPetFriendlyAnimation,
96
- },
97
- priorityStageAnim: {
98
- kupos: priorityStageAnimation,
99
- pullman: pullmanPriorityStageAnimation,
100
- opsites: opsitesPriorityStageAnimation,
101
- linatal: linatalPriorityStageAnimation,
102
- },
103
- flexibleIcon: {
104
- kupos: flexibleAnimation,
105
- pullman: pullmanFlexibleAnimation,
106
- opsites: opsitesFlexibleAnimation,
107
- linatal: linatalFlexibleAnimation,
108
- },
109
- };
110
-
111
110
  const getAnimationIcon = (icon: string) => {
112
- const animation = animationMap[icon];
111
+ const animation = ANIMATION_MAP[icon];
113
112
  if (!animation) return null;
114
- const currentSiteType = siteType || "kupos";
115
- return animation[currentSiteType];
113
+ return animation[siteType] ?? animation.kupos;
116
114
  };
117
115
  const SvgAmenities = ({
118
116
  moreAnemities,
@@ -346,6 +344,8 @@ function PeruServiceItemDesktop({
346
344
  };
347
345
 
348
346
  const checkMidnight = () => {
347
+ setIsExpand?.(null);
348
+
349
349
  if (
350
350
  cityOrigin?.label &&
351
351
  cityDestination?.label &&
@@ -450,26 +450,56 @@ function PeruServiceItemDesktop({
450
450
  ? extractStage(serviceItem.stage_details_arr, 1)
451
451
  : null;
452
452
 
453
+ const countdownSeconds = 599;
454
+
455
+ const startCountdown = (node: HTMLSpanElement | null) => {
456
+ if (!node) return;
457
+
458
+ const prevId = node.dataset.countdownId;
459
+ if (prevId) clearInterval(Number(prevId));
460
+
461
+ let remaining = countdownSeconds;
462
+
463
+ const formatTime = (totalSecs: number) => {
464
+ const m = Math.floor(totalSecs / 60);
465
+ const s = totalSecs % 60;
466
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
467
+ };
468
+
469
+ node.textContent = formatTime(remaining);
470
+
471
+ const id = setInterval(() => {
472
+ remaining -= 1;
473
+ if (remaining <= 0) {
474
+ remaining = countdownSeconds;
475
+ }
476
+ node.textContent = formatTime(remaining);
477
+ }, 1000);
478
+
479
+ node.dataset.countdownId = String(id);
480
+ };
481
+
453
482
  const items = [
454
483
  {
455
- key: "rating",
456
- width: "30%",
484
+ key: "amenities",
485
+ width: "20%",
486
+ condition: true,
457
487
  render: (
458
- <RatingBlock
459
- showRating={showRating}
488
+ <AmenitiesBlock
460
489
  serviceItem={serviceItem}
490
+ metaData={metaData}
461
491
  isSoldOut={isSoldOut}
462
492
  colors={colors}
463
- t={t}
464
- translation={translation}
493
+ getAnimationIcon={getAnimationIcon}
494
+ getAmenityName={CommonService.getAmenityName}
495
+ SvgAmenities={SvgAmenities}
465
496
  isPeru={isPeru}
466
497
  />
467
498
  ),
468
499
  },
469
-
470
500
  {
471
501
  key: "duration",
472
- width: "20%",
502
+ width: "12%",
473
503
  condition: serviceItem.duration,
474
504
  render: (
475
505
  <DurationBlock
@@ -482,6 +512,20 @@ function PeruServiceItemDesktop({
482
512
  ),
483
513
  },
484
514
 
515
+ // {
516
+ // key: "directo",
517
+ // width: "12%",
518
+ // condition: serviceItem?.is_direct_trip === true,
519
+ // render: (
520
+ // <DirectoBlock
521
+ // translation={translation}
522
+ // getAnimationIcon={getAnimationIcon}
523
+ // colors={colors}
524
+ // isSoldOut={isSoldOut}
525
+ // />
526
+ // ),
527
+ // },
528
+
485
529
  {
486
530
  key: "pet",
487
531
  width: "20%",
@@ -501,7 +545,7 @@ function PeruServiceItemDesktop({
501
545
  {
502
546
  key: "flexible",
503
547
  width: "20%",
504
- condition: false,
548
+ condition: serviceItem.is_change_ticket === true,
505
549
  render: (
506
550
  <FlexibleBlock
507
551
  translation={translation}
@@ -512,35 +556,49 @@ function PeruServiceItemDesktop({
512
556
  />
513
557
  ),
514
558
  },
515
-
516
- {
517
- key: "amenities",
518
- width: "20%",
519
- render: (
520
- <AmenitiesBlock
521
- serviceItem={serviceItem}
522
- metaData={metaData}
523
- isSoldOut={isSoldOut}
524
- colors={colors}
525
- isPeru={isPeru}
526
- getAnimationIcon={getAnimationIcon}
527
- getAmenityName={CommonService.getAmenityName}
528
- SvgAmenities={SvgAmenities}
529
- />
530
- ),
531
- },
532
559
  ];
533
560
 
534
- const amenitiesItem = items.find((i) => i.key === "amenities");
535
561
  const otherItems = items.filter(
536
- (i) => i.key !== "amenities" && i.condition !== false,
562
+ (i) => i.key !== "pet" && i.key !== "flexible" && !!i.condition,
563
+ );
564
+
565
+ const isItemExpanded = serviceItem.id === isExpand || isExpand === true;
566
+ const grayscaleClass = isSoldOut ? "grayscale" : "";
567
+
568
+ const hasOfferText = Boolean(serviceItem?.offer_text);
569
+
570
+ const offerGradient = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"} 0%, ${colors.leftGradiantColor || "#ff8842"} 100%)`;
571
+ const offerGradientWithOpacity = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"}80 0%, ${colors.leftGradiantColor || "#ff8842"}80 100%)`;
572
+ const serviceCardStyle: React.CSSProperties = hasOfferText
573
+ ? {
574
+ borderColor: "transparent",
575
+ borderStyle: "solid",
576
+ borderWidth: "3px 3px 0 3px",
577
+ borderRadius: isItemExpanded || coachKey ? "18px 18px 0 0" : "18px",
578
+ background: `linear-gradient(#fff, #fff) padding-box, ${isSoldOut ? offerGradientWithOpacity : offerGradient} border-box`,
579
+ // zIndex: 1,
580
+ }
581
+ : {};
582
+
583
+ const seats = removeDuplicateSeats
584
+ ? serviceItem.seat_types?.filter(
585
+ (seat, index, self) =>
586
+ index === self.findIndex((s) => s.label === seat.label),
587
+ ) || []
588
+ : serviceItem.seat_types || [];
589
+
590
+ const discountedSeats = seats.map((seat) => ({
591
+ ...seat,
592
+ ...CommonService.calculateDiscountedPrice(seat.fare, serviceItem as any),
593
+ }));
594
+
595
+ const hasDiscount = discountedSeats.some(
596
+ (seat) => seat.originalPrice !== seat.discountedPrice,
537
597
  );
538
598
 
539
599
  return (
540
600
  <div
541
- className={`relative ${
542
- serviceItem.offer_text ? "mb-[55px]" : "mb-[10px]"
543
- } ${
601
+ className={`relative hover:z-[150] ${hasOfferText ? "mb-[55px]" : "mb-[10px]"} ${
544
602
  serviceItem?.is_direct_trip ||
545
603
  serviceItem?.train_type_label === "Tren Express (Nuevo)" ||
546
604
  showTopLabel
@@ -549,40 +607,71 @@ function PeruServiceItemDesktop({
549
607
  } `}
550
608
  >
551
609
  <div
552
- className={"bg-white rounded-[20px] shadow-service mx-auto relative"}
610
+ id={`service-card-${serviceItem.id}`}
611
+ className={`bg-white mx-auto relative ${
612
+ hasOfferText && !isSoldOut
613
+ ? "z-[3] rounded-[18px]"
614
+ : "rounded-[10px] border border-[#ccc]"
615
+ }`}
616
+ style={serviceCardStyle}
553
617
  >
554
- <div className="p-[15px] pt-[20px]">
555
- {/* Header with operator info and favorite */}
556
- {/* <div className="flex justify-between items-center mb-[15px]">
557
- <div className="flex items-center justify-between w-[250px]">
558
- <div className="w-[120px] overflow-y-hidden">
618
+ <div
619
+ className=" pt-[20px]"
620
+ style={{
621
+ padding: hasOfferText
622
+ ? "20px 15px 10px 15px"
623
+ : coachKey
624
+ ? "20px 15px 20px 15px"
625
+ : "20px 15px 10px 15px",
626
+
627
+ marginTop: hasDiscount || hasOfferText ? "14px" : "",
628
+ }}
629
+ >
630
+ {/* <div className="grid grid-cols-[1.5fr_1fr_auto] gap-[3rem] sm:gap-[4rem] md:gap-[5rem] lg:gap-[6rem] xl:gap-[5rem] 2xl:gap-[7rem] text-[#464647]"> */}
631
+ <div
632
+ className="grid text-[#464647] w-full [grid-template-columns:14%_40%_0.5%_24%_13.5%] gap-x-[2%] items-center"
633
+ // style={{ marginTop: showTopLabel ? "8px" : "" }}
634
+ style={{
635
+ marginTop:
636
+ showTopLabel || serviceItem?.is_direct_trip ? "8px" : "",
637
+ }}
638
+ >
639
+ {/* OPERATOR LOGO */}
640
+ <div
641
+ style={{
642
+ display: "flex",
643
+ flexDirection: "column",
644
+ // gap: "5px",
645
+ }}
646
+ >
647
+ <div
648
+ // className="flex items-center justify-center m-[auto]"
649
+ className=""
650
+ >
559
651
  <img
560
652
  src={serviceItem.operator_details[0]}
561
653
  alt="service logo"
562
- className={`w-[120px] h-auto object-contain ${
654
+ className={`h-[30px] w-[auto] ${
563
655
  isSoldOut ? "grayscale" : ""
564
656
  }`}
565
657
  />
566
- </div>
567
-
568
- <div>
569
- {" "}
570
658
  {isCiva ? (
571
- <div className="text-[13.33px] black-text">
659
+ <div className="text-[13.33px] black-text ml-2">
572
660
  {serviceItem.operator_details[2]}
573
661
  </div>
574
662
  ) : null}
575
663
  </div>
664
+ <RatingBlock
665
+ showRating={showRating}
666
+ serviceItem={serviceItem}
667
+ isSoldOut={isSoldOut}
668
+ colors={colors}
669
+ t={t}
670
+ translation={translation}
671
+ isPeru={isPeru}
672
+ />
576
673
  </div>
577
- </div> */}
578
-
579
- {/* <div className="grid grid-cols-[1.5fr_1fr_auto] gap-[3rem] sm:gap-[4rem] md:gap-[5rem] lg:gap-[6rem] xl:gap-[5rem] 2xl:gap-[7rem] text-[#464647]"> */}
580
- <div
581
- className="grid text-[#464647] w-full [grid-template-columns:14%_40%_0.5%_24%_13.5%] gap-x-[2%] items-center"
582
- style={{ marginTop: showTopLabel ? "8px" : "" }}
583
- >
584
- {/* OPERATOR LOGO */}
585
- <div className="flex items-center justify-center m-[auto]">
674
+ {/* <div className="flex items-center justify-center m-[auto]">
586
675
  <div className=" ">
587
676
  <img
588
677
  src={serviceItem.operator_details[0]}
@@ -597,7 +686,7 @@ function PeruServiceItemDesktop({
597
686
  {serviceItem.operator_details[2]}
598
687
  </div>
599
688
  ) : null}
600
- </div>
689
+ </div> */}
601
690
 
602
691
  {/* DATE AND TIME - Grid Layout */}
603
692
  <div
@@ -609,7 +698,7 @@ function PeruServiceItemDesktop({
609
698
  }}
610
699
  >
611
700
  {/* ICONS COLUMN */}
612
- <div className="flex flex-col gap-[10px]">
701
+ <div className="flex flex-col gap-[4px]">
613
702
  {/* Origin Icon */}
614
703
  {orignLabel ? (
615
704
  <div className="w-[60px] h-[20px] flex items-center bold-text">
@@ -647,7 +736,7 @@ function PeruServiceItemDesktop({
647
736
  </div>
648
737
 
649
738
  {/* DATES COLUMN */}
650
- <div className="flex flex-col gap-[10px]">
739
+ <div className="flex flex-col gap-[4px]">
651
740
  {/* Departure Date */}
652
741
  <StageTooltip
653
742
  stageData={serviceItem.boarding_stages}
@@ -680,7 +769,7 @@ function PeruServiceItemDesktop({
680
769
  </div>
681
770
 
682
771
  {/* DOTS COLUMN */}
683
- <div className="flex flex-col gap-[10px] items-center">
772
+ <div className="flex flex-col gap-[4px] items-center">
684
773
  {/* Departure Dot */}
685
774
  <div className="h-[20px] flex items-center justify-center">
686
775
  <div>•</div>
@@ -697,7 +786,7 @@ function PeruServiceItemDesktop({
697
786
  </div>
698
787
 
699
788
  {/* TIMES COLUMN */}
700
- <div className="flex flex-col gap-[10px]">
789
+ <div className="flex flex-col gap-[4px]">
701
790
  {/* Departure Time */}
702
791
  <StageTooltip
703
792
  stageData={serviceItem.boarding_stages}
@@ -743,140 +832,145 @@ function PeruServiceItemDesktop({
743
832
  ></div>
744
833
  {/* SEATS */}
745
834
  <div className="content-center">
746
- <div
747
- className={`relative flex gap-[10px] text-[13.33px] justify-between min-h-[2.5rem] ${
748
- getNumberOfSeats() < 3 ? "" : ""
749
- }`}
750
- style={
751
- getNumberOfSeats() < 2
752
- ? { alignItems: "center" }
753
- : { alignItems: "center" }
754
- }
755
- >
756
- <div className="flex flex-col justify-between">
757
- {getSeatNames()}
758
- </div>
835
+ <SeatSection
836
+ seatTypes={serviceItem.seat_types}
837
+ serviceItem={serviceItem}
838
+ availableSeats={serviceItem.available_seats}
839
+ isSoldOut={isSoldOut}
840
+ priceColor={colors.priceColor}
841
+ currencySign={currencySign}
842
+ removeDuplicateSeats={removeDuplicateSeats}
843
+ isPeru={isPeru}
844
+ renderIcon={renderIcon}
845
+ discountSeatPriceColor={colors.discountSeatPriceColor}
846
+ />
847
+ </div>
759
848
 
849
+ {/* BUTTON */}
850
+ <div>
851
+ {showLastSeats ? (
760
852
  <div
761
- className="flex flex-col justify-between absolute inset-y-0 right-0 left-1/2 h-full"
853
+ className="flex justify-end mr-[11px] "
762
854
  style={{
763
- color: isSoldOut ? "#c0c0c0" : colors.priceColor,
764
- top: 0,
765
- bottom: 0,
766
- left: "68%",
767
- right: 0,
768
- justifyContent:
769
- getNumberOfSeats() < 2 ? "center" : "center",
770
- gap: "5px",
855
+ position: "absolute",
856
+ top: serviceDetailsLoading ? "7px" : "5px",
857
+ right: "16px",
771
858
  }}
772
859
  >
773
- <span
774
- style={{
775
- position: "absolute",
776
- top: getNumberOfSeats() > 1 ? -10 : -5,
777
- fontWeight: "initial",
778
- fontSize: "12px",
779
- left: 0,
780
- color: "#6a6a6a",
781
- }}
782
- >
783
- Desde
784
- </span>
785
- {getSeatPrice()}
786
- </div>
787
- </div>
788
- </div>
789
-
790
- {/* BUTTON */}
791
- <div>
792
- <button
793
- onClick={() => (!isSoldOut ? checkMidnight() : null)}
794
- disabled={serviceDetailsLoading}
795
- className={`w-full ${
796
- serviceDetailsLoading || isSoldOut ? "py-[12px]" : "py-[12px]"
797
- } text-[13.33px] font-bold text-white rounded-[10px] border-none px-[20px] flex items-center justify-center`}
798
- style={{
799
- backgroundColor:
800
- serviceDetailsLoading || isSoldOut
801
- ? "lightgray"
802
- : colors.kuposButtonColor,
803
- cursor:
804
- serviceDetailsLoading || isSoldOut
805
- ? "not-allowed"
806
- : "pointer",
807
- }}
808
- >
809
- <span className="min-w-[75px] flex justify-center items-center bold-text uppercase">
810
- {isSoldOut ? renderIcon("soldOutIcon", "14px") : null}
811
-
812
- {serviceDetailsLoading ? (
813
- <span className="loader-circle"></span>
814
- ) : !isSoldOut ? (
815
- translation?.buyButton
816
- ) : (
817
- translation?.soldOutButton
818
- )}
819
- </span>
820
- </button>
821
- </div>
822
- </div>
823
- {showLastSeats ? (
824
- <div className="flex justify-end mr-[11px]">
825
- {serviceItem?.available_seats < 10 &&
826
- serviceItem?.available_seats > 0 && (
827
- <div className="text-[12px] text-[red] mt-1 text-center">
828
- ¡ Últimos Asientos!
829
- </div>
830
- )}
831
- </div>
832
- ) : null}
833
-
834
- <div className="flex items-center mt-[15px] border-t border-[#eee] pt-[10px]">
835
- {/* 🔹 LEFT SIDE (GRID ITEMS) */}
836
- <div
837
- className="grid items-center gap-[2%] flex-1"
838
- style={{
839
- gridTemplateColumns: "30% 18% 23% 23%",
840
-
841
- // otherItems
842
- // .map((i) => i.width)
843
- // .join(" "),
844
- }}
845
- >
846
- {otherItems.map((item) => (
847
- <div key={item.key} className="flex items-center ">
848
- {item.render}
860
+ {serviceItem?.available_seats < 10 &&
861
+ serviceItem?.available_seats > 0 && (
862
+ <div className="text-[12px] text-[#464647] mt-1 text-center">
863
+ ¡Últimos Asientos!
864
+ </div>
865
+ )}
849
866
  </div>
850
- ))}
851
- </div>
852
-
853
- {/* 🔹 RIGHT SIDE (ALWAYS END) */}
854
- <div className="flex items-center ml-[12px] shrink-0 w-[130px] justify-end">
855
- {amenitiesItem?.render}
867
+ ) : null}
868
+ <KuposButton
869
+ isSoldOut={isSoldOut}
870
+ isLoading={serviceDetailsLoading}
871
+ buttonColor={colors.kuposButtonColor}
872
+ buyLabel={translation?.buyButton}
873
+ soldOutLabel={translation?.soldOutButton}
874
+ soldOutIcon={renderIcon("soldOutIcon", "14px")}
875
+ onClick={checkMidnight}
876
+ />
856
877
  </div>
857
878
  </div>
879
+ <BottomAmenities
880
+ otherItems={otherItems}
881
+ serviceItem={serviceItem}
882
+ grayscaleClass={grayscaleClass}
883
+ isSoldOut={isSoldOut}
884
+ isItemExpanded={isItemExpanded}
885
+ colors={colors}
886
+ translation={translation}
887
+ getAnimationIcon={getAnimationIcon}
888
+ downArrowIcon={renderIcon("downArrow", "10px")}
889
+ onToggleExpand={() =>
890
+ setIsExpand && setIsExpand(isItemExpanded ? null : serviceItem.id)
891
+ }
892
+ isPeru={isPeru}
893
+ />
858
894
  </div>
859
895
  </div>
860
896
 
861
897
  {children}
862
- {/* Bottom discount banner */}
898
+
863
899
  {serviceItem?.offer_text && (
864
900
  <div
865
- className={` text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[36px] pt-[50px] -z-10 rounded-b-[14px] text-[14px]`}
901
+ className="text-white p-[10px_15px] text-left w-full flex items-center absolute -bottom-[36px] pt-[50px] rounded-b-[14px] text-[14px]"
866
902
  style={{
867
- backgroundColor: isSoldOut
868
- ? colors?.bottomStripColor
869
- : colors?.bottomStripColor,
903
+ background: offerGradient,
870
904
  opacity: isSoldOut ? 0.5 : 1,
871
905
  }}
872
906
  >
873
- <LottiePlayer
874
- // animationData={serviceItem.icons.promoAnim}
875
- animationData={getAnimationIcon("promoAnim")}
876
- width="18px"
877
- height="18px"
878
- />
879
- <span className="ml-[10px]">{serviceItem?.offer_text}</span>
907
+ <div className="flex justify-between items-center w-full">
908
+ <div className="flex items-center ">
909
+ <div className="flex items-center">
910
+ <LottiePlayer
911
+ animationData={getAnimationIcon("bombAnimation")}
912
+ width="18px"
913
+ height="18px"
914
+ />
915
+ <div className="flex items-center mt-[2px]">
916
+ <span className="bold-text ml-[6px]">
917
+ {(serviceItem?.offer_text || "").length > 30
918
+ ? (serviceItem?.offer_text || "").slice(0, 30) + "..."
919
+ : serviceItem?.offer_text || ""}{" "}
920
+ {isLoggedIn ? null : (
921
+ <span onClick={showLoginModal} className="cursor-pointer">
922
+ - registro
923
+ </span>
924
+ )}{" "}
925
+ &nbsp;
926
+ </span>{" "}
927
+ | Termina en&nbsp;
928
+ <span
929
+ className="bold-text text-end"
930
+ ref={(node) => CommonService.startCountdown(node, 599)}
931
+ style={{
932
+ fontVariantNumeric: "tabular-nums",
933
+ display: "inline-block",
934
+ }}
935
+ />
936
+ </div>
937
+ </div>
938
+ </div>
939
+ <div className="flex items-center">
940
+ {/* {renderIcon("personIcon", "16px")} */}
941
+ <LottiePlayer
942
+ animationData={getAnimationIcon("dotAnimation")}
943
+ width="12px"
944
+ height="12px"
945
+ />
946
+
947
+ <span className="ml-[6px]">
948
+ <span
949
+ className="bold-text"
950
+ ref={(node) =>
951
+ CommonService.startViewerCount(node, viewersConfig)
952
+ }
953
+ style={{ fontVariantNumeric: "tabular-nums" }}
954
+ />{" "}
955
+ {/* <span className="bold-text">personas</span>{" "} */}
956
+ <span>
957
+ {" "}
958
+ {viewersConfig?.label || " viendo"} |{" "}
959
+ <span className="">
960
+ {serviceItem?.is_dp_enabled ? null : "Quedan pocos • "}
961
+ <span
962
+ className="bold-text"
963
+ ref={(node) =>
964
+ CommonService.startComprandoCount(node, 4, 16)
965
+ }
966
+ style={{ fontVariantNumeric: "tabular-nums" }}
967
+ />{" "}
968
+ comprando
969
+ </span>
970
+ </span>
971
+ </span>
972
+ </div>
973
+ </div>
880
974
  </div>
881
975
  )}
882
976
 
@@ -931,30 +1025,6 @@ function PeruServiceItemDesktop({
931
1025
  </div>
932
1026
  )}
933
1027
 
934
- {/* {dropoffName && (
935
- <div
936
- className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`}
937
- style={{
938
- backgroundColor: isSoldOut ? "#ddd" : colors.ratingBottomColor,
939
- }}
940
- >
941
- <div className={isSoldOut ? "grayscale" : ""}>
942
- <LottiePlayer
943
- // animationData={serviceItem.icons.priorityStageAnim}
944
- animationData={getAnimationIcon("priorityStageAnim")}
945
- width="14px"
946
- height="14px"
947
- />
948
- </div>
949
- <div
950
- className={
951
- isSoldOut ? "text-white" : `text-[${colors.topLabelColor}]`
952
- }
953
- >
954
- {dropoffName}
955
- </div>
956
- </div>
957
- )} */}
958
1028
  {serviceItem?.is_transpordo && (
959
1029
  <div
960
1030
  className={`flex items-center gap-[10px] py-[4px] text-white px-[14px] rounded-[38px] text-[12.5px] z-20`}
@@ -962,11 +1032,8 @@ function PeruServiceItemDesktop({
962
1032
  backgroundColor: isSoldOut ? "#ddd" : colors.tooltipColor,
963
1033
  }}
964
1034
  >
965
- <LottiePlayer
966
- animationData={serviceItem.icons.connectingServiceIcon}
967
- width="14px"
968
- height="14px"
969
- />
1035
+ {renderIcon("connectingServiceIcon", "12px")}
1036
+
970
1037
  <div>{"Conexión"}</div>
971
1038
  </div>
972
1039
  )}
@@ -414,6 +414,12 @@ function ServiceItemPB({
414
414
  isPeru={isPeru}
415
415
  siteType={siteType}
416
416
  isAllinBus={isAllinBus}
417
+ viewersConfig={viewersConfig}
418
+ isLoggedIn={isLoggedIn}
419
+ showLoginModal={showLoginModal}
420
+ isExpand={isExpand}
421
+ setIsExpand={setIsExpand}
422
+ coachKey={coachKey}
417
423
  />
418
424
  ) : (
419
425
  <div