Skip to content

Commit

Permalink
Merge pull request #1029 from CruGlobal/8087-setup-redirect
Browse files Browse the repository at this point in the history
[MPDX-8087] Take the user through the tour if their account isn't set up
  • Loading branch information
canac authored Sep 6, 2024
2 parents 4fdfe9b + e562f4a commit a12791c
Show file tree
Hide file tree
Showing 26 changed files with 598 additions and 110 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Note: there is a test account you can use. Get this from another developer if yo
- `HS_TASKS_SUGGESTIONS` - Comma-separated IDs of the HelpScout articles to suggest on the tasks page
- `PRIVACY_POLICY_URL` - URL of the privacy policy
- `TERMS_OF_USE_URL` - URL of the terms of use
- `DISABLE_SETUP_TOUR` - Set to `true` to disable starting users on the welcome tour. This should be removed from the codebase once tools are live.

#### Auth provider

Expand Down
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const config = {
PRIVACY_POLICY_URL: process.env.PRIVACY_POLICY_URL,
TERMS_OF_USE_URL: process.env.TERMS_OF_USE_URL,
DD_ENV: process.env.DD_ENV ?? 'development',
DISABLE_SETUP_TOUR: process.env.DISABLE_SETUP_TOUR,
},
experimental: {
modularizeImports: {
Expand Down
4 changes: 4 additions & 0 deletions pages/GetAccountLists.graphql
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
query GetAccountLists {
user {
id
setup
}
accountLists(first: 50) {
nodes {
id
Expand Down
15 changes: 9 additions & 6 deletions pages/_app.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NextPage } from 'next';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import React, { ReactElement, useMemo } from 'react';
import { ApolloProvider as RawApolloProvider } from '@apollo/client';
import { ApolloProvider } from '@apollo/client';
import createEmotionCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import { Box, StyledEngineProvider } from '@mui/material';
Expand All @@ -25,6 +25,7 @@ import HelpscoutBeacon from 'src/components/Helpscout/HelpscoutBeacon';
import PrimaryLayout from 'src/components/Layouts/Primary';
import Loading from 'src/components/Loading';
import { RouterGuard } from 'src/components/RouterGuard/RouterGuard';
import { SetupProvider } from 'src/components/Setup/SetupProvider';
import { AlertBanner } from 'src/components/Shared/alertBanner/AlertBanner';
import { SnackbarUtilsConfigurator } from 'src/components/Snackbar/Snackbar';
import TaskModalProvider from 'src/components/Task/Modal/TaskModalProvider';
Expand Down Expand Up @@ -60,11 +61,13 @@ const GraphQLProviders: React.FC<{
const client = useMemo(() => makeClient(apiToken), [apiToken]);

return (
<RawApolloProvider client={client}>
<UserPreferenceProvider>
<TaskModalProvider>{children}</TaskModalProvider>
</UserPreferenceProvider>
</RawApolloProvider>
<ApolloProvider client={client}>
<SetupProvider>
<UserPreferenceProvider>
<TaskModalProvider>{children}</TaskModalProvider>
</UserPreferenceProvider>
</SetupProvider>
</ApolloProvider>
);
};

Expand Down
55 changes: 49 additions & 6 deletions pages/accountLists.page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { render } from '@testing-library/react';
import { getSession } from 'next-auth/react';
import { I18nextProvider } from 'react-i18next';
import { session } from '__tests__/fixtures/session';
import { UserSetupStageEnum } from 'src/graphql/types.generated';
import makeSsrClient from 'src/lib/apollo/ssrClient';
import i18n from 'src/lib/i18n';
import theme from 'src/theme';
Expand All @@ -22,16 +23,14 @@ interface GetServerSidePropsReturn {
const accountListId = 'accountID1';

describe('Account Lists page', () => {
const context = {
req: {} as any,
};
const context = {} as GetServerSidePropsContext;

describe('NextAuth unauthorized', () => {
it('should redirect to login', async () => {
(getSession as jest.Mock).mockResolvedValue(null);

const { props, redirect } = (await getServerSideProps(
context as GetServerSidePropsContext,
context,
)) as GetServerSidePropsReturn;

expect(props).toBeUndefined();
Expand All @@ -44,20 +43,63 @@ describe('Account Lists page', () => {

describe('NextAuth authorized', () => {
beforeEach(() => {
process.env.DISABLE_SETUP_TOUR = undefined;

(getSession as jest.Mock).mockResolvedValue(session);
});

it('redirects user to the setup tour is user.setup is not null', async () => {
(makeSsrClient as jest.Mock).mockReturnValue({
query: jest.fn().mockResolvedValue({
data: {
user: { id: 'user-1', setup: UserSetupStageEnum.NoAccountLists },
accountLists: { nodes: [] },
},
}),
});

const result = await getServerSideProps(context);
expect(result).toEqual({
redirect: {
destination: '/setup/start',
permanent: false,
},
});
});

it('does not redirect to the setup tour when DISABLE_SETUP_TOUR is true', async () => {
process.env.DISABLE_SETUP_TOUR = 'true';

(makeSsrClient as jest.Mock).mockReturnValue({
query: jest.fn().mockResolvedValue({
data: {
user: { id: 'user-1', setup: UserSetupStageEnum.NoAccountLists },
accountLists: { nodes: [] },
},
}),
});

const result = await getServerSideProps(context);
expect(result).not.toEqual({
redirect: {
destination: '/setup/start',
permanent: false,
},
});
});

it('redirects user to their accountList page if only one accountList', async () => {
(makeSsrClient as jest.Mock).mockReturnValue({
query: jest.fn().mockResolvedValue({
data: {
user: { id: 'user-1', setup: null },
accountLists: { nodes: [{ id: accountListId }] },
},
}),
});

const { props, redirect } = (await getServerSideProps(
context as GetServerSidePropsContext,
context,
)) as GetServerSidePropsReturn;

expect(props).toBeUndefined();
Expand All @@ -75,13 +117,14 @@ describe('Account Lists page', () => {
(makeSsrClient as jest.Mock).mockReturnValue({
query: jest.fn().mockResolvedValue({
data: {
user: { id: 'user-1', setup: null },
accountLists,
},
}),
});

const { props, redirect } = (await getServerSideProps(
context as GetServerSidePropsContext,
context,
)) as GetServerSidePropsReturn;

const { getByText } = render(
Expand Down
10 changes: 10 additions & 0 deletions pages/accountLists.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ export const getServerSideProps = makeGetServerSideProps(async (session) => {
query: GetAccountListsDocument,
});

if (data.user.setup && process.env.DISABLE_SETUP_TOUR !== 'true') {
// The user has not finished setting up, so start them on the tour
return {
redirect: {
destination: '/setup/start',
permanent: false,
},
};
}

if (data.accountLists.nodes.length === 1) {
return {
redirect: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { GetUserOptionsQuery } from 'src/components/Contacts/ContactFlow/GetUser
import { MailchimpAccountQuery } from 'src/components/Settings/integrations/Mailchimp/MailchimpAccount.generated';
import { GetUsersOrganizationsAccountsQuery } from 'src/components/Settings/integrations/Organization/Organizations.generated';
import { PrayerlettersAccountQuery } from 'src/components/Settings/integrations/Prayerletters/PrayerlettersAccount.generated';
import { SetupStageQuery } from 'src/components/Setup/Setup.generated';
import { SetupProvider } from 'src/components/Setup/SetupProvider';
import useGetAppSettings from 'src/hooks/useGetAppSettings';
import theme from 'src/theme';
import Integrations from './index.page';
Expand Down Expand Up @@ -48,6 +50,7 @@ const MocksProviders: React.FC<MocksProvidersProps> = ({ children, setup }) => (
MailchimpAccount: MailchimpAccountQuery;
PrayerlettersAccount: PrayerlettersAccountQuery;
GetUserOptions: GetUserOptionsQuery;
SetupStage: SetupStageQuery;
}>
mocks={{
GetUsersOrganizationsAccounts: {
Expand All @@ -62,19 +65,21 @@ const MocksProviders: React.FC<MocksProvidersProps> = ({ children, setup }) => (
},
MailchimpAccount: { mailchimpAccount: [] },
PrayerlettersAccount: { prayerlettersAccount: [] },
GetUserOptions: {
SetupStage: {
user: {
setup: null,
},
userOptions: [
{
id: '1',
key: 'setup_position',
value: setup || 'finish',
value: setup || '',
},
],
},
}}
onCall={mutationSpy}
>
{children}
<SetupProvider>{children}</SetupProvider>
</GqlMockedProvider>
</TestRouter>
</ThemeProvider>
Expand All @@ -99,7 +104,7 @@ describe('Connect Services page', () => {
describe('Setup Tour', () => {
it('should not show setup banner and accordions should not be disabled', async () => {
const { queryByText, queryByRole, findByText, getByText } = render(
<MocksProviders setup="start">
<MocksProviders>
<Integrations />
</MocksProviders>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { useSnackbar } from 'notistack';
import { useTranslation } from 'react-i18next';
import { loadSession } from 'pages/api/utils/pagePropsHelpers';
import { useUpdateUserOptionsMutation } from 'src/components/Contacts/ContactFlow/ContactFlowSetup/UpdateUserOptions.generated';
import { useGetUserOptionsQuery } from 'src/components/Contacts/ContactFlow/GetUserOptions.generated';
import { ChalklineAccordion } from 'src/components/Settings/integrations/Chalkline/ChalklineAccordion';
import { GoogleAccordion } from 'src/components/Settings/integrations/Google/GoogleAccordion';
import { TheKeyAccordion } from 'src/components/Settings/integrations/Key/TheKeyAccordion';
import { MailchimpAccordion } from 'src/components/Settings/integrations/Mailchimp/MailchimpAccordion';
import { OrganizationAccordion } from 'src/components/Settings/integrations/Organization/OrganizationAccordion';
import { PrayerlettersAccordion } from 'src/components/Settings/integrations/Prayerletters/PrayerlettersAccordion';
import { SetupBanner } from 'src/components/Settings/preferences/SetupBanner';
import { useSetupContext } from 'src/components/Setup/SetupProvider';
import { AccordionGroup } from 'src/components/Shared/Forms/Accordions/AccordionGroup';
import { StickyBox } from 'src/components/Shared/Header/styledComponents';
import { useAccountListId } from 'src/hooks/useAccountListId';
Expand All @@ -29,21 +29,15 @@ const Integrations: React.FC = () => {
const accountListId = useAccountListId() || '';
const { appName } = useGetAppSettings();
const { enqueueSnackbar } = useSnackbar();
const { settingUp } = useSetupContext();
const [setup, setSetup] = useState(0);

const setupAccordions = ['google', 'mailchimp', 'prayerletters.com'];

const { data: userOptions } = useGetUserOptionsQuery();
const [updateUserOptions] = useUpdateUserOptionsMutation();

const isSettingUp = userOptions?.userOptions.some(
(option) =>
option.key === 'setup_position' &&
option.value === 'preferences.integrations',
);

const handleSetupChange = async () => {
if (!isSettingUp) {
if (!settingUp) {
return;
}
const nextNav = setup + 1;
Expand Down Expand Up @@ -76,18 +70,18 @@ const Integrations: React.FC = () => {
}, []);

useEffect(() => {
if (isSettingUp) {
if (settingUp) {
setExpandedPanel(setupAccordions[0]);
}
}, [isSettingUp]);
}, [settingUp]);

return (
<SettingsWrapper
pageTitle={t('Connect Services')}
pageHeading={t('Connect Services')}
selectedMenuId="integrations"
>
{isSettingUp && (
{settingUp && (
<StickyBox>
<SetupBanner
button={
Expand All @@ -105,34 +99,34 @@ const Integrations: React.FC = () => {
<TheKeyAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp}
disabled={settingUp}
/>
<OrganizationAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp}
disabled={settingUp}
/>
</AccordionGroup>
<AccordionGroup title={t('External Services')}>
<GoogleAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp && setup !== 0}
disabled={settingUp && setup !== 0}
/>
<MailchimpAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp && setup !== 1}
disabled={settingUp && setup !== 1}
/>
<PrayerlettersAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp && setup !== 2}
disabled={settingUp && setup !== 2}
/>
<ChalklineAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp}
disabled={settingUp}
/>
</AccordionGroup>
</SettingsWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
NotificationsPreferencesQuery,
} from 'src/components/Settings/notifications/Notifications.generated';
import { notificationSettingsMocks } from 'src/components/Settings/notifications/notificationSettingsMocks';
import { SetupStageQuery } from 'src/components/Setup/Setup.generated';
import { SetupProvider } from 'src/components/Setup/SetupProvider';
import theme from 'src/theme';
import Notifications from './notifications.page';

Expand Down Expand Up @@ -48,22 +50,25 @@ const MocksProviders: React.FC<MocksProvidersProps> = ({ children, setup }) => (
GetUserOptions: GetUserOptionsQuery;
NotificationsPreferences: NotificationsPreferencesQuery;
NotificationTypes: NotificationTypesQuery;
SetupStage: SetupStageQuery;
}>
mocks={{
...notificationSettingsMocks,
GetUserOptions: {
SetupStage: {
user: {
setup: null,
},
userOptions: [
{
id: '1',
key: 'setup_position',
value: setup || 'finish',
value: setup || '',
},
],
},
}}
onCall={mutationSpy}
>
{children}
<SetupProvider>{children}</SetupProvider>
</GqlMockedProvider>
</TestRouter>
</ThemeProvider>
Expand All @@ -86,7 +91,7 @@ describe('Notifications page', () => {
describe('Setup Tour', () => {
it('should not show setup banner', async () => {
const { queryByText, findByText } = render(
<MocksProviders setup="start">
<MocksProviders>
<Notifications />
</MocksProviders>,
);
Expand Down
Loading

0 comments on commit a12791c

Please sign in to comment.