kupos-ui-components-lib 10.4.31 → 10.4.34

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.
Files changed (71) hide show
  1. package/dist/KuposUIComponent.d.ts +1 -1
  2. package/dist/KuposUIComponent.js +3 -0
  3. package/dist/assets/images/discount/blue.png +0 -0
  4. package/dist/assets/images/discount/cash.png +0 -0
  5. package/dist/assets/images/discount/first.png +0 -0
  6. package/dist/assets/images/discount/green.png +0 -0
  7. package/dist/assets/images/discount/instant.png +0 -0
  8. package/dist/assets/images/discount/last.png +0 -0
  9. package/dist/assets/images/discount/promo.png +0 -0
  10. package/dist/assets/images/discount/round.png +0 -0
  11. package/dist/assets/images/discount/seat.png +0 -0
  12. package/dist/assets/images/discount/yellow.png +0 -0
  13. package/dist/assets/images/index.d.ts +12 -0
  14. package/dist/assets/images/index.js +12 -0
  15. package/dist/assets/images/index.ts +14 -0
  16. package/dist/components/DiscountBanners/DiscountBannerDesktop.d.ts +11 -0
  17. package/dist/components/DiscountBanners/DiscountBannerDesktop.js +133 -0
  18. package/dist/components/DiscountBanners/DiscountBannerMobile.d.ts +11 -0
  19. package/dist/components/DiscountBanners/DiscountBannerMobile.js +132 -0
  20. package/dist/components/DiscountBanners/ResponsiveDiscountBanner.d.ts +3 -0
  21. package/dist/components/DiscountBanners/ResponsiveDiscountBanner.js +10 -0
  22. package/dist/components/DiscountBanners/index.d.ts +5 -0
  23. package/dist/components/DiscountBanners/index.js +4 -0
  24. package/dist/components/DiscountBanners/types.d.ts +51 -0
  25. package/dist/components/DiscountBanners/types.js +1 -0
  26. package/dist/components/ServiceItem/ServiceItemDesktop.js +1 -1
  27. package/dist/components/ServiceItem/ServiceItemMobile.js +0 -1
  28. package/dist/index.d.ts +5 -2
  29. package/dist/index.js +6 -1
  30. package/dist/styles.css +89 -0
  31. package/dist/ui/DiscountBanners/DiscountBanners.d.ts +4 -0
  32. package/dist/ui/DiscountBanners/DiscountBanners.js +114 -0
  33. package/dist/ui/DiscountBanners/defaultBackgrounds.d.ts +12 -0
  34. package/dist/ui/DiscountBanners/defaultBackgrounds.js +38 -0
  35. package/dist/ui/DiscountBanners/discountUtils.d.ts +3 -0
  36. package/dist/ui/DiscountBanners/discountUtils.js +126 -0
  37. package/dist/ui/DiscountBanners/index.d.ts +5 -0
  38. package/dist/ui/DiscountBanners/index.js +3 -0
  39. package/dist/ui/DiscountBanners/types.d.ts +49 -0
  40. package/dist/ui/DiscountBanners/types.js +1 -0
  41. package/dist/ui/ServiceBadges/ServiceBadges.js +1 -1
  42. package/dist/ui/mobileweb/ServiceBadgesMobile.js +1 -1
  43. package/package.json +21 -6
  44. package/src/KuposUIComponent.tsx +13 -1
  45. package/src/assets/images/discount/blue.png +0 -0
  46. package/src/assets/images/discount/cash.png +0 -0
  47. package/src/assets/images/discount/first.png +0 -0
  48. package/src/assets/images/discount/green.png +0 -0
  49. package/src/assets/images/discount/instant.png +0 -0
  50. package/src/assets/images/discount/last.png +0 -0
  51. package/src/assets/images/discount/promo.png +0 -0
  52. package/src/assets/images/discount/round.png +0 -0
  53. package/src/assets/images/discount/seat.png +0 -0
  54. package/src/assets/images/discount/yellow.png +0 -0
  55. package/src/assets/images/index.ts +14 -0
  56. package/src/components/DiscountBanners/DiscountBannerDesktop.tsx +240 -0
  57. package/src/components/DiscountBanners/DiscountBannerMobile.tsx +234 -0
  58. package/src/components/DiscountBanners/ResponsiveDiscountBanner.tsx +16 -0
  59. package/src/components/DiscountBanners/index.ts +16 -0
  60. package/src/components/DiscountBanners/types.ts +55 -0
  61. package/src/components/ServiceItem/ServiceItemDesktop.tsx +1 -0
  62. package/src/components/ServiceItem/ServiceItemMobile.tsx +0 -1
  63. package/src/index.ts +34 -3
  64. package/src/types/images.d.ts +12 -0
  65. package/src/ui/DiscountBanners/DiscountBanners.tsx +215 -0
  66. package/src/ui/DiscountBanners/defaultBackgrounds.ts +42 -0
  67. package/src/ui/DiscountBanners/discountUtils.ts +166 -0
  68. package/src/ui/DiscountBanners/index.ts +14 -0
  69. package/src/ui/DiscountBanners/types.ts +53 -0
  70. package/src/ui/ServiceBadges/ServiceBadges.tsx +1 -1
  71. package/src/ui/mobileweb/ServiceBadgesMobile.tsx +1 -1
