From 24aed1178f246952c2c2dd3b60f3bf001108c8d3 Mon Sep 17 00:00:00 2001
From: Caleb Cox
Date: Thu, 22 Aug 2024 16:36:17 -0500
Subject: [PATCH 1/4] Support customized privacy policy and terms of use URLs
---
README.md | 2 ++
next.config.js | 2 ++
.../ProfileMenuPanel/ProfileMenuPanel.tsx | 24 ++++++-------------
.../TopBar/Items/ProfileMenu/ProfileMenu.tsx | 21 +++++-----------
src/components/Shared/Links/Links.test.tsx | 20 ++++++++++++++++
src/components/Shared/Links/Links.tsx | 22 +++++++++++++++++
6 files changed, 59 insertions(+), 32 deletions(-)
create mode 100644 src/components/Shared/Links/Links.test.tsx
create mode 100644 src/components/Shared/Links/Links.tsx
diff --git a/README.md b/README.md
index ecdb55b9d..a7967b492 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,8 @@ Note: there is a test account you can use. Get this from another developer if yo
- `HS_HOME_SUGGESTIONS` - Comma-separated IDs of the HelpScout articles to suggest on the dashboard page
- `HS_REPORTS_SUGGESTIONS` - Comma-separated IDs of the HelpScout articles to suggest on the reports pages
- `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
#### Auth provider
diff --git a/next.config.js b/next.config.js
index cc1dacf82..86d0e4f0c 100644
--- a/next.config.js
+++ b/next.config.js
@@ -96,6 +96,8 @@ const config = {
process.env.HS_SETTINGS_SERVICES_SUGGESTIONS,
HS_SETUP_FIND_ORGANIZATION: process.env.HS_SETUP_FIND_ORGANIZATION,
ALERT_MESSAGE: process.env.ALERT_MESSAGE,
+ 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',
},
experimental: {
diff --git a/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx b/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx
index a856171c1..e3a2f1867 100644
--- a/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx
+++ b/src/components/Layouts/Primary/NavBar/NavTools/ProfileMenuPanel/ProfileMenuPanel.tsx
@@ -3,10 +3,14 @@ import React, { useState } from 'react';
import { useApolloClient } from '@apollo/client';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ChevronRight from '@mui/icons-material/ChevronRight';
-import { Box, Button, Drawer, List, Link as MuiLink } from '@mui/material';
+import { Box, Button, Drawer, List } from '@mui/material';
import { styled } from '@mui/material/styles';
import { signOut } from 'next-auth/react';
import { useTranslation } from 'react-i18next';
+import {
+ PrivacyPolicyLink,
+ TermsOfUseLink,
+} from 'src/components/Shared/Links/Links';
import { NextLinkComposed } from 'src/components/common/Links/NextLinkComposed';
import { clearDataDogUser } from 'src/lib/dataDog';
import { useAccountListId } from '../../../../../../hooks/useAccountListId';
@@ -222,23 +226,9 @@ export const ProfileMenuPanel: React.FC = () => {
{t('Sign Out')}
-
- {t('Privacy Policy')}
-
+
•
-
- {t('Terms of Use')}
-
+
diff --git a/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx b/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx
index 8aee0ec50..478cf794a 100644
--- a/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx
+++ b/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx
@@ -15,13 +15,16 @@ import {
ListItemText,
Menu,
MenuItem,
- Link as MuiLink,
Typography,
} from '@mui/material';
import { styled } from '@mui/material/styles';
import { signOut } from 'next-auth/react';
import { useSnackbar } from 'notistack';
import { useTranslation } from 'react-i18next';
+import {
+ PrivacyPolicyLink,
+ TermsOfUseLink,
+} from 'src/components/Shared/Links/Links';
import { AccountList } from 'src/graphql/types.generated';
import { useRequiredSession } from 'src/hooks/useRequiredSession';
import { clearDataDogUser } from 'src/lib/dataDog';
@@ -374,21 +377,9 @@ const ProfileMenu = (): ReactElement => {
)}
-
- {t('Privacy Policy')}
-
+
•
-
- {t('Terms of Use')}
-
+
>
diff --git a/src/components/Shared/Links/Links.test.tsx b/src/components/Shared/Links/Links.test.tsx
new file mode 100644
index 000000000..35356255a
--- /dev/null
+++ b/src/components/Shared/Links/Links.test.tsx
@@ -0,0 +1,20 @@
+import { render } from '@testing-library/react';
+import { PrivacyPolicyLink, TermsOfUseLink } from './Links';
+
+describe('PrivacyPolicyLink', () => {
+ it('uses the link from an environment variable', () => {
+ process.env.PRIVACY_POLICY_URL = 'privacy-policy.com';
+
+ const { getByRole } = render();
+ expect(getByRole('link')).toHaveAttribute('href', 'privacy-policy.com');
+ });
+});
+
+describe('TermsOfUseLink', () => {
+ it('uses the link from an environment variable', () => {
+ process.env.TERMS_OF_USE_URL = 'terms-of-use.com';
+
+ const { getByRole } = render();
+ expect(getByRole('link')).toHaveAttribute('href', 'terms-of-use.com');
+ });
+});
diff --git a/src/components/Shared/Links/Links.tsx b/src/components/Shared/Links/Links.tsx
new file mode 100644
index 000000000..f4a705db9
--- /dev/null
+++ b/src/components/Shared/Links/Links.tsx
@@ -0,0 +1,22 @@
+import { Link, LinkProps } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+
+export const PrivacyPolicyLink: React.FC = (props) => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('Privacy Policy')}
+
+ );
+};
+
+export const TermsOfUseLink: React.FC = (props) => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('Terms of Use')}
+
+ );
+};
From 1efbbbfc2443d46dc9016485793344ae5c897f8d Mon Sep 17 00:00:00 2001
From: Caleb Cox
Date: Thu, 22 Aug 2024 16:36:50 -0500
Subject: [PATCH 2/4] Create setup start page
---
pages/setup/start.page.test.tsx | 39 +++++++++
pages/setup/start.page.tsx | 103 +++++++++++++++++++++++
src/components/Setup/PageHeader.tsx | 30 +++++++
src/components/Setup/styledComponents.ts | 17 ++++
src/components/User/GetUser.graphql | 1 +
5 files changed, 190 insertions(+)
create mode 100644 pages/setup/start.page.test.tsx
create mode 100644 pages/setup/start.page.tsx
create mode 100644 src/components/Setup/PageHeader.tsx
create mode 100644 src/components/Setup/styledComponents.ts
diff --git a/pages/setup/start.page.test.tsx b/pages/setup/start.page.test.tsx
new file mode 100644
index 000000000..57b1d9d7d
--- /dev/null
+++ b/pages/setup/start.page.test.tsx
@@ -0,0 +1,39 @@
+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 StartPage from './start.page';
+
+const push = jest.fn();
+const router = {
+ push,
+};
+
+describe('Setup start page', () => {
+ it('autocomplete renders and button saves and advances to the next page', async () => {
+ Object.defineProperty(window, 'navigator', {
+ value: { ...window.navigator, language: 'fr-FR' },
+ });
+
+ const mutationSpy = jest.fn();
+ const { getByRole } = render(
+
+
+
+
+ ,
+ );
+
+ const autocomplete = getByRole('combobox');
+ expect(autocomplete).toHaveValue('French (français)');
+ userEvent.click(autocomplete);
+ userEvent.click(getByRole('option', { name: 'German (Deutsch)' }));
+ userEvent.click(getByRole('button', { name: "Let's Begin" }));
+ await waitFor(() =>
+ expect(mutationSpy).toHaveGraphqlOperation('UpdatePersonalPreferences', {
+ input: { attributes: { locale: 'de' } },
+ }),
+ );
+ expect(push).toHaveBeenCalledWith('/setup/connect');
+ });
+});
diff --git a/pages/setup/start.page.tsx b/pages/setup/start.page.tsx
new file mode 100644
index 000000000..5e6caef51
--- /dev/null
+++ b/pages/setup/start.page.tsx
@@ -0,0 +1,103 @@
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import React, { ReactElement, useState } from 'react';
+import { Autocomplete, TextField } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { useUpdatePersonalPreferencesMutation } from 'src/components/Settings/preferences/accordions/UpdatePersonalPreferences.generated';
+import { PageHeader } from 'src/components/Setup/PageHeader';
+import {
+ LargeButton,
+ PageWrapper,
+} from 'src/components/Setup/styledComponents';
+import {
+ PrivacyPolicyLink,
+ TermsOfUseLink,
+} from 'src/components/Shared/Links/Links';
+import useGetAppSettings from 'src/hooks/useGetAppSettings';
+import { formatLanguage, languages } from 'src/lib/data/languages';
+import { loadSession } from '../api/utils/pagePropsHelpers';
+
+// This is the first page of the tour, and it lets users choose their language. It is always shown.
+const StartPage = (): ReactElement => {
+ const { t } = useTranslation();
+ const { appName } = useGetAppSettings();
+ const { push } = useRouter();
+ const [savePreferences] = useUpdatePersonalPreferencesMutation();
+
+ const [locale, setLocale] = useState(
+ (typeof window === 'undefined'
+ ? null
+ : window.navigator.language.toLowerCase()) || 'en-us',
+ );
+
+ const handleSave = async () => {
+ await savePreferences({
+ variables: {
+ input: {
+ attributes: {
+ locale,
+ },
+ },
+ },
+ });
+ push('/setup/connect');
+ };
+
+ return (
+ <>
+
+
+ {appName} | {t('Setup - Start')}
+
+
+
+
+
+ {t(
+ `Developing a healthy team of ministry partners sets your ministry up to thrive.
+{{appName}} is designed to help you do the right things, with the right people at the right time to be fully funded.`,
+ { appName },
+ )}
+
+
+ {t(
+ `To get started, we're going to walk with you through a few key steps to set you up for success in {{appName}}!`,
+ { appName },
+ )}
+
{t(
`Developing a healthy team of ministry partners sets your ministry up to thrive.
@@ -93,7 +89,7 @@ const StartPage = (): ReactElement => {
{t("Let's Begin")}
-