From b6fd009e9ee142a3aad9c6744ae2699ac47c397d Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Mon, 19 Aug 2024 22:07:53 -0700 Subject: [PATCH 1/9] Preferences Setup steps UI --- .../settings/organizations.graphql | 8 ++ .../settings/preferences.page.tsx | 114 +++++++++++++++++- .../GetPersonalPreferences.graphql | 1 + .../AccountNameAccordion.tsx | 3 + .../DefaultAccountAccordion.tsx | 10 +- .../HomeCountryAccordion.tsx | 3 + .../HourToSendNotificationsAccordion.tsx | 9 +- .../LanguageAccordion/LanguageAccordion.tsx | 3 + .../LocaleAccordion/LocaleAccordion.tsx | 3 + .../MonthlyGoalAccordion.tsx | 3 + .../TimeZoneAccordion/TimeZoneAccordion.tsx | 3 + .../Shared/Forms/Accordions/AccordionItem.tsx | 3 + 12 files changed, 159 insertions(+), 4 deletions(-) diff --git a/pages/accountLists/[accountListId]/settings/organizations.graphql b/pages/accountLists/[accountListId]/settings/organizations.graphql index ae0511e18..62418c601 100644 --- a/pages/accountLists/[accountListId]/settings/organizations.graphql +++ b/pages/accountLists/[accountListId]/settings/organizations.graphql @@ -10,3 +10,11 @@ query Organizations { } } } + +mutation UpdateUser($input: UserUpdateMutationInput!) { + updateUser(input: $input) { + user { + setup + } + } +} diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.tsx index bca5acd95..fbd79238d 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.tsx @@ -1,6 +1,7 @@ import { useRouter } from 'next/router'; import React, { useEffect, useState } from 'react'; -import { Skeleton } from '@mui/material'; +import CampaignIcon from '@mui/icons-material/Campaign'; +import { Alert, Box, Button, Skeleton, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { useTranslation } from 'react-i18next'; import { loadSession } from 'pages/api/utils/pagePropsHelpers'; @@ -29,6 +30,7 @@ import { useAccountListId } from 'src/hooks/useAccountListId'; import { useGetTimezones } from 'src/hooks/useGetTimezones'; import { getCountries } from 'src/lib/data/countries'; import { suggestArticles } from 'src/lib/helpScout'; +import theme from 'src/theme'; import { SettingsWrapper } from './Wrapper'; const AccordionLoading = styled(Skeleton)(() => ({ @@ -36,10 +38,30 @@ const AccordionLoading = styled(Skeleton)(() => ({ height: '48px', })); +const StickyBox = styled(Box)(() => ({ + position: 'sticky', + top: theme.spacing(10), + borderBottom: '1px solid', + borderBottomColor: theme.palette.grey[200], + height: theme.spacing(9), + zIndex: '700', + background: theme.palette.common.white, + paddingY: 1, +})); + const Preferences: React.FC = () => { const { t } = useTranslation(); const accountListId = useAccountListId() || ''; - const { query } = useRouter(); + const { push, query } = useRouter(); + + const setupPositions = ['', 'locale', 'monthly_goal', 'home_country']; + const accordionPanelPositions = [ + '', + 'locale', + 'monthly goal', + 'home country', + ]; + const [setup, setSetup] = useState(0); const [expandedPanel, setExpandedPanel] = useState( typeof query.selectedTab === 'string' ? query.selectedTab : '', ); @@ -53,6 +75,45 @@ const Preferences: React.FC = () => { const handleAccordionChange = (panel: string) => { const panelLowercase = panel.toLowerCase(); setExpandedPanel(expandedPanel === panelLowercase ? '' : panelLowercase); + if (setup > 0) {handleSetupChange();} + }; + + const handleSetupChange = async () => { + const nextNav = setup + 1; + if (setupPositions.length === nextNav) { + setSetup(0); + push(`/accountLists/${accountListId}/settings/notifications`); + } else { + setSetup(nextNav); + setExpandedPanel(accordionPanelPositions[nextNav]); + // push({ + // pathname: pathname, + // query: { + // accountListId: accountListId, + // selectedTab: accordionPanelPositions[nextNav], + // }, + // }); + } + // await updateUser({ + // variables: { + // input: { + // attributes: { + // setup: position, + // }, + // }, + // }, + // onCompleted: () => { + // enqueueSnackbar(t('Saved successfully.'), { + // variant: 'success', + // }); + // handleAccordionChange(label); + // }, + // onError: () => { + // enqueueSnackbar(t('Saving failed.'), { + // variant: 'error', + // }); + // }, + // }); }; const { data: personalPreferencesData, loading: personalPreferencesLoading } = @@ -77,12 +138,53 @@ const Preferences: React.FC = () => { const { data: userOrganizationAccountsData } = useGetUsersOrganizationsAccountsQuery(); + let setupData = personalPreferencesData?.user.setup; + // TODO: get actual setup data + setupData = 'locale'; + + useEffect(() => { + if (setupData && setupData !== 'finish') { + const position = setupPositions.indexOf(setupData); + setSetup(position); + setExpandedPanel(accordionPanelPositions[position]); + } + }, [setupData]); + return ( + + , + }} + action={ + + } + sx={{ marginY: 2 }} + > + {setup === 1 && ( + {t("Let's set your locale!")} + )} + {setup === 2 && ( + + {t('Great progress comes from great goals!')} + + )} + {setup === 3 && ( + + {t('What Country are you in?')} + + )} + + {personalPreferencesLoading && ( @@ -100,6 +202,7 @@ const Preferences: React.FC = () => { handleAccordionChange={handleAccordionChange} expandedPanel={expandedPanel} locale={personalPreferencesData?.user?.preferences?.locale || ''} + disabled={!!setup} /> { localeDisplay={ personalPreferencesData?.user?.preferences?.localeDisplay || '' } + disabled={!!setup && setup !== 1} /> { defaultAccountList={ personalPreferencesData?.user?.defaultAccountList || '' } + disabled={!!setup} /> { personalPreferencesData?.user?.preferences?.timeZone || '' } timeZones={timeZones} + disabled={!!setup} /> { personalPreferencesData?.user?.preferences ?.hourToSendNotifications || null } + disabled={!!setup} /> )} @@ -153,6 +260,7 @@ const Preferences: React.FC = () => { expandedPanel={expandedPanel} name={accountPreferencesData?.accountList?.name || ''} accountListId={accountListId} + disabled={!!setup} /> { currency={ accountPreferencesData?.accountList?.settings?.currency || '' } + disabled={!!setup && setup !== 2} /> { } accountListId={accountListId} countries={countries} + disabled={!!setup && setup !== 3} /> = ({ @@ -28,6 +29,7 @@ export const AccountNameAccordion: React.FC = ({ expandedPanel, name, accountListId, + disabled, }) => { const { t } = useTranslation(); const { appName } = useGetAppSettings(); @@ -67,6 +69,7 @@ export const AccountNameAccordion: React.FC = ({ label={label} value={name} fullWidth + disabled={disabled} > > = @@ -27,7 +28,13 @@ const preferencesSchema: yup.SchemaOf> = export const DefaultAccountAccordion: React.FC< DefaultAccountAccordionProps -> = ({ handleAccordionChange, expandedPanel, data, defaultAccountList }) => { +> = ({ + handleAccordionChange, + expandedPanel, + data, + defaultAccountList, + disabled, +}) => { const { t } = useTranslation(); const { appName } = useGetAppSettings(); const { enqueueSnackbar } = useSnackbar(); @@ -71,6 +78,7 @@ export const DefaultAccountAccordion: React.FC< label={label} value={selectedAccount} fullWidth + disabled={disabled} > = ({ @@ -30,6 +31,7 @@ export const HomeCountryAccordion: React.FC = ({ homeCountry, accountListId, countries, + disabled, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -78,6 +80,7 @@ export const HomeCountryAccordion: React.FC = ({ label={label} value={selectedCountry} fullWidth + disabled={disabled} > void; expandedPanel: string; hourToSendNotifications: number | null; + disabled?: boolean; } export const HourToSendNotificationsAccordion: React.FC< HourToSendNotificationsAccordionProps -> = ({ handleAccordionChange, expandedPanel, hourToSendNotifications }) => { +> = ({ + handleAccordionChange, + expandedPanel, + hourToSendNotifications, + disabled, +}) => { const { t } = useTranslation(); const { appName } = useGetAppSettings(); const { enqueueSnackbar } = useSnackbar(); @@ -77,6 +83,7 @@ export const HourToSendNotificationsAccordion: React.FC< label={label} value={selectedHour || ''} fullWidth + disabled={disabled} > void; expandedPanel: string; locale: string; + disabled?: boolean; } export const LanguageAccordion: React.FC = ({ handleAccordionChange, expandedPanel, locale, + disabled, }) => { const { t } = useTranslation(); const { appName } = useGetAppSettings(); @@ -69,6 +71,7 @@ export const LanguageAccordion: React.FC = ({ label={label} value={selectedLanguage} fullWidth + disabled={disabled} > void; expandedPanel: string; localeDisplay: string; + disabled?: boolean; } export const LocaleAccordion: React.FC = ({ handleAccordionChange, expandedPanel, localeDisplay, + disabled, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -80,6 +82,7 @@ export const LocaleAccordion: React.FC = ({ label={label} value={selectedLocale || ''} fullWidth + disabled={disabled} > = ({ @@ -32,6 +33,7 @@ export const MonthlyGoalAccordion: React.FC = ({ monthlyGoal, accountListId, currency, + disabled, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -81,6 +83,7 @@ export const MonthlyGoalAccordion: React.FC = ({ label={label} value={monthlyGoalString} fullWidth + disabled={disabled} > >; + disabled?: boolean; } export const TimeZoneAccordion: React.FC = ({ @@ -27,6 +28,7 @@ export const TimeZoneAccordion: React.FC = ({ expandedPanel, timeZone, timeZones, + disabled, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -72,6 +74,7 @@ export const TimeZoneAccordion: React.FC = ({ label={label} value={selectedTimeZone} fullWidth + disabled={disabled} > = ({ @@ -131,6 +132,7 @@ export const AccordionItem: React.FC = ({ children, fullWidth = false, image, + disabled, }) => { const expanded = useMemo( () => expandedPanel.toLowerCase() === label.toLowerCase(), @@ -141,6 +143,7 @@ export const AccordionItem: React.FC = ({ onChange={() => onAccordionChange(label)} expanded={expanded} disableGutters + disabled={disabled} > }> From b73bed0d20675fc51eefaadf83e3a2ec057fba12 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Tue, 20 Aug 2024 22:38:19 -0700 Subject: [PATCH 2/9] userOptions query, mutation and setup step logic --- .../settings/preferences.page.tsx | 208 +++++++++--------- .../Settings/preferences/SetupBanner.tsx | 28 +++ .../CurrencyAccordion/CurrencyAccordion.tsx | 3 + .../EarlyAdopterAccordion.tsx | 3 + .../ExportAllDataAccordion.tsx | 3 + .../HomeCountryAccordion.test.tsx | 3 + .../HomeCountryAccordion.tsx | 3 + .../LocaleAccordion/LocaleAccordion.test.tsx | 2 + .../LocaleAccordion/LocaleAccordion.tsx | 3 + .../MonthlyGoalAccordion.test.tsx | 2 + .../MonthlyGoalAccordion.tsx | 4 + .../MpdInfoAccordion/MpdInfoAccordion.tsx | 3 + .../PrimaryOrgAccordion.tsx | 3 + .../Shared/Forms/Accordions/AccordionItem.tsx | 6 +- 14 files changed, 169 insertions(+), 105 deletions(-) create mode 100644 src/components/Settings/preferences/SetupBanner.tsx diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.tsx index fbd79238d..2a3614c01 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.tsx @@ -1,16 +1,19 @@ import { useRouter } from 'next/router'; import React, { useEffect, useState } from 'react'; -import CampaignIcon from '@mui/icons-material/Campaign'; -import { Alert, Box, Button, Skeleton, Typography } from '@mui/material'; +import { Box, Button, Skeleton } from '@mui/material'; import { styled } from '@mui/material/styles'; +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 { useGetUsersOrganizationsAccountsQuery } from 'src/components/Settings/integrations/Organization/Organizations.generated'; import { useCanUserExportDataQuery, useGetAccountPreferencesQuery, } from 'src/components/Settings/preferences/GetAccountPreferences.generated'; import { useGetPersonalPreferencesQuery } from 'src/components/Settings/preferences/GetPersonalPreferences.generated'; +import { SetupBanner } from 'src/components/Settings/preferences/SetupBanner'; import { AccountNameAccordion } from 'src/components/Settings/preferences/accordions/AccountNameAccordion/AccountNameAccordion'; import { CurrencyAccordion } from 'src/components/Settings/preferences/accordions/CurrencyAccordion/CurrencyAccordion'; import { DefaultAccountAccordion } from 'src/components/Settings/preferences/accordions/DefaultAccountAccordion/DefaultAccountAccordion'; @@ -30,7 +33,6 @@ import { useAccountListId } from 'src/hooks/useAccountListId'; import { useGetTimezones } from 'src/hooks/useGetTimezones'; import { getCountries } from 'src/lib/data/countries'; import { suggestArticles } from 'src/lib/helpScout'; -import theme from 'src/theme'; import { SettingsWrapper } from './Wrapper'; const AccordionLoading = styled(Skeleton)(() => ({ @@ -38,28 +40,30 @@ const AccordionLoading = styled(Skeleton)(() => ({ height: '48px', })); -const StickyBox = styled(Box)(() => ({ +const StickyBox = styled(Box)(({ theme }) => ({ position: 'sticky', top: theme.spacing(10), borderBottom: '1px solid', borderBottomColor: theme.palette.grey[200], - height: theme.spacing(9), + height: theme.spacing(10), zIndex: '700', background: theme.palette.common.white, - paddingY: 1, + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + marginBottom: theme.spacing(2), })); const Preferences: React.FC = () => { const { t } = useTranslation(); const accountListId = useAccountListId() || ''; const { push, query } = useRouter(); + const { enqueueSnackbar } = useSnackbar(); - const setupPositions = ['', 'locale', 'monthly_goal', 'home_country']; - const accordionPanelPositions = [ - '', - 'locale', - 'monthly goal', - 'home country', + const setupAccordions = [ + { setupPosition: '', accordionPanel: '' }, + { setupPosition: 'locale', accordionPanel: 'locale' }, + { setupPosition: 'monthly_goal', accordionPanel: 'monthly goal' }, + { setupPosition: 'home_country', accordionPanel: 'home country' }, ]; const [setup, setSetup] = useState(0); const [expandedPanel, setExpandedPanel] = useState( @@ -68,53 +72,8 @@ const Preferences: React.FC = () => { const countries = getCountries(); const timeZones = useGetTimezones(); - useEffect(() => { - suggestArticles('HS_SETTINGS_PREFERENCES_SUGGESTIONS'); - }, []); - - const handleAccordionChange = (panel: string) => { - const panelLowercase = panel.toLowerCase(); - setExpandedPanel(expandedPanel === panelLowercase ? '' : panelLowercase); - if (setup > 0) {handleSetupChange();} - }; - - const handleSetupChange = async () => { - const nextNav = setup + 1; - if (setupPositions.length === nextNav) { - setSetup(0); - push(`/accountLists/${accountListId}/settings/notifications`); - } else { - setSetup(nextNav); - setExpandedPanel(accordionPanelPositions[nextNav]); - // push({ - // pathname: pathname, - // query: { - // accountListId: accountListId, - // selectedTab: accordionPanelPositions[nextNav], - // }, - // }); - } - // await updateUser({ - // variables: { - // input: { - // attributes: { - // setup: position, - // }, - // }, - // }, - // onCompleted: () => { - // enqueueSnackbar(t('Saved successfully.'), { - // variant: 'success', - // }); - // handleAccordionChange(label); - // }, - // onError: () => { - // enqueueSnackbar(t('Saving failed.'), { - // variant: 'error', - // }); - // }, - // }); - }; + const { data: userOptions } = useGetUserOptionsQuery(); + const [updateUserOptions] = useUpdateUserOptionsMutation(); const { data: personalPreferencesData, loading: personalPreferencesLoading } = useGetPersonalPreferencesQuery({ @@ -138,17 +97,67 @@ const Preferences: React.FC = () => { const { data: userOrganizationAccountsData } = useGetUsersOrganizationsAccountsQuery(); - let setupData = personalPreferencesData?.user.setup; - // TODO: get actual setup data - setupData = 'locale'; + const savedSetupPosition = userOptions?.userOptions.find( + (option) => option.key === 'setup_position', + )?.value; + const isSettingUp = savedSetupPosition === 'preferences.personal'; + + useEffect(() => { + suggestArticles('HS_SETTINGS_PREFERENCES_SUGGESTIONS'); + }, []); useEffect(() => { - if (setupData && setupData !== 'finish') { - const position = setupPositions.indexOf(setupData); - setSetup(position); - setExpandedPanel(accordionPanelPositions[position]); + if (isSettingUp) { + setSetup(1); + setExpandedPanel(setupAccordions[1]?.accordionPanel); + } else { + setSetup(0); } - }, [setupData]); + }, [isSettingUp]); + + const handleAccordionChange = (panel: string) => { + const panelLowercase = panel.toLowerCase(); + setExpandedPanel(expandedPanel === panelLowercase ? '' : panelLowercase); + }; + + const handleSetupChange = async () => { + if (!isSettingUp) { + return; + } + const nextNav = setup + 1; + + if (setupAccordions.length === nextNav) { + setSetup(0); + await updateUserOptions({ + variables: { + key: 'setup_position', + value: 'preferences.notifications', + }, + onError: () => { + enqueueSnackbar(t('Saving setup phase failed.'), { + variant: 'error', + }); + }, + }); + push(`/accountLists/${accountListId}/settings/notifications`); + } else { + setSetup(nextNav); + setExpandedPanel(setupAccordions[nextNav].accordionPanel); + } + }; + + const getSetupMessage = (setup) => { + switch (setup) { + case 1: + return t("Let's set your locale!"); + case 2: + return t('Great progress comes from great goals!'); + case 3: + return t('What country are you in?'); + default: + return ''; + } + }; return ( { pageHeading={t('Preferences')} selectedMenuId={'preferences'} > - - , - }} - action={ - - } - sx={{ marginY: 2 }} - > - {setup === 1 && ( - {t("Let's set your locale!")} - )} - {setup === 2 && ( - - {t('Great progress comes from great goals!')} - - )} - {setup === 3 && ( - - {t('What Country are you in?')} - - )} - - + {isSettingUp && ( + + + {t('Skip Step')} + + } + title={getSetupMessage(setup)} + /> + + )} {personalPreferencesLoading && ( @@ -202,7 +194,7 @@ const Preferences: React.FC = () => { handleAccordionChange={handleAccordionChange} expandedPanel={expandedPanel} locale={personalPreferencesData?.user?.preferences?.locale || ''} - disabled={!!setup} + disabled={isSettingUp} /> { localeDisplay={ personalPreferencesData?.user?.preferences?.localeDisplay || '' } - disabled={!!setup && setup !== 1} + disabled={isSettingUp && setup !== 1} + handleSetupChange={handleSetupChange} /> { defaultAccountList={ personalPreferencesData?.user?.defaultAccountList || '' } - disabled={!!setup} + disabled={isSettingUp} /> { personalPreferencesData?.user?.preferences?.timeZone || '' } timeZones={timeZones} - disabled={!!setup} + disabled={isSettingUp} /> { personalPreferencesData?.user?.preferences ?.hourToSendNotifications || null } - disabled={!!setup} + disabled={isSettingUp} /> )} @@ -260,7 +253,7 @@ const Preferences: React.FC = () => { expandedPanel={expandedPanel} name={accountPreferencesData?.accountList?.name || ''} accountListId={accountListId} - disabled={!!setup} + disabled={isSettingUp} /> { currency={ accountPreferencesData?.accountList?.settings?.currency || '' } - disabled={!!setup && setup !== 2} + disabled={isSettingUp && setup !== 2} + handleSetupChange={handleSetupChange} /> { } accountListId={accountListId} countries={countries} - disabled={!!setup && setup !== 3} + disabled={isSettingUp && setup !== 3} + handleSetupChange={handleSetupChange} /> { accountPreferencesData?.accountList?.settings?.currency || '' } accountListId={accountListId} + disabled={isSettingUp} /> {userOrganizationAccountsData?.userOrganizationAccounts && userOrganizationAccountsData?.userOrganizationAccounts?.length > @@ -305,6 +301,7 @@ const Preferences: React.FC = () => { '' } accountListId={accountListId} + disabled={isSettingUp} /> )} { accountPreferencesData?.accountList?.settings?.tester || false } accountListId={accountListId} + disabled={isSettingUp} /> { accountPreferencesData?.accountList?.settings?.currency || '' } accountListId={accountListId} + disabled={isSettingUp} /> {canUserExportData?.canUserExportData.allowed && ( { } accountListId={accountListId} data={personalPreferencesData} + disabled={isSettingUp} /> )} diff --git a/src/components/Settings/preferences/SetupBanner.tsx b/src/components/Settings/preferences/SetupBanner.tsx new file mode 100644 index 000000000..31a20784f --- /dev/null +++ b/src/components/Settings/preferences/SetupBanner.tsx @@ -0,0 +1,28 @@ +import CampaignIcon from '@mui/icons-material/Campaign'; +import { Alert, Typography } from '@mui/material'; + +interface SetupBannerProps { + button?: React.ReactNode; + content?: React.ReactNode; + title?: string; +} + +export const SetupBanner: React.FC = ({ + button, + content, + title, +}) => { + return ( + , + }} + action={button} + sx={{ marginBottom: 2 }} + > + {title && {title}} + {content} + + ); +}; diff --git a/src/components/Settings/preferences/accordions/CurrencyAccordion/CurrencyAccordion.tsx b/src/components/Settings/preferences/accordions/CurrencyAccordion/CurrencyAccordion.tsx index fd5470035..488e0233c 100644 --- a/src/components/Settings/preferences/accordions/CurrencyAccordion/CurrencyAccordion.tsx +++ b/src/components/Settings/preferences/accordions/CurrencyAccordion/CurrencyAccordion.tsx @@ -22,6 +22,7 @@ interface CurrencyAccordionProps { expandedPanel: string; currency: string; accountListId: string; + disabled?: boolean; } export const CurrencyAccordion: React.FC = ({ @@ -29,6 +30,7 @@ export const CurrencyAccordion: React.FC = ({ expandedPanel, currency, accountListId, + disabled, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -73,6 +75,7 @@ export const CurrencyAccordion: React.FC = ({ label={label} value={currency} fullWidth + disabled={disabled} > = ({ @@ -30,6 +31,7 @@ export const EarlyAdopterAccordion: React.FC = ({ expandedPanel, tester, accountListId, + disabled, }) => { const { t } = useTranslation(); const { userId } = useUserPreferenceContext(); @@ -90,6 +92,7 @@ export const EarlyAdopterAccordion: React.FC = ({ label={label} value={tester ? t('Yes') : t('No')} fullWidth + disabled={disabled} > = ({ @@ -33,6 +34,7 @@ export const ExportAllDataAccordion: React.FC = ({ expandedPanel, exportedAt, accountListId, + disabled, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -81,6 +83,7 @@ export const ExportAllDataAccordion: React.FC = ({ label={label} value={''} fullWidth + disabled={disabled} >
diff --git a/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.test.tsx b/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.test.tsx index d1dc8d272..f71a77e67 100644 --- a/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.test.tsx +++ b/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.test.tsx @@ -30,6 +30,7 @@ jest.mock('notistack', () => ({ })); const handleAccordionChange = jest.fn(); +const handleSetupChange = jest.fn(); const mutationSpy = jest.fn(); const countries = [ @@ -56,6 +57,7 @@ const Components: React.FC = ({ homeCountry={homeCountry} accountListId={accountListId} countries={countries} + handleSetupChange={handleSetupChange} /> @@ -146,6 +148,7 @@ describe('HomeCountryAccordion', () => { homeCountry={'USA'} accountListId={accountListId} countries={countries} + handleSetupChange={handleSetupChange} /> diff --git a/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.tsx b/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.tsx index a241cf593..2d54b38bd 100644 --- a/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.tsx +++ b/src/components/Settings/preferences/accordions/HomeCountryAccordion/HomeCountryAccordion.tsx @@ -23,6 +23,7 @@ interface HomeCountryAccordionProps { accountListId: string; countries: { name: string; code: string }[]; disabled?: boolean; + handleSetupChange: () => Promise; } export const HomeCountryAccordion: React.FC = ({ @@ -32,6 +33,7 @@ export const HomeCountryAccordion: React.FC = ({ accountListId, countries, disabled, + handleSetupChange, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -71,6 +73,7 @@ export const HomeCountryAccordion: React.FC = ({ }); }, }); + handleSetupChange(); }; return ( diff --git a/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.test.tsx b/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.test.tsx index dea360b00..0cded1320 100644 --- a/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.test.tsx +++ b/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.test.tsx @@ -28,6 +28,7 @@ jest.mock('notistack', () => ({ })); const handleAccordionChange = jest.fn(); +const handleSetupChange = jest.fn(); const mutationSpy = jest.fn(); interface ComponentsProps { @@ -72,6 +73,7 @@ const Components: React.FC = ({ handleAccordionChange={handleAccordionChange} expandedPanel={expandedPanel} localeDisplay={localeDisplay} + handleSetupChange={handleSetupChange} /> diff --git a/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.tsx b/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.tsx index 39f4238b7..eee84b963 100644 --- a/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.tsx +++ b/src/components/Settings/preferences/accordions/LocaleAccordion/LocaleAccordion.tsx @@ -21,6 +21,7 @@ interface LocaleAccordionProps { expandedPanel: string; localeDisplay: string; disabled?: boolean; + handleSetupChange: () => Promise; } export const LocaleAccordion: React.FC = ({ @@ -28,6 +29,7 @@ export const LocaleAccordion: React.FC = ({ expandedPanel, localeDisplay, disabled, + handleSetupChange, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -73,6 +75,7 @@ export const LocaleAccordion: React.FC = ({ }); }, }); + handleSetupChange(); }; return ( diff --git a/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.test.tsx b/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.test.tsx index 387b193a8..4ba69eba8 100644 --- a/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.test.tsx +++ b/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.test.tsx @@ -28,6 +28,7 @@ jest.mock('notistack', () => ({ })); const handleAccordionChange = jest.fn(); +const handleSetupChange = jest.fn(); const mutationSpy = jest.fn(); interface ComponentsProps { @@ -49,6 +50,7 @@ const Components: React.FC = ({ monthlyGoal={monthlyGoal} currency={'USD'} accountListId={accountListId} + handleSetupChange={handleSetupChange} /> diff --git a/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.tsx b/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.tsx index c5fc98a4f..b944ed92e 100644 --- a/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.tsx +++ b/src/components/Settings/preferences/accordions/MonthlyGoalAccordion/MonthlyGoalAccordion.tsx @@ -25,6 +25,7 @@ interface MonthlyGoalAccordionProps { accountListId: string; currency: string; disabled?: boolean; + handleSetupChange: () => Promise; } export const MonthlyGoalAccordion: React.FC = ({ @@ -34,6 +35,7 @@ export const MonthlyGoalAccordion: React.FC = ({ accountListId, currency, disabled, + handleSetupChange, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -74,6 +76,7 @@ export const MonthlyGoalAccordion: React.FC = ({ }); }, }); + handleSetupChange(); }; return ( @@ -126,6 +129,7 @@ export const MonthlyGoalAccordion: React.FC = ({ autoFocus label={label} sx={{ marginTop: 1 }} + id="monthlyGoalInput" /> diff --git a/src/components/Settings/preferences/accordions/MpdInfoAccordion/MpdInfoAccordion.tsx b/src/components/Settings/preferences/accordions/MpdInfoAccordion/MpdInfoAccordion.tsx index 0d38d3e79..40f720297 100644 --- a/src/components/Settings/preferences/accordions/MpdInfoAccordion/MpdInfoAccordion.tsx +++ b/src/components/Settings/preferences/accordions/MpdInfoAccordion/MpdInfoAccordion.tsx @@ -37,6 +37,7 @@ interface MpdInfoAccordionProps { activeMpdFinishAt: string | null; currency: string; accountListId: string; + disabled?: boolean; } export const MpdInfoAccordion: React.FC = ({ @@ -47,6 +48,7 @@ export const MpdInfoAccordion: React.FC = ({ activeMpdFinishAt, currency, accountListId, + disabled, }) => { const { t } = useTranslation(); const locale = useLocale(); @@ -111,6 +113,7 @@ export const MpdInfoAccordion: React.FC = ({ label={label} value={goalDateString} fullWidth + disabled={disabled} > = ({ @@ -31,6 +32,7 @@ export const PrimaryOrgAccordion: React.FC = ({ organizations, salaryOrganizationId, accountListId, + disabled, }) => { const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); @@ -79,6 +81,7 @@ export const PrimaryOrgAccordion: React.FC = ({ label={label} value={selectedOrgName} fullWidth + disabled={disabled} > ({ + '&.MuiAccordion-rounded.Mui-disabled': { + color: theme.palette.cruGrayDark, + backgroundColor: 'white', + }, overflow: 'hidden', border: `1px solid ${theme.palette.divider}`, '&:not(:last-child)': { @@ -135,7 +139,7 @@ export const AccordionItem: React.FC = ({ disabled, }) => { const expanded = useMemo( - () => expandedPanel.toLowerCase() === label.toLowerCase(), + () => expandedPanel?.toLowerCase() === label.toLowerCase(), [expandedPanel, label], ); return ( From 914754e676b0b753d75751894471770cb9dc0a3a Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 21 Aug 2024 14:47:08 -0700 Subject: [PATCH 3/9] Add tests --- .../settings/preferences.page.test.tsx | 108 +++++++++++++++++- .../Shared/Forms/Accordions/AccordionItem.tsx | 8 +- 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx index 70c3d0ad5..14f66b175 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx @@ -1,6 +1,7 @@ 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 theme from 'src/theme'; @@ -8,13 +9,16 @@ import Preferences from './preferences.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, }; -const mockEnqueue = jest.fn(); - jest.mock('notistack', () => ({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -30,6 +34,7 @@ const MocksProviders = (props: { children: JSX.Element; canUserExportData: boolean; singleOrg?: boolean; + setup?: string; }) => ( @@ -108,7 +113,17 @@ const MocksProviders = (props: { exportedAt: null, }, }, + GetUserOptions: { + userOptions: [ + { + id: 1, + key: 'setup_position', + value: props.setup || 'finish', + }, + ], + }, }} + onCall={mutationSpy} > {props.children} @@ -166,4 +181,93 @@ describe('Preferences page', () => { expect(queryByText('Primary Organization')).not.toBeInTheDocument(), ); }); + describe('Setup Flow', () => { + it('should not show setup banner and accordions should not be disabled', async () => { + const { queryByText, queryByRole, findByText, getByText } = render( + + + , + ); + + await waitFor(() => { + expect(queryByText("Let's set your locale!")).not.toBeInTheDocument(); + expect( + queryByRole('button', { name: 'Skip Step' }), + ).not.toBeInTheDocument(); + }); + + //Accordions should be clickable + userEvent.click(await findByText('Language')); + await waitFor(() => { + expect( + getByText('The language determines your default language for .'), + ).toBeVisible(); + }); + }); + + it('should show setup banner and open locale', async () => { + const { findByText, getByRole, queryByText, getByText } = render( + + + , + ); + + //Accordions should be disabled + await waitFor(() => { + const label = getByText('Language'); + expect(() => userEvent.click(label)).toThrow(); + expect( + queryByText('The language determines your default language for .'), + ).not.toBeInTheDocument(); + }); + + // Start with Locale + expect(await findByText("Let's set your locale!")).toBeInTheDocument(); + expect( + await findByText( + 'The locale determines how numbers, dates and other information are formatted.', + ), + ).toBeInTheDocument(); + + // Moves to Monthly Goal + userEvent.click(getByRole('button', { name: 'Save' })); + expect( + await findByText('Great progress comes from great goals!'), + ).toBeInTheDocument(); + expect( + await findByText( + 'This amount should be set to the amount your organization has determined is your target monthly goal. If you do not know, make your best guess for now. You can change it at any time.', + ), + ).toBeInTheDocument(); + + // Home Country + const skipButton = getByRole('button', { name: 'Skip Step' }); + userEvent.click(skipButton); + expect( + await findByText( + 'This should be the place from which you are living and sending out physical communications. This will be used in exports for mailing address information.', + ), + ).toBeInTheDocument(); + + // Move to Notifications + userEvent.click(skipButton); + await waitFor(() => { + expect(mutationSpy).toHaveGraphqlOperation('UpdateUserOptions', { + key: 'setup_position', + value: 'preferences.notifications', + }); + expect(push).toHaveBeenCalledWith( + '/accountLists/account-list-1/settings/notifications', + ); + }); + }); + }); }); diff --git a/src/components/Shared/Forms/Accordions/AccordionItem.tsx b/src/components/Shared/Forms/Accordions/AccordionItem.tsx index 00211e223..d5e13f9a0 100644 --- a/src/components/Shared/Forms/Accordions/AccordionItem.tsx +++ b/src/components/Shared/Forms/Accordions/AccordionItem.tsx @@ -19,15 +19,15 @@ export const accordionShared = { }; const StyledAccordion = styled(Accordion)(({ theme }) => ({ - '&.MuiAccordion-rounded.Mui-disabled': { - color: theme.palette.cruGrayDark, - backgroundColor: 'white', - }, overflow: 'hidden', border: `1px solid ${theme.palette.divider}`, '&:not(:last-child)': { borderBottom: 0, }, + '&.MuiAccordion-rounded.Mui-disabled': { + color: theme.palette.cruGrayDark, + backgroundColor: 'white', + }, ...accordionShared, })); From cddb0e1ff83473c203efccf9adeef2fff84d604f Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 21 Aug 2024 17:25:49 -0700 Subject: [PATCH 4/9] Notifications Welcome tour --- .../settings/notifications.page.test.tsx | 141 ++++++++++++++++++ .../settings/notifications.page.tsx | 53 ++++++- .../notifications/NotificationsTable.test.tsx | 49 +----- .../notifications/NotificationsTable.tsx | 9 +- .../notificationSettingsMocks.ts | 43 ++++++ 5 files changed, 250 insertions(+), 45 deletions(-) create mode 100644 pages/accountLists/[accountListId]/settings/notifications.page.test.tsx create mode 100644 src/components/Settings/notifications/notificationSettingsMocks.ts diff --git a/pages/accountLists/[accountListId]/settings/notifications.page.test.tsx b/pages/accountLists/[accountListId]/settings/notifications.page.test.tsx new file mode 100644 index 000000000..22a874c11 --- /dev/null +++ b/pages/accountLists/[accountListId]/settings/notifications.page.test.tsx @@ -0,0 +1,141 @@ +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 { notificationSettingsMocks } from 'src/components/Settings/notifications/notificationSettingsMocks'; +import theme from 'src/theme'; +import Notifications from './notifications.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('notistack', () => ({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ...jest.requireActual('notistack'), + useSnackbar: () => { + return { + enqueueSnackbar: mockEnqueue, + }; + }, +})); + +const MocksProviders = (props: { children: JSX.Element; setup?: string }) => ( + + + + {props.children} + + + +); + +describe('Notifications page', () => { + it('should render page', async () => { + const { findByText, getByTestId, queryByTestId } = render( + + + , + ); + await waitFor(() => { + expect(queryByTestId('skeleton-notifications')).not.toBeInTheDocument(); + }); + expect(await findByText('Notifications')).toBeInTheDocument(); + expect(getByTestId('select-all-app')).toBeInTheDocument(); + }); + + describe('Setup Tour', () => { + it('should not show setup banner', async () => { + const { queryByText, findByText } = render( + + + , + ); + + expect(await findByText('Notifications')).toBeInTheDocument(); + await waitFor(() => { + expect( + queryByText('Setup your notifications here'), + ).not.toBeInTheDocument(); + }); + }); + + it('should show setup banner move to the next part', async () => { + const { findByText, getByRole } = render( + + + , + ); + + expect( + await findByText('Setup your notifications here'), + ).toBeInTheDocument(); + + const skipButton = getByRole('button', { name: 'Skip Step' }); + userEvent.click(skipButton); + + // Move to Integrations + userEvent.click(skipButton); + await waitFor(() => { + expect(mutationSpy).toHaveGraphqlOperation('UpdateUserOptions', { + key: 'setup_position', + value: 'preferences.integrations', + }); + expect(push).toHaveBeenCalledWith( + '/accountLists/account-list-1/settings/integrations', + ); + }); + }); + + it('moves to the next section with Save Button', async () => { + const { getAllByRole, findByText } = render( + + + , + ); + + expect( + await findByText('Setup your notifications here'), + ).toBeInTheDocument(); + + const saveButton = getAllByRole('button', { name: 'Save Changes' })[0]; + + // Move to Integrations + userEvent.click(saveButton); + await waitFor(() => { + expect(mutationSpy).toHaveGraphqlOperation('UpdateUserOptions', { + key: 'setup_position', + value: 'preferences.integrations', + }); + expect(push).toHaveBeenCalledWith( + '/accountLists/account-list-1/settings/integrations', + ); + }); + }); + }); +}); diff --git a/pages/accountLists/[accountListId]/settings/notifications.page.tsx b/pages/accountLists/[accountListId]/settings/notifications.page.tsx index b89e3f5eb..9d38070ec 100644 --- a/pages/accountLists/[accountListId]/settings/notifications.page.tsx +++ b/pages/accountLists/[accountListId]/settings/notifications.page.tsx @@ -1,14 +1,51 @@ +import { useRouter } from 'next/router'; import React from 'react'; -import { Box } from '@mui/material'; +import { Box, 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 { NotificationsTable } from 'src/components/Settings/notifications/NotificationsTable'; +import { SetupBanner } from 'src/components/Settings/preferences/SetupBanner'; +import { useAccountListId } from 'src/hooks/useAccountListId'; import useGetAppSettings from 'src/hooks/useGetAppSettings'; import { SettingsWrapper } from './Wrapper'; +import { StickyBox } from './preferences.page'; const Notifications: React.FC = () => { const { t } = useTranslation(); const { appName } = useGetAppSettings(); + const accountListId = useAccountListId() || ''; + const { push } = useRouter(); + const { enqueueSnackbar } = useSnackbar(); + + const { data: userOptions } = useGetUserOptionsQuery(); + const [updateUserOptions] = useUpdateUserOptionsMutation(); + + const savedSetupPosition = userOptions?.userOptions.find( + (option) => option.key === 'setup_position', + )?.value; + const isSettingUp = savedSetupPosition === 'preferences.notifications'; + + const handleSetupChange = async () => { + if (!isSettingUp) { + return; + } + + await updateUserOptions({ + variables: { + key: 'setup_position', + value: 'preferences.integrations', + }, + onError: () => { + enqueueSnackbar(t('Saving setup phase failed.'), { + variant: 'error', + }); + }, + }); + push(`/accountLists/${accountListId}/settings/integrations`); + }; return ( { pageHeading={t('Notifications')} selectedMenuId="notifications" > + {isSettingUp && ( + + + {t('Skip Step')} + + } + title={t('Setup your notifications here')} + /> + + )}

{t( @@ -38,7 +87,7 @@ const Notifications: React.FC = () => { )}

- +
); }; diff --git a/src/components/Settings/notifications/NotificationsTable.test.tsx b/src/components/Settings/notifications/NotificationsTable.test.tsx index 9bb6b5cb0..82eb6fce6 100644 --- a/src/components/Settings/notifications/NotificationsTable.test.tsx +++ b/src/components/Settings/notifications/NotificationsTable.test.tsx @@ -5,9 +5,9 @@ import userEvent from '@testing-library/user-event'; import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; -import { NotificationTypeTypeEnum } from 'src/graphql/types.generated'; import theme from '../../../theme'; import { NotificationsTable } from './NotificationsTable'; +import { notificationSettingsMocks } from './notificationSettingsMocks'; const mockEnqueue = jest.fn(); @@ -28,53 +28,18 @@ const router = { query: { accountListId }, isReady: true, }; -const createNotification = (type, id) => ({ - app: false, - email: false, - task: false, - notificationType: { - id, - descriptionTemplate: type, - type, - }, -}); -const createNotificationType = (type, id) => ({ - id: id, - type: type, - descriptionTemplate: type, -}); -const mocks = { - NotificationsPreferences: { - notificationPreferences: { - nodes: [ - createNotification( - NotificationTypeTypeEnum.CallPartnerOncePerYear, - '111', - ), - createNotification(NotificationTypeTypeEnum.LargerGift, '222'), - createNotification(NotificationTypeTypeEnum.LongTimeFrameGift, '333'), - ], - }, - }, - NotificationTypes: { - notificationTypes: [ - createNotificationType( - NotificationTypeTypeEnum.CallPartnerOncePerYear, - '111', - ), - createNotificationType(NotificationTypeTypeEnum.LargerGift, '222'), - createNotificationType(NotificationTypeTypeEnum.LongTimeFrameGift, '333'), - ], - }, -}; const mutationSpy = jest.fn(); +const handleSetupChange = jest.fn(); const Components: React.FC = () => ( - - + + diff --git a/src/components/Settings/notifications/NotificationsTable.tsx b/src/components/Settings/notifications/NotificationsTable.tsx index 53a0a1310..32470a542 100644 --- a/src/components/Settings/notifications/NotificationsTable.tsx +++ b/src/components/Settings/notifications/NotificationsTable.tsx @@ -73,7 +73,13 @@ const notificationSchema: yup.SchemaOf<{ ), }); -export const NotificationsTable: React.FC = () => { +interface NotificationsTableProps { + handleSetupChange: () => Promise; +} + +export const NotificationsTable: React.FC = ({ + handleSetupChange, +}) => { const { t } = useTranslation(); const accountListId = useAccountListId(); const { enqueueSnackbar } = useSnackbar(); @@ -160,6 +166,7 @@ export const NotificationsTable: React.FC = () => { enqueueSnackbar(t('Notifications updated successfully'), { variant: 'success', }); + handleSetupChange(); }; return ( diff --git a/src/components/Settings/notifications/notificationSettingsMocks.ts b/src/components/Settings/notifications/notificationSettingsMocks.ts new file mode 100644 index 000000000..a454b0f2f --- /dev/null +++ b/src/components/Settings/notifications/notificationSettingsMocks.ts @@ -0,0 +1,43 @@ +import { NotificationTypeTypeEnum } from 'src/graphql/types.generated'; + +const createNotification = (type, id) => ({ + app: false, + email: false, + task: false, + notificationType: { + id, + descriptionTemplate: type, + type, + }, +}); + +const createNotificationType = (type, id) => ({ + id: id, + type: type, + descriptionTemplate: type, +}); + +export const notificationSettingsMocks = { + NotificationsPreferences: { + notificationPreferences: { + nodes: [ + createNotification( + NotificationTypeTypeEnum.CallPartnerOncePerYear, + '111', + ), + createNotification(NotificationTypeTypeEnum.LargerGift, '222'), + createNotification(NotificationTypeTypeEnum.LongTimeFrameGift, '333'), + ], + }, + }, + NotificationTypes: { + notificationTypes: [ + createNotificationType( + NotificationTypeTypeEnum.CallPartnerOncePerYear, + '111', + ), + createNotificationType(NotificationTypeTypeEnum.LargerGift, '222'), + createNotificationType(NotificationTypeTypeEnum.LongTimeFrameGift, '333'), + ], + }, +}; From 71f9a9d4cdbbe978b94873853140ef4f927d1920 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 21 Aug 2024 18:15:51 -0700 Subject: [PATCH 5/9] Integrations Setup Welcome tour --- .../settings/integrations/index.page.test.tsx | 160 ++++++++++++++++++ .../settings/integrations/index.page.tsx | 80 ++++++++- .../Chalkline/ChalklineAccordion.tsx | 2 + .../integrations/Google/GoogleAccordion.tsx | 10 +- .../integrations/Key/TheKeyAccordion.tsx | 10 +- .../Mailchimp/MailchimpAccordion.tsx | 10 +- .../Organization/OrganizationAccordion.tsx | 11 +- .../Prayerletters/PrayerlettersAccordion.tsx | 2 + .../integrations/integrationsHelper.ts | 1 + 9 files changed, 257 insertions(+), 29 deletions(-) create mode 100644 pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx diff --git a/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx b/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx new file mode 100644 index 000000000..73e41d133 --- /dev/null +++ b/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx @@ -0,0 +1,160 @@ +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 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, + }; + }, +})); + +const MocksProviders = (props: { children: JSX.Element; setup?: string }) => ( + + + + {props.children} + + + +); + +describe('Connect Services page', () => { + beforeEach(() => { + (useGetAppSettings as jest.Mock).mockReturnValue({ + appName: 'MPDX', + }); + }); + it('should render', async () => { + const { findByText } = render( + + + , + ); + 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( + + + , + ); + + 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( + + + , + ); + 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 + await waitFor(() => 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/tools'); + }); + }); + }); +}); diff --git a/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx b/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx index 0a19bfd6e..5139f36ea 100644 --- a/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx +++ b/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx @@ -1,32 +1,84 @@ 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 savedSetupPosition = userOptions?.userOptions.find( + (option) => option.key === 'setup_position', + )?.value; + const isSettingUp = savedSetupPosition === '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}/tools`); + } 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 ( { pageHeading={t('Connect Services')} selectedMenuId="integrations" > + {isSettingUp && ( + + + {t('Next Step')} + + } + title={t('Make {{appName}} a part of your everyday life', { + appName, + })} + /> + + )} diff --git a/src/components/Settings/integrations/Chalkline/ChalklineAccordion.tsx b/src/components/Settings/integrations/Chalkline/ChalklineAccordion.tsx index 4dc4c8b6c..4e3ba3bfb 100644 --- a/src/components/Settings/integrations/Chalkline/ChalklineAccordion.tsx +++ b/src/components/Settings/integrations/Chalkline/ChalklineAccordion.tsx @@ -13,6 +13,7 @@ import { useSendToChalklineMutation } from './SendToChalkline.generated'; export const ChalklineAccordion: React.FC = ({ handleAccordionChange, expandedPanel, + disabled, }) => { const { t } = useTranslation(); const accordionName = t('Chalk Line'); @@ -54,6 +55,7 @@ export const ChalklineAccordion: React.FC = ({ expandedPanel={expandedPanel} label={accordionName} value={''} + disabled={disabled} image={ void; - expandedPanel: string; -} - const EditIconButton = styled(IconButton)(() => ({ color: theme.palette.primary.main, marginLeft: '10px', @@ -74,9 +70,10 @@ export type GoogleAccountAttributesSlimmed = Pick< 'id' | 'email' | 'primary' | 'remoteId' | 'tokenExpired' >; -export const GoogleAccordion: React.FC = ({ +export const GoogleAccordion: React.FC = ({ handleAccordionChange, expandedPanel, + disabled, }) => { const { t } = useTranslation(); const [openEditGoogleAccount, setOpenEditGoogleAccount] = useState(false); @@ -107,6 +104,7 @@ export const GoogleAccordion: React.FC = ({ expandedPanel={expandedPanel} label={t('Google')} value={''} + disabled={disabled} image={ void; - expandedPanel: string; -} - -export const TheKeyAccordion: React.FC = ({ +export const TheKeyAccordion: React.FC = ({ handleAccordionChange, expandedPanel, + disabled, }) => { const { t } = useTranslation(); const { data, loading } = useGetKeyAccountsQuery(); @@ -22,6 +19,7 @@ export const TheKeyAccordion: React.FC = ({ expandedPanel={expandedPanel} label={t('The Key / Relay')} value={''} + disabled={disabled} image={ void; - expandedPanel: string; -} - const StyledFormControlLabel = styled(FormControlLabel)(() => ({ flex: '0 1 50%', margin: '0 0 0 -11px', @@ -61,9 +57,10 @@ const StyledButton = styled(Button)(() => ({ marginLeft: '15px', })); -export const MailchimpAccordion: React.FC = ({ +export const MailchimpAccordion: React.FC = ({ handleAccordionChange, expandedPanel, + disabled, }) => { const { t } = useTranslation(); const [showSettings, setShowSettings] = useState(false); @@ -172,6 +169,7 @@ export const MailchimpAccordion: React.FC = ({ expandedPanel={expandedPanel} label={t('MailChimp')} value={''} + disabled={disabled} image={ void; - expandedPanel: string; -} - type OrganizationAccountPartial = GetUsersOrganizationsAccountsQuery['userOrganizationAccounts'][0]; @@ -83,9 +78,10 @@ export const getOrganizationType = ( return undefined; }; -export const OrganizationAccordion: React.FC = ({ +export const OrganizationAccordion: React.FC = ({ handleAccordionChange, expandedPanel, + disabled, }) => { const { t } = useTranslation(); const accountListId = useAccountListId(); @@ -183,6 +179,7 @@ export const OrganizationAccordion: React.FC = ({ expandedPanel={expandedPanel} label={t('Organization')} value={''} + disabled={disabled} image={ = ({ handleAccordionChange, expandedPanel, + disabled, }) => { const { t } = useTranslation(); const [isSaving, setIsSaving] = useState(false); @@ -91,6 +92,7 @@ export const PrayerlettersAccordion: React.FC = ({ expandedPanel={expandedPanel} label={accordionName} value={''} + disabled={disabled} image={ ({ export interface AccordionProps { handleAccordionChange: (panel: string) => void; expandedPanel: string; + disabled?: boolean; } From de3f4b57b121290ab1b5c9cd94caa9a250ca51c2 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 21 Aug 2024 18:27:12 -0700 Subject: [PATCH 6/9] Add Reset Welcome Tour Button --- .../settings/preferences.page.test.tsx | 26 +++++---- .../settings/preferences.page.tsx | 53 ++++++++++++------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx index 14f66b175..99b6397f5 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx @@ -181,17 +181,23 @@ describe('Preferences page', () => { expect(queryByText('Primary Organization')).not.toBeInTheDocument(), ); }); - describe('Setup Flow', () => { + + describe('Setup Tour', () => { it('should not show setup banner and accordions should not be disabled', async () => { - const { queryByText, queryByRole, findByText, getByText } = render( - - - , - ); + const { queryByText, queryByRole, findByText, getByText, getByRole } = + render( + + + , + ); + + expect( + getByRole('button', { name: 'Reset Welcome Tour' }), + ).toBeInTheDocument(); await waitFor(() => { expect(queryByText("Let's set your locale!")).not.toBeInTheDocument(); diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.tsx index 2a3614c01..07fa95cf4 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.tsx @@ -40,7 +40,7 @@ const AccordionLoading = styled(Skeleton)(() => ({ height: '48px', })); -const StickyBox = styled(Box)(({ theme }) => ({ +export const StickyBox = styled(Box)(({ theme }) => ({ position: 'sticky', top: theme.spacing(10), borderBottom: '1px solid', @@ -59,12 +59,7 @@ const Preferences: React.FC = () => { const { push, query } = useRouter(); const { enqueueSnackbar } = useSnackbar(); - const setupAccordions = [ - { setupPosition: '', accordionPanel: '' }, - { setupPosition: 'locale', accordionPanel: 'locale' }, - { setupPosition: 'monthly_goal', accordionPanel: 'monthly goal' }, - { setupPosition: 'home_country', accordionPanel: 'home country' }, - ]; + const setupAccordions = ['locale', 'monthly goal', 'home country']; const [setup, setSetup] = useState(0); const [expandedPanel, setExpandedPanel] = useState( typeof query.selectedTab === 'string' ? query.selectedTab : '', @@ -108,10 +103,7 @@ const Preferences: React.FC = () => { useEffect(() => { if (isSettingUp) { - setSetup(1); - setExpandedPanel(setupAccordions[1]?.accordionPanel); - } else { - setSetup(0); + setExpandedPanel(setupAccordions[0]); } }, [isSettingUp]); @@ -120,6 +112,21 @@ const Preferences: React.FC = () => { setExpandedPanel(expandedPanel === panelLowercase ? '' : panelLowercase); }; + const resetWelcomeTour = async () => { + await updateUserOptions({ + variables: { + key: 'setup_position', + value: 'start', + }, + onError: () => { + enqueueSnackbar(t('Resetting the welcome tour failed.'), { + variant: 'error', + }); + }, + }); + push(`/accountLists/${accountListId}/settings/preferences`); + }; + const handleSetupChange = async () => { if (!isSettingUp) { return; @@ -127,7 +134,6 @@ const Preferences: React.FC = () => { const nextNav = setup + 1; if (setupAccordions.length === nextNav) { - setSetup(0); await updateUserOptions({ variables: { key: 'setup_position', @@ -142,17 +148,17 @@ const Preferences: React.FC = () => { push(`/accountLists/${accountListId}/settings/notifications`); } else { setSetup(nextNav); - setExpandedPanel(setupAccordions[nextNav].accordionPanel); + setExpandedPanel(setupAccordions[nextNav]); } }; const getSetupMessage = (setup) => { switch (setup) { - case 1: + case 0: return t("Let's set your locale!"); - case 2: + case 1: return t('Great progress comes from great goals!'); - case 3: + case 2: return t('What country are you in?'); default: return ''; @@ -202,7 +208,7 @@ const Preferences: React.FC = () => { localeDisplay={ personalPreferencesData?.user?.preferences?.localeDisplay || '' } - disabled={isSettingUp && setup !== 1} + disabled={isSettingUp && setup !== 0} handleSetupChange={handleSetupChange} /> { currency={ accountPreferencesData?.accountList?.settings?.currency || '' } - disabled={isSettingUp && setup !== 2} + disabled={isSettingUp && setup !== 1} handleSetupChange={handleSetupChange} /> { } accountListId={accountListId} countries={countries} - disabled={isSettingUp && setup !== 3} + disabled={isSettingUp && setup !== 2} handleSetupChange={handleSetupChange} /> { )} + + + ); }; From 6badc94be9c2ea3b1c67967fa681a2faaa794694 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Tue, 3 Sep 2024 14:42:36 -0700 Subject: [PATCH 7/9] Clean up code and add types --- .../settings/integrations/index.page.test.tsx | 27 +++++++++---- .../settings/integrations/index.page.tsx | 9 +++-- .../settings/notifications.page.test.tsx | 26 ++++++++++--- .../settings/notifications.page.tsx | 9 +++-- .../settings/organizations.graphql | 8 ---- .../settings/preferences.page.test.tsx | 39 +++++++++++++++---- .../settings/preferences.page.tsx | 2 +- .../GetPersonalPreferences.graphql | 1 - .../Settings/preferences/SetupBanner.tsx | 4 +- 9 files changed, 84 insertions(+), 41 deletions(-) diff --git a/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx b/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx index 73e41d133..c6d8de0af 100644 --- a/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx @@ -4,6 +4,10 @@ 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'; @@ -31,11 +35,20 @@ jest.mock('notistack', () => ({ }; }, })); +interface MocksProvidersProps { + children: JSX.Element; + setup?: string; +} -const MocksProviders = (props: { children: JSX.Element; setup?: string }) => ( +const MocksProviders: React.FC = ({ children, setup }) => ( - mocks={{ GetUsersOrganizationsAccounts: { userOrganizationAccounts: [ @@ -52,16 +65,16 @@ const MocksProviders = (props: { children: JSX.Element; setup?: string }) => ( GetUserOptions: { userOptions: [ { - id: 1, + id: '1', key: 'setup_position', - value: props.setup || 'finish', + value: setup || 'finish', }, ], }, }} onCall={mutationSpy} > - {props.children} + {children} @@ -132,8 +145,8 @@ describe('Connect Services page', () => { // Start with Google expect(await findByText(/Add Account/i)).toBeInTheDocument(); - // // Moves to MailChimp - await waitFor(() => userEvent.click(nextButton)); + // Moves to MailChimp + userEvent.click(nextButton); expect(await findByText(/Connect MailChimp/i)).toBeInTheDocument(); // PrayerLetters.com diff --git a/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx b/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx index 5139f36ea..3e764f461 100644 --- a/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx +++ b/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx @@ -36,10 +36,11 @@ const Integrations: React.FC = () => { const { data: userOptions } = useGetUserOptionsQuery(); const [updateUserOptions] = useUpdateUserOptionsMutation(); - const savedSetupPosition = userOptions?.userOptions.find( - (option) => option.key === 'setup_position', - )?.value; - const isSettingUp = savedSetupPosition === 'preferences.integrations'; + const isSettingUp = userOptions?.userOptions.some( + (option) => + option.key === 'setup_position' && + option.value === 'preferences.integrations', + ); const handleSetupChange = async () => { if (!isSettingUp) { diff --git a/pages/accountLists/[accountListId]/settings/notifications.page.test.tsx b/pages/accountLists/[accountListId]/settings/notifications.page.test.tsx index 22a874c11..5d2ab2a00 100644 --- a/pages/accountLists/[accountListId]/settings/notifications.page.test.tsx +++ b/pages/accountLists/[accountListId]/settings/notifications.page.test.tsx @@ -4,6 +4,11 @@ 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 { + NotificationTypesQuery, + NotificationsPreferencesQuery, +} from 'src/components/Settings/notifications/Notifications.generated'; import { notificationSettingsMocks } from 'src/components/Settings/notifications/notificationSettingsMocks'; import theme from 'src/theme'; import Notifications from './notifications.page'; @@ -31,25 +36,34 @@ jest.mock('notistack', () => ({ }, })); -const MocksProviders = (props: { children: JSX.Element; setup?: string }) => ( +interface MocksProvidersProps { + children: JSX.Element; + setup?: string; +} + +const MocksProviders: React.FC = ({ children, setup }) => ( - mocks={{ - notificationSettingsMocks, + ...notificationSettingsMocks, GetUserOptions: { userOptions: [ { - id: 1, + id: '1', key: 'setup_position', - value: props.setup || 'finish', + value: setup || 'finish', }, ], }, }} onCall={mutationSpy} > - {props.children} + {children} diff --git a/pages/accountLists/[accountListId]/settings/notifications.page.tsx b/pages/accountLists/[accountListId]/settings/notifications.page.tsx index 9d38070ec..0909ed279 100644 --- a/pages/accountLists/[accountListId]/settings/notifications.page.tsx +++ b/pages/accountLists/[accountListId]/settings/notifications.page.tsx @@ -23,10 +23,11 @@ const Notifications: React.FC = () => { const { data: userOptions } = useGetUserOptionsQuery(); const [updateUserOptions] = useUpdateUserOptionsMutation(); - const savedSetupPosition = userOptions?.userOptions.find( - (option) => option.key === 'setup_position', - )?.value; - const isSettingUp = savedSetupPosition === 'preferences.notifications'; + const isSettingUp = userOptions?.userOptions.some( + (option) => + option.key === 'setup_position' && + option.value === 'preferences.notifications', + ); const handleSetupChange = async () => { if (!isSettingUp) { diff --git a/pages/accountLists/[accountListId]/settings/organizations.graphql b/pages/accountLists/[accountListId]/settings/organizations.graphql index 62418c601..ae0511e18 100644 --- a/pages/accountLists/[accountListId]/settings/organizations.graphql +++ b/pages/accountLists/[accountListId]/settings/organizations.graphql @@ -10,11 +10,3 @@ query Organizations { } } } - -mutation UpdateUser($input: UserUpdateMutationInput!) { - updateUser(input: $input) { - user { - setup - } - } -} diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx index 99b6397f5..8f0d60cad 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.test.tsx @@ -4,6 +4,16 @@ 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 { + CanUserExportDataQuery, + GetAccountPreferencesQuery, +} from 'src/components/Settings/preferences/GetAccountPreferences.generated'; +import { GetPersonalPreferencesQuery } from 'src/components/Settings/preferences/GetPersonalPreferences.generated'; +import { GetProfileInfoQuery } from 'src/components/Settings/preferences/GetProfileInfo.generated'; import theme from 'src/theme'; import Preferences from './preferences.page'; @@ -30,15 +40,31 @@ jest.mock('notistack', () => ({ }, })); -const MocksProviders = (props: { +interface MocksProvidersProps { children: JSX.Element; canUserExportData: boolean; singleOrg?: boolean; setup?: string; +} + +const MocksProviders: React.FC = ({ + children, + canUserExportData, + singleOrg, + setup, }) => ( - mocks={{ GetAccountPreferences: { user: { @@ -92,7 +118,7 @@ const MocksProviders = (props: { }, }, GetUsersOrganizationsAccounts: { - userOrganizationAccounts: props.singleOrg + userOrganizationAccounts: singleOrg ? [ { organization: {}, @@ -109,23 +135,22 @@ const MocksProviders = (props: { }, CanUserExportData: { canUserExportData: { - allowed: props.canUserExportData, + allowed: canUserExportData, exportedAt: null, }, }, GetUserOptions: { userOptions: [ { - id: 1, key: 'setup_position', - value: props.setup || 'finish', + value: setup || 'finish', }, ], }, }} onCall={mutationSpy} > - {props.children} + {children} diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.tsx index 07fa95cf4..3e783dfb6 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.tsx @@ -152,7 +152,7 @@ const Preferences: React.FC = () => { } }; - const getSetupMessage = (setup) => { + const getSetupMessage = (setup: number) => { switch (setup) { case 0: return t("Let's set your locale!"); diff --git a/src/components/Settings/preferences/GetPersonalPreferences.graphql b/src/components/Settings/preferences/GetPersonalPreferences.graphql index f6df29756..f4edf0544 100644 --- a/src/components/Settings/preferences/GetPersonalPreferences.graphql +++ b/src/components/Settings/preferences/GetPersonalPreferences.graphql @@ -2,7 +2,6 @@ query GetPersonalPreferences($accountListId: ID!) { user { id defaultAccountList - setup preferences { id timeZone diff --git a/src/components/Settings/preferences/SetupBanner.tsx b/src/components/Settings/preferences/SetupBanner.tsx index 31a20784f..f3cc3b1b5 100644 --- a/src/components/Settings/preferences/SetupBanner.tsx +++ b/src/components/Settings/preferences/SetupBanner.tsx @@ -15,9 +15,7 @@ export const SetupBanner: React.FC = ({ return ( , - }} + icon={} action={button} sx={{ marginBottom: 2 }} > From 6a620db289b14ce05f13a147c3941535daf51a56 Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Tue, 3 Sep 2024 15:01:57 -0700 Subject: [PATCH 8/9] Update redirect urls --- .../[accountListId]/settings/integrations/index.page.test.tsx | 4 +++- .../[accountListId]/settings/integrations/index.page.tsx | 2 +- .../[accountListId]/settings/preferences.page.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx b/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx index c6d8de0af..bbd309d04 100644 --- a/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx +++ b/pages/accountLists/[accountListId]/settings/integrations/index.page.test.tsx @@ -166,7 +166,9 @@ describe('Connect Services page', () => { key: 'setup_position', value: 'finish', }); - expect(push).toHaveBeenCalledWith('/accountLists/account-list-1/tools'); + expect(push).toHaveBeenCalledWith( + '/accountLists/account-list-1/setup/finish', + ); }); }); }); diff --git a/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx b/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx index 3e764f461..9a95f53ee 100644 --- a/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx +++ b/pages/accountLists/[accountListId]/settings/integrations/index.page.tsx @@ -60,7 +60,7 @@ const Integrations: React.FC = () => { }); }, }); - push(`/accountLists/${accountListId}/tools`); + push(`/accountLists/${accountListId}/setup/finish`); } else { setSetup(nextNav); setExpandedPanel(setupAccordions[nextNav]); diff --git a/pages/accountLists/[accountListId]/settings/preferences.page.tsx b/pages/accountLists/[accountListId]/settings/preferences.page.tsx index 3e783dfb6..f9030508f 100644 --- a/pages/accountLists/[accountListId]/settings/preferences.page.tsx +++ b/pages/accountLists/[accountListId]/settings/preferences.page.tsx @@ -124,7 +124,7 @@ const Preferences: React.FC = () => { }); }, }); - push(`/accountLists/${accountListId}/settings/preferences`); + push(`/accountLists/${accountListId}/setup/start`); }; const handleSetupChange = async () => { From 37331153f0fc07256371b7a8abe6e8db5576cbba Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Wed, 4 Sep 2024 09:01:52 -0700 Subject: [PATCH 9/9] empty