From 10e2022035ccac4fc3ad80745f722e9738e5bb2b Mon Sep 17 00:00:00 2001 From: nflnkr <105123049+nflnkr@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:49:35 +0300 Subject: [PATCH] rework calcCart use swr for discounts datafetching remove discounts store refetch discount on promocode apply --- package.json | 2 +- src/api/price.ts | 49 ++++--- src/components/ProtectedLayout.tsx | 10 -- src/pages/Payment/Payment.tsx | 2 + src/pages/TariffConstructor/TariffItem.tsx | 7 +- src/pages/Tariffs/TariffsPage.tsx | 6 +- src/stores/customTariffs.ts | 5 +- src/stores/discounts.ts | 22 --- src/utils/calcCart/calcCart.test.ts | 2 +- src/utils/calcCart/calcCart.ts | 154 ++++++++------------ src/utils/calcCart/calcCustomTariffPrice.ts | 2 +- src/utils/calcCart/utils.ts | 119 +++++++++++++++ src/utils/calcTariffPrices.ts | 2 +- src/utils/hooks/useCart.ts | 14 +- src/utils/hooks/useDiscounts.ts | 38 ----- yarn.lock | 8 +- 16 files changed, 240 insertions(+), 202 deletions(-) delete mode 100644 src/stores/discounts.ts create mode 100644 src/utils/calcCart/utils.ts delete mode 100644 src/utils/hooks/useDiscounts.ts diff --git a/package.json b/package.json index 410b198..f309949 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", - "@frontend/kitui": "^1.0.70", + "@frontend/kitui": "^1.0.71", "@mui/icons-material": "^5.10.14", "@mui/material": "^5.10.14", "@popperjs/core": "^2.11.8", diff --git a/src/api/price.ts b/src/api/price.ts index ba4570c..cf626bc 100644 --- a/src/api/price.ts +++ b/src/api/price.ts @@ -1,24 +1,37 @@ -import { makeRequest } from "@frontend/kitui" +import { makeRequest } from "@frontend/kitui"; +import type { GetDiscountsResponse } from "@root/model/discount"; +import { parseAxiosError } from "@root/utils/parse-error"; +import { enqueueSnackbar } from "notistack"; +import useSWR from "swr"; -import { parseAxiosError } from "@root/utils/parse-error" -import type { GetDiscountsResponse } from "@root/model/discount" +const apiUrl = process.env.REACT_APP_DOMAIN + "/price"; -const apiUrl = process.env.REACT_APP_DOMAIN + "/price" +export async function getDiscounts() { + try { + const discountsResponse = await makeRequest({ + url: apiUrl + "/discounts", + method: "get", + useToken: true, + }); -export async function getDiscounts(signal: AbortSignal | undefined): Promise<[GetDiscountsResponse | null, string?]> { - try { - const discountsResponse = await makeRequest({ - url: apiUrl + "/discounts", - method: "get", - useToken: true, - signal, - }) + return discountsResponse.Discounts; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); - return [discountsResponse] - } catch (nativeError) { - const [error] = parseAxiosError(nativeError) - - return [null, `Ошибка получения списка скидок. ${error}`] - } + throw new Error(`Ошибка получения списка скидок. ${error}`); + } +} + +export function useDiscounts() { + const { data } = useSWR("discounts", getDiscounts, { + keepPreviousData: true, + onError: (error) => { + if (!(error instanceof Error)) return; + + enqueueSnackbar(error.message, { variant: "error" }); + } + }); + + return data; } diff --git a/src/components/ProtectedLayout.tsx b/src/components/ProtectedLayout.tsx index 271de1b..fc2b353 100644 --- a/src/components/ProtectedLayout.tsx +++ b/src/components/ProtectedLayout.tsx @@ -19,8 +19,6 @@ import { enqueueSnackbar } from "notistack"; import { updateTariffs } from "@root/stores/tariffs"; import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs"; import { setCustomTariffs } from "@root/stores/customTariffs"; -import { useDiscounts } from "@root/utils/hooks/useDiscounts"; -import { setDiscounts } from "@root/stores/discounts"; import { setPrivileges } from "@root/stores/privileges"; import { useHistoryData } from "@root/utils/hooks/useHistoryData"; import { useSSETab } from "@root/utils/hooks/useSSETab"; @@ -79,14 +77,6 @@ export default function ProtectedLayout() { }, }); - useDiscounts({ - onNewDiscounts: setDiscounts, - onError: (error) => { - const message = getMessageFromFetchError(error); - if (message) enqueueSnackbar(message); - }, - }); - usePrivilegeFetcher({ onSuccess: setPrivileges, onError: (error) => { diff --git a/src/pages/Payment/Payment.tsx b/src/pages/Payment/Payment.tsx index 2c03b1d..dca1848 100644 --- a/src/pages/Payment/Payment.tsx +++ b/src/pages/Payment/Payment.tsx @@ -29,6 +29,7 @@ import CollapsiblePromocodeField from "./CollapsiblePromocodeField"; import PaymentMethodCard from "./PaymentMethodCard"; import { SorryModal } from "./SorryModal"; import { WarnModal } from "./WarnModal"; +import { mutate } from "swr"; type PaymentMethod = { label: string; @@ -171,6 +172,7 @@ export default function Payment() { activatePromocode(promocodeField).then(response => { enqueueSnackbar(response); + mutate("discounts"); }).catch(error => { enqueueSnackbar(error.message); }); diff --git a/src/pages/TariffConstructor/TariffItem.tsx b/src/pages/TariffConstructor/TariffItem.tsx index 40b7f94..1de00a3 100644 --- a/src/pages/TariffConstructor/TariffItem.tsx +++ b/src/pages/TariffConstructor/TariffItem.tsx @@ -1,5 +1,6 @@ -import { CustomPrivilege, Privilege, useThrottle } from "@frontend/kitui"; +import { CustomPrivilege, useThrottle } from "@frontend/kitui"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useDiscounts } from "@root/api/price"; import { CustomSlider } from "@root/components/CustomSlider"; import NumberInputWithUnitAdornment from "@root/components/NumberInputWithUnitAdornment"; import CalendarIcon from "@root/components/icons/CalendarIcon"; @@ -30,6 +31,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) { const userValue = useCustomTariffsStore((state) => state.userValuesMap[privilege.serviceKey]?.[privilege._id]) ?? sliderSettingsByType[privilege.value]?.min; const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? sliderSettingsByType[privilege.value]?.min; const cartTariffs = useCartTariffs(); + const discounts = useDiscounts(); const userId = useUserStore(state => state.user?._id) ?? ""; const [value, setValue] = useState(userValue); const throttledValue = useThrottle(value, 200); @@ -38,6 +40,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) { function setStoreValue() { setCustomTariffsUserValue( cartTariffs ?? [], + discounts ?? [], privilege.serviceKey, privilege._id, throttledValue, @@ -45,7 +48,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) { userId, ); }, - [cartTariffs, purchasesAmount, privilege._id, privilege.serviceKey, throttledValue, userId] + [cartTariffs, discounts, purchasesAmount, privilege._id, privilege.serviceKey, throttledValue, userId] ); function handleSliderChange(measurement: PrivilegeName) { diff --git a/src/pages/Tariffs/TariffsPage.tsx b/src/pages/Tariffs/TariffsPage.tsx index 882b3b5..48a9add 100644 --- a/src/pages/Tariffs/TariffsPage.tsx +++ b/src/pages/Tariffs/TariffsPage.tsx @@ -3,7 +3,6 @@ import { Tariff, getMessageFromFetchError } from "@frontend/kitui"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"; import NumberIcon from "@root/components/NumberIcon"; -import { useDiscountStore } from "@root/stores/discounts"; import { useTariffStore } from "@root/stores/tariffs"; import { addTariffToCart, useUserStore } from "@root/stores/user"; import { calcIndividualTariffPrices } from "@root/utils/calcTariffPrices"; @@ -17,6 +16,7 @@ import { withErrorBoundary } from "react-error-boundary"; import { useLocation } from "react-router-dom"; import FreeTariffCard from "./FreeTariffCard"; import TariffCard from "./TariffCard"; +import { useDiscounts } from "@root/api/price"; const subPages = ["Опросник", /*"Шаблонизатор",*/ /*"Сокращатель ссылок"*/]; @@ -35,7 +35,7 @@ function TariffPage() { const location = useLocation(); const tariffs = useTariffStore((state) => state.tariffs); const [selectedItem, setSelectedItem] = useState(0); - const discounts = useDiscountStore((state) => state.discounts); + const discounts = useDiscounts(); const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0; const userId = useUserStore(state => state.user?._id) ?? ""; const isUserNko = useUserStore((state) => state.userAccount?.status) === "nko"; @@ -75,7 +75,7 @@ function TariffPage() { .map((tariff, index) => { const { priceBeforeDiscounts, priceAfterDiscounts } = calcIndividualTariffPrices( tariff, - discounts, + discounts ?? [], purchasesAmount, currentTariffs ?? [], isUserNko, diff --git a/src/stores/customTariffs.ts b/src/stores/customTariffs.ts index 190a769..93b93e9 100644 --- a/src/stores/customTariffs.ts +++ b/src/stores/customTariffs.ts @@ -1,4 +1,4 @@ -import { CustomPrivilegeWithAmount, Tariff } from "@frontend/kitui"; +import { CustomPrivilegeWithAmount, Discount, Tariff } from "@frontend/kitui"; import { createTariff } from "@root/api/tariff"; import { CustomTariffUserValuesMap, ServiceKeyToPriceMap } from "@root/model/customTariffs"; import { ServiceKeyToPrivilegesMap } from "@root/model/privilege"; @@ -6,7 +6,6 @@ import { calcCustomTariffPrice } from "@root/utils/calcCart/calcCustomTariffPric import { produce } from "immer"; import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; -import { useDiscountStore } from "./discounts"; import { useUserStore } from "./user"; @@ -48,6 +47,7 @@ export const setCustomTariffs = (customTariffs: ServiceKeyToPrivilegesMap) => us export const setCustomTariffsUserValue = ( cartTariffs: Tariff[], + discounts: Discount[], serviceKey: string, privilegeId: string, value: number, @@ -58,7 +58,6 @@ export const setCustomTariffsUserValue = ( state.userValuesMap[serviceKey] ??= {}; state.userValuesMap[serviceKey][privilegeId] = value; const isUserNko = useUserStore.getState().userAccount?.status === "nko"; - const discounts = useDiscountStore.getState().discounts; const { priceBeforeDiscounts, priceAfterDiscounts } = calcCustomTariffPrice( state.userValuesMap[serviceKey], diff --git a/src/stores/discounts.ts b/src/stores/discounts.ts deleted file mode 100644 index 5ce45c9..0000000 --- a/src/stores/discounts.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Discount } from "@frontend/kitui" -import { create } from "zustand" -import { devtools } from "zustand/middleware" - - -interface DiscountStore { - discounts: Discount[]; -} - -export const useDiscountStore = create()( - devtools( - (set, get) => ({ - discounts: [] - }), - { - name: "Discounts", - enabled: process.env.NODE_ENV === "development", - } - ) -) - -export const setDiscounts = (discounts: DiscountStore["discounts"]) => useDiscountStore.setState({ discounts }) diff --git a/src/utils/calcCart/calcCart.test.ts b/src/utils/calcCart/calcCart.test.ts index fef8a6f..b7ceae2 100644 --- a/src/utils/calcCart/calcCart.test.ts +++ b/src/utils/calcCart/calcCart.test.ts @@ -13,7 +13,7 @@ describe("Cart calculation", () => { const tariffs = testTariffs.filter((_, index) => (usedTariffsMask[index] === 1)); - const cart = calcCart(tariffs, testDiscounts, 0, isNkoApplied, "someuserid"); + const cart = calcCart(tariffs, testDiscounts, 0, "someuserid", isNkoApplied); expect(cart.priceAfterDiscounts).toBeCloseTo(cartTestResults[i][0]); }); diff --git a/src/utils/calcCart/calcCart.ts b/src/utils/calcCart/calcCart.ts index ba6b7e9..8c49fbd 100644 --- a/src/utils/calcCart/calcCart.ts +++ b/src/utils/calcCart/calcCart.ts @@ -4,30 +4,27 @@ import { PrivilegeCartData, Tariff, TariffCartData, - findCartDiscount, findDiscountFactor, +} from "@frontend/kitui"; +import { + findCartDiscount, findLoyaltyDiscount, findNkoDiscount, findPrivilegeDiscount, findServiceDiscount, -} from "@frontend/kitui"; +} from "./utils"; -export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmount: number, isUserNko?: boolean, userId: string = ""): CartData { +export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmount: number, userId: string, isUserNko?: boolean): CartData { const cartData: CartData = { services: [], priceBeforeDiscounts: 0, priceAfterDiscounts: 0, - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, allAppliedDiscounts: [], - appliedDiscountsByPrivilegeId: new Map(), }; const privilegeAmountById = new Map(); const servicePriceByKey = new Map(); const allAppliedDiscounts = new Set(); - // Формируем корзину - tariffs.forEach(tariff => { if (tariff.privileges === undefined) return; if ( @@ -43,7 +40,6 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou serviceKey: tariff.isCustom ? "custom" : tariff.privileges[0]?.serviceKey, tariffs: [], price: 0, - appliedServiceDiscount: null, }; cartData.services.push(serviceData); } @@ -59,7 +55,8 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou tariff.privileges.forEach(privilege => { let privilegePrice = privilege.amount * privilege.price; - if (tariff.price) privilegePrice = tariff.price; + if (!tariff.price) tariffCartData.price += privilegePrice; + else privilegePrice = tariff.price; const privilegeCartData: PrivilegeCartData = { serviceKey: privilege.serviceKey, @@ -71,57 +68,63 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou privilegeAmountById.set( privilege.privilegeId, - privilege.amount + (privilegeAmountById.get(privilege.privilegeId) || 0) + privilege.amount + (privilegeAmountById.get(privilege.privilegeId) ?? 0) ); servicePriceByKey.set( privilege.serviceKey, - privilegePrice + (servicePriceByKey.get(privilege.serviceKey) || 0) + privilegePrice + (servicePriceByKey.get(privilege.serviceKey) ?? 0) ); - cartData.appliedDiscountsByPrivilegeId.set(privilege.privilegeId, new Set()); tariffCartData.privileges.push(privilegeCartData); }); + + serviceData.price += tariffCartData.price; }); cartData.priceBeforeDiscounts = Array.from(servicePriceByKey.values()).reduce((a, b) => a + b, 0); + cartData.priceAfterDiscounts = cartData.priceBeforeDiscounts; const nkoDiscount = findNkoDiscount(discounts); if (isUserNko && nkoDiscount) { - cartData.priceAfterDiscounts *= nkoDiscount.Target.Factor; - allAppliedDiscounts.add(nkoDiscount); + cartData.allAppliedDiscounts = [nkoDiscount]; cartData.services.forEach(service => { service.tariffs.forEach(tariff => { tariff.privileges.forEach(privilege => { - const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId); - if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`); + const discountAmount = privilege.price * (1 - findDiscountFactor(nkoDiscount)); - privilegeAppliedDiscounts.add(nkoDiscount); + privilege.price -= discountAmount; + tariff.price -= discountAmount; + service.price -= discountAmount; + cartData.priceAfterDiscounts -= discountAmount; }); }); }); - applyDiscountsToCart(cartData); - - cartData.allAppliedDiscounts = Array.from(allAppliedDiscounts); - return cartData; } - // Ищем и собираем скидки в appliedDiscountsByPrivilegeId - cartData.services.forEach(service => { service.tariffs.forEach(tariff => { tariff.privileges.forEach(privilege => { - const privilegeDiscount = findPrivilegeDiscount(privilege.privilegeId, privilegeAmountById.get(privilege.privilegeId) || 0, discounts, userId); - if (!privilegeDiscount) return; + const privilegeTotalAmount = privilegeAmountById.get(privilege.privilegeId) ?? 0; - allAppliedDiscounts.add(privilegeDiscount); + const discount = findPrivilegeDiscount(privilege.privilegeId, privilegeTotalAmount, discounts, userId); + if (!discount) return; - const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId); - if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`); + allAppliedDiscounts.add(discount); - privilegeAppliedDiscounts.add(privilegeDiscount); + const discountAmount = privilege.price * (1 - findDiscountFactor(discount)); + + privilege.price -= discountAmount; + tariff.price -= discountAmount; + service.price -= discountAmount; + cartData.priceAfterDiscounts -= discountAmount; + + const serviceTotalPrice = servicePriceByKey.get(privilege.serviceKey); + if (!serviceTotalPrice) throw new Error(`Service key ${privilege.serviceKey} not found in servicePriceByKey`); + + servicePriceByKey.set(privilege.serviceKey, serviceTotalPrice - discountAmount); }); }); }); @@ -129,96 +132,67 @@ export function calcCart(tariffs: Tariff[], discounts: Discount[], purchasesAmou cartData.services.forEach(service => { service.tariffs.map(tariff => { tariff.privileges.forEach(privilege => { - const servicePrice = servicePriceByKey.get(privilege.serviceKey); - if (!servicePrice) return; + const serviceTotalPrice = servicePriceByKey.get(privilege.serviceKey); + if (!serviceTotalPrice) throw new Error(`Service key ${privilege.serviceKey} not found in servicePriceByKey`); - const serviceDiscount = findServiceDiscount(privilege.serviceKey, servicePrice, discounts, userId); - if (!serviceDiscount) return; + const discount = findServiceDiscount(privilege.serviceKey, serviceTotalPrice, discounts, userId); + if (!discount) return; - allAppliedDiscounts.add(serviceDiscount); - service.appliedServiceDiscount = serviceDiscount; + allAppliedDiscounts.add(discount); - const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId); - if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`); + const discountAmount = privilege.price * (1 - findDiscountFactor(discount)); - privilegeAppliedDiscounts.add(serviceDiscount); + privilege.price -= discountAmount; + tariff.price -= discountAmount; + service.price -= discountAmount; + cartData.priceAfterDiscounts -= discountAmount; }); }); }); - const intermediateCartData = structuredClone(cartData); - applyDiscountsToCart(intermediateCartData); + const userDiscount = discounts.find(discount => discount.Condition.User === userId); - const intermediateCartPriceAfterDiscounts = intermediateCartData.priceAfterDiscounts; - - const cartDiscount = findCartDiscount(intermediateCartPriceAfterDiscounts, discounts); + const cartDiscount = findCartDiscount(cartData.priceAfterDiscounts, discounts); if (cartDiscount) { cartData.services.forEach(service => { + if (service.serviceKey === userDiscount?.Condition.Group) return; + service.tariffs.forEach(tariff => { tariff.privileges.forEach(privilege => { - const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId); - if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`); + allAppliedDiscounts.add(cartDiscount); - if (!Array.from(privilegeAppliedDiscounts)[0]?.Condition.User) privilegeAppliedDiscounts.add(cartDiscount); + const discountAmount = privilege.price * (1 - findDiscountFactor(cartDiscount)); + + privilege.price -= discountAmount; + tariff.price -= discountAmount; + service.price -= discountAmount; + cartData.priceAfterDiscounts -= discountAmount; }); }); }); - - allAppliedDiscounts.add(cartDiscount); - cartData.appliedCartPurchasesDiscount = cartDiscount; } const loyalDiscount = findLoyaltyDiscount(purchasesAmount, discounts); if (loyalDiscount) { cartData.services.forEach(service => { + if (service.serviceKey === userDiscount?.Condition.Group) return; + service.tariffs.forEach(tariff => { tariff.privileges.forEach(privilege => { - const privilegeAppliedDiscounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId); - if (!privilegeAppliedDiscounts) throw new Error(`Privilege id ${privilege.privilegeId} not found in appliedDiscountsByPrivilegeId`); + allAppliedDiscounts.add(loyalDiscount); - if (!Array.from(privilegeAppliedDiscounts)[0]?.Condition.User) privilegeAppliedDiscounts.add(loyalDiscount); + const discountAmount = privilege.price * (1 - findDiscountFactor(loyalDiscount)); + + privilege.price -= discountAmount; + tariff.price -= discountAmount; + service.price -= discountAmount; + cartData.priceAfterDiscounts -= discountAmount; }); }); }); - - allAppliedDiscounts.add(loyalDiscount); - cartData.appliedLoyaltyDiscount = loyalDiscount; } - // Применяем скидки - - applyDiscountsToCart(cartData); - cartData.allAppliedDiscounts = Array.from(allAppliedDiscounts); - return Object.freeze(cartData); -} - -function applyDiscountsToCart(cartData: CartData) { - cartData.services.forEach(service => { - let servicePrice = 0; - - service.tariffs.forEach(tariff => { - let privilegePriceSum = 0; - let tariffDiscountFactor = 1; - - tariff.privileges.forEach(privilege => { - const discounts = cartData.appliedDiscountsByPrivilegeId.get(privilege.privilegeId) ?? []; - - const discountsFactor = Array.from(discounts).reduce((factor, discount) => factor * findDiscountFactor(discount), 1); - privilege.price *= discountsFactor; - tariffDiscountFactor *= discountsFactor; - - privilegePriceSum += privilege.price; - }); - - if (tariff.price) tariff.price *= tariffDiscountFactor; - else tariff.price = privilegePriceSum; - - servicePrice += tariff.price; - }); - - service.price = servicePrice; - cartData.priceAfterDiscounts += servicePrice; - }); + return cartData; } diff --git a/src/utils/calcCart/calcCustomTariffPrice.ts b/src/utils/calcCart/calcCustomTariffPrice.ts index 8031adf..2dc0e72 100644 --- a/src/utils/calcCart/calcCustomTariffPrice.ts +++ b/src/utils/calcCart/calcCustomTariffPrice.ts @@ -38,7 +38,7 @@ export function calcCustomTariffPrice( privileges: privileges, }; - const cart = calcCart([...cartTariffs, customTariff], discounts, purchasesAmount, isUserNko, userId); + const cart = calcCart([...cartTariffs, customTariff], discounts, purchasesAmount, userId, isUserNko); const customService = cart.services.flatMap(service => service.tariffs).find(tariff => tariff.id === customTariff._id); if (!customService) throw new Error("Custom service not found in cart"); diff --git a/src/utils/calcCart/utils.ts b/src/utils/calcCart/utils.ts new file mode 100644 index 0000000..a6a7662 --- /dev/null +++ b/src/utils/calcCart/utils.ts @@ -0,0 +1,119 @@ +import { Discount } from "@frontend/kitui"; + + +export function findNkoDiscount(discounts: Discount[]): Discount | null { + const applicableDiscounts = discounts.filter(discount => discount.Condition.UserType === "nko"); + + if (!applicableDiscounts.length) return null; + + const maxValueDiscount = applicableDiscounts.reduce((prev, current) => { + return Number(current.Condition.CartPurchasesAmount) > Number(prev.Condition.CartPurchasesAmount) ? current : prev; + }); + + return maxValueDiscount; +} + +export function findPrivilegeDiscount( + privilegeId: string, + privilegeAmount: number, + discounts: Discount[], + userId: string, +): Discount | null { + const applicableDiscounts = discounts.filter(discount => { + return ( + discount.Layer === 1 + && privilegeId === discount.Condition.Product + && privilegeAmount >= Number(discount.Condition.Term) + && (discount.Condition.User === "" || discount.Condition.User === userId) + ); + }); + + if (!applicableDiscounts.length) return null; + + let maxValueDiscount: Discount = applicableDiscounts[0]; + for (const discount of applicableDiscounts) { + if (discount.Condition.User !== "" && discount.Condition.User === userId) { + maxValueDiscount = discount; + break; + } + + if (Number(discount.Condition.Term) > Number(maxValueDiscount.Condition.Term)) { + maxValueDiscount = discount; + } + } + + return maxValueDiscount; +} + +export function findServiceDiscount( + serviceKey: string, + currentPrice: number, + discounts: Discount[], + userId: string, +): Discount | null { + const applicableDiscounts = discounts.filter(discount => { + return ( + discount.Layer === 2 + && serviceKey === discount.Condition.Group + && currentPrice >= Number(discount.Condition.PriceFrom) + && (discount.Condition.User === "" || discount.Condition.User === userId) + ); + }); + + if (!applicableDiscounts.length) return null; + + let maxValueDiscount: Discount = applicableDiscounts[0]; + for (const discount of applicableDiscounts) { + if (discount.Condition.User !== "" && discount.Condition.User === userId) { + maxValueDiscount = discount; + break; + } + + if (Number(discount.Condition.PriceFrom) > Number(maxValueDiscount.Condition.PriceFrom)) { + maxValueDiscount = discount; + } + } + + return maxValueDiscount; +} + +export function findCartDiscount( + cartPurchasesAmount: number, + discounts: Discount[], +): Discount | null { + const applicableDiscounts = discounts.filter(discount => { + return ( + discount.Layer === 3 + && cartPurchasesAmount >= Number(discount.Condition.CartPurchasesAmount) + ); + }); + + if (!applicableDiscounts.length) return null; + + const maxValueDiscount = applicableDiscounts.reduce((prev, current) => { + return Number(current.Condition.CartPurchasesAmount) > Number(prev.Condition.CartPurchasesAmount) ? current : prev; + }); + + return maxValueDiscount; +} + +export function findLoyaltyDiscount( + purchasesAmount: number, + discounts: Discount[], +): Discount | null { + const applicableDiscounts = discounts.filter(discount => { + return ( + discount.Layer === 4 + && discount.Condition.UserType !== "nko" + && purchasesAmount >= Number(discount.Condition.PurchasesAmount) + ); + }); + + if (!applicableDiscounts.length) return null; + + const maxValueDiscount = applicableDiscounts.reduce((prev, current) => { + return Number(current.Condition.PurchasesAmount) > Number(prev.Condition.PurchasesAmount) ? current : prev; + }); + + return maxValueDiscount; +} diff --git a/src/utils/calcTariffPrices.ts b/src/utils/calcTariffPrices.ts index b080dcd..5547c00 100644 --- a/src/utils/calcTariffPrices.ts +++ b/src/utils/calcTariffPrices.ts @@ -17,7 +17,7 @@ export function calcIndividualTariffPrices( 0 ); - const cart = calcCart([...currentTariffs, targetTariff], discounts, purchasesAmount, isUserNko, userId); + const cart = calcCart([...currentTariffs, targetTariff], discounts, purchasesAmount, userId, isUserNko); const tariffCartData = cart.services.flatMap(service => service.tariffs).find(tariff => tariff.id === targetTariff._id); if (!tariffCartData) throw new Error(`Target tariff ${targetTariff._id} not found in cart`); diff --git a/src/utils/hooks/useCart.ts b/src/utils/hooks/useCart.ts index 9f822a1..4e191f3 100644 --- a/src/utils/hooks/useCart.ts +++ b/src/utils/hooks/useCart.ts @@ -1,23 +1,21 @@ -import { useDiscountStore } from "@root/stores/discounts"; +import { CartData } from "@frontend/kitui"; import { useUserStore } from "@root/stores/user"; -import { useDebugValue, useMemo } from "react"; +import { useMemo } from "react"; import { calcCart } from "../calcCart/calcCart"; import { useCartTariffs } from "./useCartTariffs"; -import { CartData } from "@frontend/kitui"; +import { useDiscounts } from "@root/api/price"; export function useCart(): CartData { const cartTariffs = useCartTariffs(); - const discounts = useDiscountStore((state) => state.discounts); + const discounts = useDiscounts(); const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0; const isUserNko = useUserStore(state => state.userAccount?.status) === "nko"; const userId = useUserStore(state => state.user?._id) ?? ""; const cart = useMemo(() => { - return calcCart(cartTariffs ?? [], discounts, purchasesAmount, isUserNko, userId); - }, [cartTariffs, discounts, purchasesAmount, isUserNko, userId]); - - useDebugValue(cart); + return calcCart(cartTariffs ?? [], discounts ?? [], purchasesAmount, userId, isUserNko); + }, [cartTariffs, discounts, purchasesAmount, userId, isUserNko]); return cart; } diff --git a/src/utils/hooks/useDiscounts.ts b/src/utils/hooks/useDiscounts.ts deleted file mode 100644 index 154c438..0000000 --- a/src/utils/hooks/useDiscounts.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect, useLayoutEffect, useRef } from "react" -import { Discount, devlog } from "@frontend/kitui" - -import { getDiscounts } from "@root/api/price" - -export function useDiscounts({ - onNewDiscounts, - onError, -}: { - url?: string; - onNewDiscounts: (response: Discount[]) => void; - onError: (error: Error) => void; -}) { - const onNewTariffsRef = useRef(onNewDiscounts) - const onErrorRef = useRef(onError) - - useLayoutEffect(() => { - onNewTariffsRef.current = onNewDiscounts - onErrorRef.current = onError - }, [onError, onNewDiscounts]) - - useEffect(() => { - const controller = new AbortController() - - getDiscounts(controller.signal) - .then(([discounts]) => { - if (discounts) { - onNewTariffsRef.current(discounts.Discounts) - } - }) - .catch((error) => { - devlog("Error fetching tariffs", error) - onErrorRef.current(error) - }) - - return () => controller.abort() - }, []) -} diff --git a/yarn.lock b/yarn.lock index 5030f9d..c51e170 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1607,10 +1607,10 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== -"@frontend/kitui@^1.0.70": - version "1.0.70" - resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.70.tgz#4ff9d4ef51132bbf06d4efda561b95efdaf4e9f0" - integrity sha1-T/nU71ETK78G1O/aVhuV79r06fA= +"@frontend/kitui@^1.0.71": + version "1.0.71" + resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.71.tgz#16d20c163a1574ba98a4720c0deb21eab3e67b75" + integrity sha1-FtIMFjoVdLqYpHIMDesh6rPme3U= dependencies: immer "^10.0.2" reconnecting-eventsource "^1.6.2"