@@ -10,6 +10,10 @@ import { CuponeraFilterBarProps } from "./components/CuponeraFilterBar/types";
10
10
  import { ResponsiveFilterBar } from "./components/FilterBar";
11
11
  import ResponsiveCuponeraFilterBar from "./components/CuponeraFilterBar/ResponsiveCuponeraFilterBar";
12
12
  import { ResponsiveSurvey, SurveyProps } from "./components/Survey";
13
+ import {
14
+ DiscountBannersProps,
15
+ ResponsiveDiscountBanner,
16
+ } from "./components/DiscountBanners";
13
17
 
14
18
  interface KuposUIComponentProps {
15
19
  typeOfComponent:
@@ -18,7 +22,8 @@ interface KuposUIComponentProps {
18
22
  | "servicelist"
19
23
  | "filterbar"
20
24
  | "survey"
21
- | "cuponerafilterbar";
25
+ | "cuponerafilterbar"
26
+ | "discountbanners";
22
27
  variant?: "mobile" | "desktop";
23
28
 
24
29
  // Original ServiceItem props
@@ -165,6 +170,13 @@ export default function KuposUIComponent(
165
170
  case "survey":
166
171
  return <ResponsiveSurvey {...(restProps as unknown as SurveyProps)} />;
167
172
 
173
+ case "discountbanners":
174
+ return (
175
+ <ResponsiveDiscountBanner
176
+ {...(restProps as unknown as DiscountBannersProps)}
177
+ />
178
+ );
179
+
168
180
  default:
169
181
  return (
170
182
  <div className="error-message">
@@ -33,3 +33,17 @@ export const amenities = {
33
33
  // PNG
34
34
  "amenity-cortina": require("./amenities/amenity-cortina.png"),
35
35
  };
36
+
37
+
38
+ export const backgroundImages = {
39
+ "discount-banner-1": require("./discount/green.png"),
40
+ "discount-banner-2": require("./discount/yellow.png"),
41
+ "discount-banner-3": require("./discount/blue.png"),
42
+ "discount-last-minute": require("./discount/last.png"),
43
+ "discount-first-time": require("./discount/first.png"),
44
+ "discount-cashback": require("./discount/cash.png"),
45
+ "discount-round-trip": require("./discount/round.png"),
46
+ "discount-promo-code": require("./discount/promo.png"),
47
+ "discount-seat-level": require("./discount/seat.png"),
48
+ "discount-instant": require("./discount/instant.png"),
49
+ };
@@ -0,0 +1,240 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { DiscountBannerCard, DiscountBannersProps } from "./types";
5
+
6
+ import {
7
+ CAMPAIGN_ART,
8
+ DEFAULT_BACKGROUND_IMAGES,
9
+ toImageUrl,
10
+ } from "../../ui/DiscountBanners/defaultBackgrounds";
11
+ import { deriveDiscountCards } from "../../ui/DiscountBanners";
12
+
13
+ const DEFAULT_LABEL = "Oferta especial";
14
+ const DEFAULT_ACCENT_PALETTE = [
15
+ "#107E23", // green (remate)
16
+ "#FFC000", // orange (primera compra)
17
+ "#0069D1", // blue (descuentos especiales)
18
+ ];
19
+
20
+ const SIZE_CLASSES = {
21
+ default: {
22
+ card: "w-[200px] h-[80px] pt-[8px] pb-0 pl-[10px] pr-[8px]",
23
+ chip: "w-[81px] h-[17px] text-[9px] leading-[17px]",
24
+ column: "max-w-[116px] mt-[3px]",
25
+ discount: "text-[15px] leading-[17px]",
26
+ subtitle: "text-[12px] leading-[14px] mt-[1px]",
27
+ flameBadge: "text-[18px] leading-[16px]",
28
+ flamePercent: "text-[10px]",
29
+ },
30
+ compact: {
31
+ card: "w-[170px] h-[68px] pt-[7px] pb-0 pl-[8px] pr-[7px]",
32
+ chip: "w-[69px] h-[15px] text-[8px] leading-[15px]",
33
+ column: "max-w-[99px] mt-[2px]",
34
+ discount: "text-[13px] leading-[15px]",
35
+ subtitle: "text-[10.5px] leading-[12px] mt-[1px]",
36
+ flameBadge: "text-[13.5px] leading-[13.5px]",
37
+ flamePercent: "text-[8px]",
38
+ },
39
+ };
40
+
41
+ const FLAME_CENTER = { left: "76.8%", top: "56.4%" };
42
+
43
+ const REMATE_KEY = "remate";
44
+
45
+ function isRemateCard(card: DiscountBannerCard): boolean {
46
+ return card.isWowDeal || card.key === REMATE_KEY;
47
+ }
48
+
49
+ // Remate always takes the green flame. A campaign with its own art (CAMPAIGN_ART)
50
+ // uses it. Anything left over — return_booking and early_bird, which have no art of
51
+ // their own, plus unrecognised campaign types — alternates yellow, blue, yellow...
52
+ // The ordinal counts only those leftovers, so the alternation is unaffected by how
53
+ // many campaign-specific cards sit between them.
54
+ type CardArt = { image: string; accent: string };
55
+
56
+ const FALLBACK_ART: CardArt[] = [
57
+ { image: DEFAULT_BACKGROUND_IMAGES[1], accent: "#FFC000" },
58
+ { image: DEFAULT_BACKGROUND_IMAGES[2], accent: "#0069D1" },
59
+ ];
60
+ const REMATE_ART: CardArt = {
61
+ image: DEFAULT_BACKGROUND_IMAGES[0],
62
+ accent: "#107E23",
63
+ };
64
+
65
+ function resolveArt(cards: DiscountBannerCard[]): CardArt[] {
66
+ let ordinal = 0;
67
+ return cards.map((card) => {
68
+ if (isRemateCard(card)) return REMATE_ART;
69
+ const campaignArt = card.campaignType
70
+ ? CAMPAIGN_ART[card.campaignType]
71
+ : undefined;
72
+ if (campaignArt) return campaignArt;
73
+ const art = FALLBACK_ART[ordinal % FALLBACK_ART.length];
74
+ ordinal += 1;
75
+ return art;
76
+ });
77
+ }
78
+
79
+ interface DiscountBannersDesktopState {
80
+ internalSelectedKey: string | null;
81
+ }
82
+
83
+ class DiscountBannersDesktop extends React.Component<
84
+ DiscountBannersProps,
85
+ DiscountBannersDesktopState
86
+ > {
87
+ constructor(props: DiscountBannersProps) {
88
+ super(props);
89
+ this.state = {
90
+ internalSelectedKey: props.defaultSelectedKey ?? null,
91
+ };
92
+ this.handleCardClick = this.handleCardClick.bind(this);
93
+ }
94
+
95
+ handleCardClick(card: DiscountBannerCard) {
96
+ const { selectedKey, onDiscountSelect } = this.props;
97
+ const isControlled = selectedKey !== undefined;
98
+ const activeKey = isControlled
99
+ ? selectedKey
100
+ : this.state.internalSelectedKey;
101
+
102
+ const nextKey = activeKey === card.key ? null : card.key;
103
+
104
+ if (!isControlled) {
105
+ this.setState({ internalSelectedKey: nextKey });
106
+ }
107
+
108
+ const cards = deriveDiscountCards(
109
+ this.props.services ?? [],
110
+ this.props.wowDealData,
111
+ this.props.currencySign ?? "$",
112
+ );
113
+ const selectedCard = nextKey
114
+ ? (cards.find((c) => c.key === nextKey) ?? null)
115
+ : null;
116
+ onDiscountSelect?.(selectedCard, selectedCard?.matchedServices ?? []);
117
+ }
118
+
119
+ render() {
120
+ const {
121
+ services = [],
122
+ wowDealData,
123
+ backgroundImages = [],
124
+ artIncludesChipPill,
125
+ accentColors = [],
126
+ label = DEFAULT_LABEL,
127
+ currencySign = "$",
128
+ size = "default",
129
+ selectedKey,
130
+ className = "",
131
+ style,
132
+ } = this.props;
133
+
134
+ // Compute cards directly — no useMemo
135
+ const cards = deriveDiscountCards(services, wowDealData, currencySign);
136
+
137
+ if (!cards.length) return null;
138
+
139
+ const isControlled = selectedKey !== undefined;
140
+ const activeKey = isControlled
141
+ ? selectedKey
142
+ : this.state.internalSelectedKey;
143
+
144
+ const usingDefaultArt = !backgroundImages.length;
145
+ const defaultArt = resolveArt(cards);
146
+ const chipPillInArt = artIncludesChipPill ?? usingDefaultArt;
147
+ const sizeClasses = SIZE_CLASSES[size ?? "default"];
148
+
149
+ return (
150
+ <div
151
+ className={`kupos-discount-banners flex items-stretch gap-[10px] overflow-x-auto [scrollbar-width:none] ${className}`}
152
+ style={style}
153
+ >
154
+ {cards.map((card, index) => {
155
+ const isActive = activeKey === card.key;
156
+ const isDimmed = activeKey != null && !isActive;
157
+ const backgroundImage = usingDefaultArt
158
+ ? defaultArt[index].image
159
+ : toImageUrl(backgroundImages[index % backgroundImages.length]);
160
+ const accentColor = accentColors.length
161
+ ? accentColors[index % accentColors.length]
162
+ : usingDefaultArt
163
+ ? defaultArt[index].accent
164
+ : DEFAULT_ACCENT_PALETTE[index % DEFAULT_ACCENT_PALETTE.length];
165
+
166
+ const isFlameArt = backgroundImage === DEFAULT_BACKGROUND_IMAGES[0];
167
+ const showFlamePercent =
168
+ isRemateCard(card) &&
169
+ isFlameArt &&
170
+ card.discountType === "percentage" &&
171
+ card.amount > 0;
172
+
173
+ return (
174
+ <button
175
+ key={card.key}
176
+ type="button"
177
+ aria-pressed={isActive}
178
+ onClick={() => this.handleCardClick(card)}
179
+ className={`relative flex-shrink-0 flex flex-col items-start text-left border-none bg-transparent cursor-pointer bg-no-repeat transition-opacity duration-200 ease-in-out ${sizeClasses.card} ${
180
+ isDimmed ? "opacity-[0.65]" : "opacity-100"
181
+ }`}
182
+ style={{
183
+ backgroundImage: backgroundImage
184
+ ? `url(${backgroundImage})`
185
+ : undefined,
186
+ backgroundSize: "100% 100%",
187
+ }}
188
+ >
189
+ <span
190
+ className={`bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`}
191
+ style={
192
+ chipPillInArt
193
+ ? undefined
194
+ : { backgroundColor: "rgba(255,255,255,0.55)" }
195
+ }
196
+ >
197
+ {label}
198
+ </span>
199
+ {showFlamePercent && (
200
+ <span
201
+ className={`bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`}
202
+ style={{
203
+ ...FLAME_CENTER,
204
+ transform: "translate(-50%, -50%)",
205
+ textShadow: "0 1px 2px rgba(0,0,0,0.28)",
206
+ fontWeight: 900,
207
+ whiteSpace: "nowrap",
208
+ }}
209
+ >
210
+ {card.amount}
211
+ <span
212
+ className={sizeClasses.flamePercent}
213
+ style={{ fontWeight: 700 }}
214
+ >
215
+ %
216
+ </span>
217
+ </span>
218
+ )}
219
+ <span className={`flex flex-col ${sizeClasses.column}`}>
220
+ <span
221
+ className={`bold-text text-[#464647] ${sizeClasses.discount}`}
222
+ >
223
+ {card.discountLabel}
224
+ </span>
225
+ <span
226
+ className={`bold-text ${sizeClasses.subtitle}`}
227
+ style={{ color: accentColor }}
228
+ >
229
+ {card.subtitle}
230
+ </span>
231
+ </span>
232
+ </button>
233
+ );
234
+ })}
235
+ </div>
236
+ );
237
+ }
238
+ }
239
+
240
+ export default DiscountBannersDesktop;
@@ -0,0 +1,234 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { DiscountBannerCard, DiscountBannersProps } from "./types";
5
+
6
+ import {
7
+ CAMPAIGN_ART,
8
+ DEFAULT_BACKGROUND_IMAGES,
9
+ toImageUrl,
10
+ } from "../../ui/DiscountBanners/defaultBackgrounds";
11
+ import { deriveDiscountCards } from "../../ui/DiscountBanners";
12
+
13
+ const DEFAULT_LABEL = "Oferta especial";
14
+ const DEFAULT_ACCENT_PALETTE = [
15
+ "#107E23", // green (remate)
16
+ "#FFC000", // orange (primera compra)
17
+ "#0069D1", // blue (descuentos especiales)
18
+ ];
19
+
20
+ const SIZE_CLASSES = {
21
+ default: {
22
+ card: "w-[200px] h-[80px] pt-[8px] pb-0 pl-[10px] pr-[8px]",
23
+ chip: "w-[81px] h-[17px] text-[9px] leading-[17px]",
24
+ column: "max-w-[116px] mt-[3px]",
25
+ discount: "text-[15px] leading-[17px]",
26
+ subtitle: "text-[12px] leading-[14px] mt-[1px]",
27
+ flameBadge: "text-[18px] leading-[16px]",
28
+ flamePercent: "text-[10px]",
29
+ },
30
+ compact: {
31
+ card: "w-[170px] h-[68px] pt-[7px] pb-0 pl-[8px] pr-[7px]",
32
+ chip: "w-[69px] h-[15px] text-[8px] leading-[15px]",
33
+ column: "max-w-[99px] mt-[2px]",
34
+ discount: "text-[13px] leading-[15px]",
35
+ subtitle: "text-[10.5px] leading-[12px] mt-[1px]",
36
+ flameBadge: "text-[13.5px] leading-[13.5px]",
37
+ flamePercent: "text-[8px]",
38
+ },
39
+ };
40
+
41
+ const FLAME_CENTER = { left: "76.8%", top: "56.4%" };
42
+
43
+ const REMATE_KEY = "remate";
44
+
45
+ function isRemateCard(card: DiscountBannerCard): boolean {
46
+ return card.isWowDeal || card.key === REMATE_KEY;
47
+ }
48
+
49
+ type CardArt = { image: string; accent: string };
50
+
51
+ const FALLBACK_ART: CardArt[] = [
52
+ { image: DEFAULT_BACKGROUND_IMAGES[1], accent: "#FFC000" },
53
+ { image: DEFAULT_BACKGROUND_IMAGES[2], accent: "#0069D1" },
54
+ ];
55
+ const REMATE_ART: CardArt = {
56
+ image: DEFAULT_BACKGROUND_IMAGES[0],
57
+ accent: "#107E23",
58
+ };
59
+
60
+ function resolveArt(cards: DiscountBannerCard[]): CardArt[] {
61
+ let ordinal = 0;
62
+ return cards.map((card) => {
63
+ if (isRemateCard(card)) return REMATE_ART;
64
+ const campaignArt = card.campaignType
65
+ ? CAMPAIGN_ART[card.campaignType]
66
+ : undefined;
67
+ if (campaignArt) return campaignArt;
68
+ const art = FALLBACK_ART[ordinal % FALLBACK_ART.length];
69
+ ordinal += 1;
70
+ return art;
71
+ });
72
+ }
73
+
74
+ interface DiscountBannersMobileState {
75
+ internalSelectedKey: string | null;
76
+ }
77
+
78
+ class DiscountBannersMobile extends React.Component<
79
+ DiscountBannersProps,
80
+ DiscountBannersMobileState
81
+ > {
82
+ constructor(props: DiscountBannersProps) {
83
+ super(props);
84
+ this.state = {
85
+ internalSelectedKey: props.defaultSelectedKey ?? null,
86
+ };
87
+ this.handleCardClick = this.handleCardClick.bind(this);
88
+ }
89
+
90
+ handleCardClick(card: DiscountBannerCard) {
91
+ const { selectedKey, onDiscountSelect } = this.props;
92
+ const isControlled = selectedKey !== undefined;
93
+ const activeKey = isControlled
94
+ ? selectedKey
95
+ : this.state.internalSelectedKey;
96
+
97
+ const nextKey = activeKey === card.key ? null : card.key;
98
+
99
+ if (!isControlled) {
100
+ this.setState({ internalSelectedKey: nextKey });
101
+ }
102
+
103
+ const cards = deriveDiscountCards(
104
+ this.props.services ?? [],
105
+ this.props.wowDealData,
106
+ this.props.currencySign ?? "$",
107
+ );
108
+ const selectedCard = nextKey
109
+ ? (cards.find((c) => c.key === nextKey) ?? null)
110
+ : null;
111
+ onDiscountSelect?.(selectedCard, selectedCard?.matchedServices ?? []);
112
+ }
113
+
114
+ render() {
115
+ const {
116
+ services = [],
117
+ wowDealData,
118
+ backgroundImages = [],
119
+ artIncludesChipPill,
120
+ accentColors = [],
121
+ label = DEFAULT_LABEL,
122
+ currencySign = "$",
123
+ size = "default",
124
+ selectedKey,
125
+ className = "",
126
+ style,
127
+ } = this.props;
128
+
129
+ const cards = deriveDiscountCards(services, wowDealData, currencySign);
130
+
131
+ if (!cards.length) return null;
132
+
133
+ const isControlled = selectedKey !== undefined;
134
+ const activeKey = isControlled
135
+ ? selectedKey
136
+ : this.state.internalSelectedKey;
137
+
138
+ const usingDefaultArt = !backgroundImages.length;
139
+ const defaultArt = resolveArt(cards);
140
+ const chipPillInArt = artIncludesChipPill ?? usingDefaultArt;
141
+ const sizeClasses = SIZE_CLASSES[size ?? "default"];
142
+
143
+ return (
144
+ <div
145
+ className={`kupos-discount-banners flex items-stretch gap-[10px] overflow-x-auto [scrollbar-width:none] ${className}`}
146
+ style={style}
147
+ >
148
+ {cards.map((card, index) => {
149
+ const isActive = activeKey === card.key;
150
+ const isDimmed = activeKey != null && !isActive;
151
+ const backgroundImage = usingDefaultArt
152
+ ? defaultArt[index].image
153
+ : toImageUrl(backgroundImages[index % backgroundImages.length]);
154
+ const accentColor = accentColors.length
155
+ ? accentColors[index % accentColors.length]
156
+ : usingDefaultArt
157
+ ? defaultArt[index].accent
158
+ : DEFAULT_ACCENT_PALETTE[index % DEFAULT_ACCENT_PALETTE.length];
159
+
160
+ const isFlameArt = backgroundImage === DEFAULT_BACKGROUND_IMAGES[0];
161
+ const showFlamePercent =
162
+ isRemateCard(card) &&
163
+ isFlameArt &&
164
+ card.discountType === "percentage" &&
165
+ card.amount > 0;
166
+
167
+ return (
168
+ <button
169
+ key={card.key}
170
+ type="button"
171
+ aria-pressed={isActive}
172
+ onClick={() => this.handleCardClick(card)}
173
+ className={`relative flex-shrink-0 flex flex-col items-start text-left border-none bg-transparent cursor-pointer bg-no-repeat transition-opacity duration-200 ease-in-out ${sizeClasses.card} ${
174
+ isDimmed ? "opacity-[0.65]" : "opacity-100"
175
+ }`}
176
+ style={{
177
+ backgroundImage: backgroundImage
178
+ ? `url(${backgroundImage})`
179
+ : undefined,
180
+ backgroundSize: "100% 100%",
181
+ }}
182
+ >
183
+ <span
184
+ className={`bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`}
185
+ style={
186
+ chipPillInArt
187
+ ? undefined
188
+ : { backgroundColor: "rgba(255,255,255,0.55)" }
189
+ }
190
+ >
191
+ {label}
192
+ </span>
193
+ {showFlamePercent && (
194
+ <span
195
+ className={`bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`}
196
+ style={{
197
+ ...FLAME_CENTER,
198
+ transform: "translate(-50%, -50%)",
199
+ textShadow: "0 1px 2px rgba(0,0,0,0.28)",
200
+ fontWeight: 900,
201
+ whiteSpace: "nowrap",
202
+ }}
203
+ >
204
+ {card.amount}
205
+ <span
206
+ className={sizeClasses.flamePercent}
207
+ style={{ fontWeight: 700 }}
208
+ >
209
+ %
210
+ </span>
211
+ </span>
212
+ )}
213
+ <span className={`flex flex-col ${sizeClasses.column}`}>
214
+ <span
215
+ className={`bold-text text-[#464647] ${sizeClasses.discount}`}
216
+ >
217
+ {card.discountLabel}
218
+ </span>
219
+ <span
220
+ className={`bold-text ${sizeClasses.subtitle}`}
221
+ style={{ color: accentColor }}
222
+ >
223
+ {card.subtitle}
224
+ </span>
225
+ </span>
226
+ </button>
227
+ );
228
+ })}
229
+ </div>
230
+ );
231
+ }
232
+ }
233
+
234
+ export default DiscountBannersMobile;
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { DiscountBannersProps } from "../DiscountBanners/types";
3
+ import DiscountBannersMobile from "./DiscountBannerMobile";
4
+ import DiscountBannersDesktop from "./DiscountBannerDesktop";
5
+
6
+ export default function ResponsiveDiscountBanner(props: DiscountBannersProps) {
7
+ const { variant } = props;
8
+
9
+ if (variant === "mobile") {
10
+ return (
11
+ <DiscountBannersMobile {...(props as unknown as DiscountBannersProps)} />
12
+ );
13
+ }
14
+
15
+ return <DiscountBannersDesktop {...props} />;
16
+ }
@@ -0,0 +1,16 @@
1
+ import DiscountBannerDesktop from "./DiscountBannerDesktop";
2
+ import DiscountBannerMobile from "./DiscountBannerMobile";
3
+ import ResponsiveDiscountBanner from "./ResponsiveDiscountBanner";
4
+
5
+ export {
6
+ DiscountBannerDesktop,
7
+ DiscountBannerMobile,
8
+ ResponsiveDiscountBanner,
9
+ };
10
+
11
+ export type {
12
+ DiscountBannerCard,
13
+ DiscountBannerServiceItem,
14
+ DiscountBannersProps,
15
+ WowDealData,
16
+ } from "./types";
@@ -0,0 +1,55 @@
1
+ import type { CSSProperties } from "react";
2
+ import { ImageRef } from "../../ui/DiscountBanners";
3
+
4
+ export interface DiscountBannerServiceItem {
5
+ id?: string | number;
6
+ offer_text?: string;
7
+ is_dp_enabled?: boolean;
8
+ dp_discount_percents?: Record<string, number>;
9
+ dp_discounted_seats?: string[];
10
+ discount_type?: string;
11
+ discount_value?: number;
12
+ max_discount?: number;
13
+ discounts?: Array<{ new_ui_enabled?: boolean; campaign_type?: string }>;
14
+ [key: string]: any;
15
+ }
16
+
17
+ export interface WowDealData {
18
+ savings_percent?: number;
19
+ services?: any[];
20
+ final_price?: number;
21
+ original_price?: number;
22
+ [key: string]: any;
23
+ }
24
+
25
+ export interface DiscountBannerCard {
26
+ key: string;
27
+ subtitle: string;
28
+ campaignType?: string;
29
+ discountType: "percentage" | "fixed";
30
+ amount: number;
31
+ discountLabel: string;
32
+ matchedServices: DiscountBannerServiceItem[];
33
+ isWowDeal: boolean;
34
+ }
35
+
36
+ export interface DiscountBannersProps {
37
+ services?: DiscountBannerServiceItem[];
38
+ wowDealData?: WowDealData;
39
+ backgroundImages?: ImageRef[];
40
+ artIncludesChipPill?: boolean;
41
+ accentColors?: string[];
42
+ label?: string;
43
+ currencySign?: string;
44
+ size?: "default" | "compact";
45
+ selectedKey?: string | null;
46
+ defaultSelectedKey?: string | null;
47
+ onDiscountSelect?: (
48
+ card: DiscountBannerCard | null,
49
+ filteredServices: DiscountBannerServiceItem[],
50
+ ) => void;
51
+ className?: string;
52
+ style?: CSSProperties;
53
+ variant?: "mobile" | "desktop";
54
+ typeOfComponent?: string;
55
+ }
@@ -520,6 +520,7 @@ function ServiceItemPB({
520
520
  isFlores={isFlores}
521
521
  isNewUiEnabled={isNewUiEnabledPeru}
522
522
  showLoginOption={showLoginOption}
523
+ priority_stage_ids={priority_stage_ids}
523
524
  />
524
525
  ) : isNewUi && !isTrain ? (
525
526
  <FeatureServiceUi
@@ -68,7 +68,6 @@ function ServiceItemMobile({
68
68
  isPeruSite,
69
69
  priority_stage_ids,
70
70
  }: MobileServiceItemProps): React.ReactElement {
71
- console.log("🚀 ~ ServiceItemMobile ~ serviceItem:", serviceItem);
72
71
  const isItemExpanded = serviceItem.id === isExpanded;
73
72
  const isPetSeat = (Object.keys(serviceItem?.pet_seat_info) || []).length > 0;
74
73
  const isNewUiEnabled = serviceItem?.discounts?.[0]?.new_ui_enabled === true;