kupos-ui-components-lib 9.3.2 → 9.3.4

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.
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ interface ServiceBadgesMobileProps {
3
+ showTopLabel?: string;
4
+ isSoldOut: boolean;
5
+ colors: any;
6
+ renderIcon: (iconKey: string, size?: string) => React.ReactNode;
7
+ translation?: {
8
+ directService?: string;
9
+ };
10
+ serviceItem: {
11
+ is_direct_trip?: boolean;
12
+ train_type_label?: string;
13
+ };
14
+ isConexion?: boolean;
15
+ }
16
+ declare const ServiceBadgesMobile: React.FC<ServiceBadgesMobileProps>;
17
+ export default ServiceBadgesMobile;
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ const ServiceBadgesMobile = ({ showTopLabel, isSoldOut, colors, renderIcon, serviceItem, isConexion, }) => {
3
+ return (React.createElement("div", { className: "absolute -top-[9px] left-0 w-full flex items-center justify-end gap-[12px] pr-[20px] z-10" },
4
+ showTopLabel && (React.createElement("div", { className: `flex items-center gap-[2px] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20`, style: {
5
+ backgroundColor: "#fff",
6
+ border: `1px solid ${colors.topLabelColor}`,
7
+ color: colors.topLabelColor,
8
+ } },
9
+ React.createElement("div", { className: isSoldOut ? "grayscale" : "" }, renderIcon("specialDeparture", "12px")),
10
+ React.createElement("div", { style: {
11
+ color: colors.topLabelColor,
12
+ } }, showTopLabel))),
13
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) && (React.createElement("div", { className: `flex items-center gap-[2px] py-[5px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20`, style: {
14
+ backgroundColor: "#fff",
15
+ border: `1px solid ${colors.topLabelColor}`,
16
+ color: colors.topLabelColor,
17
+ } },
18
+ renderIcon("directo", "12px"),
19
+ React.createElement("div", { className: "ml-[5px]" }, "Directo"))),
20
+ isConexion && (React.createElement("div", { className: `flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[11px] z-20`, style: {
21
+ backgroundColor: "#fff",
22
+ border: `1px solid ${colors.topLabelColor}`,
23
+ color: colors.topLabelColor,
24
+ } },
25
+ renderIcon("airportIcon", "14px"),
26
+ React.createElement("div", null, "Conexi\u00F3n"))),
27
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.train_type_label) === "Tren Express (Nuevo)" && (React.createElement("div", { className: `flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20`, style: {
28
+ backgroundColor: "#fff",
29
+ border: `1px solid ${colors.topLabelColor}`,
30
+ color: colors.topLabelColor,
31
+ } },
32
+ renderIcon("directo", "12px"),
33
+ React.createElement("div", null, "Tren Express")))));
34
+ };
35
+ export default ServiceBadgesMobile;
@@ -17,5 +17,12 @@ declare const commonService: {
17
17
  originalPrice: number;
18
18
  discountedPrice: number;
19
19
  };
20
+ startViewerCount: (node: HTMLSpanElement | null, viewersConfig?: {
21
+ min: number;
22
+ max: number;
23
+ interval?: number;
24
+ }) => void;
25
+ startCountdown: (node: HTMLSpanElement | null, countdownSeconds?: number) => void;
26
+ startComprandoCount: (node: HTMLSpanElement | null, min?: number, max?: number) => void;
20
27
  };
21
28
  export default commonService;
@@ -284,5 +284,66 @@ const commonService = {
284
284
  const discountedPrice = Math.max(0, price - finalDiscount);
285
285
  return { originalPrice: price, discountedPrice };
286
286
  },
