From d55132cc9ad7b0c3caff9fb8688df65a326eff74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 24 Dec 2024 09:12:28 +0100 Subject: [PATCH] Refacto --- .../usePageChangeEffectNavigateLocation.ts | 3 + .../__tests__/useBillingCheckout.test.tsx | 142 ++++++++++++++++++ .../hooks/__tests__/useBillingPlan.test.tsx | 72 --------- .../billing/hooks/useBillingCheckout.ts | 31 ++-- 4 files changed, 165 insertions(+), 83 deletions(-) create mode 100644 packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingCheckout.test.tsx delete mode 100644 packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingPlan.test.tsx diff --git a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts index 5d637c77b5..4e4689a82f 100644 --- a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts +++ b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts @@ -1,4 +1,5 @@ import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { useBillingCheckout } from '@/billing/hooks/useBillingCheckout'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { AppPath } from '@/types/AppPath'; @@ -32,6 +33,8 @@ export const usePageChangeEffectNavigateLocation = () => { isMatchingLocation(AppPath.PlanRequired) || isMatchingLocation(AppPath.PlanRequiredSuccess); + useBillingCheckout(); + if (isMatchingOpenRoute) { return; } diff --git a/packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingCheckout.test.tsx b/packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingCheckout.test.tsx new file mode 100644 index 0000000000..68f9b15b94 --- /dev/null +++ b/packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingCheckout.test.tsx @@ -0,0 +1,142 @@ +import { cleanup, renderHook, waitFor } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { RecoilRoot } from 'recoil'; + +import { useBillingCheckout } from '@/billing/hooks/useBillingCheckout'; +import { BillingPlanKey } from '~/generated/graphql'; + +type WrapperProps = { + children: React.ReactNode; + initialUrl?: string; +}; + +const Wrapper = ({ children, initialUrl = '' }: WrapperProps) => ( + + {children} + +); + +describe('useBillingCheckout', () => { + afterEach(() => { + cleanup(); + }); + + it('should return null as default plan', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => {children}, + }); + + await waitFor(() => { + expect(result.current.plan).toBe(null); + }); + }); + + it('should set plan from URL parameter - FREE', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.plan).toBe(BillingPlanKey.Free); + }); + }); + + it('should set plan from URL parameter - PRO', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.plan).toBe(BillingPlanKey.Pro); + }); + }); + + it('should set plan from URL parameter - ENTERPRISE', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.plan).toBe(BillingPlanKey.Enterprise); + }); + }); + + it('should ignore invalid plan from URL parameter', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.plan).toBe(null); + }); + }); + + it('should handle URL without plan parameter', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.plan).toBe(null); + }); + }); + + it('should set requirePaymentMethod to false when freepass parameter is present', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.requirePaymentMethod).toBe(false); + expect(result.current.skipPlanPage).toBe(true); + }); + }); + + it('should set requirePaymentMethod to false for all freepass parameter variations', async () => { + const freePassVariations = [ + 'freepass=true', + 'freePass=true', + 'free-pass=true', + 'Free-pass=true', + 'FreePass=true', + ]; + + for (const param of freePassVariations) { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.requirePaymentMethod).toBe(false); + expect(result.current.skipPlanPage).toBe(true); + }); + } + }); + + it('should set requirePaymentMethod to false when requirePaymentMethod=true is in URL', async () => { + const { result } = renderHook(() => useBillingCheckout(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + await waitFor(() => { + expect(result.current.requirePaymentMethod).toBe(false); + expect(result.current.skipPlanPage).toBe(true); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingPlan.test.tsx b/packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingPlan.test.tsx deleted file mode 100644 index 612ddb7a55..0000000000 --- a/packages/twenty-front/src/modules/billing/hooks/__tests__/useBillingPlan.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; - -import { useBillingCheckout } from '@/billing/hooks/useBillingCheckout'; -import { BillingPlanKey } from '~/generated/graphql'; - -const Wrapper = ({ children, initialUrl = '' }: any) => ( - - {children} - -); - -describe('useBillingCheckout', () => { - it('should return null as default plan', () => { - const { result } = renderHook(() => useBillingCheckout(), { - wrapper: Wrapper, - }); - - expect(result.current.plan).toBe(null); - }); - - it('should set plan from URL parameter - FREE', () => { - const { result } = renderHook(() => useBillingCheckout(), { - wrapper: ({ children }) => ( - {children} - ), - }); - - expect(result.current.plan).toBe(BillingPlanKey.Free); - }); - - it('should set plan from URL parameter - PRO', () => { - const { result } = renderHook(() => useBillingCheckout(), { - wrapper: ({ children }) => ( - {children} - ), - }); - - expect(result.current.plan).toBe(BillingPlanKey.Pro); - }); - - it('should set plan from URL parameter - ENTERPRISE', () => { - const { result } = renderHook(() => useBillingCheckout(), { - wrapper: ({ children }) => ( - {children} - ), - }); - - expect(result.current.plan).toBe(BillingPlanKey.Enterprise); - }); - - it('should ignore invalid plan from URL parameter', () => { - const { result } = renderHook(() => useBillingCheckout(), { - wrapper: ({ children }) => ( - {children} - ), - }); - - expect(result.current.plan).toBe(null); - }); - - it('should handle URL without plan parameter', () => { - const { result } = renderHook(() => useBillingCheckout(), { - wrapper: ({ children }) => ( - {children} - ), - }); - - expect(result.current.plan).toBe(null); - }); -}); diff --git a/packages/twenty-front/src/modules/billing/hooks/useBillingCheckout.ts b/packages/twenty-front/src/modules/billing/hooks/useBillingCheckout.ts index feaef7bc3d..08a9306578 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useBillingCheckout.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useBillingCheckout.ts @@ -1,9 +1,17 @@ import { billingCheckoutState } from '@/billing/states/billingCheckoutState'; import { useLocation } from 'react-router-dom'; import { useRecoilState } from 'recoil'; + import { BillingPlanKey } from '~/generated/graphql'; -export const useBillingCheckout = () => { +type BillingCheckout = { + plan: BillingPlanKey | null; + interval?: string; + requirePaymentMethod: boolean; + skipPlanPage: boolean; +}; + +export const useBillingCheckout = (): BillingCheckout => { const { search } = useLocation(); const [billingCheckout, setBillingCheckout] = useRecoilState(billingCheckoutState); @@ -15,28 +23,29 @@ export const useBillingCheckout = () => { search.includes('Free-pass') || search.includes('FreePass'); - if (hasFreePassParameter || search.includes('requirePaymentMethod=true')) { - setBillingCheckout({ - plan: billingCheckout.plan, - interval: billingCheckout.interval, + if ( + billingCheckout.requirePaymentMethod && + (hasFreePassParameter || search.includes('requirePaymentMethod=true')) + ) { + setBillingCheckout((prev) => ({ + ...prev, requirePaymentMethod: false, skipPlanPage: true, - }); + })); } const planFromUrl = search.match(/[?&]plan=([^&]+)/)?.[1]?.toUpperCase(); if ( - planFromUrl !== null && planFromUrl !== undefined && + planFromUrl !== '' && Object.values(BillingPlanKey).includes(planFromUrl as BillingPlanKey) ) { - setBillingCheckout({ + setBillingCheckout((prev) => ({ + ...prev, plan: planFromUrl as BillingPlanKey, - interval: billingCheckout.interval, - requirePaymentMethod: billingCheckout.requirePaymentMethod, skipPlanPage: true, - }); + })); } return billingCheckout;