mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
refactor(purchase): extract usePromoDiscount hook
Prerequisite for the SubscriptionPurchase god-page decomposition. The applyPromoDiscount helper + its active-discount useQuery were inlined in SubscriptionPurchase.tsx and called from 9 sites across 3 purchase flows (classic wizard, tariff picker, tariff form, switch-tariff sheet). Lifting both into src/hooks/usePromoDiscount gives every future extracted sub-component a single seam to call, without threading the function through props or re-fetching the discount in each child. SubscriptionPurchase.tsx: 2054 → 2009 lines (-45).
This commit is contained in:
73
src/hooks/usePromoDiscount.ts
Normal file
73
src/hooks/usePromoDiscount.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { promoApi } from '../api/promo';
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────
|
||||||
|
// usePromoDiscount
|
||||||
|
//
|
||||||
|
// Single source of truth for the active-discount query + the
|
||||||
|
// applyPromoDiscount helper. Extracted from SubscriptionPurchase.tsx
|
||||||
|
// so that every flow / sub-component (tariff picker, tariff purchase
|
||||||
|
// form, classic wizard, switch-tariff sheet) can call the same hook
|
||||||
|
// without re-fetching or threading a function through props.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// activeDiscount: the raw API value (or undefined while loading)
|
||||||
|
// applyPromoDiscount: combines the active discount with any
|
||||||
|
// pre-existing price reduction (promo-group pricing) and reports
|
||||||
|
// final price, original price, total percent off, and whether
|
||||||
|
// the existing reduction is a promo-group price.
|
||||||
|
// ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface PromoDiscountResult {
|
||||||
|
price: number;
|
||||||
|
original: number | null;
|
||||||
|
percent: number | null;
|
||||||
|
isPromoGroup: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePromoDiscount() {
|
||||||
|
const { data: activeDiscount } = useQuery({
|
||||||
|
queryKey: ['active-discount'],
|
||||||
|
queryFn: promoApi.getActiveDiscount,
|
||||||
|
staleTime: 30000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const applyPromoDiscount = useCallback(
|
||||||
|
(priceKopeks: number, existingOriginalPrice?: number | null): PromoDiscountResult => {
|
||||||
|
const hasExisting = (existingOriginalPrice ?? 0) > priceKopeks;
|
||||||
|
const hasPromo = !!activeDiscount?.is_active && !!activeDiscount.discount_percent;
|
||||||
|
|
||||||
|
if (!hasExisting && !hasPromo) {
|
||||||
|
return { price: priceKopeks, original: null, percent: null, isPromoGroup: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalPrice = priceKopeks;
|
||||||
|
if (hasPromo) {
|
||||||
|
finalPrice = Math.round(priceKopeks * (1 - activeDiscount!.discount_percent! / 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasExisting) {
|
||||||
|
const combinedPercent = hasPromo
|
||||||
|
? Math.round((1 - finalPrice / existingOriginalPrice!) * 100)
|
||||||
|
: Math.round((1 - priceKopeks / existingOriginalPrice!) * 100);
|
||||||
|
return {
|
||||||
|
price: finalPrice,
|
||||||
|
original: existingOriginalPrice!,
|
||||||
|
percent: combinedPercent,
|
||||||
|
isPromoGroup: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
price: finalPrice,
|
||||||
|
original: priceKopeks,
|
||||||
|
percent: activeDiscount!.discount_percent!,
|
||||||
|
isPromoGroup: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[activeDiscount],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { activeDiscount, applyPromoDiscount };
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useNavigate, useSearchParams } from 'react-router';
|
import { useNavigate, useSearchParams } from 'react-router';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { subscriptionApi } from '../api/subscription';
|
import { subscriptionApi } from '../api/subscription';
|
||||||
import { promoApi } from '../api/promo';
|
import { usePromoDiscount } from '../hooks/usePromoDiscount';
|
||||||
import { WebBackButton } from '../components/WebBackButton';
|
import { WebBackButton } from '../components/WebBackButton';
|
||||||
import { getGlassColors } from '../utils/glassTheme';
|
import { getGlassColors } from '../utils/glassTheme';
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
@@ -66,12 +66,8 @@ export default function SubscriptionPurchase() {
|
|||||||
refetchOnMount: 'always',
|
refetchOnMount: 'always',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Active promo discount
|
// Active promo discount + applyPromoDiscount helper (hook owns both)
|
||||||
const { data: activeDiscount } = useQuery({
|
const { activeDiscount, applyPromoDiscount } = usePromoDiscount();
|
||||||
queryKey: ['active-discount'],
|
|
||||||
queryFn: promoApi.getActiveDiscount,
|
|
||||||
staleTime: 30000,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sales mode detection
|
// Sales mode detection
|
||||||
const isTariffsMode = purchaseOptions?.sales_mode === 'tariffs';
|
const isTariffsMode = purchaseOptions?.sales_mode === 'tariffs';
|
||||||
@@ -87,47 +83,7 @@ export default function SubscriptionPurchase() {
|
|||||||
});
|
});
|
||||||
const isMultiTariff = multiSubData?.multi_tariff_enabled ?? false;
|
const isMultiTariff = multiSubData?.multi_tariff_enabled ?? false;
|
||||||
|
|
||||||
// Helper to apply promo discount
|
// (applyPromoDiscount moved into usePromoDiscount above)
|
||||||
const applyPromoDiscount = (
|
|
||||||
priceKopeks: number,
|
|
||||||
existingOriginalPrice?: number | null,
|
|
||||||
): {
|
|
||||||
price: number;
|
|
||||||
original: number | null;
|
|
||||||
percent: number | null;
|
|
||||||
isPromoGroup: boolean;
|
|
||||||
} => {
|
|
||||||
const hasExisting = (existingOriginalPrice ?? 0) > priceKopeks;
|
|
||||||
const hasPromo = !!activeDiscount?.is_active && !!activeDiscount.discount_percent;
|
|
||||||
|
|
||||||
if (!hasExisting && !hasPromo) {
|
|
||||||
return { price: priceKopeks, original: null, percent: null, isPromoGroup: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
let finalPrice = priceKopeks;
|
|
||||||
if (hasPromo) {
|
|
||||||
finalPrice = Math.round(priceKopeks * (1 - activeDiscount!.discount_percent! / 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasExisting) {
|
|
||||||
const combinedPercent = hasPromo
|
|
||||||
? Math.round((1 - finalPrice / existingOriginalPrice!) * 100)
|
|
||||||
: Math.round((1 - priceKopeks / existingOriginalPrice!) * 100);
|
|
||||||
return {
|
|
||||||
price: finalPrice,
|
|
||||||
original: existingOriginalPrice!,
|
|
||||||
percent: combinedPercent,
|
|
||||||
isPromoGroup: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
price: finalPrice,
|
|
||||||
original: priceKopeks,
|
|
||||||
percent: activeDiscount!.discount_percent!,
|
|
||||||
isPromoGroup: false,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Classic mode state
|
// Classic mode state
|
||||||
const [currentStep, setCurrentStep] = useState<PurchaseStep>('period');
|
const [currentStep, setCurrentStep] = useState<PurchaseStep>('period');
|
||||||
|
|||||||
Reference in New Issue
Block a user