kupos-ui-components-lib 10.4.54 → 10.4.56
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 +13 -7
- package/dist/components/DiscountBanners/DiscountBannerMobile.js +13 -7
- package/dist/components/DiscountBanners/types.d.ts +12 -0
- package/dist/ui/DiscountBanners/DiscountBanners.js +7 -5
- package/dist/ui/DiscountBanners/discountUtils.js +47 -4
- package/dist/ui/DiscountBanners/types.d.ts +12 -0
- package/dist/ui/PaymentCardMobile/PaymentCardMobile.js +2 -5
- package/package.json +1 -1
- package/src/components/DiscountBanners/DiscountBannerDesktop.tsx +13 -7
- package/src/components/DiscountBanners/DiscountBannerMobile.tsx +13 -7
- package/src/components/DiscountBanners/types.ts +9 -1
- package/src/ui/DiscountBanners/DiscountBanners.tsx +10 -8
- package/src/ui/DiscountBanners/discountUtils.ts +56 -2
- package/src/ui/DiscountBanners/types.ts +9 -1
- package/src/ui/PaymentCardMobile/PaymentCardMobile.tsx +2 -8
|
@@ -99,9 +99,11 @@ class DiscountBannersDesktop extends React.Component {
|
|
|
99
99
|
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) => {
|
|
100
100
|
const isActive = activeKey === card.key;
|
|
101
101
|
const isDimmed = activeKey != null && !isActive;
|
|
102
|
-
const backgroundImage =
|
|
103
|
-
?
|
|
104
|
-
:
|
|
102
|
+
const backgroundImage = card.campaignImageUrl
|
|
103
|
+
? card.campaignImageUrl
|
|
104
|
+
: usingDefaultArt
|
|
105
|
+
? defaultArt[index].image
|
|
106
|
+
: toImageUrl(backgroundImages[index % backgroundImages.length]);
|
|
105
107
|
const accentColor = accentColors.length
|
|
106
108
|
? accentColors[index % accentColors.length]
|
|
107
109
|
: usingDefaultArt
|
|
@@ -118,15 +120,19 @@ class DiscountBannersDesktop extends React.Component {
|
|
|
118
120
|
: undefined,
|
|
119
121
|
backgroundSize: "100% 100%",
|
|
120
122
|
} },
|
|
121
|
-
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: chipPillInArt
|
|
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
|
|
122
124
|
? undefined
|
|
123
|
-
: { backgroundColor: "rgba(255,255,255,0.55)" } }, label),
|
|
125
|
+
: { backgroundColor: "rgba(255,255,255,0.55)" } }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
124
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" }) },
|
|
125
127
|
card.amount,
|
|
126
128
|
React.createElement("span", { className: sizeClasses.flamePercent, style: { fontWeight: 700 } }, "%"))),
|
|
127
129
|
React.createElement("span", { className: `flex flex-col ${sizeClasses.column}` },
|
|
128
|
-
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}` }, card.
|
|
129
|
-
|
|
130
|
+
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}` }, card.campaignTexts
|
|
131
|
+
? card.campaignTexts.text2
|
|
132
|
+
: card.discountLabel),
|
|
133
|
+
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: { color: accentColor } }, card.campaignTexts
|
|
134
|
+
? card.campaignTexts.text3
|
|
135
|
+
: card.subtitle))));
|
|
130
136
|
})));
|
|
131
137
|
}
|
|
132
138
|
}
|
|
@@ -98,9 +98,11 @@ class DiscountBannersMobile extends React.Component {
|
|
|
98
98
|
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) => {
|
|
99
99
|
const isActive = activeKey === card.key;
|
|
100
100
|
const isDimmed = activeKey != null && !isActive;
|
|
101
|
-
const backgroundImage =
|
|
102
|
-
?
|
|
103
|
-
:
|
|
101
|
+
const backgroundImage = card.campaignImageUrl
|
|
102
|
+
? card.campaignImageUrl
|
|
103
|
+
: usingDefaultArt
|
|
104
|
+
? defaultArt[index].image
|
|
105
|
+
: toImageUrl(backgroundImages[index % backgroundImages.length]);
|
|
104
106
|
const accentColor = accentColors.length
|
|
105
107
|
? accentColors[index % accentColors.length]
|
|
106
108
|
: usingDefaultArt
|
|
@@ -117,15 +119,19 @@ class DiscountBannersMobile extends React.Component {
|
|
|
117
119
|
: undefined,
|
|
118
120
|
backgroundSize: "100% 100%",
|
|
119
121
|
} },
|
|
120
|
-
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: chipPillInArt
|
|
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
|
|
121
123
|
? undefined
|
|
122
|
-
: { backgroundColor: "rgba(255,255,255,0.55)" } }, label),
|
|
124
|
+
: { backgroundColor: "rgba(255,255,255,0.55)" } }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
123
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" }) },
|
|
124
126
|
card.amount,
|
|
125
127
|
React.createElement("span", { className: sizeClasses.flamePercent, style: { fontWeight: 700 } }, "%"))),
|
|
126
128
|
React.createElement("span", { className: `flex flex-col ${sizeClasses.column}` },
|
|
127
|
-
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}` }, card.
|
|
128
|
-
|
|
129
|
+
React.createElement("span", { className: `bold-text text-[#464647] ${sizeClasses.discount}` }, card.campaignTexts
|
|
130
|
+
? card.campaignTexts.text2
|
|
131
|
+
: card.discountLabel),
|
|
132
|
+
React.createElement("span", { className: `bold-text ${sizeClasses.subtitle}`, style: { color: accentColor } }, card.campaignTexts
|
|
133
|
+
? card.campaignTexts.text3
|
|
134
|
+
: card.subtitle))));
|
|
129
135
|
})));
|
|
130
136
|
}
|
|
131
137
|
}
|
|
@@ -12,6 +12,12 @@ export interface DiscountBannerServiceItem {
|
|
|
12
12
|
discounts?: Array<{
|
|
13
13
|
new_ui_enabled?: boolean;
|
|
14
14
|
campaign_type?: string;
|
|
15
|
+
campaign_image_url?: string;
|
|
16
|
+
campaign_text?: {
|
|
17
|
+
text_1?: string;
|
|
18
|
+
text_2?: string;
|
|
19
|
+
text_3?: string;
|
|
20
|
+
};
|
|
15
21
|
}>;
|
|
16
22
|
[key: string]: any;
|
|
17
23
|
}
|
|
@@ -31,6 +37,12 @@ export interface DiscountBannerCard {
|
|
|
31
37
|
discountLabel: string;
|
|
32
38
|
matchedServices: DiscountBannerServiceItem[];
|
|
33
39
|
isWowDeal: boolean;
|
|
40
|
+
campaignImageUrl?: string;
|
|
41
|
+
campaignTexts?: {
|
|
42
|
+
text1: string;
|
|
43
|
+
text2: string;
|
|
44
|
+
text3: string;
|
|
45
|
+
};
|
|
34
46
|
}
|
|
35
47
|
export interface DiscountBannersProps {
|
|
36
48
|
services?: DiscountBannerServiceItem[];
|
|
@@ -81,9 +81,11 @@ const DiscountBanners = ({ services = [], wowDealData, backgroundImages = [], ar
|
|
|
81
81
|
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) => {
|
|
82
82
|
const isActive = activeKey === card.key;
|
|
83
83
|
const isDimmed = activeKey != null && !isActive;
|
|
84
|
-
const backgroundImage =
|
|
85
|
-
?
|
|
86
|
-
:
|
|
84
|
+
const backgroundImage = card.campaignImageUrl
|
|
85
|
+
? card.campaignImageUrl
|
|
86
|
+
: usingDefaultArt
|
|
87
|
+
? defaultArt[index].image
|
|
88
|
+
: toImageUrl(backgroundImages[index % backgroundImages.length]);
|
|
87
89
|
const accentColor = accentColors.length
|
|
88
90
|
? accentColors[index % accentColors.length]
|
|
89
91
|
: usingDefaultArt
|
|
@@ -100,9 +102,9 @@ const DiscountBanners = ({ services = [], wowDealData, backgroundImages = [], ar
|
|
|
100
102
|
: undefined,
|
|
101
103
|
backgroundSize: "100% 100%",
|
|
102
104
|
} },
|
|
103
|
-
React.createElement("span", { className: `bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`, style: chipPillInArt
|
|
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
|
|
104
106
|
? undefined
|
|
105
|
-
: { backgroundColor: "rgba(255,255,255,0.55)" } }, label),
|
|
107
|
+
: { backgroundColor: "rgba(255,255,255,0.55)" } }, card.campaignTexts ? card.campaignTexts.text1 : label),
|
|
106
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" }) },
|
|
107
109
|
card.amount,
|
|
108
110
|
React.createElement("span", { className: sizeClasses.flamePercent, style: { fontWeight: 700 } }, "%"))),
|
|
@@ -30,6 +30,23 @@ function slugify(text) {
|
|
|
30
30
|
.replace(/[^a-z0-9]+/g, "_")
|
|
31
31
|
.replace(/^_+|_+$/g, "");
|
|
32
32
|
}
|
|
33
|
+
// Backend-driven campaign art and copy (campaign_image_url + campaign_text.text_1..3)
|
|
34
|
+
function getBackendCampaign(service) {
|
|
35
|
+
var _a, _b, _c, _d;
|
|
36
|
+
const campaign = (_a = service === null || service === void 0 ? void 0 : service.discounts) === null || _a === void 0 ? void 0 : _a[0];
|
|
37
|
+
const imageUrl = campaign === null || campaign === void 0 ? void 0 : campaign.campaign_image_url;
|
|
38
|
+
const text = campaign === null || campaign === void 0 ? void 0 : campaign.campaign_text;
|
|
39
|
+
if (!imageUrl || !text)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
imageUrl,
|
|
43
|
+
texts: {
|
|
44
|
+
text1: (_b = text.text_1) !== null && _b !== void 0 ? _b : "",
|
|
45
|
+
text2: (_c = text.text_2) !== null && _c !== void 0 ? _c : "",
|
|
46
|
+
text3: (_d = text.text_3) !== null && _d !== void 0 ? _d : "",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
33
50
|
// Dynamic-pricing discounts store one percent per seat type; the best (lowest) one is what's advertised elsewhere in this library
|
|
34
51
|
function getDpPercent(service) {
|
|
35
52
|
if (!(service === null || service === void 0 ? void 0 : service.is_dp_enabled) || !(service === null || service === void 0 ? void 0 : service.dp_discount_percents))
|
|
@@ -42,7 +59,7 @@ function getDpPercent(service) {
|
|
|
42
59
|
function getServiceDiscount(service) {
|
|
43
60
|
const dpPercent = getDpPercent(service);
|
|
44
61
|
if (dpPercent > 0)
|
|
45
|
-
return { type: "percentage", amount: dpPercent };
|
|
62
|
+
return { type: "percentage", amount: dpPercent, isDp: true };
|
|
46
63
|
if ((service === null || service === void 0 ? void 0 : service.discount_type) === "percentage" &&
|
|
47
64
|
(service === null || service === void 0 ? void 0 : service.discount_value) != null &&
|
|
48
65
|
service.discount_value > 0 &&
|
|
@@ -71,7 +88,7 @@ function toDiscountLabel(discount, currencySign) {
|
|
|
71
88
|
}
|
|
72
89
|
// Groups services into one card per discount category. Remate/wow-deal is always the first card.
|
|
73
90
|
export function deriveDiscountCards(services = [], wowDealData, currencySign = "$") {
|
|
74
|
-
var _a, _b;
|
|
91
|
+
var _a, _b, _c, _d;
|
|
75
92
|
const cards = new Map();
|
|
76
93
|
const wowServices = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services;
|
|
77
94
|
const hasWowDeal = !!wowDealData &&
|
|
@@ -96,7 +113,34 @@ export function deriveDiscountCards(services = [], wowDealData, currencySign = "
|
|
|
96
113
|
const discount = getServiceDiscount(service);
|
|
97
114
|
if (!discount)
|
|
98
115
|
continue;
|
|
99
|
-
const
|
|
116
|
+
const roundedAmount = Math.round(discount.amount);
|
|
117
|
+
// Backend-driven cards: art and text come from the service; services with the
|
|
118
|
+
// same discount (and same campaign content) are grouped into one card.
|
|
119
|
+
const backendCampaign = discount.isDp ? null : getBackendCampaign(service);
|
|
120
|
+
if (backendCampaign) {
|
|
121
|
+
const { imageUrl, texts } = backendCampaign;
|
|
122
|
+
const key = `campaign_${discount.type}_${roundedAmount}_${slugify(`${texts.text1} ${texts.text2} ${texts.text3}`)}_${imageUrl}`;
|
|
123
|
+
const existing = cards.get(key);
|
|
124
|
+
if (existing) {
|
|
125
|
+
existing.matchedServices.push(service);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
cards.set(key, {
|
|
129
|
+
key,
|
|
130
|
+
subtitle: texts.text3,
|
|
131
|
+
campaignType: (_b = (_a = service.discounts) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.campaign_type,
|
|
132
|
+
discountType: discount.type,
|
|
133
|
+
amount: roundedAmount,
|
|
134
|
+
discountLabel: texts.text2,
|
|
135
|
+
matchedServices: [service],
|
|
136
|
+
isWowDeal: false,
|
|
137
|
+
campaignImageUrl: imageUrl,
|
|
138
|
+
campaignTexts: texts,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const campaignType = (_d = (_c = service.discounts) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.campaign_type;
|
|
100
144
|
const campaignLabel = campaignType
|
|
101
145
|
? CAMPAIGN_TYPE_LABELS[campaignType]
|
|
102
146
|
: undefined;
|
|
@@ -106,7 +150,6 @@ export function deriveDiscountCards(services = [], wowDealData, currencySign = "
|
|
|
106
150
|
? slugify(service.offer_text)
|
|
107
151
|
: DESCUENTOS_ESPECIALES_KEY;
|
|
108
152
|
const subtitle = campaignLabel || DESCUENTOS_ESPECIALES_LABEL;
|
|
109
|
-
const roundedAmount = Math.round(discount.amount);
|
|
110
153
|
const key = `${category}_${discount.type}_${roundedAmount}`;
|
|
111
154
|
const existing = cards.get(key);
|
|
112
155
|
if (existing) {
|
|
@@ -12,6 +12,12 @@ export interface DiscountBannerServiceItem {
|
|
|
12
12
|
discounts?: Array<{
|
|
13
13
|
new_ui_enabled?: boolean;
|
|
14
14
|
campaign_type?: string;
|
|
15
|
+
campaign_image_url?: string;
|
|
16
|
+
campaign_text?: {
|
|
17
|
+
text_1?: string;
|
|
18
|
+
text_2?: string;
|
|
19
|
+
text_3?: string;
|
|
20
|
+
};
|
|
15
21
|
}>;
|
|
16
22
|
[key: string]: any;
|
|
17
23
|
}
|
|
@@ -31,6 +37,12 @@ export interface DiscountBannerCard {
|
|
|
31
37
|
discountLabel: string;
|
|
32
38
|
matchedServices: DiscountBannerServiceItem[];
|
|
33
39
|
isWowDeal: boolean;
|
|
40
|
+
campaignImageUrl?: string;
|
|
41
|
+
campaignTexts?: {
|
|
42
|
+
text1: string;
|
|
43
|
+
text2: string;
|
|
44
|
+
text3: string;
|
|
45
|
+
};
|
|
34
46
|
}
|
|
35
47
|
export interface DiscountBannersProps {
|
|
36
48
|
services?: DiscountBannerServiceItem[];
|
|
@@ -497,14 +497,11 @@ export const PaymentCardMobile = (props) => {
|
|
|
497
497
|
? Math.round(Math.max(0, ((removeDiscountAtomValue
|
|
498
498
|
? 0
|
|
499
499
|
: (netFare !== null && netFare !== void 0 ? netFare : 0)) -
|
|
500
|
-
(walletMoney !== null && walletMoney !== void 0 ? walletMoney : 0)
|
|
501
|
-
(virtualMoney !== null && virtualMoney !== void 0 ? virtualMoney : 0)) *
|
|
500
|
+
(walletMoney !== null && walletMoney !== void 0 ? walletMoney : 0)) *
|
|
502
501
|
100)) / 100
|
|
503
502
|
: removeDiscountAtomValue
|
|
504
503
|
? 0
|
|
505
|
-
: Math.max(0, (netFare !== null && netFare !== void 0 ? netFare : 0) -
|
|
506
|
-
(walletMoney !== null && walletMoney !== void 0 ? walletMoney : 0) -
|
|
507
|
-
(virtualMoney !== null && virtualMoney !== void 0 ? virtualMoney : 0)), currencySign))))));
|
|
504
|
+
: Math.max(0, (netFare !== null && netFare !== void 0 ? netFare : 0) - (walletMoney !== null && walletMoney !== void 0 ? walletMoney : 0)), currencySign))))));
|
|
508
505
|
} }),
|
|
509
506
|
React.createElement("div", { style: {
|
|
510
507
|
display: "flex",
|
package/package.json
CHANGED
|
@@ -154,9 +154,11 @@ class DiscountBannersDesktop extends React.Component<
|
|
|
154
154
|
{cards.map((card, index) => {
|
|
155
155
|
const isActive = activeKey === card.key;
|
|
156
156
|
const isDimmed = activeKey != null && !isActive;
|
|
157
|
-
const backgroundImage =
|
|
158
|
-
?
|
|
159
|
-
:
|
|
157
|
+
const backgroundImage = card.campaignImageUrl
|
|
158
|
+
? card.campaignImageUrl
|
|
159
|
+
: usingDefaultArt
|
|
160
|
+
? defaultArt[index].image
|
|
161
|
+
: toImageUrl(backgroundImages[index % backgroundImages.length]);
|
|
160
162
|
const accentColor = accentColors.length
|
|
161
163
|
? accentColors[index % accentColors.length]
|
|
162
164
|
: usingDefaultArt
|
|
@@ -189,12 +191,12 @@ class DiscountBannersDesktop extends React.Component<
|
|
|
189
191
|
<span
|
|
190
192
|
className={`bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`}
|
|
191
193
|
style={
|
|
192
|
-
chipPillInArt
|
|
194
|
+
chipPillInArt || card.campaignImageUrl
|
|
193
195
|
? undefined
|
|
194
196
|
: { backgroundColor: "rgba(255,255,255,0.55)" }
|
|
195
197
|
}
|
|
196
198
|
>
|
|
197
|
-
{label}
|
|
199
|
+
{card.campaignTexts ? card.campaignTexts.text1 : label}
|
|
198
200
|
</span>
|
|
199
201
|
{showFlamePercent && (
|
|
200
202
|
<span
|
|
@@ -220,13 +222,17 @@ class DiscountBannersDesktop extends React.Component<
|
|
|
220
222
|
<span
|
|
221
223
|
className={`bold-text text-[#464647] ${sizeClasses.discount}`}
|
|
222
224
|
>
|
|
223
|
-
{card.
|
|
225
|
+
{card.campaignTexts
|
|
226
|
+
? card.campaignTexts.text2
|
|
227
|
+
: card.discountLabel}
|
|
224
228
|
</span>
|
|
225
229
|
<span
|
|
226
230
|
className={`bold-text ${sizeClasses.subtitle}`}
|
|
227
231
|
style={{ color: accentColor }}
|
|
228
232
|
>
|
|
229
|
-
{card.
|
|
233
|
+
{card.campaignTexts
|
|
234
|
+
? card.campaignTexts.text3
|
|
235
|
+
: card.subtitle}
|
|
230
236
|
</span>
|
|
231
237
|
</span>
|
|
232
238
|
</button>
|
|
@@ -148,9 +148,11 @@ class DiscountBannersMobile extends React.Component<
|
|
|
148
148
|
{cards.map((card, index) => {
|
|
149
149
|
const isActive = activeKey === card.key;
|
|
150
150
|
const isDimmed = activeKey != null && !isActive;
|
|
151
|
-
const backgroundImage =
|
|
152
|
-
?
|
|
153
|
-
:
|
|
151
|
+
const backgroundImage = card.campaignImageUrl
|
|
152
|
+
? card.campaignImageUrl
|
|
153
|
+
: usingDefaultArt
|
|
154
|
+
? defaultArt[index].image
|
|
155
|
+
: toImageUrl(backgroundImages[index % backgroundImages.length]);
|
|
154
156
|
const accentColor = accentColors.length
|
|
155
157
|
? accentColors[index % accentColors.length]
|
|
156
158
|
: usingDefaultArt
|
|
@@ -183,12 +185,12 @@ class DiscountBannersMobile extends React.Component<
|
|
|
183
185
|
<span
|
|
184
186
|
className={`bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`}
|
|
185
187
|
style={
|
|
186
|
-
chipPillInArt
|
|
188
|
+
chipPillInArt || card.campaignImageUrl
|
|
187
189
|
? undefined
|
|
188
190
|
: { backgroundColor: "rgba(255,255,255,0.55)" }
|
|
189
191
|
}
|
|
190
192
|
>
|
|
191
|
-
{label}
|
|
193
|
+
{card.campaignTexts ? card.campaignTexts.text1 : label}
|
|
192
194
|
</span>
|
|
193
195
|
{showFlamePercent && (
|
|
194
196
|
<span
|
|
@@ -214,13 +216,17 @@ class DiscountBannersMobile extends React.Component<
|
|
|
214
216
|
<span
|
|
215
217
|
className={`bold-text text-[#464647] ${sizeClasses.discount}`}
|
|
216
218
|
>
|
|
217
|
-
{card.
|
|
219
|
+
{card.campaignTexts
|
|
220
|
+
? card.campaignTexts.text2
|
|
221
|
+
: card.discountLabel}
|
|
218
222
|
</span>
|
|
219
223
|
<span
|
|
220
224
|
className={`bold-text ${sizeClasses.subtitle}`}
|
|
221
225
|
style={{ color: accentColor }}
|
|
222
226
|
>
|
|
223
|
-
{card.
|
|
227
|
+
{card.campaignTexts
|
|
228
|
+
? card.campaignTexts.text3
|
|
229
|
+
: card.subtitle}
|
|
224
230
|
</span>
|
|
225
231
|
</span>
|
|
226
232
|
</button>
|
|
@@ -10,7 +10,12 @@ export interface DiscountBannerServiceItem {
|
|
|
10
10
|
discount_type?: string;
|
|
11
11
|
discount_value?: number;
|
|
12
12
|
max_discount?: number;
|
|
13
|
-
discounts?: Array<{
|
|
13
|
+
discounts?: Array<{
|
|
14
|
+
new_ui_enabled?: boolean;
|
|
15
|
+
campaign_type?: string;
|
|
16
|
+
campaign_image_url?: string;
|
|
17
|
+
campaign_text?: { text_1?: string; text_2?: string; text_3?: string };
|
|
18
|
+
}>;
|
|
14
19
|
[key: string]: any;
|
|
15
20
|
}
|
|
16
21
|
|
|
@@ -31,6 +36,9 @@ export interface DiscountBannerCard {
|
|
|
31
36
|
discountLabel: string;
|
|
32
37
|
matchedServices: DiscountBannerServiceItem[];
|
|
33
38
|
isWowDeal: boolean;
|
|
39
|
+
// Present when the card's art and copy come from the backend
|
|
40
|
+
campaignImageUrl?: string;
|
|
41
|
+
campaignTexts?: { text1: string; text2: string; text3: string };
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
export interface DiscountBannersProps {
|
|
@@ -95,9 +95,9 @@ const DiscountBanners: React.FC<DiscountBannersProps> = ({
|
|
|
95
95
|
[services, wowDealData, currencySign],
|
|
96
96
|
);
|
|
97
97
|
|
|
98
|
-
const [internalSelectedKey, setInternalSelectedKey] = useState<
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
const [internalSelectedKey, setInternalSelectedKey] = useState<string | null>(
|
|
99
|
+
defaultSelectedKey,
|
|
100
|
+
);
|
|
101
101
|
|
|
102
102
|
const isControlled = selectedKey !== undefined;
|
|
103
103
|
const activeKey = isControlled ? selectedKey : internalSelectedKey;
|
|
@@ -130,9 +130,11 @@ const DiscountBanners: React.FC<DiscountBannersProps> = ({
|
|
|
130
130
|
{cards.map((card, index) => {
|
|
131
131
|
const isActive = activeKey === card.key;
|
|
132
132
|
const isDimmed = activeKey != null && !isActive;
|
|
133
|
-
const backgroundImage =
|
|
134
|
-
?
|
|
135
|
-
:
|
|
133
|
+
const backgroundImage = card.campaignImageUrl
|
|
134
|
+
? card.campaignImageUrl
|
|
135
|
+
: usingDefaultArt
|
|
136
|
+
? defaultArt[index].image
|
|
137
|
+
: toImageUrl(backgroundImages[index % backgroundImages.length]);
|
|
136
138
|
const accentColor = accentColors.length
|
|
137
139
|
? accentColors[index % accentColors.length]
|
|
138
140
|
: usingDefaultArt
|
|
@@ -165,12 +167,12 @@ const DiscountBanners: React.FC<DiscountBannersProps> = ({
|
|
|
165
167
|
<span
|
|
166
168
|
className={`bold-text flex-shrink-0 rounded-full text-center text-[#464647] overflow-hidden whitespace-nowrap ${sizeClasses.chip}`}
|
|
167
169
|
style={
|
|
168
|
-
chipPillInArt
|
|
170
|
+
chipPillInArt || card.campaignImageUrl
|
|
169
171
|
? undefined
|
|
170
172
|
: { backgroundColor: "rgba(255,255,255,0.55)" }
|
|
171
173
|
}
|
|
172
174
|
>
|
|
173
|
-
{label}
|
|
175
|
+
{card.campaignTexts ? card.campaignTexts.text1 : label}
|
|
174
176
|
</span>
|
|
175
177
|
{showFlamePercent && (
|
|
176
178
|
<span
|
|
@@ -42,6 +42,30 @@ function slugify(text: string): string {
|
|
|
42
42
|
interface ServiceDiscount {
|
|
43
43
|
type: "percentage" | "fixed";
|
|
44
44
|
amount: number;
|
|
45
|
+
isDp?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface BackendCampaign {
|
|
49
|
+
imageUrl: string;
|
|
50
|
+
texts: { text1: string; text2: string; text3: string };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Backend-driven campaign art and copy (campaign_image_url + campaign_text.text_1..3)
|
|
54
|
+
function getBackendCampaign(
|
|
55
|
+
service: DiscountBannerServiceItem,
|
|
56
|
+
): BackendCampaign | null {
|
|
57
|
+
const campaign = service?.discounts?.[0];
|
|
58
|
+
const imageUrl = campaign?.campaign_image_url;
|
|
59
|
+
const text = campaign?.campaign_text;
|
|
60
|
+
if (!imageUrl || !text) return null;
|
|
61
|
+
return {
|
|
62
|
+
imageUrl,
|
|
63
|
+
texts: {
|
|
64
|
+
text1: text.text_1 ?? "",
|
|
65
|
+
text2: text.text_2 ?? "",
|
|
66
|
+
text3: text.text_3 ?? "",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
// Dynamic-pricing discounts store one percent per seat type; the best (lowest) one is what's advertised elsewhere in this library
|
|
@@ -57,7 +81,8 @@ function getServiceDiscount(
|
|
|
57
81
|
service: DiscountBannerServiceItem,
|
|
58
82
|
): ServiceDiscount | null {
|
|
59
83
|
const dpPercent = getDpPercent(service);
|
|
60
|
-
if (dpPercent > 0)
|
|
84
|
+
if (dpPercent > 0)
|
|
85
|
+
return { type: "percentage", amount: dpPercent, isDp: true };
|
|
61
86
|
|
|
62
87
|
if (
|
|
63
88
|
service?.discount_type === "percentage" &&
|
|
@@ -131,6 +156,36 @@ export function deriveDiscountCards(
|
|
|
131
156
|
const discount = getServiceDiscount(service);
|
|
132
157
|
if (!discount) continue;
|
|
133
158
|
|
|
159
|
+
const roundedAmount = Math.round(discount.amount);
|
|
160
|
+
|
|
161
|
+
// Backend-driven cards: art and text come from the service; services with the
|
|
162
|
+
// same discount (and same campaign content) are grouped into one card.
|
|
163
|
+
const backendCampaign = discount.isDp ? null : getBackendCampaign(service);
|
|
164
|
+
if (backendCampaign) {
|
|
165
|
+
const { imageUrl, texts } = backendCampaign;
|
|
166
|
+
const key = `campaign_${discount.type}_${roundedAmount}_${slugify(
|
|
167
|
+
`${texts.text1} ${texts.text2} ${texts.text3}`,
|
|
168
|
+
)}_${imageUrl}`;
|
|
169
|
+
const existing = cards.get(key);
|
|
170
|
+
if (existing) {
|
|
171
|
+
existing.matchedServices.push(service);
|
|
172
|
+
} else {
|
|
173
|
+
cards.set(key, {
|
|
174
|
+
key,
|
|
175
|
+
subtitle: texts.text3,
|
|
176
|
+
campaignType: service.discounts?.[0]?.campaign_type,
|
|
177
|
+
discountType: discount.type,
|
|
178
|
+
amount: roundedAmount,
|
|
179
|
+
discountLabel: texts.text2,
|
|
180
|
+
matchedServices: [service],
|
|
181
|
+
isWowDeal: false,
|
|
182
|
+
campaignImageUrl: imageUrl,
|
|
183
|
+
campaignTexts: texts,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
134
189
|
const campaignType = service.discounts?.[0]?.campaign_type;
|
|
135
190
|
const campaignLabel = campaignType
|
|
136
191
|
? CAMPAIGN_TYPE_LABELS[campaignType]
|
|
@@ -142,7 +197,6 @@ export function deriveDiscountCards(
|
|
|
142
197
|
? slugify(service.offer_text)
|
|
143
198
|
: DESCUENTOS_ESPECIALES_KEY;
|
|
144
199
|
const subtitle = campaignLabel || DESCUENTOS_ESPECIALES_LABEL;
|
|
145
|
-
const roundedAmount = Math.round(discount.amount);
|
|
146
200
|
const key = `${category}_${discount.type}_${roundedAmount}`;
|
|
147
201
|
|
|
148
202
|
const existing = cards.get(key);
|
|
@@ -10,7 +10,12 @@ export interface DiscountBannerServiceItem {
|
|
|
10
10
|
discount_type?: string;
|
|
11
11
|
discount_value?: number;
|
|
12
12
|
max_discount?: number;
|
|
13
|
-
discounts?: Array<{
|
|
13
|
+
discounts?: Array<{
|
|
14
|
+
new_ui_enabled?: boolean;
|
|
15
|
+
campaign_type?: string;
|
|
16
|
+
campaign_image_url?: string;
|
|
17
|
+
campaign_text?: { text_1?: string; text_2?: string; text_3?: string };
|
|
18
|
+
}>;
|
|
14
19
|
[key: string]: any;
|
|
15
20
|
}
|
|
16
21
|
|
|
@@ -31,6 +36,9 @@ export interface DiscountBannerCard {
|
|
|
31
36
|
discountLabel: string;
|
|
32
37
|
matchedServices: DiscountBannerServiceItem[];
|
|
33
38
|
isWowDeal: boolean;
|
|
39
|
+
// Present when the card's art and copy come from the backend
|
|
40
|
+
campaignImageUrl?: string;
|
|
41
|
+
campaignTexts?: { text1: string; text2: string; text3: string };
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
export interface DiscountBannersProps {
|
|
@@ -969,19 +969,13 @@ export const PaymentCardMobile: React.FC<PaymentSideBarProps> = (props) => {
|
|
|
969
969
|
((removeDiscountAtomValue
|
|
970
970
|
? 0
|
|
971
971
|
: (netFare ?? 0)) -
|
|
972
|
-
(walletMoney ?? 0)
|
|
973
|
-
(virtualMoney ?? 0)) *
|
|
972
|
+
(walletMoney ?? 0)) *
|
|
974
973
|
100,
|
|
975
974
|
),
|
|
976
975
|
) / 100
|
|
977
976
|
: removeDiscountAtomValue
|
|
978
977
|
? 0
|
|
979
|
-
: Math.max(
|
|
980
|
-
0,
|
|
981
|
-
(netFare ?? 0) -
|
|
982
|
-
(walletMoney ?? 0) -
|
|
983
|
-
(virtualMoney ?? 0),
|
|
984
|
-
),
|
|
978
|
+
: Math.max(0, (netFare ?? 0) - (walletMoney ?? 0)),
|
|
985
979
|
currencySign,
|
|
986
980
|
)}
|
|
987
981
|
</span>
|