Skip to content

Commit

Permalink
MPDX-8091 Preferences Setup steps UI (#1002)
Browse files Browse the repository at this point in the history
* Preferences Setup steps UI

* userOptions query, mutation and setup step logic

* Add tests

* Notifications Welcome tour

* Integrations Setup Welcome tour

* Add Reset Welcome Tour Button
  • Loading branch information
caleballdrin authored Sep 4, 2024
1 parent e3e6a5a commit af2818c
Show file tree
Hide file tree
Showing 34 changed files with 916 additions and 94 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React from 'react';
import { ThemeProvider } from '@mui/material/styles';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
import { GetUserOptionsQuery } from 'src/components/Contacts/ContactFlow/GetUserOptions.generated';
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 useGetAppSettings from 'src/hooks/useGetAppSettings';
import theme from 'src/theme';
import Integrations from './index.page';

const accountListId = 'account-list-1';

const mockEnqueue = jest.fn();
const mutationSpy = jest.fn();
const push = jest.fn();

const router = {
query: { accountListId },
isReady: true,
push,
};

jest.mock('src/hooks/useGetAppSettings');
jest.mock('notistack', () => ({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
...jest.requireActual('notistack'),
useSnackbar: () => {
return {
enqueueSnackbar: mockEnqueue,
};
},
}));
interface MocksProvidersProps {
children: JSX.Element;
setup?: string;
}

const MocksProviders: React.FC<MocksProvidersProps> = ({ children, setup }) => (
<ThemeProvider theme={theme}>
<TestRouter router={router}>
<GqlMockedProvider<{
GetUsersOrganizationsAccounts: GetUsersOrganizationsAccountsQuery;
MailchimpAccount: MailchimpAccountQuery;
PrayerlettersAccount: PrayerlettersAccountQuery;
GetUserOptions: GetUserOptionsQuery;
}>
mocks={{
GetUsersOrganizationsAccounts: {
userOrganizationAccounts: [
{
organization: {},
},
{
organization: {},
},
],
},
MailchimpAccount: { mailchimpAccount: [] },
PrayerlettersAccount: { prayerlettersAccount: [] },
GetUserOptions: {
userOptions: [
{
id: '1',
key: 'setup_position',
value: setup || 'finish',
},
],
},
}}
onCall={mutationSpy}
>
{children}
</GqlMockedProvider>
</TestRouter>
</ThemeProvider>
);

describe('Connect Services page', () => {
beforeEach(() => {
(useGetAppSettings as jest.Mock).mockReturnValue({
appName: 'MPDX',
});
});
it('should render', async () => {
const { findByText } = render(
<MocksProviders>
<Integrations />
</MocksProviders>,
);
expect(await findByText('Connect Services')).toBeInTheDocument();
expect(await findByText('Organization')).toBeInTheDocument();
});

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">
<Integrations />
</MocksProviders>,
);

await waitFor(() => {
expect(
queryByText('Make MPDX a part of your everyday life'),
).not.toBeInTheDocument();
expect(
queryByRole('button', { name: 'Next Step' }),
).not.toBeInTheDocument();
});

//Accordions should be clickable
userEvent.click(await findByText('Organization'));
await waitFor(() => {
expect(
getByText(
'Add or change the organizations that sync donation information with this MPDX account. Removing an organization will not remove past information, but will prevent future donations and contacts from syncing.',
),
).toBeVisible();
});
});

it('should show setup banner and open google', async () => {
const { findByText, getByRole, getByText } = render(
<MocksProviders setup="preferences.integrations">
<Integrations />
</MocksProviders>,
);
expect(
await findByText('Make MPDX a part of your everyday life'),
).toBeInTheDocument();

//Accordions should be disabled
await waitFor(() => {
const label = getByText('Organization');
expect(() => userEvent.click(label)).toThrow();
});

const nextButton = getByRole('button', { name: 'Next Step' });

// Start with Google
expect(await findByText(/Add Account/i)).toBeInTheDocument();

// Moves to MailChimp
userEvent.click(nextButton);
expect(await findByText(/Connect MailChimp/i)).toBeInTheDocument();

// PrayerLetters.com
await waitFor(() => userEvent.click(nextButton));
await waitFor(() =>
expect(
getByText(
'prayerletters.com is a significant way to save valuable ministry time while more effectively connecting with your partners. Keep your physical newsletter list up to date in MPDX and then sync it to your prayerletters.com account with this integration.',
),
).toBeInTheDocument(),
);

// Move to finish
userEvent.click(nextButton);
await waitFor(() => {
expect(mutationSpy).toHaveGraphqlOperation('UpdateUserOptions', {
key: 'setup_position',
value: 'finish',
});
expect(push).toHaveBeenCalledWith(
'/accountLists/account-list-1/setup/finish',
);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,65 +1,138 @@
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
import { Button } from '@mui/material';
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 { AccordionGroup } from 'src/components/Shared/Forms/Accordions/AccordionGroup';
import { useAccountListId } from 'src/hooks/useAccountListId';
import useGetAppSettings from 'src/hooks/useGetAppSettings';
import { suggestArticles } from 'src/lib/helpScout';
import { SettingsWrapper } from '../Wrapper';
import { StickyBox } from '../preferences.page';

const Integrations: React.FC = () => {
const { t } = useTranslation();
const { query } = useRouter();
const { push, query } = useRouter();
const [expandedPanel, setExpandedPanel] = useState(
(query?.selectedTab as string | undefined) || '',
);
const accountListId = useAccountListId() || '';
const { appName } = useGetAppSettings();
const { enqueueSnackbar } = useSnackbar();
const [setup, setSetup] = useState(0);

useEffect(() => {
suggestArticles('HS_SETTINGS_SERVICES_SUGGESTIONS');
}, []);
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) {
return;
}
const nextNav = setup + 1;

if (setupAccordions.length === nextNav) {
await updateUserOptions({
variables: {
key: 'setup_position',
value: 'finish',
},
onError: () => {
enqueueSnackbar(t('Saving setup phase failed.'), {
variant: 'error',
});
},
});
push(`/accountLists/${accountListId}/setup/finish`);
} else {
setSetup(nextNav);
setExpandedPanel(setupAccordions[nextNav]);
}
};

const handleAccordionChange = (panel: string) => {
const panelLowercase = panel.toLowerCase();
setExpandedPanel(expandedPanel === panelLowercase ? '' : panelLowercase);
};
useEffect(() => {
suggestArticles('HS_SETTINGS_SERVICES_SUGGESTIONS');
}, []);

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

return (
<SettingsWrapper
pageTitle={t('Connect Services')}
pageHeading={t('Connect Services')}
selectedMenuId="integrations"
>
{isSettingUp && (
<StickyBox>
<SetupBanner
button={
<Button variant="contained" onClick={handleSetupChange}>
{t('Next Step')}
</Button>
}
title={t('Make {{appName}} a part of your everyday life', {
appName,
})}
/>
</StickyBox>
)}
<AccordionGroup title="">
<TheKeyAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp}
/>
<OrganizationAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp}
/>
</AccordionGroup>
<AccordionGroup title={t('External Services')}>
<GoogleAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp && setup !== 0}
/>
<MailchimpAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp && setup !== 1}
/>
<PrayerlettersAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp && setup !== 2}
/>
<ChalklineAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
disabled={isSettingUp}
/>
</AccordionGroup>
</SettingsWrapper>
Expand Down
Loading

0 comments on commit af2818c

Please sign in to comment.