287
+ startViewerCount: (node, viewersConfig) => {
288
+ if (!node || !viewersConfig)
289
+ return;
290
+ const prevId = node.dataset.viewerId;
291
+ if (prevId)
292
+ clearInterval(Number(prevId));
293
+ const { min, max, interval = 5000 } = viewersConfig;
294
+ const clamp = (v) => Math.min(max, Math.max(min, v));
295
+ const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
296
+ node.textContent = String(initialValue);
297
+ const id = setInterval(() => {
298
+ const current = Number(node.textContent) || initialValue;
299
+ const delta = Math.ceil(current * 0.2);
300
+ const next = current + Math.floor(Math.random() * (2 * delta + 1)) - delta;
301
+ node.textContent = String(clamp(Math.round(next)));
302
+ }, interval);
303
+ node.dataset.viewerId = String(id);
304
+ },
305
+ startCountdown: (node, countdownSeconds = 599) => {
306
+ if (!node)
307
+ return;
308
+ const prevId = node.dataset.countdownId;
309
+ if (prevId)
310
+ clearInterval(Number(prevId));
311
+ let remaining = countdownSeconds * 1000; // Convert to milliseconds
312
+ const formatTime = (totalMs) => {
313
+ const m = Math.floor(totalMs / 60000);
314
+ const s = Math.floor((totalMs % 60000) / 1000);
315
+ const ms = Math.floor((totalMs % 1000) / 10); // Show 2 digits for milliseconds
316
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}:${String(ms).padStart(2, "0")}`;
317
+ };
318
+ node.textContent = formatTime(remaining);
319
+ const id = setInterval(() => {
320
+ remaining -= 100; // Decrease by 100ms
321
+ if (remaining <= 0) {
322
+ remaining = countdownSeconds * 1000;
323
+ }
324
+ node.textContent = formatTime(remaining);
325
+ }, 100); // Update every 100ms
326
+ node.dataset.countdownId = String(id);
327
+ },
328
+ startComprandoCount: (node, min = 4, max = 16) => {
329
+ if (!node)
330
+ return;
331
+ const prevId = node.dataset.comprandoId;
332
+ if (prevId)
333
+ clearInterval(Number(prevId));
334
+ const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
335
+ node.textContent = String(initialValue);
336
+ const id = setInterval(() => {
337
+ const current = Number(node.textContent) || initialValue;
338
+ const changePercent = 0.05; // 5% change
339
+ const change = Math.ceil(current * changePercent);
340
+ const direction = Math.random() > 0.5 ? 1 : -1;
341
+ let next = current + (change * direction);
342
+ // Clamp within min and max
343
+ next = Math.min(max, Math.max(min, next));
344
+ node.textContent = String(next);
345
+ }, 5000); // Update every 5 seconds
346
+ node.dataset.comprandoId = String(id);
347
+ },
287
348
  };
288
349
  export default commonService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.3.2",
3
+ "version": "9.3.4",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -28,15 +28,14 @@ import dotAnimation from "../../assets/images/anims/service_list/dot_animation.j
28
28
 
29
29
  import RatingBlock from "../../ui/RatingBlock";
30
30
  import DurationBlock from "../../ui/DurationBlock";
31
- import DirectoBlock from "../../ui/DirectoBlock";
32
31
  import PetBlock from "../../ui/PetBlock";
33
32
  import FlexibleBlock from "../../ui/FlexibleBlock";
34
33
  import AmenitiesBlock from "../../ui/AmenitiesBlock";
35
34
  import KuposButton from "../../ui/KuposButton/KuposButton";
36
- import TopAmenities from "../../ui/TopAmenities/TopAmenities";
37
35
  import BottomAmenities from "../../ui/BottomAmenities/BottomAmenities";
38
36
  import SeatSection from "../../ui/SeatSection/SeatSection";
39
37
  import DateTimeSection from "../../ui/DateTimeSection/DateTimeSection";
38
+ import ServiceBadges from "../../ui/ServiceBadges/ServiceBadges";
40
39
 
41
40
  const SEAT_EXCEPTIONS = ["Asiento mascota"];
42
41
 
@@ -107,28 +106,7 @@ function ServiceItemPB({
107
106
  coachKey,
108
107
  viewersConfig,
109
108
  }: ServiceItemProps & { currencySign?: string }): React.ReactElement {
110
- const startViewerCount = (node: HTMLSpanElement | null) => {
111
- if (!node || !viewersConfig) return;
112
-
113
- const prevId = node.dataset.viewerId;
114
- if (prevId) clearInterval(Number(prevId));
115
-
116
- const { min, max, interval = 5000 } = viewersConfig;
117
- const clamp = (v: number) => Math.min(max, Math.max(min, v));
118
- const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
119
-
120
- node.textContent = String(initialValue);
121
-
122
- const id = setInterval(() => {
123
- const current = Number(node.textContent) || initialValue;
124
- const delta = Math.ceil(current * 0.2);
125
- const next =
126
- current + Math.floor(Math.random() * (2 * delta + 1)) - delta;
127
- node.textContent = String(clamp(Math.round(next)));
128
- }, interval);
129
-
130
- node.dataset.viewerId = String(id);
131
- };
109
+ console.log("🚀 ~ ServiceItemPB ~ serviceItem:", serviceItem);
132
110
  const getAnimationIcon = (icon: string) => {
133
111
  const animation = ANIMATION_MAP[icon];
134
112
  if (!animation) return null;
@@ -187,18 +165,33 @@ function ServiceItemPB({
187
165
 
188
166
  let isSoldOut = serviceItem.available_seats <= 0;
189
167
 
190
- const showPromo = Math.random() > 0.5;
191
-
192
168
  const isItemExpanded = serviceItem.id === isExpand || isExpand === true;
193
169
  const grayscaleClass = isSoldOut ? "grayscale" : "";
194
170
  const hasOfferText = Boolean(serviceItem?.offer_text);
195
- const offerGradient = "linear-gradient(90deg, #ff5964 0%, #ff8842 100%)";
171
+
172
+ const seats = removeDuplicateSeats
173
+ ? serviceItem.seat_types?.filter(
174
+ (seat, index, self) =>
175
+ index === self.findIndex((s) => s.label === seat.label),
176
+ ) || []
177
+ : serviceItem.seat_types || [];
178
+
179
+ const discountedSeats = seats.map((seat) => ({
180
+ ...seat,
181
+ ...CommonService.calculateDiscountedPrice(seat.fare, serviceItem as any),
182
+ }));
183
+
184
+ const hasDiscount = discountedSeats.some(
185
+ (seat) => seat.originalPrice !== seat.discountedPrice,
186
+ );
187
+
188
+ const offerGradient = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"} 0%, ${colors.leftGradiantColor || "#ff8842"} 100%)`;
196
189
  const serviceCardStyle: React.CSSProperties = hasOfferText
197
190
  ? {
198
191
  borderColor: "transparent",
199
192
  borderStyle: "solid",
200
193
  borderWidth: "6px 6px 0 6px",
201
- borderRadius: isItemExpanded ? "18px 18px 0 0" : "18px",
194
+ borderRadius: isItemExpanded || coachKey ? "18px 18px 0 0" : "18px",
202
195
  background: `linear-gradient(#fff, #fff) padding-box, ${offerGradient} border-box`,
203
196
  }
204
197
  : {};
@@ -303,34 +296,6 @@ function ServiceItemPB({
303
296
 
304
297
  onBookButtonPress();
305
298
  };
306
- const countdownSeconds = 599;
307
-
308
- const startCountdown = (node: HTMLSpanElement | null) => {
309
- if (!node) return;
310
-
311
- const prevId = node.dataset.countdownId;
312
- if (prevId) clearInterval(Number(prevId));
313
-
314
- let remaining = countdownSeconds;
315
-
316
- const formatTime = (totalSecs: number) => {
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
-
322
- node.textContent = formatTime(remaining);
323
-
324
- const id = setInterval(() => {
325
- remaining -= 1;
326
- if (remaining <= 0) {
327
- remaining = countdownSeconds;
328
- }
329
- node.textContent = formatTime(remaining);
330
- }, 1000);
331
-
332
- node.dataset.countdownId = String(id);
333
- };
334
299
 
335
300
  const items = [
336
301
  {
@@ -456,14 +421,15 @@ function ServiceItemPB({
456
421
  padding: coachKey
457
422
  ? "15px 15px 20px 15px"
458
423
  : "20px 15px 11px 15px",
424
+ marginTop: hasDiscount || hasOfferText ? "14px" : "0",
459
425
  }}
460
426
  >
461
427
  <div
462
428
  className="grid text-[#464647] w-full [grid-template-columns:22%_28%_2.5%_24%_15.5%] gap-x-[2%] items-center"
463
- style={{
464
- marginTop:
465
- showTopLabel || serviceItem?.is_direct_trip ? "8px" : "",
466
- }}
429
+ // style={{
430
+ // marginTop:
431
+ // showTopLabel || serviceItem?.is_direct_trip ? "8px" : "",
432
+ // }}
467
433
  >
468
434
  {/* OPERATOR LOGO */}
469
435
  <div className="flex flex-col gap-[5px]">
@@ -530,7 +496,7 @@ function ServiceItemPB({
530
496
  {/* BUTTON */}
531
497
 
532
498
  <div className="relative">
533
- {showLastSeats ? (
499
+ {/* {showLastSeats ? (
534
500
  <div
535
501
  className="flex justify-end mr-[11px] w-[100%] right-[0px] absolute"
536
502
  style={{
@@ -544,7 +510,7 @@ function ServiceItemPB({
544
510
  </div>
545
511
  )}
546
512
  </div>
547
- ) : null}
513
+ ) : null} */}
548
514
  <KuposButton
549
515
  isSoldOut={isSoldOut}
550
516
  isLoading={serviceDetailsLoading}
@@ -556,7 +522,7 @@ function ServiceItemPB({
556
522
  />
557
523
  {showLastSeats ? (
558
524
  <div
559
- className="flex justify-end mr-[11px] w-[100%] right-[0px]"
525
+ className="flex justify-center mr-[11px] w-[100%] right-[0px]"
560
526
  style={{
561
527
  top: serviceDetailsLoading ? "-17px" : "-20px",
562
528
  }}
@@ -654,7 +620,7 @@ function ServiceItemPB({
654
620
  | Termina en&nbsp;
655
621
  <span
656
622
  className="bold-text text-end"
657
- ref={startCountdown}
623
+ ref={(node) => CommonService.startCountdown(node, 599)}
658
624
  style={{
659
625
  fontVariantNumeric: "tabular-nums",
660
626
  display: "inline-block",
@@ -675,84 +641,40 @@ function ServiceItemPB({
675
641
  <span className="ml-[6px]">
676
642
  <span
677
643
  className="bold-text"
678
- ref={startViewerCount}
644
+ ref={(node) =>
645
+ CommonService.startViewerCount(node, viewersConfig)
646
+ }
679
647
  style={{ fontVariantNumeric: "tabular-nums" }}
680
648
  />{" "}
681
649
  <span className="bold-text">personas</span>{" "}
682
650
  <span>
683
651
  {" "}
684
- {viewersConfig?.label || " están viendo este viaje"}
652
+ {viewersConfig?.label || " viendo"} |{" "}
653
+ <span className="">
654
+ ⚡ Quedan pocos{" "}
655
+ <span
656
+ className="bold-text"
657
+ ref={(node) =>
658
+ CommonService.startComprandoCount(node, 4, 16)
659
+ }
660
+ style={{ fontVariantNumeric: "tabular-nums" }}
661
+ />{" "}
662
+ comprando
663
+ </span>
685
664
  </span>
686
665
  </span>
687
666
  </div>
688
667
  </div>
689
668
  </div>
690
669
  )}
691
- <div className="absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[22px] z-10 ">
692
- {showTopLabel && (
693
- <div
694
- className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-10`}
695
- style={{
696
- backgroundColor: "#fff",
697
- border: `1px solid ${colors.topLabelColor}`,
698
- color: colors.topLabelColor,
699
- }}
700
- >
701
- <div className={isSoldOut ? "grayscale" : ""}>
702
- {renderIcon("specialDeparture", "12px")}
703
- </div>
704
- <div
705
- className={
706
- isSoldOut ? "text-white" : `text-[${colors.topLabelColor}]`
707
- }
708
- >
709
- {showTopLabel}
710
- </div>
711
- </div>
712
- )}
713
- {serviceItem?.is_transpordo && (
714
- <div
715
- className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`}
716
- style={{
717
- backgroundColor: "#fff",
718
- border: `1px solid ${colors.topLabelColor}`,
719
- color: colors.topLabelColor,
720
- }}
721
- >
722
- {renderIcon("connectingServiceIcon", "12px")}
723
-
724
- <div>{"Conexión"}</div>
725
- </div>
726
- )}
727
- {serviceItem?.is_direct_trip && (
728
- <div
729
- className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20 `}
730
- style={{
731
- backgroundColor: "#fff",
732
- border: `1px solid ${colors.topLabelColor}`,
733
- color: colors.topLabelColor,
734
- }}
735
- >
736
- {renderIcon("directo", "12px")}
737
-
738
- <div>{translation?.directService}</div>
739
- </div>
740
- )}
741
- {serviceItem?.train_type_label === "Tren Express (Nuevo)" && (
742
- <div
743
- className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20 `}
744
- style={{
745
- backgroundColor: "#fff",
746
- border: `1px solid ${colors.topLabelColor}`,
747
- color: colors.topLabelColor,
748
- }}
749
- >
750
- {renderIcon("directo", "12px")}
751
-
752
- <div>{"Tren Express"}</div>
753
- </div>
754
- )}
755
- </div>
670
+ <ServiceBadges
671
+ showTopLabel={showTopLabel}
672
+ isSoldOut={isSoldOut}
673
+ colors={colors}
674
+ renderIcon={renderIcon}
675
+ translation={translation}
676
+ serviceItem={serviceItem}
677
+ />
756
678
  </div>
757
679
  )}
758
680
  </>