diff --git a/pages/accountLists/[accountListId]/contacts/Contacts.graphql b/pages/accountLists/[accountListId]/contacts/Contacts.graphql
index 794ac7a9c..a4e94bb3f 100644
--- a/pages/accountLists/[accountListId]/contacts/Contacts.graphql
+++ b/pages/accountLists/[accountListId]/contacts/Contacts.graphql
@@ -11,8 +11,6 @@ query Contacts(
first: $first
) {
nodes {
- id
- avatar
...ContactRow
}
totalCount
diff --git a/pages/accountLists/[accountListId]/tools/appeals.page.tsx b/pages/accountLists/[accountListId]/tools/appeals.page.tsx
deleted file mode 100644
index 6c9405c2f..000000000
--- a/pages/accountLists/[accountListId]/tools/appeals.page.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import React from 'react';
-import { Box, Divider, Grid, Theme, Typography } from '@mui/material';
-import { useTranslation } from 'react-i18next';
-import { makeStyles } from 'tss-react/mui';
-import { loadSession } from 'pages/api/utils/pagePropsHelpers';
-import Loading from 'src/components/Loading';
-import AddAppealForm from 'src/components/Tool/Appeal/AddAppealForm';
-import Appeals from 'src/components/Tool/Appeal/Appeals';
-import { ToolsWrapper } from './ToolsWrapper';
-import { useToolsHelper } from './useToolsHelper';
-
-const useStyles = makeStyles()((theme: Theme) => ({
- container: {
- padding: `${theme.spacing(3)} ${theme.spacing(3)} 0`,
- display: 'flex',
- },
- outer: {
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'center',
- width: '100%',
- },
- loadingIndicator: {
- margin: theme.spacing(0, 1, 0, 0),
- },
-}));
-
-const AppealsPage: React.FC = () => {
- const { t } = useTranslation();
- const { classes } = useStyles();
-
- const { accountListId } = useToolsHelper();
- const pageUrl = 'tools/fixCommitmentInfo';
-
- return (
-
- {accountListId ? (
-
-
-
-
- {t('Appeals')}
-
-
-
-
- {t(
- 'You can track recurring support goals or special need ' +
- 'support goals through our appeals wizard. Track the ' +
- 'recurring support you raise for an increase ask for example, ' +
- 'or special gifts you raise for a summer mission trip or your ' +
- 'new staff special gift goal.',
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ) : (
-
- )}
-
- );
-};
-
-export const getServerSideProps = loadSession;
-
-export default AppealsPage;
diff --git a/pages/accountLists/[accountListId]/tools/appeals/AppealsWrapper.tsx b/pages/accountLists/[accountListId]/tools/appeals/AppealsWrapper.tsx
new file mode 100644
index 000000000..3d6b5785d
--- /dev/null
+++ b/pages/accountLists/[accountListId]/tools/appeals/AppealsWrapper.tsx
@@ -0,0 +1,108 @@
+import { useRouter } from 'next/router';
+import React, { useEffect, useMemo, useState } from 'react';
+import { AppealsProvider } from 'src/components/Tool/Appeal/AppealsContext/AppealsContext';
+import { ContactFilterSetInput } from 'src/graphql/types.generated';
+import { suggestArticles } from 'src/lib/helpScout';
+import { sanitizeFilters } from 'src/lib/sanitizeFilters';
+
+interface Props {
+ children?: React.ReactNode;
+}
+
+export enum PageEnum {
+ DetailsPage = 'DetailsPage',
+ ContactsPage = 'ContactsPage',
+}
+
+export const AppealsWrapper: React.FC = ({ children }) => {
+ const router = useRouter();
+ const { query, replace, push, pathname, isReady } = router;
+
+ const urlFilters =
+ query?.filters && JSON.parse(decodeURI(query.filters as string));
+
+ const [activeFilters, setActiveFilters] = useState(
+ urlFilters ?? {},
+ );
+ const [starredFilter, setStarredFilter] = useState({});
+ const [filterPanelOpen, setFilterPanelOpen] = useState(false);
+ const [page, setPage] = useState();
+ const [appealId, setAppealId] = useState(undefined);
+ const [contactId, setContactId] = useState(
+ undefined,
+ );
+ const sanitizedFilters = useMemo(
+ () => sanitizeFilters(activeFilters),
+ [activeFilters],
+ );
+
+ const { appealId: appealIdParams, searchTerm, accountListId } = query;
+
+ useEffect(() => {
+ // TODO: Fix these suggested Articles
+ suggestArticles(
+ appealIdParams
+ ? 'HS_CONTACTS_CONTACT_SUGGESTIONS'
+ : 'HS_CONTACTS_SUGGESTIONS',
+ );
+ if (appealIdParams === undefined) {
+ push({
+ pathname: '/accountLists/[accountListId]/tools/appeals',
+ query: {
+ accountListId,
+ },
+ });
+ return;
+ }
+ const length = appealIdParams.length;
+ setAppealId(appealIdParams[0]);
+ if (length === 1) {
+ setPage(PageEnum.DetailsPage);
+ } else if (
+ length === 2 &&
+ (appealIdParams[1].toLowerCase() === 'flows' ||
+ appealIdParams[1].toLowerCase() === 'list')
+ ) {
+ setPage(PageEnum.DetailsPage);
+ setContactId(appealIdParams);
+ } else if (length > 2) {
+ setPage(PageEnum.ContactsPage);
+ setContactId(appealIdParams);
+ }
+ }, [appealIdParams, accountListId]);
+
+ useEffect(() => {
+ if (!isReady) {
+ return;
+ }
+
+ const { filters: _, ...oldQuery } = query;
+ replace({
+ pathname,
+ query: {
+ ...oldQuery,
+ ...(Object.keys(sanitizedFilters).length
+ ? { filters: encodeURI(JSON.stringify(sanitizedFilters)) }
+ : undefined),
+ },
+ });
+ }, [sanitizedFilters, isReady]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/pages/accountLists/[accountListId]/tools/appeals/[appealId].page.tsx b/pages/accountLists/[accountListId]/tools/appeals/[appealId].page.tsx
deleted file mode 100644
index 19330855f..000000000
--- a/pages/accountLists/[accountListId]/tools/appeals/[appealId].page.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import Head from 'next/head';
-import React, { ReactElement, useState } from 'react';
-import { Box, Container, Theme } from '@mui/material';
-import { useTranslation } from 'react-i18next';
-import { makeStyles } from 'tss-react/mui';
-import { loadSession } from 'pages/api/utils/pagePropsHelpers';
-import { AppealProvider } from 'src/components/Tool/Appeal/AppealContextProvider/AppealContextProvider';
-import AppealDetailsMain from 'src/components/Tool/Appeal/AppealDetails/AppealDetailsMain';
-import AppealDrawer from 'src/components/Tool/Appeal/AppealDrawer/AppealDrawer';
-import useGetAppSettings from 'src/hooks/useGetAppSettings';
-import { testAppeal2 } from './testAppeal';
-
-const useStyles = makeStyles()((theme: Theme) => ({
- container: {
- padding: theme.spacing(3),
- marginRight: theme.spacing(2),
- display: 'flex',
- [theme.breakpoints.down('lg')]: {
- paddingLeft: theme.spacing(4),
- marginRight: theme.spacing(3),
- },
- [theme.breakpoints.down('md')]: {
- paddingLeft: theme.spacing(5),
- marginRight: theme.spacing(2),
- },
- [theme.breakpoints.down('sm')]: {
- paddingLeft: theme.spacing(6),
- },
- },
- outer: {
- display: 'flex',
- flexDirection: 'row',
- minWidth: '100vw',
- },
- loadingIndicator: {
- margin: theme.spacing(0, 1, 0, 0),
- },
-}));
-
-const AppealIdPage = (): ReactElement => {
- const { t } = useTranslation();
- const [isNavListOpen, setNavListOpen] = useState(true);
- const { classes } = useStyles();
- const { appName } = useGetAppSettings();
-
- const handleNavListToggle = () => {
- setNavListOpen(!isNavListOpen);
- };
-
- return (
- <>
-
-
- {appName} | {t('Appeals')}
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-export const getServerSideProps = loadSession;
-
-export default AppealIdPage;
diff --git a/pages/accountLists/[accountListId]/tools/appeals/appeal/[[...appealId]].page.test.tsx b/pages/accountLists/[accountListId]/tools/appeals/appeal/[[...appealId]].page.test.tsx
new file mode 100644
index 000000000..c6b93a557
--- /dev/null
+++ b/pages/accountLists/[accountListId]/tools/appeals/appeal/[[...appealId]].page.test.tsx
@@ -0,0 +1,202 @@
+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 { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import { VirtuosoMockContext } from 'react-virtuoso';
+import TestRouter from '__tests__/util/TestRouter';
+import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
+import { ListHeaderCheckBoxState } from 'src/components/Shared/Header/ListHeader';
+import { AppealQuery } from 'src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/appealInfo.generated';
+import { ContactsQuery } from 'src/components/Tool/Appeal/AppealsContext/contacts.generated';
+import {
+ PledgeFrequencyEnum,
+ SendNewsletterEnum,
+ StatusEnum,
+} from 'src/graphql/types.generated';
+import { useMassSelection } from 'src/hooks/useMassSelection';
+import theme from 'src/theme';
+import Appeal from './[[...appealId]].page';
+
+const accountListId = 'account-list-1';
+
+const defaultRouter = {
+ query: { accountListId },
+ isReady: true,
+};
+
+const contact = {
+ id: '1',
+ name: 'Test Person',
+ avatar: 'img.png',
+ primaryAddress: null,
+ status: StatusEnum.PartnerFinancial,
+ pledgeAmount: 100,
+ pledgeFrequency: PledgeFrequencyEnum.Monthly,
+ pledgeCurrency: 'USD',
+ pledgeReceived: true,
+ lateAt: new Date().toISOString(),
+ sendNewsletter: SendNewsletterEnum.Both,
+ starred: false,
+ uncompletedTasksCount: 0,
+ people: { nodes: [] },
+};
+
+const mockResponse = {
+ contacts: {
+ nodes: [contact],
+ totalCount: 1,
+ pageInfo: { endCursor: 'Mg', hasNextPage: false },
+ },
+ allContacts: {
+ totalCount: 1,
+ },
+};
+
+const mockAppealResponse = {
+ appeal: {
+ amount: 4531,
+ amountCurrency: 'USD',
+ id: '9d660aed-1291-4c5b-874d-409a94b5ed3b',
+ name: 'End Of Year Gift',
+ pledgesAmountNotReceivedNotProcessed: 2000,
+ pledgesAmountProcessed: 50,
+ pledgesAmountReceivedNotProcessed: 50,
+ pledgesAmountTotal: 2115.93,
+ },
+};
+
+jest.mock('src/hooks/useMassSelection');
+
+(useMassSelection as jest.Mock).mockReturnValue({
+ ids: [],
+ selectionType: ListHeaderCheckBoxState.Unchecked,
+ isRowChecked: jest.fn(),
+ toggleSelectAll: jest.fn(),
+ toggleSelectionById: jest.fn(),
+});
+
+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,
+ };
+ },
+}));
+
+const Components = ({ router = defaultRouter }: { router?: object }) => (
+
+
+
+
+ mocks={{
+ Contacts: mockResponse,
+ Appeal: mockAppealResponse,
+ }}
+ >
+
+
+
+
+
+
+
+);
+
+describe('Appeal navigation', () => {
+ it('should show list detail appeal page and close filters', async () => {
+ const { getByText, findByTestId, queryByText, getByRole, queryByRole } =
+ render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(queryByText('Primary Appeal')).not.toBeInTheDocument();
+ expect(queryByText('Add Appeal')).not.toBeInTheDocument();
+ });
+
+ await waitFor(() => expect(getByText('Test Person')).toBeInTheDocument());
+ expect(await findByTestId('rowButton')).toHaveTextContent(contact.name);
+
+ await waitFor(() => {
+ expect(getByRole('heading', { name: 'Given' })).toBeInTheDocument();
+ expect(getByRole('heading', { name: 'Received' })).toBeInTheDocument();
+ expect(getByRole('heading', { name: 'Committed' })).toBeInTheDocument();
+
+ expect(
+ getByRole('heading', { name: 'Export to CSV' }),
+ ).toBeInTheDocument();
+ expect(
+ getByRole('heading', { name: 'Export Emails' }),
+ ).toBeInTheDocument();
+ });
+
+ userEvent.click(getByRole('img', { name: 'Toggle Filter Panel' }));
+
+ await waitFor(() => {
+ expect(queryByRole('heading', { name: 'Given' })).not.toBeInTheDocument();
+ expect(
+ queryByRole('heading', { name: 'Received' }),
+ ).not.toBeInTheDocument();
+ expect(
+ queryByRole('heading', { name: 'Committed' }),
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ it('should show flows detail appeal page and open filters', async () => {
+ const { queryByText, getByRole } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(queryByText('Primary Appeal')).not.toBeInTheDocument();
+ expect(queryByText('Add Appeal')).not.toBeInTheDocument();
+ });
+
+ await waitFor(() =>
+ expect(getByRole('heading', { name: 'Excluded' })).toBeInTheDocument(),
+ );
+
+ await waitFor(() => {
+ expect(getByRole('heading', { name: 'Excluded' })).toBeInTheDocument();
+ expect(getByRole('heading', { name: 'Asked' })).toBeInTheDocument();
+ expect(getByRole('heading', { name: 'Committed' })).toBeInTheDocument();
+ expect(getByRole('heading', { name: 'Received' })).toBeInTheDocument();
+ expect(getByRole('heading', { name: 'Given' })).toBeInTheDocument();
+ });
+
+ userEvent.click(getByRole('img', { name: 'Toggle Filter Panel' }));
+
+ await waitFor(() => {
+ expect(getByRole('heading', { name: 'Filter' })).toBeInTheDocument();
+ expect(
+ getByRole('heading', { name: 'See More Filters' }),
+ ).toBeInTheDocument();
+ });
+ });
+});
diff --git a/pages/accountLists/[accountListId]/tools/appeals/appeal/[[...appealId]].page.tsx b/pages/accountLists/[accountListId]/tools/appeals/appeal/[[...appealId]].page.tsx
new file mode 100644
index 000000000..9f82d6e4f
--- /dev/null
+++ b/pages/accountLists/[accountListId]/tools/appeals/appeal/[[...appealId]].page.tsx
@@ -0,0 +1,31 @@
+import React, { ReactElement } from 'react';
+import { useTranslation } from 'react-i18next';
+import { loadSession } from 'pages/api/utils/pagePropsHelpers';
+import AppealsDetailsPage from 'src/components/Tool/Appeal/AppealsDetailsPage';
+import { ToolsWrapper } from '../../ToolsWrapper';
+import { AppealsWrapper } from '../AppealsWrapper';
+
+const Appeals = (): ReactElement => {
+ const { t } = useTranslation();
+ const pageUrl = 'appeals';
+
+ return (
+
+
+
+ );
+};
+
+const AppealsPage: React.FC = () => (
+
+
+
+);
+
+export default AppealsPage;
+
+export const getServerSideProps = loadSession;
diff --git a/pages/accountLists/[accountListId]/tools/appeals/index.page.test.tsx b/pages/accountLists/[accountListId]/tools/appeals/index.page.test.tsx
new file mode 100644
index 000000000..c4a9e8dbe
--- /dev/null
+++ b/pages/accountLists/[accountListId]/tools/appeals/index.page.test.tsx
@@ -0,0 +1,125 @@
+import React from 'react';
+import { ThemeProvider } from '@mui/material/styles';
+import { render, waitFor } from '@testing-library/react';
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import { VirtuosoMockContext } from 'react-virtuoso';
+import TestRouter from '__tests__/util/TestRouter';
+import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
+import { ListHeaderCheckBoxState } from 'src/components/Shared/Header/ListHeader';
+import { AppealQuery } from 'src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/appealInfo.generated';
+import { ContactsQuery } from 'src/components/Tool/Appeal/AppealsContext/contacts.generated';
+import {
+ PledgeFrequencyEnum,
+ SendNewsletterEnum,
+ StatusEnum,
+} from 'src/graphql/types.generated';
+import { useMassSelection } from 'src/hooks/useMassSelection';
+import theme from 'src/theme';
+import AppealsInitialPage from './index.page';
+
+const accountListId = 'account-list-1';
+
+const defaultRouter = {
+ query: { accountListId },
+ isReady: true,
+};
+
+const contact = {
+ id: '1',
+ name: 'Test Person',
+ avatar: 'img.png',
+ primaryAddress: null,
+ status: StatusEnum.PartnerFinancial,
+ pledgeAmount: 100,
+ pledgeFrequency: PledgeFrequencyEnum.Monthly,
+ pledgeCurrency: 'USD',
+ pledgeReceived: true,
+ lateAt: new Date().toISOString(),
+ sendNewsletter: SendNewsletterEnum.Both,
+ starred: false,
+ uncompletedTasksCount: 0,
+ people: { nodes: [] },
+};
+
+const mockResponse = {
+ contacts: {
+ nodes: [contact],
+ totalCount: 1,
+ pageInfo: { endCursor: 'Mg', hasNextPage: false },
+ },
+ allContacts: {
+ totalCount: 1,
+ },
+};
+
+const mockAppealResponse = {
+ appeal: {
+ amount: 4531,
+ amountCurrency: 'USD',
+ id: '9d660aed-1291-4c5b-874d-409a94b5ed3b',
+ name: 'End Of Year Gift',
+ pledgesAmountNotReceivedNotProcessed: 2000,
+ pledgesAmountProcessed: 50,
+ pledgesAmountReceivedNotProcessed: 50,
+ pledgesAmountTotal: 2115.93,
+ },
+};
+
+jest.mock('src/hooks/useMassSelection');
+
+(useMassSelection as jest.Mock).mockReturnValue({
+ ids: [],
+ selectionType: ListHeaderCheckBoxState.Unchecked,
+ isRowChecked: jest.fn(),
+ toggleSelectAll: jest.fn(),
+ toggleSelectionById: jest.fn(),
+});
+
+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,
+ };
+ },
+}));
+
+const Components = ({ router = defaultRouter }: { router?: object }) => (
+
+
+
+
+ mocks={{
+ Contacts: mockResponse,
+ Appeal: mockAppealResponse,
+ }}
+ >
+
+
+
+
+
+
+
+);
+
+describe('Appeal navigation', () => {
+ it('should show initial appeal page', async () => {
+ const { getByText, getAllByText } = render();
+
+ await waitFor(() =>
+ expect(getByText('Primary Appeal')).toBeInTheDocument(),
+ );
+
+ await waitFor(() =>
+ expect(getAllByText('Add Appeal')[0]).toBeInTheDocument(),
+ );
+ });
+});
diff --git a/pages/accountLists/[accountListId]/tools/appeals/index.page.tsx b/pages/accountLists/[accountListId]/tools/appeals/index.page.tsx
new file mode 100644
index 000000000..9c6791897
--- /dev/null
+++ b/pages/accountLists/[accountListId]/tools/appeals/index.page.tsx
@@ -0,0 +1,24 @@
+import React, { ReactElement } from 'react';
+import { useTranslation } from 'react-i18next';
+import { loadSession } from 'pages/api/utils/pagePropsHelpers';
+import AppealsInitialPage from 'src/components/Tool/Appeal/InitialPage/AppealsInitialPage';
+import { ToolsWrapper } from '../ToolsWrapper';
+
+const AppealsPage = (): ReactElement => {
+ const { t } = useTranslation();
+ const pageUrl = 'appeals';
+
+ return (
+
+
+
+ );
+};
+
+export default AppealsPage;
+
+export const getServerSideProps = loadSession;
diff --git a/src/components/Contacts/ContactDetails/ContactDetails.tsx b/src/components/Contacts/ContactDetails/ContactDetails.tsx
index 82e6691d3..f81695958 100644
--- a/src/components/Contacts/ContactDetails/ContactDetails.tsx
+++ b/src/components/Contacts/ContactDetails/ContactDetails.tsx
@@ -5,6 +5,11 @@ import TabPanel from '@mui/lab/TabPanel';
import { Box, Tab } from '@mui/material';
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
+import {
+ AppealsContext,
+ AppealsType,
+} from 'src/components/Tool/Appeal/AppealsContext/AppealsContext';
+import { ContactContextTypesEnum } from 'src/lib/contactContextTypes';
import theme from '../../../theme';
import {
ContactsContext,
@@ -33,8 +38,9 @@ import {
} from './ContactReferralTab/DynamicContactReferralTab';
import { ContactTasksTab } from './ContactTasksTab/ContactTasksTab';
-interface Props {
+interface ContactDetailsProps {
onClose: () => void;
+ contextType?: ContactContextTypesEnum;
}
const ContactDetailsWrapper = styled(Box)(({}) => ({
@@ -83,7 +89,10 @@ export enum TabKey {
Notes = 'Notes',
}
-export const ContactDetails: React.FC = ({ onClose }) => {
+export const ContactDetails: React.FC = ({
+ onClose,
+ contextType = ContactContextTypesEnum.Contacts,
+}) => {
const { t } = useTranslation();
const [contactDetailsLoaded, setContactDetailsLoaded] = useState(false);
@@ -91,7 +100,9 @@ export const ContactDetails: React.FC = ({ onClose }) => {
accountListId,
contactDetailsId: contactId,
setContactFocus,
- } = React.useContext(ContactsContext) as ContactsType;
+ } = contextType === ContactContextTypesEnum.Contacts
+ ? (React.useContext(ContactsContext) as ContactsType)
+ : (React.useContext(AppealsContext) as AppealsType);
const { selectedTabKey, handleTabChange: handleChange } = React.useContext(
ContactDetailContext,
@@ -106,6 +117,7 @@ export const ContactDetails: React.FC = ({ onClose }) => {
onClose={onClose}
contactDetailsLoaded={contactDetailsLoaded}
setContactDetailsLoaded={setContactDetailsLoaded}
+ contextType={contextType}
/>
)}
diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx
index 101ea8146..fccd6bf72 100644
--- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx
+++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx
@@ -4,6 +4,7 @@ import { Avatar, Box, IconButton, Skeleton, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { StatusEnum } from 'src/graphql/types.generated';
+import { ContactContextTypesEnum } from 'src/lib/contactContextTypes';
import theme from '../../../../theme';
import { StarContactIconButton } from '../../StarContactIconButton/StarContactIconButton';
import {
@@ -27,6 +28,7 @@ interface Props {
onClose: () => void;
setContactDetailsLoaded: (value: boolean) => void;
contactDetailsLoaded: boolean;
+ contextType?: ContactContextTypesEnum;
}
const HeaderBar = styled(Box)(({}) => ({
@@ -68,6 +70,7 @@ export const ContactDetailsHeader: React.FC = ({
onClose,
setContactDetailsLoaded,
contactDetailsLoaded,
+ contextType,
}: Props) => {
const { data } = useGetContactDetailsHeaderQuery({
variables: { accountListId, contactId },
@@ -127,6 +130,7 @@ export const ContactDetailsHeader: React.FC = ({
contactId={contactId}
status={data?.contact.status ?? StatusEnum.Unresponsive}
onClose={onClose}
+ contextType={contextType}
/>
void;
+ contextType?: ContactContextTypesEnum;
}
export const ContactDetailsMoreAcitions: React.FC<
ContactDetailsMoreAcitionsProps
-> = ({ contactId, status, onClose }) => {
+> = ({
+ contactId,
+ status,
+ onClose,
+ contextType = ContactContextTypesEnum.Contacts,
+}) => {
const { openTaskModal, preloadTaskModal } = useTaskModal();
const { t } = useTranslation();
- const { accountListId } = React.useContext(ContactsContext) as ContactsType;
+ const { accountListId } =
+ contextType === ContactContextTypesEnum.Contacts
+ ? (React.useContext(ContactsContext) as ContactsType)
+ : (React.useContext(AppealsContext) as AppealsType);
const {
referralsModalOpen,
diff --git a/src/components/Contacts/ContactFlow/ContactFlowColumn/ContactFlowColumn.tsx b/src/components/Contacts/ContactFlow/ContactFlowColumn/ContactFlowColumn.tsx
index 8d2ccd25f..9a8b65a60 100644
--- a/src/components/Contacts/ContactFlow/ContactFlowColumn/ContactFlowColumn.tsx
+++ b/src/components/Contacts/ContactFlow/ContactFlowColumn/ContactFlowColumn.tsx
@@ -6,6 +6,7 @@ import {
CircularProgress,
Typography,
} from '@mui/material';
+import { styled } from '@mui/material/styles';
import { useDrop } from 'react-dnd';
import { useContactsQuery } from 'pages/accountLists/[accountListId]/contacts/Contacts.generated';
import {
@@ -17,14 +18,49 @@ import {
ContactFilterStatusEnum,
IdValue,
} from 'src/graphql/types.generated';
-import theme from '../../../../theme';
+import theme from 'src/theme';
import { useLoadConstantsQuery } from '../../../Constants/LoadConstants.generated';
import { InfiniteList } from '../../../InfiniteList/InfiniteList';
import { ContactRowFragment } from '../../ContactRow/ContactRow.generated';
import { ContactFlowDropZone } from '../ContactFlowDropZone/ContactFlowDropZone';
import { ContactFlowRow } from '../ContactFlowRow/ContactFlowRow';
-interface Props {
+export const ContainerBox = styled(Box, {
+ shouldForwardProp: (prop) => prop !== 'color',
+})(({ color }: { color: string }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ borderBottom: `5px solid ${color}`,
+ height: theme.spacing(7),
+}));
+
+export const ColumnTitle = styled(Typography)(() => ({
+ fontWeight: 600,
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+}));
+
+export const StyledCardContent = styled(CardContent)(() => ({
+ position: 'relative',
+ height: 'calc(100vh - 260px)',
+ padding: 0,
+ background: theme.palette.cruGrayLight.main,
+}));
+
+export const CardContentInner = styled(Box, {
+ shouldForwardProp: (prop) => prop !== 'canDrop',
+})(({ canDrop }: { canDrop: boolean }) => ({
+ position: 'absolute',
+ width: '100%',
+ height: '100%',
+ top: '0',
+ right: '0',
+ display: canDrop ? 'grid' : 'none',
+}));
+
+export interface ContactFlowColumnProps {
data?: ContactRowFragment[];
statuses: ContactFilterStatusEnum[];
selectedFilters: ContactFilterSetInput;
@@ -44,7 +80,6 @@ interface Props {
} & Pick,
) => Promise;
}
-
export interface StatusStructure {
id: string | undefined;
value: string | undefined;
@@ -52,7 +87,7 @@ export interface StatusStructure {
const nullStatus = { id: 'NULL', value: '' };
-export const ContactFlowColumn: React.FC = ({
+export const ContactFlowColumn: React.FC = ({
statuses,
title,
color,
@@ -60,7 +95,7 @@ export const ContactFlowColumn: React.FC = ({
searchTerm,
onContactSelected,
changeContactStatus,
-}: Props) => {
+}) => {
const { sanitizedFilters } = React.useContext(
ContactsContext,
) as ContactsType;
@@ -95,49 +130,19 @@ export const ContactFlowColumn: React.FC = ({
) : (
-
+
-
- {title}
-
+ {title}
{data?.contacts.totalCount || 0}
-
-
-
+
+
{statusesStructured.map((status) => (
= ({
changeContactStatus={changeContactStatus}
/>
))}
-
+
= ({
itemContent={(_index, contact) => (
constant.id === contact.status,
) || nullStatus
}
- starred={contact.starred}
onContactSelected={onContactSelected}
columnWidth={cardContentRef.current?.offsetWidth}
- avatar={contact.avatar}
/>
)}
endReached={() =>
@@ -176,7 +178,7 @@ export const ContactFlowColumn: React.FC = ({
EmptyPlaceholder={undefined}
/>
-
+
);
};
diff --git a/src/components/Contacts/ContactFlow/ContactFlowDropZone/ContactFlowDropZone.tsx b/src/components/Contacts/ContactFlow/ContactFlowDropZone/ContactFlowDropZone.tsx
index 6a13a37f8..b6499b350 100644
--- a/src/components/Contacts/ContactFlow/ContactFlowDropZone/ContactFlowDropZone.tsx
+++ b/src/components/Contacts/ContactFlow/ContactFlowDropZone/ContactFlowDropZone.tsx
@@ -1,11 +1,35 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
+import { styled } from '@mui/material/styles';
import { useDrop } from 'react-dnd';
import { useTranslation } from 'react-i18next';
import { IdValue } from 'src/graphql/types.generated';
import theme from '../../../../theme';
import { DraggedContact } from '../ContactFlowRow/ContactFlowRow';
+// When making changes in this file, also check to see if you don't need to make changes to the below file
+// src/components/Tool/Appeal/Flow/ContactFlowDropZone/ContactFlowDropZone.tsx
+
+export const DropZoneBox = styled(Box, {
+ shouldForwardProp: (prop) => prop !== 'canDrop' && prop !== 'isOver',
+})(({ canDrop, isOver }: { canDrop: boolean; isOver: boolean }) => ({
+ display: 'flex',
+ height: '100%',
+ width: '100%',
+ border: canDrop
+ ? `3px dashed ${theme.palette.mpdxBlue.main}`
+ : `3px solid ${theme.palette.cruGrayMedium.main}`,
+ zIndex: canDrop ? 1 : 0,
+ color: canDrop ? theme.palette.common.white : theme.palette.cruGrayDark.main,
+ backgroundColor: canDrop
+ ? isOver
+ ? theme.palette.info.main
+ : theme.palette.info.light
+ : theme.palette.cruGrayLight.main,
+ justifyContent: 'center',
+ alignItems: 'center',
+}));
+
interface Props {
status: {
__typename?: 'IdValue' | undefined;
@@ -38,32 +62,15 @@ export const ContactFlowDropZone: React.FC = ({
const { t } = useTranslation();
return (
-
{t('{{status}}', { status: status.value })}
-
+
);
};
diff --git a/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx b/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx
index 8370adbad..0994c0646 100644
--- a/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx
+++ b/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx
@@ -7,15 +7,25 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import TestWrapper from '__tests__/util/TestWrapper';
import { StatusEnum } from 'src/graphql/types.generated';
import theme from '../../../../theme';
+import { ContactRowFragment } from '../../ContactRow/ContactRow.generated';
import { ContactFlowRow } from './ContactFlowRow';
const accountListId = 'abc';
-const id = '123';
-const name = 'Test Name';
const status = {
id: StatusEnum.PartnerFinancial,
value: 'Partner - Financial',
};
+const contact = {
+ id: '123',
+ name: 'Test Name',
+ starred: true,
+ avatar: 'avatar.jpg',
+ pledgeAmount: 100,
+ pledgeCurrency: 'USD',
+ pledgeReceived: false,
+ uncompletedTasksCount: 0,
+} as ContactRowFragment;
+
const onContactSelected = jest.fn();
describe('ContactFlowRow', () => {
@@ -26,10 +36,8 @@ describe('ContactFlowRow', () => {
@@ -47,10 +55,8 @@ describe('ContactFlowRow', () => {
diff --git a/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.tsx b/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.tsx
index e15623fd2..53cf5c9fe 100644
--- a/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.tsx
+++ b/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.tsx
@@ -5,26 +5,27 @@ import { useDrag } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { IdValue } from 'src/graphql/types.generated';
import theme from '../../../../theme';
+import { ContactRowFragment } from '../../ContactRow/ContactRow.generated';
import { StarContactIconButton } from '../../StarContactIconButton/StarContactIconButton';
-interface Props {
+// When making changes in this file, also check to see if you don't need to make changes to the below file
+// src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.tsx
+
+export interface ContactFlowRowProps {
accountListId: string;
- id: string;
- name: string;
+ contact: ContactRowFragment;
status: {
__typename?: 'IdValue' | undefined;
} & Pick;
- starred: boolean;
onContactSelected: (
contactId: string,
openDetails: boolean,
flows: boolean,
) => void;
columnWidth?: number;
- avatar?: string;
}
-const ContactLink = styled(Typography)(() => ({
+export const ContactLink = styled(Typography)(() => ({
color: theme.palette.mpdxBlue.main,
'&:hover': {
textDecoration: 'underline',
@@ -32,7 +33,17 @@ const ContactLink = styled(Typography)(() => ({
},
}));
-const DraggableBox = styled(Box)(() => ({
+export const ContainerBox = styled(Box, {
+ shouldForwardProp: (prop) => prop !== 'isDragging',
+})(({ isDragging }: { isDragging: boolean }) => ({
+ display: 'flex',
+ width: '100%',
+ background: 'white',
+ zIndex: isDragging ? 3 : 0,
+ opacity: isDragging ? 0 : 1,
+}));
+
+export const DraggableBox = styled(Box)(() => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
@@ -45,6 +56,11 @@ const DraggableBox = styled(Box)(() => ({
},
}));
+export const StyledAvatar = styled(Avatar)(() => ({
+ width: theme.spacing(4),
+ height: theme.spacing(4),
+}));
+
export interface DraggedContact {
id: string;
status: {
@@ -55,16 +71,15 @@ export interface DraggedContact {
width: number;
}
-export const ContactFlowRow: React.FC = ({
+export const ContactFlowRow: React.FC = ({
accountListId,
- id,
- name,
+ contact,
status,
- starred,
onContactSelected,
columnWidth,
- avatar,
-}: Props) => {
+}) => {
+ const { id, name, starred, avatar } = contact;
+
const [{ isDragging }, drag, preview] = useDrag(
() => ({
type: 'contact',
@@ -87,25 +102,13 @@ export const ContactFlowRow: React.FC = ({
}, []);
return (
-
-
+
onContactSelected(id, true, true)}>
{name}
@@ -121,6 +124,6 @@ export const ContactFlowRow: React.FC = ({
/>
-
+
);
};
diff --git a/src/components/Contacts/ContactRow/ContactRow.graphql b/src/components/Contacts/ContactRow/ContactRow.graphql
index 42c670a91..fc36fe4c5 100644
--- a/src/components/Contacts/ContactRow/ContactRow.graphql
+++ b/src/components/Contacts/ContactRow/ContactRow.graphql
@@ -1,5 +1,6 @@
fragment ContactRow on Contact {
id
+ avatar
name
primaryAddress {
id
diff --git a/src/components/Contacts/ContactRow/ContactRow.test.tsx b/src/components/Contacts/ContactRow/ContactRow.test.tsx
index cf66fb488..b8ec1fd67 100644
--- a/src/components/Contacts/ContactRow/ContactRow.test.tsx
+++ b/src/components/Contacts/ContactRow/ContactRow.test.tsx
@@ -8,6 +8,10 @@ import { ContactsWrapper } from 'pages/accountLists/[accountListId]/contacts/Con
import { TaskModalEnum } from 'src/components/Task/Modal/TaskModal';
import theme from 'src/theme';
import useTaskModal from '../../../hooks/useTaskModal';
+import {
+ ContactsContext,
+ ContactsType,
+} from '../ContactsContext/ContactsContext';
import { ContactRow } from './ContactRow';
import {
ContactRowFragment,
@@ -58,6 +62,34 @@ const contact = gqlMock(ContactRowFragmentDoc, {
jest.mock('../../../hooks/useTaskModal');
const openTaskModal = jest.fn();
+const setContactFocus = jest.fn();
+const contactDetailsOpen = true;
+const toggleSelectionById = jest.fn();
+const isRowChecked = jest.fn();
+
+const Components = () => (
+
+
+
+
+
+
+
+
+
+
+
+);
beforeEach(() => {
(useTaskModal as jest.Mock).mockReturnValue({
@@ -68,17 +100,7 @@ beforeEach(() => {
describe('ContactsRow', () => {
it('default', () => {
- const { getByText } = render(
-
-
-
-
-
-
-
-
- ,
- );
+ const { getByText } = render();
expect(
getByText(
@@ -93,40 +115,19 @@ describe('ContactsRow', () => {
});
it('should render check event', async () => {
- const { getByRole } = render(
-
-
-
-
-
-
-
-
- ,
- );
+ const { getByRole } = render();
const checkbox = getByRole('checkbox');
expect(checkbox).not.toBeChecked();
userEvent.click(checkbox);
- // TODO: Find a way to check that click event was pressed.
+ expect(checkbox).toBeChecked();
});
it('should open log task modal', async () => {
- const { getByTitle } = render(
-
-
-
-
-
-
-
-
- ,
- );
+ const { getByTitle } = render();
const taskButton = getByTitle('Log Task');
userEvent.click(taskButton);
- // TODO: Find a way to check that click event was pressed.
expect(openTaskModal).toHaveBeenCalledWith({
view: TaskModalEnum.Log,
defaultValues: {
@@ -136,20 +137,15 @@ describe('ContactsRow', () => {
});
it('should render contact select event', () => {
- const { getByTestId } = render(
-
-
-
-
-
-
-
-
- ,
- );
+ isRowChecked.mockImplementationOnce((id) => id === contact.id);
+
+ const { getByTestId } = render();
+
+ expect(setContactFocus).not.toHaveBeenCalled();
const rowButton = getByTestId('rowButton');
userEvent.click(rowButton);
- // TODO: Find a way to check that click event was pressed.
+
+ expect(setContactFocus).toHaveBeenCalledWith(contact.id);
});
});
diff --git a/src/components/Contacts/ContactRow/ContactRow.tsx b/src/components/Contacts/ContactRow/ContactRow.tsx
index ce02d3eb7..e79ba64bf 100644
--- a/src/components/Contacts/ContactRow/ContactRow.tsx
+++ b/src/components/Contacts/ContactRow/ContactRow.tsx
@@ -23,7 +23,10 @@ import { preloadContactsRightPanel } from '../ContactsRightPanel/DynamicContacts
import { StarContactIconButton } from '../StarContactIconButton/StarContactIconButton';
import { ContactRowFragment } from './ContactRow.generated';
-const ListItemButton = styled(ButtonBase)(({ theme }) => ({
+// When making changes in this file, also check to see if you don't need to make changes to the below file
+// src/components/Tool/Appeal/List/ContactRow/ContactRow.tsx
+
+export const ListItemButton = styled(ButtonBase)(({ theme }) => ({
flex: '1 1 auto',
textAlign: 'left',
padding: theme.spacing(0, 0.5, 0, 2),
@@ -38,7 +41,7 @@ const ListItemButton = styled(ButtonBase)(({ theme }) => ({
},
}));
-const StyledCheckbox = styled(Checkbox, {
+export const StyledCheckbox = styled(Checkbox, {
shouldForwardProp: (prop) => prop !== 'value',
})(() => ({
'&:hover': {
diff --git a/src/components/Contacts/ContactsContext/ContactsContext.tsx b/src/components/Contacts/ContactsContext/ContactsContext.tsx
index c4af76d21..d110d769a 100644
--- a/src/components/Contacts/ContactsContext/ContactsContext.tsx
+++ b/src/components/Contacts/ContactsContext/ContactsContext.tsx
@@ -85,7 +85,7 @@ export type ContactsType = {
export const ContactsContext = React.createContext(null);
-interface Props {
+export interface ContactsContextProps {
children?: React.ReactNode;
urlFilters?: any;
activeFilters: ContactFilterSetInput;
@@ -122,7 +122,7 @@ export const ContactsContextSavedFilters = (
);
};
-export const ContactsProvider: React.FC = ({
+export const ContactsProvider: React.FC = ({
children,
urlFilters,
activeFilters,
diff --git a/src/components/Contacts/ContactsRightPanel/ContactsRightPanel.tsx b/src/components/Contacts/ContactsRightPanel/ContactsRightPanel.tsx
index 4ce9c2d5f..456cbba22 100644
--- a/src/components/Contacts/ContactsRightPanel/ContactsRightPanel.tsx
+++ b/src/components/Contacts/ContactsRightPanel/ContactsRightPanel.tsx
@@ -1,14 +1,19 @@
import React from 'react';
+import { ContactContextTypesEnum } from 'src/lib/contactContextTypes';
import { ContactDetailProvider } from '../ContactDetails/ContactDetailContext';
import { ContactDetails } from '../ContactDetails/ContactDetails';
interface Props {
onClose: () => void;
+ contextType?: ContactContextTypesEnum;
}
-export const ContactsRightPanel: React.FC = ({ onClose }) => {
+export const ContactsRightPanel: React.FC = ({
+ onClose,
+ contextType,
+}) => {
return (
-
+
);
};
diff --git a/src/components/InfiniteList/InfiniteList.tsx b/src/components/InfiniteList/InfiniteList.tsx
index dc56c3efb..c5194b21b 100644
--- a/src/components/InfiniteList/InfiniteList.tsx
+++ b/src/components/InfiniteList/InfiniteList.tsx
@@ -110,8 +110,8 @@ export const InfiniteList = ({
...props.components,
},
scrollSeekConfiguration: {
- enter: (velocity) => Math.abs(velocity) > 200,
- exit: (velocity) => Math.abs(velocity) < 10,
+ enter: (velocity) => Math.abs(velocity) > 2000,
+ exit: (velocity) => Math.abs(velocity) < 100,
...props.scrollSeekConfiguration,
},
overscan: 2000,
diff --git a/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx b/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx
index b561c7447..8aee0ec50 100644
--- a/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx
+++ b/src/components/Layouts/Primary/TopBar/Items/ProfileMenu/ProfileMenu.tsx
@@ -22,6 +22,7 @@ import { styled } from '@mui/material/styles';
import { signOut } from 'next-auth/react';
import { useSnackbar } from 'notistack';
import { useTranslation } from 'react-i18next';
+import { AccountList } from 'src/graphql/types.generated';
import { useRequiredSession } from 'src/hooks/useRequiredSession';
import { clearDataDogUser } from 'src/lib/dataDog';
import { useAccountListId } from '../../../../../../hooks/useAccountListId';
@@ -163,6 +164,32 @@ const ProfileMenu = (): ReactElement => {
window.location.href = url.href;
};
+ const handleAccountListClick = (
+ accountList: Pick,
+ ) => {
+ if (
+ router.pathname ===
+ '/accountLists/[accountListId]/tools/appeals/appeal/[[...appealId]]'
+ ) {
+ router.push({
+ pathname: '/accountLists/[accountListId]/tools/appeals',
+ query: {
+ accountListId: accountList.id,
+ },
+ });
+ } else {
+ router.push({
+ pathname: accountListId
+ ? router.pathname
+ : '/accountLists/[accountListId]/',
+ query: {
+ ...queryWithoutContactId,
+ accountListId: accountList.id,
+ },
+ });
+ }
+ };
+
return (
<>
{
? theme.palette.cruGrayMedium.main
: 'inherit',
}}
- onClick={() =>
- router.push({
- pathname: accountListId
- ? router.pathname
- : '/accountLists/[accountListId]/',
- query: {
- ...queryWithoutContactId,
- accountListId: accountList.id,
- },
- })
- }
+ onClick={() => handleAccountListClick(accountList)}
>
diff --git a/src/components/Shared/Filters/FilterPanel.tsx b/src/components/Shared/Filters/FilterPanel.tsx
index e382403ba..46d9b35d2 100644
--- a/src/components/Shared/Filters/FilterPanel.tsx
+++ b/src/components/Shared/Filters/FilterPanel.tsx
@@ -20,6 +20,10 @@ import {
import { styled, useTheme } from '@mui/material/styles';
import { filter } from 'lodash';
import { useTranslation } from 'react-i18next';
+import {
+ AppealsContext,
+ AppealsType,
+} from 'src/components/Tool/Appeal/AppealsContext/AppealsContext';
import {
ActivityTypeEnum,
ContactFilterNewsletterEnum,
@@ -100,6 +104,10 @@ type FilterInput = ContactFilterSetInput &
TaskFilterSetInput &
ReportContactFilterSetInput;
+export enum ContextTypesEnum {
+ Contacts = 'contacts',
+ Appeals = 'appeals',
+}
export interface FilterPanelProps {
filters: FilterPanelGroupFragment[];
defaultExpandedFilterGroups?: Set;
@@ -108,6 +116,7 @@ export interface FilterPanelProps {
onClose: () => void;
onSelectedFiltersChanged: (selectedFilters: FilterInput) => void;
onHandleClearSearch?: () => void;
+ contextType?: ContextTypesEnum;
}
export const FilterPanel: React.FC = ({
@@ -118,16 +127,22 @@ export const FilterPanel: React.FC = ({
selectedFilters,
onSelectedFiltersChanged,
onHandleClearSearch,
+ contextType = ContextTypesEnum.Contacts,
...boxProps
}) => {
const theme = useTheme();
const { t } = useTranslation();
- const { handleClearAll } = React.useContext(ContactsContext) as ContactsType;
const [saveFilterModalOpen, setSaveFilterModalOpen] = useState(false);
const [deleteFilterModalOpen, setDeleteFilterModalOpen] = useState(false);
const [showAll, setShowAll] = useState(false);
const [filterToBeDeleted, setFilterToBeDeleted] =
useState(null);
+
+ const handleClearAll =
+ contextType === ContextTypesEnum.Contacts
+ ? (React.useContext(ContactsContext) as ContactsType).handleClearAll
+ : (React.useContext(AppealsContext) as AppealsType).handleClearAll;
+
const updateSelectedFilter = (name: FilterKey, value?: FilterValue) => {
if (value && (!Array.isArray(value) || value.length > 0)) {
let filterValue = value;
diff --git a/src/components/Shared/Filters/NullState/NullState.tsx b/src/components/Shared/Filters/NullState/NullState.tsx
index 18057b330..184f22a69 100644
--- a/src/components/Shared/Filters/NullState/NullState.tsx
+++ b/src/components/Shared/Filters/NullState/NullState.tsx
@@ -14,6 +14,7 @@ import {
TaskFilterSetInput,
} from 'src/graphql/types.generated';
import useTaskModal from 'src/hooks/useTaskModal';
+import i18n from 'src/lib/i18n';
import theme from 'src/theme';
import { NullStateBox } from './NullStateBox';
@@ -68,6 +69,8 @@ interface Props {
page: 'contact' | 'task';
totalCount: number;
filtered: boolean;
+ title?: string;
+ paragraph?: string;
changeFilters:
| Dispatch>
| Dispatch>;
@@ -78,6 +81,13 @@ const NullState: React.FC = ({
totalCount,
filtered,
changeFilters,
+ title = i18n.t('You have {{count}} total {{page}}s', {
+ count: totalCount,
+ page,
+ }),
+ paragraph = i18n.t(
+ 'Unfortunately none of them match your current search or filters.',
+ ),
}: Props) => {
const { t } = useTranslation();
@@ -89,17 +99,8 @@ const NullState: React.FC = ({
/>
{filtered ? (
<>
-
-
-
-
- {t(
- 'Unfortunately none of them match your current search or filters.',
- )}
-
+ {title}
+ {paragraph}