mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
fix: stack promo offer discounts with promo group discounts
Redesign applyPromoDiscount to apply promo offer on top of promo group prices instead of treating them as mutually exclusive. Calculate combined discount percent from the deepest original price. Show promo banner in confirm step regardless of existing server discounts.
This commit is contained in:
@@ -89,24 +89,47 @@ export default function Subscription() {
|
|||||||
// Helper to format price from kopeks
|
// Helper to format price from kopeks
|
||||||
const formatPrice = (kopeks: number) => `${formatAmount(kopeks / 100)} ${currencySymbol}`;
|
const formatPrice = (kopeks: number) => `${formatAmount(kopeks / 100)} ${currencySymbol}`;
|
||||||
|
|
||||||
// Helper to apply promo discount to a price
|
// Helper to apply promo discount to a price, stacking with existing promo group discount
|
||||||
const applyPromoDiscount = (
|
const applyPromoDiscount = (
|
||||||
priceKopeks: number,
|
priceKopeks: number,
|
||||||
hasExistingDiscount: boolean = false,
|
existingOriginalPrice?: number | null,
|
||||||
): {
|
): {
|
||||||
price: number;
|
price: number;
|
||||||
original: number | null;
|
original: number | null;
|
||||||
percent: number | null;
|
percent: number | null;
|
||||||
|
isPromoGroup: boolean;
|
||||||
} => {
|
} => {
|
||||||
// Only apply promo discount if no existing discount (from promo group) and we have an active promo discount
|
const hasExisting = (existingOriginalPrice ?? 0) > priceKopeks;
|
||||||
if (!activeDiscount?.is_active || !activeDiscount.discount_percent || hasExistingDiscount) {
|
const hasPromo = !!activeDiscount?.is_active && !!activeDiscount.discount_percent;
|
||||||
return { price: priceKopeks, original: null, percent: null };
|
|
||||||
|
if (!hasExisting && !hasPromo) {
|
||||||
|
return { price: priceKopeks, original: null, percent: null, isPromoGroup: false };
|
||||||
}
|
}
|
||||||
const discountedPrice = Math.round(priceKopeks * (1 - activeDiscount.discount_percent / 100));
|
|
||||||
|
let finalPrice = priceKopeks;
|
||||||
|
if (hasPromo) {
|
||||||
|
finalPrice = Math.round(priceKopeks * (1 - activeDiscount!.discount_percent! / 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasExisting) {
|
||||||
|
// Promo group discount exists — calculate combined percent from deepest original
|
||||||
|
const combinedPercent = hasPromo
|
||||||
|
? Math.round((1 - finalPrice / existingOriginalPrice!) * 100)
|
||||||
|
: Math.round((1 - priceKopeks / existingOriginalPrice!) * 100);
|
||||||
|
return {
|
||||||
|
price: finalPrice,
|
||||||
|
original: existingOriginalPrice!,
|
||||||
|
percent: combinedPercent,
|
||||||
|
isPromoGroup: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only promo offer discount (no promo group)
|
||||||
return {
|
return {
|
||||||
price: discountedPrice,
|
price: finalPrice,
|
||||||
original: priceKopeks,
|
original: priceKopeks,
|
||||||
percent: activeDiscount.discount_percent,
|
percent: activeDiscount!.discount_percent!,
|
||||||
|
isPromoGroup: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2416,41 +2439,34 @@ export default function Subscription() {
|
|||||||
const dailyPrice =
|
const dailyPrice =
|
||||||
tariff.daily_price_kopeks ?? tariff.price_per_day_kopeks ?? 0;
|
tariff.daily_price_kopeks ?? tariff.price_per_day_kopeks ?? 0;
|
||||||
const originalDailyPrice = tariff.original_daily_price_kopeks || 0;
|
const originalDailyPrice = tariff.original_daily_price_kopeks || 0;
|
||||||
const hasExistingDailyDiscount = originalDailyPrice > dailyPrice;
|
|
||||||
if (dailyPrice > 0) {
|
if (dailyPrice > 0) {
|
||||||
// Apply promo discount if no existing discount
|
|
||||||
const promoDaily = applyPromoDiscount(
|
const promoDaily = applyPromoDiscount(
|
||||||
dailyPrice,
|
dailyPrice,
|
||||||
hasExistingDailyDiscount,
|
originalDailyPrice > dailyPrice ? originalDailyPrice : undefined,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<span className="font-medium text-accent-400">
|
<span className="font-medium text-accent-400">
|
||||||
{formatPrice(promoDaily.price)}
|
{formatPrice(promoDaily.price)}
|
||||||
</span>
|
</span>
|
||||||
{/* Show original price from promo group or promo offer */}
|
{promoDaily.original &&
|
||||||
{(hasExistingDailyDiscount || promoDaily.original) && (
|
promoDaily.original > promoDaily.price && (
|
||||||
<span className="text-xs text-dark-500 line-through">
|
<span className="text-xs text-dark-500 line-through">
|
||||||
{formatPrice(
|
{formatPrice(promoDaily.original)}
|
||||||
hasExistingDailyDiscount
|
</span>
|
||||||
? originalDailyPrice
|
)}
|
||||||
: promoDaily.original!,
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span>{t('subscription.tariff.perDay')}</span>
|
<span>{t('subscription.tariff.perDay')}</span>
|
||||||
{/* Show discount badge */}
|
{/* Show discount badge */}
|
||||||
{tariff.daily_discount_percent &&
|
{promoDaily.percent && promoDaily.percent > 0 && (
|
||||||
tariff.daily_discount_percent > 0 ? (
|
<span
|
||||||
<span className="rounded bg-success-500/20 px-1.5 py-0.5 text-xs text-success-400">
|
className={`rounded px-1.5 py-0.5 text-xs ${
|
||||||
-{tariff.daily_discount_percent}%
|
promoDaily.isPromoGroup
|
||||||
|
? 'bg-success-500/20 text-success-400'
|
||||||
|
: 'bg-orange-500/20 text-orange-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
-{promoDaily.percent}%
|
||||||
</span>
|
</span>
|
||||||
) : (
|
|
||||||
promoDaily.percent && (
|
|
||||||
<span className="rounded bg-orange-500/20 px-1.5 py-0.5 text-xs text-orange-400">
|
|
||||||
-{promoDaily.percent}%
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -2458,14 +2474,9 @@ export default function Subscription() {
|
|||||||
// Period-based price
|
// Period-based price
|
||||||
if (tariff.periods.length > 0) {
|
if (tariff.periods.length > 0) {
|
||||||
const firstPeriod = tariff.periods[0];
|
const firstPeriod = tariff.periods[0];
|
||||||
const hasExistingDiscount = !!(
|
|
||||||
firstPeriod?.original_price_kopeks &&
|
|
||||||
firstPeriod.original_price_kopeks > firstPeriod.price_kopeks
|
|
||||||
);
|
|
||||||
// Apply promo discount if no existing discount
|
|
||||||
const promoPeriod = applyPromoDiscount(
|
const promoPeriod = applyPromoDiscount(
|
||||||
firstPeriod?.price_kopeks || 0,
|
firstPeriod?.price_kopeks || 0,
|
||||||
hasExistingDiscount,
|
firstPeriod?.original_price_kopeks,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<span className="flex flex-wrap items-center gap-2">
|
<span className="flex flex-wrap items-center gap-2">
|
||||||
@@ -2473,27 +2484,22 @@ export default function Subscription() {
|
|||||||
<span className="font-medium text-accent-400">
|
<span className="font-medium text-accent-400">
|
||||||
{formatPrice(promoPeriod.price)}
|
{formatPrice(promoPeriod.price)}
|
||||||
</span>
|
</span>
|
||||||
{/* Show original price from promo group or promo offer */}
|
{promoPeriod.original &&
|
||||||
{(hasExistingDiscount || promoPeriod.original) && (
|
promoPeriod.original > promoPeriod.price && (
|
||||||
<span className="text-xs text-dark-500 line-through">
|
<span className="text-xs text-dark-500 line-through">
|
||||||
{formatPrice(
|
{formatPrice(promoPeriod.original)}
|
||||||
hasExistingDiscount
|
|
||||||
? firstPeriod.original_price_kopeks!
|
|
||||||
: promoPeriod.original!,
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{/* Show discount badge */}
|
|
||||||
{hasExistingDiscount && firstPeriod.discount_percent ? (
|
|
||||||
<span className="rounded bg-success-500/20 px-1.5 py-0.5 text-xs text-success-400">
|
|
||||||
-{firstPeriod.discount_percent}%
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
promoPeriod.percent && (
|
|
||||||
<span className="rounded bg-orange-500/20 px-1.5 py-0.5 text-xs text-orange-400">
|
|
||||||
-{promoPeriod.percent}%
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)}
|
||||||
|
{promoPeriod.percent && promoPeriod.percent > 0 && (
|
||||||
|
<span
|
||||||
|
className={`rounded px-1.5 py-0.5 text-xs ${
|
||||||
|
promoPeriod.isPromoGroup
|
||||||
|
? 'bg-success-500/20 text-success-400'
|
||||||
|
: 'bg-orange-500/20 text-orange-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
-{promoPeriod.percent}%
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -2702,24 +2708,17 @@ export default function Subscription() {
|
|||||||
{selectedTariff.periods.length > 0 && !useCustomDays && (
|
{selectedTariff.periods.length > 0 && !useCustomDays && (
|
||||||
<div className="mb-4 grid grid-cols-2 gap-3 sm:grid-cols-3">
|
<div className="mb-4 grid grid-cols-2 gap-3 sm:grid-cols-3">
|
||||||
{selectedTariff.periods.map((period) => {
|
{selectedTariff.periods.map((period) => {
|
||||||
const hasExistingDiscount = !!(
|
|
||||||
period.original_price_kopeks &&
|
|
||||||
period.original_price_kopeks > period.price_kopeks
|
|
||||||
);
|
|
||||||
const promoPeriod = applyPromoDiscount(
|
const promoPeriod = applyPromoDiscount(
|
||||||
period.price_kopeks,
|
period.price_kopeks,
|
||||||
hasExistingDiscount,
|
period.original_price_kopeks,
|
||||||
);
|
);
|
||||||
const displayDiscount = hasExistingDiscount
|
const displayDiscount = promoPeriod.percent;
|
||||||
? period.discount_percent
|
const displayOriginal = promoPeriod.original;
|
||||||
: promoPeriod.percent;
|
|
||||||
const displayOriginal = hasExistingDiscount
|
|
||||||
? period.original_price_kopeks
|
|
||||||
: promoPeriod.original;
|
|
||||||
const displayPrice = promoPeriod.price;
|
const displayPrice = promoPeriod.price;
|
||||||
const displayPerMonth = hasExistingDiscount
|
const displayPerMonth =
|
||||||
? period.price_per_month_kopeks
|
displayPrice !== period.price_kopeks
|
||||||
: Math.round(promoPeriod.price / (period.days / 30));
|
? Math.round(displayPrice / Math.max(1, period.days / 30))
|
||||||
|
: period.price_per_month_kopeks;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -2737,7 +2736,7 @@ export default function Subscription() {
|
|||||||
{displayDiscount && displayDiscount > 0 && (
|
{displayDiscount && displayDiscount > 0 && (
|
||||||
<div
|
<div
|
||||||
className={`absolute -right-2 -top-2 rounded-full px-2 py-0.5 text-xs font-medium text-white ${
|
className={`absolute -right-2 -top-2 rounded-full px-2 py-0.5 text-xs font-medium text-white ${
|
||||||
hasExistingDiscount ? 'bg-success-500' : 'bg-orange-500'
|
promoPeriod.isPromoGroup ? 'bg-success-500' : 'bg-orange-500'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
-{displayDiscount}%
|
-{displayDiscount}%
|
||||||
@@ -2821,14 +2820,15 @@ export default function Subscription() {
|
|||||||
{(() => {
|
{(() => {
|
||||||
const basePrice =
|
const basePrice =
|
||||||
customDays * (selectedTariff.price_per_day_kopeks ?? 0);
|
customDays * (selectedTariff.price_per_day_kopeks ?? 0);
|
||||||
const hasExistingDiscount = !!(
|
const existingOriginal =
|
||||||
selectedTariff.original_price_per_day_kopeks &&
|
selectedTariff.original_price_per_day_kopeks &&
|
||||||
selectedTariff.original_price_per_day_kopeks >
|
selectedTariff.original_price_per_day_kopeks >
|
||||||
(selectedTariff.price_per_day_kopeks ?? 0)
|
(selectedTariff.price_per_day_kopeks ?? 0)
|
||||||
);
|
? customDays * selectedTariff.original_price_per_day_kopeks
|
||||||
|
: undefined;
|
||||||
const promoCustom = applyPromoDiscount(
|
const promoCustom = applyPromoDiscount(
|
||||||
basePrice,
|
basePrice,
|
||||||
hasExistingDiscount,
|
existingOriginal,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
@@ -2841,16 +2841,23 @@ export default function Subscription() {
|
|||||||
<span className="font-medium text-accent-400">
|
<span className="font-medium text-accent-400">
|
||||||
{formatPrice(promoCustom.price)}
|
{formatPrice(promoCustom.price)}
|
||||||
</span>
|
</span>
|
||||||
{promoCustom.original && (
|
{promoCustom.original &&
|
||||||
<>
|
promoCustom.original > promoCustom.price && (
|
||||||
<span className="text-xs text-dark-500 line-through">
|
<>
|
||||||
{formatPrice(promoCustom.original)}
|
<span className="text-xs text-dark-500 line-through">
|
||||||
</span>
|
{formatPrice(promoCustom.original)}
|
||||||
<span className="rounded bg-orange-500/20 px-1.5 py-0.5 text-xs text-orange-400">
|
</span>
|
||||||
-{promoCustom.percent}%
|
<span
|
||||||
</span>
|
className={`rounded px-1.5 py-0.5 text-xs ${
|
||||||
</>
|
promoCustom.isPromoGroup
|
||||||
)}
|
? 'bg-success-500/20 text-success-400'
|
||||||
|
: 'bg-orange-500/20 text-orange-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
-{promoCustom.percent}%
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -2956,14 +2963,20 @@ export default function Subscription() {
|
|||||||
const basePeriodPrice = useCustomDays
|
const basePeriodPrice = useCustomDays
|
||||||
? customDays * (selectedTariff.price_per_day_kopeks ?? 0)
|
? customDays * (selectedTariff.price_per_day_kopeks ?? 0)
|
||||||
: selectedTariffPeriod?.price_kopeks || 0;
|
: selectedTariffPeriod?.price_kopeks || 0;
|
||||||
const hasExistingPeriodDiscount =
|
const existingPeriodOriginal = useCustomDays
|
||||||
!useCustomDays && selectedTariffPeriod?.original_price_kopeks
|
? selectedTariff.original_price_per_day_kopeks &&
|
||||||
? selectedTariffPeriod.original_price_kopeks >
|
selectedTariff.original_price_per_day_kopeks >
|
||||||
selectedTariffPeriod.price_kopeks
|
(selectedTariff.price_per_day_kopeks ?? 0)
|
||||||
: false;
|
? customDays * selectedTariff.original_price_per_day_kopeks
|
||||||
|
: undefined
|
||||||
|
: selectedTariffPeriod?.original_price_kopeks &&
|
||||||
|
selectedTariffPeriod.original_price_kopeks >
|
||||||
|
selectedTariffPeriod.price_kopeks
|
||||||
|
? selectedTariffPeriod.original_price_kopeks
|
||||||
|
: undefined;
|
||||||
const promoPeriod = applyPromoDiscount(
|
const promoPeriod = applyPromoDiscount(
|
||||||
basePeriodPrice,
|
basePeriodPrice,
|
||||||
hasExistingPeriodDiscount,
|
existingPeriodOriginal,
|
||||||
);
|
);
|
||||||
|
|
||||||
const trafficPrice =
|
const trafficPrice =
|
||||||
@@ -2988,11 +3001,12 @@ export default function Subscription() {
|
|||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span>{formatPrice(promoPeriod.price)}</span>
|
<span>{formatPrice(promoPeriod.price)}</span>
|
||||||
{promoPeriod.original && (
|
{promoPeriod.original &&
|
||||||
<span className="text-xs text-dark-500 line-through">
|
promoPeriod.original > promoPeriod.price && (
|
||||||
{formatPrice(promoPeriod.original)}
|
<span className="text-xs text-dark-500 line-through">
|
||||||
</span>
|
{formatPrice(promoPeriod.original)}
|
||||||
)}
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -3035,16 +3049,12 @@ export default function Subscription() {
|
|||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span>{formatPrice(promoPeriod.price)}</span>
|
<span>{formatPrice(promoPeriod.price)}</span>
|
||||||
{(hasExistingPeriodDiscount ||
|
{promoPeriod.original &&
|
||||||
promoPeriod.original) && (
|
promoPeriod.original > promoPeriod.price && (
|
||||||
<span className="text-xs text-dark-500 line-through">
|
<span className="text-xs text-dark-500 line-through">
|
||||||
{formatPrice(
|
{formatPrice(promoPeriod.original)}
|
||||||
hasExistingPeriodDiscount
|
</span>
|
||||||
? selectedTariffPeriod.original_price_kopeks!
|
)}
|
||||||
: promoPeriod.original!,
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -3177,19 +3187,10 @@ export default function Subscription() {
|
|||||||
{currentStep === 'period' && classicOptions && (
|
{currentStep === 'period' && classicOptions && (
|
||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
||||||
{classicOptions.periods.map((period) => {
|
{classicOptions.periods.map((period) => {
|
||||||
const hasExistingDiscount = !!(
|
|
||||||
period.discount_percent && period.discount_percent > 0
|
|
||||||
);
|
|
||||||
const promoPeriod = applyPromoDiscount(
|
const promoPeriod = applyPromoDiscount(
|
||||||
period.price_kopeks,
|
period.price_kopeks,
|
||||||
hasExistingDiscount,
|
period.original_price_kopeks,
|
||||||
);
|
);
|
||||||
const displayDiscount = hasExistingDiscount
|
|
||||||
? period.discount_percent
|
|
||||||
: promoPeriod.percent;
|
|
||||||
const displayOriginal = hasExistingDiscount
|
|
||||||
? period.original_price_kopeks
|
|
||||||
: promoPeriod.original;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -3219,13 +3220,13 @@ export default function Subscription() {
|
|||||||
: ''
|
: ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{displayDiscount && displayDiscount > 0 && (
|
{promoPeriod.percent && promoPeriod.percent > 0 && (
|
||||||
<div
|
<div
|
||||||
className={`absolute right-2 top-2 z-10 rounded-full px-2 py-0.5 text-xs font-medium text-white shadow-sm ${
|
className={`absolute right-2 top-2 z-10 rounded-full px-2 py-0.5 text-xs font-medium text-white shadow-sm ${
|
||||||
hasExistingDiscount ? 'bg-success-500' : 'bg-orange-500'
|
promoPeriod.isPromoGroup ? 'bg-success-500' : 'bg-orange-500'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
-{displayDiscount}%
|
-{promoPeriod.percent}%
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-lg font-semibold text-dark-100">{period.label}</div>
|
<div className="text-lg font-semibold text-dark-100">{period.label}</div>
|
||||||
@@ -3233,9 +3234,9 @@ export default function Subscription() {
|
|||||||
<span className="font-medium text-accent-400">
|
<span className="font-medium text-accent-400">
|
||||||
{formatPrice(promoPeriod.price)}
|
{formatPrice(promoPeriod.price)}
|
||||||
</span>
|
</span>
|
||||||
{displayOriginal && displayOriginal > promoPeriod.price && (
|
{promoPeriod.original && promoPeriod.original > promoPeriod.price && (
|
||||||
<span className="text-sm text-dark-500 line-through">
|
<span className="text-sm text-dark-500 line-through">
|
||||||
{formatPrice(displayOriginal)}
|
{formatPrice(promoPeriod.original)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -3249,12 +3250,9 @@ export default function Subscription() {
|
|||||||
{currentStep === 'traffic' && selectedPeriod?.traffic.options && (
|
{currentStep === 'traffic' && selectedPeriod?.traffic.options && (
|
||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||||
{selectedPeriod.traffic.options.map((option) => {
|
{selectedPeriod.traffic.options.map((option) => {
|
||||||
const hasExistingDiscount = !!(
|
|
||||||
option.discount_percent && option.discount_percent > 0
|
|
||||||
);
|
|
||||||
const promoTraffic = applyPromoDiscount(
|
const promoTraffic = applyPromoDiscount(
|
||||||
option.price_kopeks,
|
option.price_kopeks,
|
||||||
hasExistingDiscount,
|
option.original_price_kopeks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -3268,41 +3266,24 @@ export default function Subscription() {
|
|||||||
: ''
|
: ''
|
||||||
} ${!option.is_available ? 'cursor-not-allowed opacity-50' : ''}`}
|
} ${!option.is_available ? 'cursor-not-allowed opacity-50' : ''}`}
|
||||||
>
|
>
|
||||||
{(() => {
|
{promoTraffic.percent && promoTraffic.percent > 0 && (
|
||||||
const trafficDisplayDiscount = hasExistingDiscount
|
<div
|
||||||
? option.discount_percent
|
className={`absolute right-2 top-2 z-10 rounded-full px-2 py-0.5 text-xs font-medium text-white shadow-sm ${
|
||||||
: promoTraffic.percent;
|
promoTraffic.isPromoGroup ? 'bg-success-500' : 'bg-orange-500'
|
||||||
const trafficDisplayOriginal = hasExistingDiscount
|
}`}
|
||||||
? option.original_price_kopeks
|
>
|
||||||
: promoTraffic.original;
|
-{promoTraffic.percent}%
|
||||||
return (
|
</div>
|
||||||
<>
|
)}
|
||||||
{trafficDisplayDiscount && trafficDisplayDiscount > 0 && (
|
<div className="text-lg font-semibold text-dark-100">{option.label}</div>
|
||||||
<div
|
<div className="mt-1 flex flex-wrap items-center justify-center gap-x-2 gap-y-1">
|
||||||
className={`absolute right-2 top-2 z-10 rounded-full px-2 py-0.5 text-xs font-medium text-white shadow-sm ${
|
<span className="text-accent-400">{formatPrice(promoTraffic.price)}</span>
|
||||||
hasExistingDiscount ? 'bg-success-500' : 'bg-orange-500'
|
{promoTraffic.original && promoTraffic.original > promoTraffic.price && (
|
||||||
}`}
|
<span className="text-xs text-dark-500 line-through">
|
||||||
>
|
{formatPrice(promoTraffic.original)}
|
||||||
-{trafficDisplayDiscount}%
|
</span>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
<div className="text-lg font-semibold text-dark-100">
|
|
||||||
{option.label}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 flex flex-wrap items-center justify-center gap-x-2 gap-y-1">
|
|
||||||
<span className="text-accent-400">
|
|
||||||
{formatPrice(promoTraffic.price)}
|
|
||||||
</span>
|
|
||||||
{trafficDisplayOriginal &&
|
|
||||||
trafficDisplayOriginal > promoTraffic.price && (
|
|
||||||
<span className="text-xs text-dark-500 line-through">
|
|
||||||
{formatPrice(trafficDisplayOriginal)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -3322,12 +3303,9 @@ export default function Subscription() {
|
|||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((server) => {
|
.map((server) => {
|
||||||
const hasExistingDiscount = !!(
|
|
||||||
server.discount_percent && server.discount_percent > 0
|
|
||||||
);
|
|
||||||
const promoServer = applyPromoDiscount(
|
const promoServer = applyPromoDiscount(
|
||||||
server.price_kopeks,
|
server.price_kopeks,
|
||||||
hasExistingDiscount,
|
server.original_price_kopeks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -3343,20 +3321,15 @@ export default function Subscription() {
|
|||||||
: 'cursor-not-allowed border-dark-800/30 bg-dark-900/30 opacity-50'
|
: 'cursor-not-allowed border-dark-800/30 bg-dark-900/30 opacity-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{(() => {
|
{promoServer.percent && promoServer.percent > 0 ? (
|
||||||
const serverDisplayDiscount = hasExistingDiscount
|
<div
|
||||||
? server.discount_percent
|
className={`absolute right-2 top-2 z-10 rounded-full px-2 py-0.5 text-xs font-medium text-white shadow-sm ${
|
||||||
: promoServer.percent;
|
promoServer.isPromoGroup ? 'bg-success-500' : 'bg-orange-500'
|
||||||
return serverDisplayDiscount && serverDisplayDiscount > 0 ? (
|
}`}
|
||||||
<div
|
>
|
||||||
className={`absolute right-2 top-2 z-10 rounded-full px-2 py-0.5 text-xs font-medium text-white shadow-sm ${
|
-{promoServer.percent}%
|
||||||
hasExistingDiscount ? 'bg-success-500' : 'bg-orange-500'
|
</div>
|
||||||
}`}
|
) : null}
|
||||||
>
|
|
||||||
-{serverDisplayDiscount}%
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
})()}
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
className={`flex h-5 w-5 flex-shrink-0 items-center justify-center rounded border-2 ${
|
className={`flex h-5 w-5 flex-shrink-0 items-center justify-center rounded border-2 ${
|
||||||
@@ -3374,16 +3347,12 @@ export default function Subscription() {
|
|||||||
{formatPrice(promoServer.price)}
|
{formatPrice(promoServer.price)}
|
||||||
{t('subscription.perMonth')}
|
{t('subscription.perMonth')}
|
||||||
</span>
|
</span>
|
||||||
{(() => {
|
{promoServer.original &&
|
||||||
const serverOriginal = hasExistingDiscount
|
promoServer.original > promoServer.price ? (
|
||||||
? server.original_price_kopeks
|
<span className="text-xs text-dark-500 line-through">
|
||||||
: promoServer.original;
|
{formatPrice(promoServer.original)}
|
||||||
return serverOriginal && serverOriginal > promoServer.price ? (
|
</span>
|
||||||
<span className="text-xs text-dark-500 line-through">
|
) : null}
|
||||||
{formatPrice(serverOriginal)}
|
|
||||||
</span>
|
|
||||||
) : null;
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3448,28 +3417,26 @@ export default function Subscription() {
|
|||||||
) : preview ? (
|
) : preview ? (
|
||||||
<div className="space-y-4 rounded-xl bg-dark-800/50 p-5">
|
<div className="space-y-4 rounded-xl bg-dark-800/50 p-5">
|
||||||
{/* Active promo discount banner */}
|
{/* Active promo discount banner */}
|
||||||
{activeDiscount?.is_active &&
|
{activeDiscount?.is_active && activeDiscount.discount_percent && (
|
||||||
activeDiscount.discount_percent &&
|
<div className="flex items-center justify-center gap-2 rounded-lg border border-orange-500/30 bg-orange-500/10 p-3">
|
||||||
!preview.original_price_kopeks && (
|
<svg
|
||||||
<div className="flex items-center justify-center gap-2 rounded-lg border border-orange-500/30 bg-orange-500/10 p-3">
|
className="h-4 w-4 text-orange-400"
|
||||||
<svg
|
fill="none"
|
||||||
className="h-4 w-4 text-orange-400"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
strokeWidth={2}
|
||||||
stroke="currentColor"
|
>
|
||||||
strokeWidth={2}
|
<path
|
||||||
>
|
strokeLinecap="round"
|
||||||
<path
|
strokeLinejoin="round"
|
||||||
strokeLinecap="round"
|
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
|
||||||
strokeLinejoin="round"
|
/>
|
||||||
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
|
</svg>
|
||||||
/>
|
<span className="text-sm font-medium text-orange-400">
|
||||||
</svg>
|
{t('promo.discountApplied')} -{activeDiscount.discount_percent}%
|
||||||
<span className="text-sm font-medium text-orange-400">
|
</span>
|
||||||
{t('promo.discountApplied')} -{activeDiscount.discount_percent}%
|
</div>
|
||||||
</span>
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{preview.breakdown.map((item, idx) => (
|
{preview.breakdown.map((item, idx) => (
|
||||||
<div key={idx} className="flex justify-between text-dark-300">
|
<div key={idx} className="flex justify-between text-dark-300">
|
||||||
@@ -3479,16 +3446,10 @@ export default function Subscription() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
// Apply promo discount if not already applied by server
|
|
||||||
const hasServerDiscount = !!preview.original_price_kopeks;
|
|
||||||
const promoTotal = applyPromoDiscount(
|
const promoTotal = applyPromoDiscount(
|
||||||
preview.total_price_kopeks,
|
preview.total_price_kopeks,
|
||||||
hasServerDiscount,
|
preview.original_price_kopeks,
|
||||||
);
|
);
|
||||||
const displayTotal = promoTotal.price;
|
|
||||||
const displayOriginal = hasServerDiscount
|
|
||||||
? preview.original_price_kopeks
|
|
||||||
: promoTotal.original;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between border-t border-dark-700/50 pt-4">
|
<div className="flex items-center justify-between border-t border-dark-700/50 pt-4">
|
||||||
@@ -3497,11 +3458,11 @@ export default function Subscription() {
|
|||||||
</span>
|
</span>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-2xl font-bold text-accent-400">
|
<div className="text-2xl font-bold text-accent-400">
|
||||||
{formatPrice(displayTotal)}
|
{formatPrice(promoTotal.price)}
|
||||||
</div>
|
</div>
|
||||||
{displayOriginal && (
|
{promoTotal.original && promoTotal.original > promoTotal.price && (
|
||||||
<div className="text-sm text-dark-500 line-through">
|
<div className="text-sm text-dark-500 line-through">
|
||||||
{formatPrice(displayOriginal)}
|
{formatPrice(promoTotal.original)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user