kupos-ui-components-lib 9.11.1 → 9.11.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.11.1",
3
+ "version": "9.11.3",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -131,6 +131,13 @@ function ServiceItemPB({
131
131
  showLoginModal,
132
132
  isLoggedIn,
133
133
  showLoginOption,
134
+ isTrain,
135
+ selectedSeatKey,
136
+ onSeatSelect,
137
+ onTrainButtonClick,
138
+ showSeatSelectionError,
139
+ onShowSeatSelectionError,
140
+ onClearSeatSelectionError,
134
141
  isFeatureDropDownExpand,
135
142
  setIsFeatureDropDownExpand,
136
143
  ticketQuantity,
@@ -145,7 +152,15 @@ function ServiceItemPB({
145
152
  isFlores,
146
153
  operatorLabel,
147
154
  }: ServiceItemProps & { currencySign?: string }): React.ReactElement {
148
- console.log("🚀 ~ ServiceItemPB ~ serviceItem:", serviceItem);
155
+ const handleSeatSelect = (
156
+ key: any,
157
+ price: number,
158
+ seatKey: string,
159
+ apiSeatType?: string,
160
+ ) => {
161
+ onClearSeatSelectionError?.();
162
+ onSeatSelect?.(key, price, seatKey, apiSeatType);
163
+ };
149
164
  const getAnimationIcon = (icon: string) => {
150
165
  const animation = ANIMATION_MAP[icon];
151
166
  if (!animation) return null;
@@ -225,6 +240,27 @@ function ServiceItemPB({
225
240
  (seat) => seat.originalPrice !== seat.discountedPrice,
226
241
  );
227
242
 
243
+ // Mirror the same check as SeatSection: hide badge (and its top margin) when
244
+ // both percentage and max_discount exist and the cap is being applied.
245
+ const isMaxDiscountApplied = (() => {
246
+ const { discount_type, discount_value, max_discount } = serviceItem as any;
247
+ if (
248
+ discount_type === "percentage" &&
249
+ typeof discount_value === "number" &&
250
+ max_discount != null &&
251
+ max_discount > 0
252
+ ) {
253
+ const lowestFare = discountedSeats
254
+ .map((s) => s.originalPrice)
255
+ .filter((p) => p > 0)
256
+ .sort((a, b) => a - b)[0];
257
+ if (lowestFare != null) {
258
+ return (lowestFare * discount_value) / 100 > max_discount;
259
+ }
260
+ }
261
+ return false;
262
+ })();
263
+
228
264
  const dpDiscountEntry = Object.entries(
229
265
  serviceItem?.dp_discount_percents || {},
230
266
  )[0];
@@ -346,6 +382,17 @@ function ServiceItemPB({
346
382
  return;
347
383
  }
348
384
 
385
+ if (isTrain) {
386
+ if (!selectedSeatKey) {
387
+ onShowSeatSelectionError?.(serviceItem.id);
388
+ return;
389
+ }
390
+ if (onTrainButtonClick) {
391
+ onTrainButtonClick();
392
+ return;
393
+ }
394
+ }
395
+
349
396
  onBookButtonPress();
350
397
  };
351
398
 
@@ -532,12 +579,15 @@ function ServiceItemPB({
532
579
  : "20px 15px 10px 15px",
533
580
 
534
581
  marginTop:
535
- hasDiscount || hasOfferText || dpDiscountPercent
582
+ (hasDiscount || hasOfferText || dpDiscountPercent) &&
583
+ !isMaxDiscountApplied
536
584
  ? "14px"
537
585
  : "",
538
586
  }}
539
587
  >
540
- <div className="grid text-[#464647] w-full [grid-template-columns:20%_30%_2.5%_24%_15.5%] gap-x-[2%] items-center">
588
+ <div
589
+ className={`grid text-[#464647] w-full ${isTrain ? "[grid-template-columns:16%_30%_2.5%_28%_15.5%]" : "[grid-template-columns:20%_30%_2.5%_24%_15.5%]"} gap-x-[2%] items-center`}
590
+ >
541
591
  {/* OPERATOR LOGO */}
542
592
  <div className="flex flex-col gap-[5px]">
543
593
  <div>
@@ -599,6 +649,10 @@ function ServiceItemPB({
599
649
  isPeru={isPeru}
600
650
  renderIcon={renderIcon}
601
651
  discountSeatPriceColor={colors.discountSeatPriceColor}
652
+ isTrain={isTrain}
653
+ selectedSeatKey={selectedSeatKey}
654
+ onSeatSelect={handleSeatSelect}
655
+ topLabelColor={colors.topLabelColor}
602
656
  tooltipColor={colors.tooltipColor}
603
657
  />
604
658
  </div>
@@ -615,6 +669,18 @@ function ServiceItemPB({
615
669
  soldOutIcon={renderIcon("soldOutIcon", "14px")}
616
670
  onClick={checkMidnight}
617
671
  />
672
+ {showSeatSelectionError === serviceItem.id && isTrain && (
673
+ <div className="flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[40px]">
674
+ <div
675
+ className="text-[9px] text-center whitespace-nowrap"
676
+ style={{
677
+ color: colors.seatPriceColor,
678
+ }}
679
+ >
680
+ Selecciona el tipo de servicio
681
+ </div>
682
+ </div>
683
+ )}
618
684
  {showLastSeats ? (
619
685
  <div className="flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[40px]">
620
686
  {serviceItem?.available_seats < 10 &&
@@ -46,6 +46,8 @@ function ServiceItemMobile({
46
46
  removeDuplicateSeats,
47
47
  isLinatal,
48
48
  viewersConfig,
49
+ operatorLabel,
50
+ isTrain,
49
51
  isFeatureDropDownExpand,
50
52
  setIsFeatureDropDownExpand,
51
53
  ticketQuantity,
@@ -61,7 +63,6 @@ function ServiceItemMobile({
61
63
  onTimeDropdownToggle,
62
64
  wowDealData,
63
65
  isFlores,
64
- operatorLabel
65
66
  }: MobileServiceItemProps): React.ReactElement {
66
67
  const isItemExpanded = serviceItem.id === isExpanded;
67
68
  const isPetSeat = (Object.keys(serviceItem?.pet_seat_info) || []).length > 0;
@@ -135,36 +135,36 @@ export interface MobileServiceItemProps {
135
135
  bombAnim?: string;
136
136
  whiteBoardingIcon?: string;
137
137
  downArrow?: string;
138
- personIcon?: string
138
+ personIcon?: string;
139
139
  specialDeparture?: string;
140
140
  fireIcon?: string;
141
141
  directoIcon?: string;
142
- whiteFireIcon?: string
143
- femaleAnim?:string
144
- thunderAnim?: string
145
- personsAnim?: string
146
- whiteOrigin?: string,
147
- whiteDestination?: string,
148
- userIcon?: string,
149
-
150
- sheildIcon?: string,
151
- busIcon?: string,
152
- whiteDownArrow?: string,
153
- empressaIcon?: string,
154
- flexibleIcon?: string,
155
- listoIcon?: string,
156
- precioIcon?: string,
157
- confirmarIcon?: string
142
+ whiteFireIcon?: string;
143
+ femaleAnim?: string;
144
+ thunderAnim?: string;
145
+ personsAnim?: string;
146
+ whiteOrigin?: string;
147
+ whiteDestination?: string;
148
+ userIcon?: string;
149
+
150
+ sheildIcon?: string;
151
+ busIcon?: string;
152
+ whiteDownArrow?: string;
153
+ empressaIcon?: string;
154
+ flexibleIcon?: string;
155
+ listoIcon?: string;
156
+ precioIcon?: string;
157
+ confirmarIcon?: string;
158
158
  cancelTicketIcon?: string;
159
159
  changeTicketIcon?: string;
160
160
  petFriendlyIcon?: string;
161
- womenSeatIcon?: string
161
+ womenSeatIcon?: string;
162
162
  [key: string]: string | Record<string, string | undefined> | undefined;
163
163
  };
164
164
  useLottieFor?: string[];
165
165
  };
166
166
  onBookButtonPress?: () => void;
167
- onRemateUiButtonClick?: ()=> void;
167
+ onRemateUiButtonClick?: () => void;
168
168
  terminals?: any[];
169
169
  showDropdown?: boolean;
170
170
  setShowDropdown?: (value: boolean) => void;
@@ -208,7 +208,7 @@ export interface MobileServiceItemProps {
208
208
  seatPriceColor?: string;
209
209
  rightGradiantColor?: string;
210
210
  leftGradiantColor?: string;
211
- discountSeatPriceColor?: string
211
+ discountSeatPriceColor?: string;
212
212
  };
213
213
  isCiva?: boolean;
214
214
  currencySign?: string;
@@ -221,23 +221,28 @@ export interface MobileServiceItemProps {
221
221
  showLastSeats?: boolean;
222
222
  removeDuplicateSeats?: boolean;
223
223
  isLinatal?: boolean;
224
- viewersConfig?: {
224
+ viewersConfig?: {
225
225
  min: number;
226
226
  max: number;
227
227
  interval?: number; // ms, default 5000
228
228
  label?: string; // e.g. "personas están viendo este viaje"
229
229
  icon?: string; // optional icon URL
230
230
  };
231
- isFeatureDropDownExpand?: any;
231
+ isTrain?: boolean;
232
+ isFeatureDropDownExpand?: any;
232
233
  setIsFeatureDropDownExpand?: (value: any) => void;
233
234
  ticketQuantity?: number;
234
- onIncreaseTicketQuantity?: (serviceItem: MobileServiceItemProps["serviceItem"]) => void;
235
- onDecreaseTicketQuantity?: (serviceItem: MobileServiceItemProps["serviceItem"]) => void;
235
+ onIncreaseTicketQuantity?: (
236
+ serviceItem: MobileServiceItemProps["serviceItem"],
237
+ ) => void;
238
+ onDecreaseTicketQuantity?: (
239
+ serviceItem: MobileServiceItemProps["serviceItem"],
240
+ ) => void;
236
241
  cityOrigin?: { value: number; label: string };
237
242
  cityDestination?: { value: number; label: string };
238
- isNewUi?: boolean
243
+ isNewUi?: boolean;
239
244
 
240
- selectedTimeSlot?: string;
245
+ selectedTimeSlot?: string;
241
246
  onTimeSlotChange?: (slot: string) => void;
242
247
  isTimeDropdownOpen?: string | number | null;
243
248
  onTimeDropdownToggle?: (id?: string | number | null) => void;
@@ -252,6 +252,18 @@ export interface ServiceItemProps {
252
252
  showLoginModal?: any;
253
253
  isLoggedIn?: any;
254
254
  showLoginOption?: boolean;
255
+ isTrain?: boolean;
256
+ selectedSeatKey?: any;
257
+ onSeatSelect?: (
258
+ key: any,
259
+ price: number,
260
+ seatKey: string,
261
+ apiSeatType?: string,
262
+ ) => void;
263
+ onTrainButtonClick?: any;
264
+ showSeatSelectionError?: string | null;
265
+ onShowSeatSelectionError?: (serviceId: string) => void;
266
+ onClearSeatSelectionError?: () => void;
255
267
  selectedTimeSlot?: string;
256
268
  onTimeSlotChange?: (slot: string) => void;
257
269
  isTimeDropdownOpen?: string | number | null;
@@ -119,7 +119,7 @@ function DateTimeSection({
119
119
  metaData={metaData}
120
120
  colors={colors}
121
121
  >
122
- <span className="cursor-pointer bold-text capitalize">
122
+ <span className="cursor-pointer bold-text capitalize whitespace-nowrap">
123
123
  {DateService.getServiceItemDate(serviceItem.travel_date)}
124
124
  </span>
125
125
  </StageTooltip>
@@ -134,7 +134,7 @@ function DateTimeSection({
134
134
  metaData={metaData}
135
135
  colors={colors}
136
136
  >
137
- <span className="cursor-pointer bold-text capitalize">
137
+ <span className="cursor-pointer bold-text capitalize whitespace-nowrap">
138
138
  {DateService.getServiceItemDate(serviceItem.arrival_date)}
139
139
  </span>
140
140
  </StageTooltip>
@@ -169,7 +169,7 @@ function DateTimeSection({
169
169
  metaData={metaData}
170
170
  colors={colors}
171
171
  >
172
- <div className="font-[900] bold-text">
172
+ <div className="font-[900] bold-text whitespace-nowrap">
173
173
  {isLinatal ? (
174
174
  <>
175
175
  {cleanedDepTime} <span>{hasPM ? "PM" : hasAM ? "AM" : ""}</span>
@@ -193,7 +193,7 @@ function DateTimeSection({
193
193
  metaData={metaData}
194
194
  colors={colors}
195
195
  >
196
- <div className="font-[900] bold-text">
196
+ <div className="font-[900] bold-text whitespace-nowrap">
197
197
  {removeArrivalTime
198
198
  ? "\u00A0"
199
199
  : serviceItem.arr_time
@@ -7,6 +7,8 @@ interface SeatType {
7
7
  label: string;
8
8
  fare: number;
9
9
  key: any;
10
+ apiSeatType?: string;
11
+ api_seat_type?: string;
10
12
  }
11
13
 
12
14
  interface SeatSectionProps {
@@ -21,6 +23,15 @@ interface SeatSectionProps {
21
23
  serviceItem?: any;
22
24
  renderIcon?: (iconKey: string, size?: string) => React.ReactNode;
23
25
  discountSeatPriceColor?: string;
26
+ isTrain?: boolean;
27
+ selectedSeatKey?: any;
28
+ onSeatSelect?: (
29
+ key: any,
30
+ price: number,
31
+ seatKey: string,
32
+ apiSeatType?: string,
33
+ ) => void;
34
+ topLabelColor?: string;
24
35
  tooltipColor?: string;
25
36
  }
26
37
 
@@ -32,6 +43,8 @@ function getAllSeatTypes(seatTypes: SeatType[]) {
32
43
  let seatTypesWithPrices = seatTypes.filter(Boolean).map((val) => ({
33
44
  label: val?.label,
34
45
  price: val?.fare,
46
+ key: val?.key,
47
+ apiSeatType: val?.apiSeatType || val?.api_seat_type,
35
48
  }));
36
49
 
37
50
  seatTypesWithPrices.sort((a, b) => a.price - b.price);
@@ -39,7 +52,7 @@ function getAllSeatTypes(seatTypes: SeatType[]) {
39
52
  return seatTypesWithPrices;
40
53
  }
41
54
 
42
- function getSortedSeatTypes(seatTypes: SeatType[]) {
55
+ function getSortedSeatTypes(seatTypes: SeatType[], isTrain: any) {
43
56
  if (!seatTypes?.length) {
44
57
  return [{ label: "Salon cama", price: 0 }];
45
58
  }
@@ -53,7 +66,9 @@ function getSortedSeatTypes(seatTypes: SeatType[]) {
53
66
  seatTypesWithPrices[2] = seatTypesWithPrices[premiumIndex];
54
67
  }
55
68
 
56
- seatTypesWithPrices = seatTypesWithPrices.slice(0, 2);
69
+ if (!isTrain) {
70
+ seatTypesWithPrices = seatTypesWithPrices.slice(0, 2);
71
+ }
57
72
 
58
73
  const seenPrices = new Set<number>();
59
74
  seatTypesWithPrices = seatTypesWithPrices.filter((seat) => {
@@ -98,16 +113,19 @@ function SeatSection({
98
113
  priceColor,
99
114
  currencySign,
100
115
  removeDuplicateSeats,
116
+ selectedSeatKey,
117
+ onSeatSelect,
101
118
  isPeru,
102
119
  serviceItem,
103
120
  renderIcon,
104
121
  dpSeatColor,
105
122
  discountSeatPriceColor,
123
+ isTrain,
124
+ topLabelColor,
106
125
  tooltipColor,
107
126
  }: SeatSectionProps): React.ReactElement {
108
127
  const uniqueSeats = getUniqueSeats(seatTypes);
109
- const sortedSeatTypes = getSortedSeatTypes(seatTypes);
110
- console.log("🚀 ~ SeatSection ~ sortedSeatTypes:", sortedSeatTypes);
128
+ const sortedSeatTypes = getSortedSeatTypes(seatTypes, isTrain);
111
129
  const numberOfSeats = getNumberOfSeats(seatTypes);
112
130
  const isCentered = numberOfSeats < 2 || removeDuplicateSeats;
113
131
 
@@ -119,22 +137,68 @@ function SeatSection({
119
137
  const renderSeatNames = () => {
120
138
  const seats = removeDuplicateSeats ? uniqueSeats : sortedSeatTypes;
121
139
 
122
- return seats.map((val, key: number) =>
123
- SEAT_EXCEPTIONS.includes(val.label) ? null : (
124
- <span
125
- key={key}
126
- className={`flex items-center justify-between text-[13.33px] ${
127
- isSoldOut ? "text-[#c0c0c0]" : ""
128
- }`}
140
+ return seats.map((val, key: number) => {
141
+ return SEAT_EXCEPTIONS.includes(val.label) ? null : (
142
+ <div
143
+ className="flex items-center"
144
+ style={isTrain ? { cursor: "pointer" } : undefined}
145
+ onClick={
146
+ isTrain && !isSoldOut
147
+ ? () =>
148
+ val.label === selectedSeatKey
149
+ ? onSeatSelect?.(null, 0, "", "")
150
+ : onSeatSelect?.(
151
+ val.label,
152
+ val.price,
153
+ val.key,
154
+ (val as any).apiSeatType,
155
+ )
156
+ : undefined
157
+ }
129
158
  >
130
- {typeof val.label === "string" || typeof val.label === "number"
131
- ? removeDuplicateSeats && isPeru
132
- ? CommonService.truncateSeatLabel(val.label)
133
- : val.label
134
- : null}
135
- </span>
136
- ),
137
- );
159
+ {isTrain && (
160
+ <div
161
+ style={{
162
+ border: `1px solid ${val.label === selectedSeatKey ? topLabelColor : "#ccc"}`,
163
+ borderRadius: "50%",
164
+ width: "14px",
165
+ height: "14px",
166
+ minWidth: "14px",
167
+ marginRight: "10px",
168
+ display: "flex",
169
+ alignItems: "center",
170
+ justifyContent: "center",
171
+ }}
172
+ >
173
+ {val.label === selectedSeatKey && (
174
+ <div
175
+ style={{
176
+ backgroundColor: topLabelColor,
177
+ borderRadius: "50%",
178
+ width: "7px",
179
+ height: "7px",
180
+ }}
181
+ />
182
+ )}
183
+ </div>
184
+ )}
185
+ <span
186
+ key={key}
187
+ className={`flex items-center justify-between text-[13.33px] whitespace-nowrap ${
188
+ isSoldOut ? "text-[#c0c0c0]" : ""
189
+ }`}
190
+ >
191
+ {typeof val.label === "string" || typeof val.label === "number"
192
+ ? removeDuplicateSeats && isPeru
193
+ ? CommonService.truncateSeatLabel(val.label)
194
+ : isTrain
195
+ ? CommonService.capitalize(String(val.label))
196
+ : val.label
197
+ : null}
198
+ </span>
199
+ </div>
200
+ );
201
+ });
138
202
  };
139
203
 
140
204
  const renderSeatPrices = () => {
@@ -151,7 +215,10 @@ function SeatSection({
151
215
  serviceItem,
152
216
  );
153
217
  return (
154
- <span key={key} className="flex items-center text-[13.33px] bold-text">
218
+ <span
219
+ key={key}
220
+ className="flex items-center text-[13.33px] bold-text"
221
+ >
155
222
  {formatPrice(discountedPrice)}
156
223
  </span>
157
224
  );
@@ -235,6 +302,25 @@ function SeatSection({
235
302
  }
236
303
  return null;
237
304
  })();
305
+ console.log("🚀 ~ SeatSection ~ serviceItem:", serviceItem);
306
+
307
+ // Hide the % OFF badge when max_discount is capping the percentage discount
308
+ // (i.e. both percentage and max_discount exist, and the raw % amount exceeds the cap)
309
+ const isMaxDiscountApplied = (() => {
310
+ const { discount_type, discount_value, max_discount } = serviceItem ?? {};
311
+ if (
312
+ discount_type === "percentage" &&
313
+ typeof discount_value === "number" &&
314
+ max_discount != null &&
315
+ max_discount > 0 &&
316
+ discountSeat
317
+ ) {
318
+ const rawPercentageDiscount =
319
+ (discountSeat.originalPrice * discount_value) / 100;
320
+ return rawPercentageDiscount > max_discount;
321
+ }
322
+ return false;
323
+ })();
238
324
 
239
325
  const renderLabels = () => {
240
326
  if (isPeru) {
@@ -286,7 +372,9 @@ function SeatSection({
286
372
 
287
373
  <span className="text-[13.33px] flex flex-col">
288
374
  {operatorServiceName ? (
289
- <span className="text-[13.33px]">{seatLabel}</span>
375
+ <span className="text-[13.33px] whitespace-nowrap">
376
+ {seatLabel}
377
+ </span>
290
378
  ) : (
291
379
  <span className="text-[13.33px]">Desde</span>
292
380
  )}
@@ -490,7 +578,7 @@ function SeatSection({
490
578
  className="col-start-2 row-start-1 flex items-center justify-center absolute"
491
579
  style={{ top: "-22px", left: "50%", transform: "translateX(-50%)" }}
492
580
  >
493
- {discountValue != null && (
581
+ {discountValue != null && !isMaxDiscountApplied && (
494
582
  <span
495
583
  className="rounded-[100px] bg-[#ff5964] px-[6px] text-[12px] bold-text leading-[20px] text-white"
496
584
  style={{
@@ -509,7 +597,7 @@ function SeatSection({
509
597
  style={{ textAlign: "center" }}
510
598
  >
511
599
  <span
512
- className="text-[13.33px] font-normal leading-[20px] text-[#9f9f9f] relative"
600
+ className="text-[13.33px] font-normal leading-[20px] text-[#9f9f9f] relative whitespace-nowrap"
513
601
  style={{
514
602
  position: "relative",
515
603
  }}
@@ -571,7 +659,9 @@ function SeatSection({
571
659
  color: isSoldOut ? "#c0c0c0" : priceColor,
572
660
  top: 0,
573
661
  bottom: 0,
574
- left: "clamp(60%, 65% + (100vw - 1300px) * 0.1, 65%)",
662
+ left: isTrain
663
+ ? "73%"
664
+ : "clamp(60%, 65% + (100vw - 1300px) * 0.1, 65%)",
575
665
  right: 0,
576
666
  justifyContent: isCentered ? "center" : "",
577
667
  gap: "10px",
@@ -25,6 +25,7 @@ interface DateTimeSectionMobileProps {
25
25
  tooltipBgColor?: string;
26
26
  showLastSeats?: boolean;
27
27
  discountSeatPriceColor?: string;
28
+ isTrain?: boolean;
28
29
  }
29
30
 
30
31
  const pad = (n: number) => (n < 10 ? "0" + n : String(n));
@@ -67,41 +68,47 @@ const TimeRow: React.FC<TimeRowProps> = ({
67
68
  isSoldOut,
68
69
  }) => {
69
70
  const formattedDate = DateService.getServiceItemDate(date);
70
- const dotPositionClass = formattedDate.includes("dom") ? "max-[399px]:left-[53%]" : "";
71
- return <div
72
- className={`flex items-center min-[420]:text-[13px] text-[12px] justify-between ${
73
- isSoldOut ? "text-[#c0c0c0]" : ""
74
- }`}
75
- >
76
- <div className="flex items-center" style={{ flex: 1 }}>
77
- <div>
78
- {" "}
79
- {label ? (
80
- <div className="w-[60px]">{label}</div>
81
- ) : (
82
- <div className="w-[12px] h-auto mr-[5px]">
83
- <img
84
- src={icon}
85
- alt={alt}
86
- className={`w-[12px] h-auto mr-[5px] ${
87
- isSoldOut ? "grayscale" : ""
88
- }`}
89
- />
71
+ const dotPositionClass = formattedDate.includes("dom")
72
+ ? "max-[399px]:left-[53%]"
73
+ : "";
74
+ return (
75
+ <div
76
+ className={`flex items-center min-[420]:text-[13px] text-[12px] justify-between ${
77
+ isSoldOut ? "text-[#c0c0c0]" : ""
78
+ }`}
79
+ >
80
+ <div className="flex items-center" style={{ flex: 1 }}>
81
+ <div>
82
+ {" "}
83
+ {label ? (
84
+ <div className="w-[60px]">{label}</div>
85
+ ) : (
86
+ <div className="w-[12px] h-auto mr-[5px]">
87
+ <img
88
+ src={icon}
89
+ alt={alt}
90
+ className={`w-[12px] h-auto mr-[5px] ${
91
+ isSoldOut ? "grayscale" : ""
92
+ }`}
93
+ />
94
+ </div>
95
+ )}
96
+ </div>
97
+ <div
98
+ className="flex items-center relative capitalize justify-between"
99
+ style={{ flex: 1 }}
100
+ >
101
+ <span className="cursor-pointer black-text whitespace-nowrap">
102
+ {formattedDate}
103
+ </span>
104
+ <div className={`absolute left-[50%] ${dotPositionClass}`}>•</div>
105
+ <div className="font-[900] relative black-text whitespace-nowrap">
106
+ {timeContent}
90
107
  </div>
91
- )}
92
- </div>
93
- <div
94
- className="flex items-center relative capitalize justify-between"
95
- style={{ flex: 1 }}
96
- >
97
- <span className="cursor-pointer black-text">
98
- {formattedDate}
99
- </span>
100
- <div className={`absolute left-[50%] ${dotPositionClass}`}>•</div>
101
- <div className="font-[900] relative black-text">{timeContent}</div>
108
+ </div>
102
109
  </div>
103
110
  </div>
104
- </div>;
111
+ );
105
112
  };
106
113
 
107
114
  function DateTimeSectionMobile({
@@ -127,6 +134,7 @@ function DateTimeSectionMobile({
127
134
  tooltipBgColor,
128
135
  showLastSeats,
129
136
  discountSeatPriceColor,
137
+ isTrain,
130
138
  }: DateTimeSectionMobileProps): React.ReactElement {
131
139
  const { cleaned: cleanedDepTime, hasAM, hasPM } = getCleanedDepTime(depTime);
132
140
 
@@ -153,8 +161,12 @@ function DateTimeSectionMobile({
153
161
  >
154
162
  {/* DATE AND TIME */}
155
163
  <div
156
- className="min-h-[2.5rem] flex flex-col justify-between gap-[4px] w-[50%] "
157
- style={{ justifyContent: isCiva && "center" }}
164
+ className={`flex flex-col gap-[4px] w-[50%] ${isTrain ? "justify-center" : "justify-between"}`}
165
+ style={{
166
+ justifyContent: isCiva && "center",
167
+ minHeight: isTrain ? undefined : "2.5rem",
168
+ alignSelf: isTrain ? "stretch" : undefined,
169
+ }}
158
170
  >
159
171
  <TimeRow
160
172
  label={orignLabel}
@@ -198,6 +210,7 @@ function DateTimeSectionMobile({
198
210
  tooltipBgColor={tooltipBgColor}
199
211
  showLastSeats={showLastSeats}
200
212
  discountSeatPriceColor={discountSeatPriceColor}
213
+ isTrain={isTrain}
201
214
  />
202
215
  </div>
203
216
  );