kupos-ui-components-lib 9.3.2 → 9.3.3

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.3",
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,6 @@ 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
- };
132
109
  const getAnimationIcon = (icon: string) => {
133
110
  const animation = ANIMATION_MAP[icon];
134
111
  if (!animation) return null;
@@ -187,18 +164,16 @@ function ServiceItemPB({
187
164
 
188
165
  let isSoldOut = serviceItem.available_seats <= 0;
189
166
 
190
- const showPromo = Math.random() > 0.5;
191
-
192
167
  const isItemExpanded = serviceItem.id === isExpand || isExpand === true;
193
168
  const grayscaleClass = isSoldOut ? "grayscale" : "";
194
169
  const hasOfferText = Boolean(serviceItem?.offer_text);
195
- const offerGradient = "linear-gradient(90deg, #ff5964 0%, #ff8842 100%)";
170
+ const offerGradient = `linear-gradient(90deg, ${colors.rightGradiantColor || "#ff5964"} 0%, ${colors.leftGradiantColor || "#ff8842"} 100%)`;
196
171
  const serviceCardStyle: React.CSSProperties = hasOfferText
197
172
  ? {
198
173
  borderColor: "transparent",
199
174
  borderStyle: "solid",
200
175
  borderWidth: "6px 6px 0 6px",
201
- borderRadius: isItemExpanded ? "18px 18px 0 0" : "18px",
176
+ borderRadius: isItemExpanded || coachKey ? "18px 18px 0 0" : "18px",
202
177
  background: `linear-gradient(#fff, #fff) padding-box, ${offerGradient} border-box`,
203
178
  }
204
179
  : {};
@@ -303,34 +278,6 @@ function ServiceItemPB({
303
278
 
304
279
  onBookButtonPress();
305
280
  };
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
281
 
335
282
  const items = [
336
283
  {
@@ -456,14 +403,15 @@ function ServiceItemPB({
456
403
  padding: coachKey
457
404
  ? "15px 15px 20px 15px"
458
405
  : "20px 15px 11px 15px",
406
+ marginTop: hasOfferText ? "14px" : "0",
459
407
  }}
460
408
  >
461
409
  <div
462
410
  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
- }}
411
+ // style={{
412
+ // marginTop:
413
+ // showTopLabel || serviceItem?.is_direct_trip ? "8px" : "",
414
+ // }}
467
415
  >
468
416
  {/* OPERATOR LOGO */}
469
417
  <div className="flex flex-col gap-[5px]">
@@ -530,7 +478,7 @@ function ServiceItemPB({
530
478
  {/* BUTTON */}
531
479
 
532
480
  <div className="relative">
533
- {showLastSeats ? (
481
+ {/* {showLastSeats ? (
534
482
  <div
535
483
  className="flex justify-end mr-[11px] w-[100%] right-[0px] absolute"
536
484
  style={{
@@ -544,7 +492,7 @@ function ServiceItemPB({
544
492
  </div>
545
493
  )}
546
494
  </div>
547
- ) : null}
495
+ ) : null} */}
548
496
  <KuposButton
549
497
  isSoldOut={isSoldOut}
550
498
  isLoading={serviceDetailsLoading}
@@ -556,7 +504,7 @@ function ServiceItemPB({
556
504
  />
557
505
  {showLastSeats ? (
558
506
  <div
559
- className="flex justify-end mr-[11px] w-[100%] right-[0px]"
507
+ className="flex justify-center mr-[11px] w-[100%] right-[0px]"
560
508
  style={{
561
509
  top: serviceDetailsLoading ? "-17px" : "-20px",
562
510
  }}
@@ -654,7 +602,7 @@ function ServiceItemPB({
654
602
  | Termina en&nbsp;
655
603
  <span
656
604
  className="bold-text text-end"
657
- ref={startCountdown}
605
+ ref={(node) => CommonService.startCountdown(node, 599)}
658
606
  style={{
659
607
  fontVariantNumeric: "tabular-nums",
660
608
  display: "inline-block",
@@ -675,84 +623,40 @@ function ServiceItemPB({
675
623
  <span className="ml-[6px]">
676
624
  <span
677
625
  className="bold-text"
678
- ref={startViewerCount}
626
+ ref={(node) =>
627
+ CommonService.startViewerCount(node, viewersConfig)
628
+ }
679
629
  style={{ fontVariantNumeric: "tabular-nums" }}
680
630
  />{" "}
681
631
  <span className="bold-text">personas</span>{" "}
682
632
  <span>
683
633
  {" "}
684
- {viewersConfig?.label || " están viendo este viaje"}
634
+ {viewersConfig?.label || " viendo"} |{" "}
635
+ <span className="">
636
+ ⚡ Quedan pocos{" "}
637
+ <span
638
+ className="bold-text"
639
+ ref={(node) =>
640
+ CommonService.startComprandoCount(node, 4, 16)
641
+ }
642
+ style={{ fontVariantNumeric: "tabular-nums" }}
643
+ />{" "}
644
+ comprando
645
+ </span>
685
646
  </span>
686
647
  </span>
687
648
  </div>
688
649
  </div>
689
650
  </div>
690
651
  )}
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>
652
+ <ServiceBadges
653
+ showTopLabel={showTopLabel}
654
+ isSoldOut={isSoldOut}
655
+ colors={colors}
656
+ renderIcon={renderIcon}
657
+ translation={translation}
658
+ serviceItem={serviceItem}
659
+ />
756
660
  </div>
757
661
  )}
758
662
  </>