kupos-ui-components-lib 10.4.67 → 10.4.69
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/dist/components/DiscountBanners/DiscountBannerDesktop.js +74 -12
- package/dist/components/DiscountBanners/DiscountBannerMobile.js +73 -12
- package/dist/components/DiscountBanners/types.d.ts +5 -0
- package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
- package/dist/components/ServiceItem/ServiceItemDesktop.js +2 -2
- package/dist/components/ServiceItem/ServiceItemMobile.d.ts +1 -1
- package/dist/components/ServiceItem/ServiceItemMobile.js +2 -2
- package/dist/components/ServiceItem/mobileTypes.d.ts +1 -0
- package/dist/components/ServiceItem/types.d.ts +1 -0
- package/dist/styles.css +4 -0
- package/dist/ui/DiscountBanners/DiscountBanners.js +72 -11
- package/dist/ui/DiscountBanners/discountUtils.js +41 -2
- package/dist/ui/DiscountBanners/types.d.ts +5 -0
- package/dist/ui/SeatSection/SeatSection.d.ts +2 -1
- package/dist/ui/SeatSection/SeatSection.js +13 -5
- package/dist/ui/mobileweb/DateTimeSectionMobile.d.ts +2 -1
- package/dist/ui/mobileweb/DateTimeSectionMobile.js +2 -2
- package/dist/ui/mobileweb/SeatSectionMobile.d.ts +2 -1
- package/dist/ui/mobileweb/SeatSectionMobile.js +16 -3
- package/package.json +1 -1
- package/src/components/DiscountBanners/DiscountBannerDesktop.tsx +94 -6
- package/src/components/DiscountBanners/DiscountBannerMobile.tsx +93 -6
- package/src/components/DiscountBanners/types.ts +4 -0
- package/src/components/ServiceItem/ServiceItemDesktop.tsx +2 -0
- package/src/components/ServiceItem/ServiceItemMobile.tsx +2 -0
- package/src/components/ServiceItem/mobileTypes.ts +2 -0
- package/src/components/ServiceItem/types.ts +2 -0
- package/src/ui/DiscountBanners/DiscountBanners.tsx +93 -6
- package/src/ui/DiscountBanners/discountUtils.ts +53 -5
- package/src/ui/DiscountBanners/types.ts +4 -0
- package/src/ui/SeatSection/SeatSection.tsx +19 -4
- package/src/ui/mobileweb/DateTimeSectionMobile.tsx +3 -0
- package/src/ui/mobileweb/SeatSectionMobile.tsx +24 -9
|
@@ -28,6 +28,53 @@ const SIZE_CLASSES = {
|
|
|
28
28
|
flamePercent: "text-[8px]",
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
|
+
// Rows with logos scale every card up 20%, keeping the art's 5:2 ratio so the pill baked
|
|
32
|
+
// into it doesn't distort. Inline so the sizes don't depend on the host's stylesheet.
|
|
33
|
+
const OPERATOR_LOGO_LAYOUT = {
|
|
34
|
+
default: {
|
|
35
|
+
card: { width: "240px", height: "96px", paddingTop: "10px" },
|
|
36
|
+
chip: {
|
|
37
|
+
width: "97px",
|
|
38
|
+
height: "20px",
|
|
39
|
+
marginLeft: "5px",
|
|
40
|
+
fontSize: "10px",
|
|
41
|
+
lineHeight: "20px",
|
|
42
|
+
},
|
|
43
|
+
flameBadge: { fontSize: "22px", lineHeight: "19px" },
|
|
44
|
+
flamePercent: { fontSize: "12px" },
|
|
45
|
+
discount: { lineHeight: "16px" },
|
|
46
|
+
subtitle: {
|
|
47
|
+
fontSize: "11px",
|
|
48
|
+
lineHeight: "11px",
|
|
49
|
+
marginTop: 0,
|
|
50
|
+
maxWidth: "100px",
|
|
51
|
+
},
|
|
52
|
+
row: { gap: "3px", fontSize: "10px", marginTop: "4px", maxWidth: "128px" },
|
|
53
|
+
logo: { height: "18px", width: "84px", minWidth: 0 },
|
|
54
|
+
},
|
|
55
|
+
compact: {
|
|
56
|
+
card: { width: "204px", height: "82px", paddingTop: "9px" },
|
|
57
|
+
chip: {
|
|
58
|
+
width: "83px",
|
|
59
|
+
height: "18px",
|
|
60
|
+
marginLeft: "5px",
|
|
61
|
+
fontSize: "9px",
|
|
62
|
+
lineHeight: "18px",
|
|
63
|
+
},
|
|
64
|
+
flameBadge: { fontSize: "16px", lineHeight: "16px" },
|
|
65
|
+
flamePercent: { fontSize: "10px" },
|
|
66
|
+
discount: { lineHeight: "14px" },
|
|
67
|
+
subtitle: {
|
|
68
|
+
fontSize: "9.5px",
|
|
69
|
+
lineHeight: "9.5px",
|
|
70
|
+
marginTop: 0,
|
|
71
|
+
maxWidth: "85px",
|
|
72
|
+
},
|
|
73
|
+
row: { gap: "2px", fontSize: "8px", marginTop: "3px", maxWidth: "108px" },
|
|
74
|
+
logo: { height: "14px", width: "70px", minWidth: 0 },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const MAX_VISIBLE_OPERATOR_LOGOS = 3;
|
|
31
78
|
const FLAME_CENTER = { left: "76.8%", top: "56.4%" };
|
|
32
79
|
const REMATE_KEY = "remate";
|
|
33
80
|
function isRemateCard(card) {
|
|
@@ -84,6 +131,7 @@ class DiscountBannersDesktop extends React.Component {
|
|
|
84
131
|
}
|
|
85
132
|
render() {
|
|
86
133
|
const { services = [], wowDealData, backgroundImages = [], artIncludesChipPill, accentColors = [], label = DEFAULT_LABEL, currencySign = "$", size = "default", selectedKey, className = "", style, } = this.props;
|
|
134
|
+
console.log("🚀 ~ DiscountBannersDesktop ~ render ~ services:", services);
|
|
87
135
|
// Compute cards directly — no useMemo
|
|
88
136
|
const cards = deriveDiscountCards(services, wowDealData, currencySign);
|
|
89
137
|
if (!cards.length)
|
|
@@ -96,8 +144,17 @@ class DiscountBannersDesktop extends React.Component {
|
|
|
96
144
|
const defaultArt = resolveArt(cards);
|
|
97
145
|
const chipPillInArt = artIncludesChipPill !== null && artIncludesChipPill !== void 0 ? artIncludesChipPill : usingDefaultArt;
|
|
98
146
|
const sizeClasses = SIZE_CLASSES[size !== null && size !== void 0 ? size : "default"];
|
|
147
|
+
const hasLogoRow = cards.some((card) => { var _a; return (_a = card.operatorLogos) === null || _a === void 0 ? void 0 : _a.length; });
|
|
148
|
+
const logoLayout = OPERATOR_LOGO_LAYOUT[size !== null && size !== void 0 ? size : "default"];
|
|
99
149
|
return (React.createElement("div", { className: `kupos-discount-banners flex items-stretch gap-[10px] overflow-x-auto [scrollbar-width:none] ${className}`, style: style }, cards.map((card, index) => {
|
|
150
|
+
var _a;
|
|
100
151
|
const isActive = activeKey === card.key;
|
|
152
|
+
const operatorLogos = (_a = card.operatorLogos) !== null && _a !== void 0 ? _a : [];
|
|
153
|
+
const hasLogos = operatorLogos.length > 0;
|
|
154
|
+
// Three logos fill the space beside the art, so a "+N" takes the third slot
|
|
155
|
+
const visibleLogos = operatorLogos.length > MAX_VISIBLE_OPERATOR_LOGOS
|
|
156
|
+
? operatorLogos.slice(0, MAX_VISIBLE_OPERATOR_LOGOS - 1)
|
|
157
|
+
: operatorLogos;
|
|
101
158
|
const isDimmed = activeKey != null && !isActive;
|
|
102
159
|
const backgroundImage = card.campaignImageUrl
|
|
103
160
|
? card.campaignImageUrl
|
|
@@ -114,25 +171,30 @@ class DiscountBannersDesktop extends React.Component {
|
|
|
114
171
|
isFlameArt &&
|
|
115
172
|
card.discountType === "percentage" &&
|
|
116
173
|
card.amount > 0;
|
|
117
|
-
return (React.createElement("button", { key: card.key, type: "button", "aria-pressed": isActive, onClick: () => this.handleCardClick(card), 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} ${isDimmed ? "opacity-[0.65]" : "opacity-100"}`, style: {
|
|
118
|
-
backgroundImage: backgroundImage
|
|
174
|
+
return (React.createElement("button", { key: card.key, type: "button", "aria-pressed": isActive, onClick: () => this.handleCardClick(card), 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} ${isDimmed ? "opacity-[0.65]" : "opacity-100"}`, style: Object.assign({ backgroundImage: backgroundImage
|
|
119
175
|
? `url(${backgroundImage})`
|
|
120
|
-
: undefined,
|
|
121
|
-
|
|
122
|
-
} },
|
|
123
|
-
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: chipPillInArt || card.campaignImageUrl
|
|
176
|
+
: undefined, backgroundSize: "100% 100%" }, (hasLogoRow ? logoLayout.card : undefined)) },
|
|
177
|
+
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: Object.assign(Object.assign({}, (chipPillInArt || card.campaignImageUrl
|
|
124
178
|
? undefined
|
|
125
|
-
: { backgroundColor: "rgba(255,255,255,0.55)" } }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
126
|
-
showFlamePercent && (React.createElement("span", { className: `bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`, style: Object.assign(Object.assign({}, FLAME_CENTER), { transform: "translate(-50%, -50%)", textShadow: "0 1px 2px rgba(0,0,0,0.28)", fontWeight: 900, whiteSpace: "nowrap" }) },
|
|
179
|
+
: { backgroundColor: "rgba(255,255,255,0.55)" })), (hasLogoRow ? logoLayout.chip : undefined)) }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
180
|
+
showFlamePercent && (React.createElement("span", { className: `bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`, style: Object.assign(Object.assign(Object.assign({}, FLAME_CENTER), { transform: "translate(-50%, -50%)", textShadow: "0 1px 2px rgba(0,0,0,0.28)", fontWeight: 900, whiteSpace: "nowrap" }), (hasLogoRow ? logoLayout.flameBadge : undefined)) },
|
|
127
181
|
card.amount,
|
|
128
|
-
React.createElement("span", { className: sizeClasses.flamePercent, style: { fontWeight: 700 } }, "%"))),
|
|
182
|
+
React.createElement("span", { className: sizeClasses.flamePercent, style: Object.assign({ fontWeight: 700 }, (hasLogoRow ? logoLayout.flamePercent : undefined)) }, "%"))),
|
|
129
183
|
React.createElement("span", { className: `flex flex-col ${sizeClasses.column}` },
|
|
130
|
-
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}
|
|
184
|
+
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}`, style: hasLogos ? logoLayout.discount : undefined }, card.campaignTexts
|
|
131
185
|
? card.campaignTexts.text2
|
|
132
186
|
: card.discountLabel),
|
|
133
|
-
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: { color: accentColor } }, card.campaignTexts
|
|
187
|
+
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: Object.assign({ color: accentColor }, (hasLogos ? logoLayout.subtitle : undefined)) }, card.campaignTexts
|
|
134
188
|
? card.campaignTexts.text3
|
|
135
|
-
: card.subtitle))
|
|
189
|
+
: card.subtitle)),
|
|
190
|
+
hasLogos && (React.createElement("span", { className: "bold-text flex items-center whitespace-nowrap text-[#464647]", style: logoLayout.row },
|
|
191
|
+
visibleLogos.map((logo) => {
|
|
192
|
+
var _a;
|
|
193
|
+
return (React.createElement("img", { key: logo.url, src: logo.url, alt: (_a = logo.name) !== null && _a !== void 0 ? _a : "", className: "block object-contain object-left", style: logoLayout.logo }));
|
|
194
|
+
}),
|
|
195
|
+
visibleLogos.length < operatorLogos.length && (React.createElement("span", null,
|
|
196
|
+
"+",
|
|
197
|
+
operatorLogos.length - visibleLogos.length))))));
|
|
136
198
|
})));
|
|
137
199
|
}
|
|
138
200
|
}
|
|
@@ -28,6 +28,53 @@ const SIZE_CLASSES = {
|
|
|
28
28
|
flamePercent: "text-[8px]",
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
|
+
// Rows with logos scale every card up 20%, keeping the art's 5:2 ratio so the pill baked
|
|
32
|
+
// into it doesn't distort. Inline so the sizes don't depend on the host's stylesheet.
|
|
33
|
+
const OPERATOR_LOGO_LAYOUT = {
|
|
34
|
+
default: {
|
|
35
|
+
card: { width: "240px", height: "96px", paddingTop: "10px" },
|
|
36
|
+
chip: {
|
|
37
|
+
width: "97px",
|
|
38
|
+
height: "20px",
|
|
39
|
+
marginLeft: "5px",
|
|
40
|
+
fontSize: "10px",
|
|
41
|
+
lineHeight: "20px",
|
|
42
|
+
},
|
|
43
|
+
flameBadge: { fontSize: "22px", lineHeight: "19px" },
|
|
44
|
+
flamePercent: { fontSize: "12px" },
|
|
45
|
+
discount: { lineHeight: "16px" },
|
|
46
|
+
subtitle: {
|
|
47
|
+
fontSize: "11px",
|
|
48
|
+
lineHeight: "11px",
|
|
49
|
+
marginTop: 0,
|
|
50
|
+
maxWidth: "100px",
|
|
51
|
+
},
|
|
52
|
+
row: { gap: "3px", fontSize: "10px", marginTop: "4px", maxWidth: "128px" },
|
|
53
|
+
logo: { height: "18px", width: "84px", minWidth: 0 },
|
|
54
|
+
},
|
|
55
|
+
compact: {
|
|
56
|
+
card: { width: "204px", height: "82px", paddingTop: "9px" },
|
|
57
|
+
chip: {
|
|
58
|
+
width: "83px",
|
|
59
|
+
height: "18px",
|
|
60
|
+
marginLeft: "5px",
|
|
61
|
+
fontSize: "9px",
|
|
62
|
+
lineHeight: "18px",
|
|
63
|
+
},
|
|
64
|
+
flameBadge: { fontSize: "16px", lineHeight: "16px" },
|
|
65
|
+
flamePercent: { fontSize: "10px" },
|
|
66
|
+
discount: { lineHeight: "14px" },
|
|
67
|
+
subtitle: {
|
|
68
|
+
fontSize: "9.5px",
|
|
69
|
+
lineHeight: "9.5px",
|
|
70
|
+
marginTop: 0,
|
|
71
|
+
maxWidth: "85px",
|
|
72
|
+
},
|
|
73
|
+
row: { gap: "2px", fontSize: "8px", marginTop: "3px", maxWidth: "108px" },
|
|
74
|
+
logo: { height: "14px", width: "70px", minWidth: 0 },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const MAX_VISIBLE_OPERATOR_LOGOS = 3;
|
|
31
78
|
const FLAME_CENTER = { left: "76.8%", top: "56.4%" };
|
|
32
79
|
const REMATE_KEY = "remate";
|
|
33
80
|
function isRemateCard(card) {
|
|
@@ -95,8 +142,17 @@ class DiscountBannersMobile extends React.Component {
|
|
|
95
142
|
const defaultArt = resolveArt(cards);
|
|
96
143
|
const chipPillInArt = artIncludesChipPill !== null && artIncludesChipPill !== void 0 ? artIncludesChipPill : usingDefaultArt;
|
|
97
144
|
const sizeClasses = SIZE_CLASSES[size !== null && size !== void 0 ? size : "default"];
|
|
145
|
+
const hasLogoRow = cards.some((card) => { var _a; return (_a = card.operatorLogos) === null || _a === void 0 ? void 0 : _a.length; });
|
|
146
|
+
const logoLayout = OPERATOR_LOGO_LAYOUT[size !== null && size !== void 0 ? size : "default"];
|
|
98
147
|
return (React.createElement("div", { className: `kupos-discount-banners flex items-stretch gap-[10px] overflow-x-auto [scrollbar-width:none] ${className}`, style: style }, cards.map((card, index) => {
|
|
148
|
+
var _a;
|
|
99
149
|
const isActive = activeKey === card.key;
|
|
150
|
+
const operatorLogos = (_a = card.operatorLogos) !== null && _a !== void 0 ? _a : [];
|
|
151
|
+
const hasLogos = operatorLogos.length > 0;
|
|
152
|
+
// Three logos fill the space beside the art, so a "+N" takes the third slot
|
|
153
|
+
const visibleLogos = operatorLogos.length > MAX_VISIBLE_OPERATOR_LOGOS
|
|
154
|
+
? operatorLogos.slice(0, MAX_VISIBLE_OPERATOR_LOGOS - 1)
|
|
155
|
+
: operatorLogos;
|
|
100
156
|
const isDimmed = activeKey != null && !isActive;
|
|
101
157
|
const backgroundImage = card.campaignImageUrl
|
|
102
158
|
? card.campaignImageUrl
|
|
@@ -113,25 +169,30 @@ class DiscountBannersMobile extends React.Component {
|
|
|
113
169
|
isFlameArt &&
|
|
114
170
|
card.discountType === "percentage" &&
|
|
115
171
|
card.amount > 0;
|
|
116
|
-
return (React.createElement("button", { key: card.key, type: "button", "aria-pressed": isActive, onClick: () => this.handleCardClick(card), 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} ${isDimmed ? "opacity-[0.65]" : "opacity-100"}`, style: {
|
|
117
|
-
backgroundImage: backgroundImage
|
|
172
|
+
return (React.createElement("button", { key: card.key, type: "button", "aria-pressed": isActive, onClick: () => this.handleCardClick(card), 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} ${isDimmed ? "opacity-[0.65]" : "opacity-100"}`, style: Object.assign({ backgroundImage: backgroundImage
|
|
118
173
|
? `url(${backgroundImage})`
|
|
119
|
-
: undefined,
|
|
120
|
-
|
|
121
|
-
} },
|
|
122
|
-
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: chipPillInArt || card.campaignImageUrl
|
|
174
|
+
: undefined, backgroundSize: "100% 100%" }, (hasLogoRow ? logoLayout.card : undefined)) },
|
|
175
|
+
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: Object.assign(Object.assign({}, (chipPillInArt || card.campaignImageUrl
|
|
123
176
|
? undefined
|
|
124
|
-
: { backgroundColor: "rgba(255,255,255,0.55)" } }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
125
|
-
showFlamePercent && (React.createElement("span", { className: `bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`, style: Object.assign(Object.assign({}, FLAME_CENTER), { transform: "translate(-50%, -50%)", textShadow: "0 1px 2px rgba(0,0,0,0.28)", fontWeight: 900, whiteSpace: "nowrap" }) },
|
|
177
|
+
: { backgroundColor: "rgba(255,255,255,0.55)" })), (hasLogoRow ? logoLayout.chip : undefined)) }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
178
|
+
showFlamePercent && (React.createElement("span", { className: `bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`, style: Object.assign(Object.assign(Object.assign({}, FLAME_CENTER), { transform: "translate(-50%, -50%)", textShadow: "0 1px 2px rgba(0,0,0,0.28)", fontWeight: 900, whiteSpace: "nowrap" }), (hasLogoRow ? logoLayout.flameBadge : undefined)) },
|
|
126
179
|
card.amount,
|
|
127
|
-
React.createElement("span", { className: sizeClasses.flamePercent, style: { fontWeight: 700 } }, "%"))),
|
|
180
|
+
React.createElement("span", { className: sizeClasses.flamePercent, style: Object.assign({ fontWeight: 700 }, (hasLogoRow ? logoLayout.flamePercent : undefined)) }, "%"))),
|
|
128
181
|
React.createElement("span", { className: `flex flex-col ${sizeClasses.column}` },
|
|
129
|
-
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}
|
|
182
|
+
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}`, style: hasLogos ? logoLayout.discount : undefined }, card.campaignTexts
|
|
130
183
|
? card.campaignTexts.text2
|
|
131
184
|
: card.discountLabel),
|
|
132
|
-
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: { color: accentColor } }, card.campaignTexts
|
|
185
|
+
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: Object.assign({ color: accentColor }, (hasLogos ? logoLayout.subtitle : undefined)) }, card.campaignTexts
|
|
133
186
|
? card.campaignTexts.text3
|
|
134
|
-
: card.subtitle))
|
|
187
|
+
: card.subtitle)),
|
|
188
|
+
hasLogos && (React.createElement("span", { className: "bold-text flex items-center whitespace-nowrap text-[#464647]", style: logoLayout.row },
|
|
189
|
+
visibleLogos.map((logo) => {
|
|
190
|
+
var _a;
|
|
191
|
+
return (React.createElement("img", { key: logo.url, src: logo.url, alt: (_a = logo.name) !== null && _a !== void 0 ? _a : "", className: "block object-contain object-left", style: logoLayout.logo }));
|
|
192
|
+
}),
|
|
193
|
+
visibleLogos.length < operatorLogos.length && (React.createElement("span", null,
|
|
194
|
+
"+",
|
|
195
|
+
operatorLogos.length - visibleLogos.length))))));
|
|
135
196
|
})));
|
|
136
197
|
}
|
|
137
198
|
}
|
|
@@ -12,6 +12,7 @@ export interface DiscountBannerServiceItem {
|
|
|
12
12
|
discounts?: Array<{
|
|
13
13
|
new_ui_enabled?: boolean;
|
|
14
14
|
campaign_type?: string;
|
|
15
|
+
operator_logo?: string;
|
|
15
16
|
campaign_image_url?: string;
|
|
16
17
|
campaign_text?: {
|
|
17
18
|
text_1?: string;
|
|
@@ -43,6 +44,10 @@ export interface DiscountBannerCard {
|
|
|
43
44
|
text2: string;
|
|
44
45
|
text3: string;
|
|
45
46
|
};
|
|
47
|
+
operatorLogos?: Array<{
|
|
48
|
+
url: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
}>;
|
|
46
51
|
}
|
|
47
52
|
export interface DiscountBannersProps {
|
|
48
53
|
services?: DiscountBannerServiceItem[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { ServiceItemProps } from "./types";
|
|
3
|
-
declare function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isPeruSites, showAvailableSeats, isSeatIcon, isLinatal, isPeru, t, siteType, isAllinBus, isExpand, setIsExpand, coachKey, viewersConfig, isNewUi, showLoginModal, isLoggedIn, showLoginOption, isTrain, selectedSeatKey, onSeatSelect, onTrainButtonClick, showSeatSelectionError, onShowSeatSelectionError, onClearSeatSelectionError, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, operatorLabel, priority_stage_ids, isShowSeatIcon, showDesdeLabel, showServiceTypeUnderLogo, showSeatsWithDiscount, }: ServiceItemProps & {
|
|
3
|
+
declare function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isPeruSites, showAvailableSeats, isSeatIcon, isLinatal, isPeru, t, siteType, isAllinBus, isExpand, setIsExpand, coachKey, viewersConfig, isNewUi, showLoginModal, isLoggedIn, showLoginOption, isTrain, selectedSeatKey, onSeatSelect, onTrainButtonClick, showSeatSelectionError, onShowSeatSelectionError, onClearSeatSelectionError, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, operatorLabel, priority_stage_ids, isShowSeatIcon, showDesdeLabel, showServiceTypeUnderLogo, showSeatsWithDiscount, showSeatSoldOutLabel, }: ServiceItemProps & {
|
|
4
4
|
currencySign?: string;
|
|
5
5
|
}): React.ReactElement;
|
|
6
6
|
export default ServiceItemPB;
|
|
@@ -87,7 +87,7 @@ const ANIMATION_MAP = {
|
|
|
87
87
|
kupos: flameAnimation,
|
|
88
88
|
},
|
|
89
89
|
};
|
|
90
|
-
function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isPeruSites, showAvailableSeats, isSeatIcon, isLinatal, isPeru, t = (key) => key, siteType, isAllinBus, isExpand, setIsExpand, coachKey, viewersConfig, isNewUi, showLoginModal, isLoggedIn, showLoginOption, isTrain, selectedSeatKey, onSeatSelect, onTrainButtonClick, showSeatSelectionError, onShowSeatSelectionError, onClearSeatSelectionError, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, operatorLabel, priority_stage_ids, isShowSeatIcon = false, showDesdeLabel = false, showServiceTypeUnderLogo = false, showSeatsWithDiscount = false, }) {
|
|
90
|
+
function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, children, busStage, serviceDetailsLoading, cityOrigin, cityDestination, translation, orignLabel, destinationLabel, currencySign, isCiva, showRating, showLastSeats, removeArrivalTime, removeDuplicateSeats, isPeruSites, showAvailableSeats, isSeatIcon, isLinatal, isPeru, t = (key) => key, siteType, isAllinBus, isExpand, setIsExpand, coachKey, viewersConfig, isNewUi, showLoginModal, isLoggedIn, showLoginOption, isTrain, selectedSeatKey, onSeatSelect, onTrainButtonClick, showSeatSelectionError, onShowSeatSelectionError, onClearSeatSelectionError, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, operatorLabel, priority_stage_ids, isShowSeatIcon = false, showDesdeLabel = false, showServiceTypeUnderLogo = false, showSeatsWithDiscount = false, showSeatSoldOutLabel = false, }) {
|
|
91
91
|
var _a, _b, _c;
|
|
92
92
|
const handleSeatSelect = (key, price, seatKey, apiSeatType) => {
|
|
93
93
|
onClearSeatSelectionError === null || onClearSeatSelectionError === void 0 ? void 0 : onClearSeatSelectionError();
|
|
@@ -366,7 +366,7 @@ function ServiceItemPB({ serviceItem, onBookButtonPress, colors, metaData, child
|
|
|
366
366
|
backgroundColor: "#ccc",
|
|
367
367
|
} }),
|
|
368
368
|
React.createElement("div", { className: "content-center" },
|
|
369
|
-
React.createElement(SeatSection, { seatTypes: serviceItem.seat_types, serviceItem: serviceItem, availableSeats: serviceItem.available_seats, isSoldOut: isSoldOut, priceColor: colors.priceColor, dpSeatColor: colors.seatPriceColor, currencySign: currencySign, removeDuplicateSeats: removeDuplicateSeats, isPeru: isPeru, renderIcon: renderIcon, discountSeatPriceColor: colors.discountSeatPriceColor, isSeatIcon: isSeatIcon, isTrain: isTrain, selectedSeatKey: selectedSeatKey, onSeatSelect: handleSeatSelect, topLabelColor: colors.topLabelColor, tooltipColor: colors.tooltipColor, discountSeatCrossPriceColor: colors.discountSeatCrossPriceColor, isShowSeatIcon: isShowSeatIcon, showDesdeLabel: showDesdeLabel, showSeatsWithDiscount: showSeatsWithDiscount })),
|
|
369
|
+
React.createElement(SeatSection, { seatTypes: serviceItem.seat_types, serviceItem: serviceItem, availableSeats: serviceItem.available_seats, isSoldOut: isSoldOut, priceColor: colors.priceColor, dpSeatColor: colors.seatPriceColor, currencySign: currencySign, removeDuplicateSeats: removeDuplicateSeats, isPeru: isPeru, renderIcon: renderIcon, discountSeatPriceColor: colors.discountSeatPriceColor, isSeatIcon: isSeatIcon, isTrain: isTrain, selectedSeatKey: selectedSeatKey, onSeatSelect: handleSeatSelect, topLabelColor: colors.topLabelColor, tooltipColor: colors.tooltipColor, discountSeatCrossPriceColor: colors.discountSeatCrossPriceColor, isShowSeatIcon: isShowSeatIcon, showDesdeLabel: showDesdeLabel, showSeatsWithDiscount: showSeatsWithDiscount, showSeatSoldOutLabel: showSeatSoldOutLabel })),
|
|
370
370
|
React.createElement("div", { className: "relative" },
|
|
371
371
|
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, buttonTextColor: colors.primaryButtonTextColor }),
|
|
372
372
|
showSeatSelectionError === serviceItem.id && isTrain && (React.createElement("div", { className: "flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[40px]" },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { MobileServiceItemProps } from "./mobileTypes";
|
|
3
|
-
declare function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, isAllinBus, isPeruSite, priority_stage_ids, isShowSeatIcon, showDesdeLabel, showServiceTypeUnderLogo, }: MobileServiceItemProps): React.ReactElement;
|
|
3
|
+
declare function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, isAllinBus, isPeruSite, priority_stage_ids, isShowSeatIcon, showDesdeLabel, showSeatSoldOutLabel, showServiceTypeUnderLogo, }: MobileServiceItemProps): React.ReactElement;
|
|
4
4
|
export default ServiceItemMobile;
|
|
@@ -19,7 +19,7 @@ const exceptions = [
|
|
|
19
19
|
"blanco",
|
|
20
20
|
"asiento_mascota",
|
|
21
21
|
];
|
|
22
|
-
function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, isAllinBus, isPeruSite, priority_stage_ids, isShowSeatIcon = false, showDesdeLabel = false, showServiceTypeUnderLogo = false, }) {
|
|
22
|
+
function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, isAllinBus, isPeruSite, priority_stage_ids, isShowSeatIcon = false, showDesdeLabel = false, showSeatSoldOutLabel = false, showServiceTypeUnderLogo = false, }) {
|
|
23
23
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
24
24
|
const isItemExpanded = serviceItem.id === isExpanded;
|
|
25
25
|
const isPetSeat = (Object.keys(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.pet_seat_info) || []).length > 0;
|
|
@@ -155,7 +155,7 @@ function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, o
|
|
|
155
155
|
color: isSoldOut ? "#bbb" : "text-[#464647]",
|
|
156
156
|
} },
|
|
157
157
|
React.createElement("span", { className: "ml-[3px] min-[420]:text-[13px] text-[12px] text-ellipsis overflow-hidden whitespace-nowrap max-w-[120px]" }, serviceItem.operator_details[2]))))) : null)),
|
|
158
|
-
React.createElement(DateTimeSectionMobile, { onBookButtonPress: onBookButtonPress, isCiva: isCiva, isSoldOut: isSoldOut, isLinatal: isLinatal, isPeru: isPeru, orignLabel: orignLabel, destinationLabel: destinationLabel, originIcon: (_g = serviceItem.icons) === null || _g === void 0 ? void 0 : _g.origin, destinationIcon: (_h = serviceItem.icons) === null || _h === void 0 ? void 0 : _h.destination, travelDate: serviceItem.travel_date, arrivalDate: serviceItem.arrival_date, depTime: serviceItem.dep_time, arrTime: serviceItem.arr_time, seatTypes: serviceItem.seat_types, seatPriceColor: colors.seatPriceColor, tooltipBgColor: colors.tooltipBgColor, currencySign: currencySign, availableSeats: serviceItem.available_seats, removeDuplicateSeats: removeDuplicateSeats, serviceItem: serviceItem, showLastSeats: showLastSeats, discountSeatPriceColor: colors.discountSeatPriceColor, isTrain: isTrain, discountSeatCrossPriceColor: colors.discountSeatCrossPriceColor, isShowSeatIcon: isShowSeatIcon, showDesdeLabel: showDesdeLabel }),
|
|
158
|
+
React.createElement(DateTimeSectionMobile, { onBookButtonPress: onBookButtonPress, isCiva: isCiva, isSoldOut: isSoldOut, isLinatal: isLinatal, isPeru: isPeru, orignLabel: orignLabel, destinationLabel: destinationLabel, originIcon: (_g = serviceItem.icons) === null || _g === void 0 ? void 0 : _g.origin, destinationIcon: (_h = serviceItem.icons) === null || _h === void 0 ? void 0 : _h.destination, travelDate: serviceItem.travel_date, arrivalDate: serviceItem.arrival_date, depTime: serviceItem.dep_time, arrTime: serviceItem.arr_time, seatTypes: serviceItem.seat_types, seatPriceColor: colors.seatPriceColor, tooltipBgColor: colors.tooltipBgColor, currencySign: currencySign, availableSeats: serviceItem.available_seats, removeDuplicateSeats: removeDuplicateSeats, serviceItem: serviceItem, showLastSeats: showLastSeats, discountSeatPriceColor: colors.discountSeatPriceColor, isTrain: isTrain, discountSeatCrossPriceColor: colors.discountSeatCrossPriceColor, isShowSeatIcon: isShowSeatIcon, showDesdeLabel: showDesdeLabel, showSeatSoldOutLabel: showSeatSoldOutLabel }),
|
|
159
159
|
hasDiscount && (React.createElement("div", { className: "flex justify-end" }, isSoldOut ? (React.createElement("span", { className: "col-span-2 min-[420]:text-[13px] text-right text-[12px] text-[#ccc]" }, "Agotado")) : null)),
|
|
160
160
|
showLastSeats ? (React.createElement("div", { className: "flex justify-end " }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) < 10 &&
|
|
161
161
|
(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) > 0 && (React.createElement("div", { className: "text-[10px] text-center", style: {
|
package/dist/styles.css
CHANGED
|
@@ -28,6 +28,53 @@ const SIZE_CLASSES = {
|
|
|
28
28
|
flamePercent: "text-[8px]",
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
|
+
// Rows with logos scale every card up 20%, keeping the art's 5:2 ratio so the pill baked
|
|
32
|
+
// into it doesn't distort. Inline so the sizes don't depend on the host's stylesheet.
|
|
33
|
+
const OPERATOR_LOGO_LAYOUT = {
|
|
34
|
+
default: {
|
|
35
|
+
card: { width: "240px", height: "96px", paddingTop: "10px" },
|
|
36
|
+
chip: {
|
|
37
|
+
width: "97px",
|
|
38
|
+
height: "20px",
|
|
39
|
+
marginLeft: "5px",
|
|
40
|
+
fontSize: "10px",
|
|
41
|
+
lineHeight: "20px",
|
|
42
|
+
},
|
|
43
|
+
flameBadge: { fontSize: "22px", lineHeight: "19px" },
|
|
44
|
+
flamePercent: { fontSize: "12px" },
|
|
45
|
+
discount: { lineHeight: "16px" },
|
|
46
|
+
subtitle: {
|
|
47
|
+
fontSize: "11px",
|
|
48
|
+
lineHeight: "11px",
|
|
49
|
+
marginTop: 0,
|
|
50
|
+
maxWidth: "100px",
|
|
51
|
+
},
|
|
52
|
+
row: { gap: "3px", fontSize: "10px", marginTop: "4px", maxWidth: "128px" },
|
|
53
|
+
logo: { height: "18px", width: "84px", minWidth: 0 },
|
|
54
|
+
},
|
|
55
|
+
compact: {
|
|
56
|
+
card: { width: "204px", height: "82px", paddingTop: "9px" },
|
|
57
|
+
chip: {
|
|
58
|
+
width: "83px",
|
|
59
|
+
height: "18px",
|
|
60
|
+
marginLeft: "5px",
|
|
61
|
+
fontSize: "9px",
|
|
62
|
+
lineHeight: "18px",
|
|
63
|
+
},
|
|
64
|
+
flameBadge: { fontSize: "16px", lineHeight: "16px" },
|
|
65
|
+
flamePercent: { fontSize: "10px" },
|
|
66
|
+
discount: { lineHeight: "14px" },
|
|
67
|
+
subtitle: {
|
|
68
|
+
fontSize: "9.5px",
|
|
69
|
+
lineHeight: "9.5px",
|
|
70
|
+
marginTop: 0,
|
|
71
|
+
maxWidth: "85px",
|
|
72
|
+
},
|
|
73
|
+
row: { gap: "2px", fontSize: "8px", marginTop: "3px", maxWidth: "108px" },
|
|
74
|
+
logo: { height: "14px", width: "70px", minWidth: 0 },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const MAX_VISIBLE_OPERATOR_LOGOS = 3;
|
|
31
78
|
const FLAME_CENTER = { left: "76.8%", top: "56.4%" };
|
|
32
79
|
const REMATE_KEY = "remate";
|
|
33
80
|
function isRemateCard(card) {
|
|
@@ -78,8 +125,17 @@ const DiscountBanners = ({ services = [], wowDealData, backgroundImages = [], ar
|
|
|
78
125
|
const defaultArt = resolveArt(cards);
|
|
79
126
|
const chipPillInArt = artIncludesChipPill !== null && artIncludesChipPill !== void 0 ? artIncludesChipPill : usingDefaultArt;
|
|
80
127
|
const sizeClasses = SIZE_CLASSES[size];
|
|
128
|
+
const hasLogoRow = cards.some((card) => { var _a; return (_a = card.operatorLogos) === null || _a === void 0 ? void 0 : _a.length; });
|
|
129
|
+
const logoLayout = OPERATOR_LOGO_LAYOUT[size !== null && size !== void 0 ? size : "default"];
|
|
81
130
|
return (React.createElement("div", { className: `kupos-discount-banners flex items-stretch gap-[10px] overflow-x-auto [scrollbar-width:none] ${className}`, style: style }, cards.map((card, index) => {
|
|
131
|
+
var _a;
|
|
82
132
|
const isActive = activeKey === card.key;
|
|
133
|
+
const operatorLogos = (_a = card.operatorLogos) !== null && _a !== void 0 ? _a : [];
|
|
134
|
+
const hasLogos = operatorLogos.length > 0;
|
|
135
|
+
// Three logos fill the space beside the art, so a "+N" takes the third slot
|
|
136
|
+
const visibleLogos = operatorLogos.length > MAX_VISIBLE_OPERATOR_LOGOS
|
|
137
|
+
? operatorLogos.slice(0, MAX_VISIBLE_OPERATOR_LOGOS - 1)
|
|
138
|
+
: operatorLogos;
|
|
83
139
|
const isDimmed = activeKey != null && !isActive;
|
|
84
140
|
const backgroundImage = card.campaignImageUrl
|
|
85
141
|
? card.campaignImageUrl
|
|
@@ -96,21 +152,26 @@ const DiscountBanners = ({ services = [], wowDealData, backgroundImages = [], ar
|
|
|
96
152
|
isFlameArt &&
|
|
97
153
|
card.discountType === "percentage" &&
|
|
98
154
|
card.amount > 0;
|
|
99
|
-
return (React.createElement("button", { key: card.key, type: "button", "aria-pressed": isActive, onClick: () => handleCardClick(card), 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} ${isDimmed ? "opacity-[0.65]" : "opacity-100"}`, style: {
|
|
100
|
-
backgroundImage: backgroundImage
|
|
155
|
+
return (React.createElement("button", { key: card.key, type: "button", "aria-pressed": isActive, onClick: () => handleCardClick(card), 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} ${isDimmed ? "opacity-[0.65]" : "opacity-100"}`, style: Object.assign({ backgroundImage: backgroundImage
|
|
101
156
|
? `url(${backgroundImage})`
|
|
102
|
-
: undefined,
|
|
103
|
-
|
|
104
|
-
} },
|
|
105
|
-
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: chipPillInArt || card.campaignImageUrl
|
|
157
|
+
: undefined, backgroundSize: "100% 100%" }, (hasLogoRow ? logoLayout.card : undefined)) },
|
|
158
|
+
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: Object.assign(Object.assign({}, (chipPillInArt || card.campaignImageUrl
|
|
106
159
|
? undefined
|
|
107
|
-
: { backgroundColor: "rgba(255,255,255,0.55)" } }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
108
|
-
showFlamePercent && (React.createElement("span", { className: `bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`, style: Object.assign(Object.assign({}, FLAME_CENTER), { transform: "translate(-50%, -50%)", textShadow: "0 1px 2px rgba(0,0,0,0.28)", fontWeight: 900, whiteSpace: "nowrap" }) },
|
|
160
|
+
: { backgroundColor: "rgba(255,255,255,0.55)" })), (hasLogoRow ? logoLayout.chip : undefined)) }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
161
|
+
showFlamePercent && (React.createElement("span", { className: `bold-text absolute text-[#ffffff] text-center pointer-events-none ${sizeClasses.flameBadge}`, style: Object.assign(Object.assign(Object.assign({}, FLAME_CENTER), { transform: "translate(-50%, -50%)", textShadow: "0 1px 2px rgba(0,0,0,0.28)", fontWeight: 900, whiteSpace: "nowrap" }), (hasLogoRow ? logoLayout.flameBadge : undefined)) },
|
|
109
162
|
card.amount,
|
|
110
|
-
React.createElement("span", { className: sizeClasses.flamePercent, style: { fontWeight: 700 } }, "%"))),
|
|
163
|
+
React.createElement("span", { className: sizeClasses.flamePercent, style: Object.assign({ fontWeight: 700 }, (hasLogoRow ? logoLayout.flamePercent : undefined)) }, "%"))),
|
|
111
164
|
React.createElement("span", { className: `flex flex-col ${sizeClasses.column}` },
|
|
112
|
-
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}
|
|
113
|
-
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: { color: accentColor } }, card.subtitle))
|
|
165
|
+
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}`, style: hasLogos ? logoLayout.discount : undefined }, card.discountLabel),
|
|
166
|
+
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: Object.assign({ color: accentColor }, (hasLogos ? logoLayout.subtitle : undefined)) }, card.subtitle)),
|
|
167
|
+
hasLogos && (React.createElement("span", { className: "bold-text flex items-center whitespace-nowrap text-[#464647]", style: logoLayout.row },
|
|
168
|
+
visibleLogos.map((logo) => {
|
|
169
|
+
var _a;
|
|
170
|
+
return (React.createElement("img", { key: logo.url, src: logo.url, alt: (_a = logo.name) !== null && _a !== void 0 ? _a : "", className: "block object-contain object-left", style: logoLayout.logo }));
|
|
171
|
+
}),
|
|
172
|
+
visibleLogos.length < operatorLogos.length && (React.createElement("span", null,
|
|
173
|
+
"+",
|
|
174
|
+
operatorLogos.length - visibleLogos.length))))));
|
|
114
175
|
})));
|
|
115
176
|
};
|
|
116
177
|
export default DiscountBanners;
|
|
@@ -2,6 +2,8 @@ import CommonService from "../../utils/CommonService";
|
|
|
2
2
|
const DESCUENTOS_ESPECIALES_KEY = "descuentos_especiales";
|
|
3
3
|
const DESCUENTOS_ESPECIALES_LABEL = "Descuentos especiales";
|
|
4
4
|
const REMATE_KEY = "remate";
|
|
5
|
+
const OPERATOR_LOGO_MIN_PERCENT = 30;
|
|
6
|
+
const OPERATOR_LOGO_CAMPAIGN_TYPE = "operator_logo";
|
|
5
7
|
export const CAMPAIGN_TYPE_LABELS = {
|
|
6
8
|
last_minute: "Oferta de último minuto",
|
|
7
9
|
first_time: "Primera compra",
|
|
@@ -80,6 +82,31 @@ function getServiceDiscount(service) {
|
|
|
80
82
|
}
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
85
|
+
// The check uses the rounded amount so it agrees with the percent printed on the card.
|
|
86
|
+
function meetsLogoThreshold(type, amount) {
|
|
87
|
+
return type === "percentage" && Math.round(amount) >= OPERATOR_LOGO_MIN_PERCENT;
|
|
88
|
+
}
|
|
89
|
+
function showsOperatorLogo(service, discount) {
|
|
90
|
+
var _a;
|
|
91
|
+
const campaign = (_a = service.discounts) === null || _a === void 0 ? void 0 : _a[0];
|
|
92
|
+
return (!!(campaign === null || campaign === void 0 ? void 0 : campaign.operator_logo) ||
|
|
93
|
+
(campaign === null || campaign === void 0 ? void 0 : campaign.campaign_type) === OPERATOR_LOGO_CAMPAIGN_TYPE ||
|
|
94
|
+
meetsLogoThreshold(discount.type, discount.amount));
|
|
95
|
+
}
|
|
96
|
+
// Wow-deal services send operator_logo_url/operator_name; regular services send
|
|
97
|
+
// operator_details. A logo sent on the campaign wins over both.
|
|
98
|
+
function addOperatorLogo(card, service) {
|
|
99
|
+
var _a, _b, _c, _d, _e, _f;
|
|
100
|
+
const url = ((_b = (_a = service.discounts) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.operator_logo) ||
|
|
101
|
+
service.operator_logo_url ||
|
|
102
|
+
((_c = service.operator_details) === null || _c === void 0 ? void 0 : _c[0]);
|
|
103
|
+
if (!url || ((_d = card.operatorLogos) === null || _d === void 0 ? void 0 : _d.some((logo) => logo.url === url)))
|
|
104
|
+
return;
|
|
105
|
+
card.operatorLogos = [
|
|
106
|
+
...((_e = card.operatorLogos) !== null && _e !== void 0 ? _e : []),
|
|
107
|
+
{ url, name: service.operator_name || ((_f = service.operator_details) === null || _f === void 0 ? void 0 : _f[2]) },
|
|
108
|
+
];
|
|
109
|
+
}
|
|
83
110
|
function toDiscountLabel(discount, currencySign) {
|
|
84
111
|
if (discount.type === "fixed") {
|
|
85
112
|
return `${CommonService.currencyRounded(discount.amount, currencySign)} de dcto`;
|
|
@@ -108,12 +135,18 @@ export function deriveDiscountCards(services = [], wowDealData, currencySign = "
|
|
|
108
135
|
matchedServices: Array.isArray(wowServices) ? wowServices : [],
|
|
109
136
|
isWowDeal: true,
|
|
110
137
|
});
|
|
138
|
+
if (meetsLogoThreshold(remateDiscount.type, remateDiscount.amount)) {
|
|
139
|
+
for (const service of cards.get(REMATE_KEY).matchedServices) {
|
|
140
|
+
addOperatorLogo(cards.get(REMATE_KEY), service);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
111
143
|
}
|
|
112
144
|
for (const service of services || []) {
|
|
113
145
|
const discount = getServiceDiscount(service);
|
|
114
146
|
if (!discount)
|
|
115
147
|
continue;
|
|
116
148
|
const roundedAmount = Math.round(discount.amount);
|
|
149
|
+
const showsLogo = showsOperatorLogo(service, discount);
|
|
117
150
|
// Backend-driven cards: art and text come from the service; services with the
|
|
118
151
|
// same discount (and same campaign content) are grouped into one card.
|
|
119
152
|
const backendCampaign = discount.isDp ? null : getBackendCampaign(service);
|
|
@@ -138,15 +171,19 @@ export function deriveDiscountCards(services = [], wowDealData, currencySign = "
|
|
|
138
171
|
campaignTexts: texts,
|
|
139
172
|
});
|
|
140
173
|
}
|
|
174
|
+
if (showsLogo)
|
|
175
|
+
addOperatorLogo(cards.get(key), service);
|
|
141
176
|
continue;
|
|
142
177
|
}
|
|
143
178
|
const campaignType = (_d = (_c = service.discounts) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.campaign_type;
|
|
144
179
|
const campaignLabel = campaignType
|
|
145
180
|
? CAMPAIGN_TYPE_LABELS[campaignType]
|
|
146
181
|
: undefined;
|
|
147
|
-
|
|
182
|
+
// Logo cards group purely by discount, so every operator offering the same percent
|
|
183
|
+
// lands on one banner even when their offer_text differs.
|
|
184
|
+
const category = campaignType && campaignType !== OPERATOR_LOGO_CAMPAIGN_TYPE
|
|
148
185
|
? slugify(campaignType)
|
|
149
|
-
: service.offer_text
|
|
186
|
+
: service.offer_text && !showsLogo
|
|
150
187
|
? slugify(service.offer_text)
|
|
151
188
|
: DESCUENTOS_ESPECIALES_KEY;
|
|
152
189
|
const subtitle = campaignLabel || DESCUENTOS_ESPECIALES_LABEL;
|
|
@@ -167,6 +204,8 @@ export function deriveDiscountCards(services = [], wowDealData, currencySign = "
|
|
|
167
204
|
isWowDeal: false,
|
|
168
205
|
});
|
|
169
206
|
}
|
|
207
|
+
if (showsLogo)
|
|
208
|
+
addOperatorLogo(cards.get(key), service);
|
|
170
209
|
}
|
|
171
210
|
return Array.from(cards.values());
|
|
172
211
|
}
|
|
@@ -12,6 +12,7 @@ export interface DiscountBannerServiceItem {
|
|
|
12
12
|
discounts?: Array<{
|
|
13
13
|
new_ui_enabled?: boolean;
|
|
14
14
|
campaign_type?: string;
|
|
15
|
+
operator_logo?: string;
|
|
15
16
|
campaign_image_url?: string;
|
|
16
17
|
campaign_text?: {
|
|
17
18
|
text_1?: string;
|
|
@@ -43,6 +44,10 @@ export interface DiscountBannerCard {
|
|
|
43
44
|
text2: string;
|
|
44
45
|
text3: string;
|
|
45
46
|
};
|
|
47
|
+
operatorLogos?: Array<{
|
|
48
|
+
url: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
}>;
|
|
46
51
|
}
|
|
47
52
|
export interface DiscountBannersProps {
|
|
48
53
|
services?: DiscountBannerServiceItem[];
|
|
@@ -17,6 +17,7 @@ interface SeatSectionProps {
|
|
|
17
17
|
isPeru?: boolean;
|
|
18
18
|
showDesdeLabel?: boolean;
|
|
19
19
|
showSeatsWithDiscount?: boolean;
|
|
20
|
+
showSeatSoldOutLabel?: boolean;
|
|
20
21
|
serviceItem?: any;
|
|
21
22
|
renderIcon?: (iconKey: string, size?: string) => React.ReactNode;
|
|
22
23
|
discountSeatPriceColor?: string;
|
|
@@ -29,5 +30,5 @@ interface SeatSectionProps {
|
|
|
29
30
|
discountSeatCrossPriceColor?: string;
|
|
30
31
|
isShowSeatIcon?: boolean;
|
|
31
32
|
}
|
|
32
|
-
declare function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currencySign, removeDuplicateSeats, selectedSeatKey, onSeatSelect, isPeru, serviceItem, renderIcon, dpSeatColor, discountSeatPriceColor, isSeatIcon, isTrain, topLabelColor, tooltipColor, discountSeatCrossPriceColor, isShowSeatIcon, showDesdeLabel, showSeatsWithDiscount, }: SeatSectionProps): React.ReactElement;
|
|
33
|
+
declare function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currencySign, removeDuplicateSeats, selectedSeatKey, onSeatSelect, isPeru, serviceItem, renderIcon, dpSeatColor, discountSeatPriceColor, isSeatIcon, isTrain, topLabelColor, tooltipColor, discountSeatCrossPriceColor, isShowSeatIcon, showDesdeLabel, showSeatsWithDiscount, showSeatSoldOutLabel, }: SeatSectionProps): React.ReactElement;
|
|
33
34
|
export default SeatSection;
|