Merge branch 'pass-lifetime-fixes' into 'main'
[ProtonMail-WebClient.git] / packages / components / containers / offers / hooks / useFetchOffer.ts
blob6d876e0d0bb4e81f707d85d9b282438e267d1257
1 import { useEffect, useState } from 'react';
3 import { usePlans } from '@proton/account/plans/hooks';
4 import { usePaymentsApiWithCheckFallback } from '@proton/components/payments/react-extensions/usePaymentsApi';
5 import { useLoading } from '@proton/hooks';
6 import { type Currency } from '@proton/payments';
8 import { fetchDealPrices } from '../helpers/dealPrices';
9 import type { Offer, OfferConfig } from '../interface';
11 interface Props {
12     offerConfig: OfferConfig | undefined;
13     currency: Currency;
14     onSuccess?: () => void;
15     onError?: () => void;
18 function useFetchOffer({ offerConfig, currency, onSuccess, onError }: Props): [Offer | undefined, boolean] {
19     const { paymentsApi } = usePaymentsApiWithCheckFallback();
20     const [loading, withLoading] = useLoading();
21     const [state, setState] = useState<Partial<{ offer: Offer; offerConfig: OfferConfig }>>();
22     const [plansResult, plansLoading] = usePlans();
23     const plans = plansResult?.plans;
25     useEffect(() => {
26         if (!offerConfig || !plans) {
27             return;
28         }
30         const updateOfferPrices = async () => {
31             try {
32                 // Reset previous offer prices in case the offer config has changed from what was previously cached
33                 if (state?.offerConfig !== offerConfig) {
34                     setState(undefined);
35                 }
37                 const result = await fetchDealPrices(paymentsApi, offerConfig, currency, plans);
39                 // We make an offer based on offerConfig + fetched results above
40                 const offer: Offer = {
41                     ...offerConfig,
42                     deals: offerConfig.deals.map((deal, index) => {
43                         const [withCoupon, withoutCoupon, withoutCouponMonthly] = result[index];
45                         return {
46                             ...deal,
47                             prices: {
48                                 withCoupon: withCoupon.Amount + (withCoupon.CouponDiscount || 0),
49                                 withoutCoupon: withoutCoupon.Amount + (withoutCoupon.CouponDiscount || 0), // BUNDLE discount can be applied
50                                 // in rare cases a plan doesn't have a monthly price, so we use 0 as a fallback.
51                                 // It can be potentially done smarter, e.g. take the amount without coupon and devide it
52                                 // byt the cycle length. But that wasn't required for the purpose of Pass Lifetime offer
53                                 withoutCouponMonthly: withoutCouponMonthly?.Amount ?? 0,
54                             },
55                         };
56                     }),
57                 };
58                 setState({ offer, offerConfig });
59                 onSuccess?.();
60             } catch (error) {
61                 onError?.();
62             }
63         };
65         void withLoading(updateOfferPrices());
66     }, [offerConfig, currency, plans]);
68     return [state?.offer, loading || plansLoading];
71 export default useFetchOffer;