From 5c6da6fa3203ce06c1a4fb97827071f08023c024 Mon Sep 17 00:00:00 2001 From: Rula Abu Hasna Date: Wed, 18 Oct 2023 15:03:18 +0200 Subject: [PATCH 01/11] Update with service less requests --- src/App.jsx | 6 + src/App.spec.jsx | 10 ++ src/layouts/BaseLayout/BaseLayout.jsx | 7 +- src/layouts/BaseLayout/BaseLayout.spec.jsx | 37 ++-- src/pages/TermsOfService/TermsOfService.jsx | 34 +++- .../TermsOfService/TermsOfService.spec.jsx | 163 +++++++++++------- .../hooks/useTermsOfService.spec.tsx | 9 +- .../TermsOfService/hooks/useTermsOfService.ts | 55 ++---- src/services/user/useInternalUser.ts | 1 + src/shared/api/api.js | 15 +- 10 files changed, 192 insertions(+), 145 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 926b52e10c..e6892ea51c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -25,6 +25,7 @@ const OwnerPage = lazy(() => import('./pages/OwnerPage')) const PullRequestPage = lazy(() => import('./pages/PullRequestPage')) const RepoPage = lazy(() => import('./pages/RepoPage')) const SyncProviderPage = lazy(() => import('./pages/SyncProviderPage')) +const TermsOfService = lazy(() => import('pages/TermsOfService')) const HomePageRedirect = () => { const { provider } = useParams() @@ -60,6 +61,11 @@ const MainAppRoutes = () => ( + + + + + {config.IS_SELF_HOSTED && ( diff --git a/src/App.spec.jsx b/src/App.spec.jsx index 6d54f9e1ac..76af45102c 100644 --- a/src/App.spec.jsx +++ b/src/App.spec.jsx @@ -276,6 +276,16 @@ describe('App', () => { }, }, ], + [ + { + testLabel: 'TOSPage', + pathname: '/terms', + expected: { + page: /TermsOfService/i, + location: '/terms', + }, + }, + ], ] describe.each(cloudFullRouterCases)( diff --git a/src/layouts/BaseLayout/BaseLayout.jsx b/src/layouts/BaseLayout/BaseLayout.jsx index 936970bb12..01929a1b9b 100644 --- a/src/layouts/BaseLayout/BaseLayout.jsx +++ b/src/layouts/BaseLayout/BaseLayout.jsx @@ -17,7 +17,6 @@ import { useUserAccessGate } from './hooks/useUserAccessGate' const LimitedHeader = lazy(() => import('layouts/LimitedHeader')) const DefaultOrgSelector = lazy(() => import('pages/DefaultOrgSelector')) const InstallationHelpBanner = lazy(() => import('./InstallationHelpBanner')) -const TermsOfService = lazy(() => import('pages/TermsOfService')) const FullPageLoader = () => (
@@ -36,11 +35,7 @@ const OnboardingOrChildren = ({ children }) => { const { isImpersonating } = useImpersonate() if (showAgreeToTerms && !isFullExperience) { - return ( - - - - ) + return } if (redirectToSyncPage && !isFullExperience) { diff --git a/src/layouts/BaseLayout/BaseLayout.spec.jsx b/src/layouts/BaseLayout/BaseLayout.spec.jsx index ee55dabbc0..2323b62e96 100644 --- a/src/layouts/BaseLayout/BaseLayout.spec.jsx +++ b/src/layouts/BaseLayout/BaseLayout.spec.jsx @@ -17,7 +17,6 @@ jest.mock('services/impersonate') jest.mock('shared/featureFlags') jest.mock('shared/GlobalTopBanners', () => () => 'GlobalTopBanners') jest.mock('./InstallationHelpBanner', () => () => 'InstallationHelpBanner') -jest.mock('pages/TermsOfService', () => () => 'TermsOfService') jest.mock('pages/DefaultOrgSelector', () => () => 'DefaultOrgSelector') const mockOwner = { @@ -167,9 +166,6 @@ describe('BaseLayout', () => { graphql.query('Seats', (_, res, ctx) => res(ctx.status(200), ctx.data({})) ), - graphql.query('TermsOfService', (_, res, ctx) => - res(ctx.status(200), ctx.data({})) - ), graphql.query('UseMyOrganizations', (_, res, ctx) => res( ctx.status(200), @@ -218,9 +214,6 @@ describe('BaseLayout', () => { const defaultOrg = screen.queryByText(/DefaultOrgSelector/) expect(defaultOrg).not.toBeInTheDocument() - - const termsOfService = screen.queryByText(/TermsOfService/) - expect(termsOfService).not.toBeInTheDocument() }) }) @@ -239,9 +232,6 @@ describe('BaseLayout', () => { const defaultOrg = screen.queryByText(/DefaultOrgSelector/) expect(defaultOrg).not.toBeInTheDocument() - - const termsOfService = screen.queryByText(/TermsOfService/) - expect(termsOfService).not.toBeInTheDocument() }) }) @@ -261,22 +251,7 @@ describe('BaseLayout', () => { const defaultOrg = screen.queryByText(/DefaultOrgSelector/) expect(defaultOrg).not.toBeInTheDocument() - - const termsOfService = screen.queryByText(/TermsOfService/) - expect(termsOfService).not.toBeInTheDocument() - }) - }) - - it(`renders the ${expectedPage}`, async () => { - setup({ termsOfServicePage: true, currentUser: loggedInUser }) - - render(hello, { - wrapper: wrapper(), }) - - expect(await screen.findByText(expectedMatcher)).toBeTruthy() - const tos = screen.getByText(expectedMatcher) - expect(tos).toBeInTheDocument() }) }) @@ -308,6 +283,18 @@ describe('BaseLayout', () => { }) }) + describe('user has not accepted terms of service', () => { + it('redirects the user to /terms', async () => { + setup({ termsOfServicePage: true }) + + render(hello, { + wrapper: wrapper(), + }) + + await waitFor(() => expect(testLocation.pathname).toBe('/terms')) + }) + }) + describe('user has not synced with providers', () => { it('redirects the user to /sync', async () => { setup({ diff --git a/src/pages/TermsOfService/TermsOfService.jsx b/src/pages/TermsOfService/TermsOfService.jsx index 1aacdc2f6d..1265c1eddc 100644 --- a/src/pages/TermsOfService/TermsOfService.jsx +++ b/src/pages/TermsOfService/TermsOfService.jsx @@ -1,11 +1,15 @@ import { zodResolver } from '@hookform/resolvers/zod' +import gt from 'lodash/gt' import PropType from 'prop-types' import { useEffect } from 'react' import { useForm } from 'react-hook-form' +import { Redirect } from 'react-router-dom' import { z } from 'zod' import umbrellaSvg from 'assets/svg/umbrella.svg' -import { useUser } from 'services/user' +import { useInternalUser } from 'services/user' +import { useFlags } from 'shared/featureFlags' +import { loginProviderToShortName } from 'shared/utils/loginProviders' import A from 'ui/A' import Button from 'ui/Button' import TextInput from 'ui/TextInput' @@ -18,8 +22,8 @@ const FormSchema = z.object({ tos: z.literal(true), }) -function isDisabled({ isValid, isDirty }) { - return (!isValid && isDirty) || !isDirty +function isDisabled({ isValid, isDirty, isMutationLoading }) { + return (!isValid && isDirty) || !isDirty || isMutationLoading } function EmailInput({ register, marketingEmailMessage, showEmailRequired }) { @@ -61,7 +65,7 @@ export default function TermsOfService() { resolver: zodResolver(FormSchema), mode: 'onChange', }) - const { mutate } = useSaveTermsAgreement({ + const { mutate, isLoading: isMutationLoading } = useSaveTermsAgreement({ onSuccess: ({ data }) => { if (data?.saveTermsAgreement?.error) { setError('apiError', data?.saveTermsAgreement?.error) @@ -70,12 +74,18 @@ export default function TermsOfService() { }, onError: (error) => setError('apiError', error), }) - const { data: currentUser, isLoading: userIsLoading } = useUser() + const { data: currentUser, isLoading: userIsLoading } = useInternalUser() + const hasSynced = gt(currentUser?.owners?.length, 0) + + const { termsOfServicePage } = useFlags({ + termsOfServicePage: false, + }) const onSubmit = (data) => { mutate({ businessEmail: data?.marketingEmail || currentUser?.email, termsAgreement: true, + marketingConsent: data?.marketingConsent, }) } @@ -93,6 +103,18 @@ export default function TermsOfService() { if (userIsLoading) return null + if (!termsOfServicePage || currentUser?.termsAgreement) { + if (hasSynced) { + const service = currentUser?.owners?.[0]?.service + const owner = currentUser?.owners?.[0]?.username + if (service) { + const provider = loginProviderToShortName(service) + return + } + } + return + } + return (
@@ -177,7 +199,7 @@ export default function TermsOfService() { )}