diff --git a/pages/accountLists/[accountListId]/tools/csv.page.test.tsx b/pages/accountLists/[accountListId]/tools/import/csv.page.test.tsx
similarity index 100%
rename from pages/accountLists/[accountListId]/tools/csv.page.test.tsx
rename to pages/accountLists/[accountListId]/tools/import/csv.page.test.tsx
diff --git a/pages/accountLists/[accountListId]/tools/csv.page.tsx b/pages/accountLists/[accountListId]/tools/import/csv.page.tsx
similarity index 100%
rename from pages/accountLists/[accountListId]/tools/csv.page.tsx
rename to pages/accountLists/[accountListId]/tools/import/csv.page.tsx
diff --git a/pages/accountLists/[accountListId]/tools/import/google.page.test.tsx b/pages/accountLists/[accountListId]/tools/import/google.page.test.tsx
new file mode 100644
index 000000000..8349b9823
--- /dev/null
+++ b/pages/accountLists/[accountListId]/tools/import/google.page.test.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { ThemeProvider } from '@mui/material/styles';
+import { render } from '@testing-library/react';
+import TestRouter from '__tests__/util/TestRouter';
+import TestWrapper from '__tests__/util/TestWrapper';
+import theme from 'src/theme';
+import GoogleImportPage from './google.page';
+
+const accountListId = 'accountListId';
+const router = {
+ query: { accountListId },
+ isReady: true,
+};
+
+const RenderGoogleImportPage = () => (
+
+
+
+
+
+
+
+);
+describe('render', () => {
+ it('google import page', async () => {
+ const { findByText } = render();
+
+ expect(await findByText('Import from Google')).toBeVisible();
+ });
+});
diff --git a/pages/accountLists/[accountListId]/tools/import/google.page.tsx b/pages/accountLists/[accountListId]/tools/import/google.page.tsx
new file mode 100644
index 000000000..4e1e3cb49
--- /dev/null
+++ b/pages/accountLists/[accountListId]/tools/import/google.page.tsx
@@ -0,0 +1,33 @@
+import Head from 'next/head';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { loadSession } from 'pages/api/utils/pagePropsHelpers';
+import Loading from 'src/components/Loading';
+import GoogleImport from 'src/components/Tool/GoogleImport/GoogleImport';
+import { useAccountListId } from 'src/hooks/useAccountListId';
+import useGetAppSettings from 'src/hooks/useGetAppSettings';
+
+const GoogleImportPage: React.FC = () => {
+ const { t } = useTranslation();
+ const accountListId = useAccountListId();
+ const { appName } = useGetAppSettings();
+
+ return (
+ <>
+
+
+ {appName} | {t('Import from Google')}
+
+
+ {accountListId ? (
+
+ ) : (
+
+ )}
+ >
+ );
+};
+
+export const getServerSideProps = loadSession;
+
+export default GoogleImportPage;
diff --git a/pages/accountLists/[accountListId]/tools/tntConnect.page.tsx b/pages/accountLists/[accountListId]/tools/import/tnt.page.tsx
similarity index 100%
rename from pages/accountLists/[accountListId]/tools/tntConnect.page.tsx
rename to pages/accountLists/[accountListId]/tools/import/tnt.page.tsx
diff --git a/pages/api/Schema/Settings/Integrations/Google/googleAccounts/datahandler.ts b/pages/api/Schema/Settings/Integrations/Google/googleAccounts/datahandler.ts
index f19c4a147..fe04d953d 100644
--- a/pages/api/Schema/Settings/Integrations/Google/googleAccounts/datahandler.ts
+++ b/pages/api/Schema/Settings/Integrations/Google/googleAccounts/datahandler.ts
@@ -1,14 +1,16 @@
-import { snakeToCamel } from 'src/lib/snakeToCamel';
+import { fetchAllData } from 'src/lib/deserializeJsonApi';
export interface GoogleAccountsResponse {
- attributes: Omit;
- id: string;
- relationships: {
- contact_groups: {
- data: unknown[];
+ data: {
+ attributes: Omit;
+ id: string;
+ relationships: {
+ contact_groups: {
+ data: unknown[];
+ };
};
+ type: string;
};
- type: string;
}
export interface GoogleAccountAttributes {
@@ -37,17 +39,24 @@ interface GoogleAccountAttributesCamel {
tokenExpired: boolean;
updatedAt: string;
updatedInDbAt: string;
+ contactGroups: ContactGroupCamel[];
}
-export const GoogleAccounts = (
- data: GoogleAccountsResponse[],
-): GoogleAccountAttributesCamel[] => {
- return data.map((accounts) => {
- const attributes = {} as Omit;
- Object.keys(accounts.attributes).map((key) => {
- attributes[snakeToCamel(key)] = accounts.attributes[key];
- });
+type ContactGroupCamel = {
+ id: string;
+ createdAt: string;
+ tag: string;
+ title: string;
+ updatedAt: string;
+ updatedInDbAt: string;
+};
- return { id: accounts.id, ...attributes };
+export const GoogleAccounts = (response): GoogleAccountAttributesCamel[] => {
+ return response.data.map((account) => {
+ const attributes = fetchAllData(account, response.included) as Omit<
+ GoogleAccountAttributesCamel,
+ 'id'
+ >;
+ return { id: account.id, ...attributes };
});
};
diff --git a/pages/api/Schema/Settings/Integrations/Google/googleAccounts/googleAccounts.graphql b/pages/api/Schema/Settings/Integrations/Google/googleAccounts/googleAccounts.graphql
index 38ae1cc19..7d7b50be8 100644
--- a/pages/api/Schema/Settings/Integrations/Google/googleAccounts/googleAccounts.graphql
+++ b/pages/api/Schema/Settings/Integrations/Google/googleAccounts/googleAccounts.graphql
@@ -14,4 +14,14 @@ type GoogleAccountAttributes {
tokenExpired: Boolean!
updatedAt: String!
updatedInDbAt: String!
+ contactGroups: [ContactGroup]!
+}
+
+type ContactGroup {
+ id: String!
+ createdAt: String
+ tag: String
+ title: String
+ updatedAt: String
+ updatedInDbAt: String
}
diff --git a/pages/api/graphql-rest.page.ts b/pages/api/graphql-rest.page.ts
index 2e3bc3fdc..2ddffbdeb 100644
--- a/pages/api/graphql-rest.page.ts
+++ b/pages/api/graphql-rest.page.ts
@@ -900,14 +900,14 @@ class MpdxRestApi extends RESTDataSource {
//
async googleAccounts() {
- const { data }: { data: GoogleAccountsResponse[] } = await this.get(
+ const response: GoogleAccountsResponse[] = await this.get(
'user/google_accounts',
{
sort: 'created_at',
include: 'contact_groups',
},
);
- return GoogleAccounts(data);
+ return GoogleAccounts(response);
}
async googleAccountIntegrations(
diff --git a/src/components/Settings/integrations/Google/Modals/DeleteGoogleAccountModal.tsx b/src/components/Settings/integrations/Google/Modals/DeleteGoogleAccountModal.tsx
index d01a82266..9875fdfdb 100644
--- a/src/components/Settings/integrations/Google/Modals/DeleteGoogleAccountModal.tsx
+++ b/src/components/Settings/integrations/Google/Modals/DeleteGoogleAccountModal.tsx
@@ -8,6 +8,7 @@ import {
SubmitButton,
} from 'src/components/common/Modal/ActionButtons/ActionButtons';
import Modal from 'src/components/common/Modal/Modal';
+import useGetAppSettings from 'src/hooks/useGetAppSettings';
import { GoogleAccountAttributesSlimmed } from '../GoogleAccordion';
import { useDeleteGoogleAccountMutation } from '../GoogleAccounts.generated';
@@ -24,6 +25,7 @@ export const DeleteGoogleAccountModal: React.FC<
DeleteGoogleAccountModalProps
> = ({ account, handleClose }) => {
const { t } = useTranslation();
+ const { appName } = useGetAppSettings();
const [isSubmitting, setIsSubmitting] = useState(false);
const { enqueueSnackbar } = useSnackbar();
@@ -45,7 +47,7 @@ export const DeleteGoogleAccountModal: React.FC<
},
onCompleted: () => {
enqueueSnackbar(
- t('{{appName}} removed your integration with Google.'),
+ t('{{appName}} removed your integration with Google.', { appName }),
{
variant: 'success',
},
@@ -54,7 +56,10 @@ export const DeleteGoogleAccountModal: React.FC<
},
onError: () => {
enqueueSnackbar(
- t("{{appName}} couldn't save your configuration changes for Google."),
+ t(
+ "{{appName}} couldn't save your configuration changes for Google.",
+ { appName },
+ ),
{
variant: 'error',
},
diff --git a/src/components/Tool/GoogleImport/GoogleImport.test.tsx b/src/components/Tool/GoogleImport/GoogleImport.test.tsx
new file mode 100644
index 000000000..fe0d38e58
--- /dev/null
+++ b/src/components/Tool/GoogleImport/GoogleImport.test.tsx
@@ -0,0 +1,362 @@
+import { ApolloCache } from '@apollo/client';
+import { ThemeProvider } from '@mui/material/styles';
+import { render, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { ApolloErgonoMockMap } from 'graphql-ergonomock';
+import { MockLinkCallHandler } from 'graphql-ergonomock/dist/apollo/MockLink';
+import { getSession } from 'next-auth/react';
+import { SnackbarProvider } from 'notistack';
+import TestRouter from '__tests__/util/TestRouter';
+import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
+import { fetchAllData } from 'src/lib/deserializeJsonApi';
+import theme from 'src/theme';
+import GoogleImport from './GoogleImport';
+import { mockGoogleContactGroupsResponse } from './GoogleImportMocks';
+import { GoogleContactGroupsQuery } from './googleContactGroups.generated';
+
+const mockEnqueue = jest.fn();
+jest.mock('notistack', () => ({
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ ...jest.requireActual('notistack'),
+ useSnackbar: () => {
+ return {
+ enqueueSnackbar: mockEnqueue,
+ };
+ },
+}));
+
+jest.mock('src/lib/deserializeJsonApi');
+
+const accountListId = 'account-id';
+const router = {
+ pathname: `/accountLists/${accountListId}/tools`,
+ push: jest.fn(),
+ isReady: true,
+};
+
+const TestComponent = ({
+ mocks,
+ cache,
+ onCall,
+}: {
+ mocks: ApolloErgonoMockMap;
+ cache?: ApolloCache