diff --git a/CHANGELOG.md b/CHANGELOG.md index 1addf08b..8067f46b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.14.0](https://github.com/zextras/carbonio-contacts-ui/compare/v1.13.1...v1.14.0) (2024-12-12) + + +### Features + +* add AuthGuard component to segregate authenticated-only content ([3541880](https://github.com/zextras/carbonio-contacts-ui/commit/3541880b21782e99571b4fea6d5a1e2db7af4c5d)) + + +### Bug Fixes + +* prevent module to load if the user is not authenticated ([9e7e393](https://github.com/zextras/carbonio-contacts-ui/commit/9e7e3939346cf82c82554604809a1e0cb32e7202)) + ### [1.13.1](https://github.com/zextras/carbonio-contacts-ui/compare/v1.13.0...v1.13.1) (2024-11-20) diff --git a/__mocks__/@zextras/carbonio-shell-ui.ts b/__mocks__/@zextras/carbonio-shell-ui.ts index ce61dbd8..c91568b0 100644 --- a/__mocks__/@zextras/carbonio-shell-ui.ts +++ b/__mocks__/@zextras/carbonio-shell-ui.ts @@ -3,4 +3,11 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import shell from '@zextras/carbonio-shell-ui'; + export * from '../../src/carbonio-ui-commons/test/mocks/carbonio-shell-ui'; + +// TODO move it in the Commons submodule +export const useAuthenticated = jest + .fn, Parameters>() + .mockReturnValue(true); diff --git a/package-lock.json b/package-lock.json index bbc93832..6225f921 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "carbonio-contacts-ui", - "version": "1.13.1", + "version": "1.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "carbonio-contacts-ui", - "version": "1.13.1", + "version": "1.14.0", "license": "AGPL-3.0-only", "dependencies": { "@emotion/react": "^11.13.3", diff --git a/package.json b/package.json index 19b38530..ad81a43e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbonio-contacts-ui", - "version": "1.13.1", + "version": "1.14.0", "description": "Contacts module", "main": "src/app.tsx", "engines": { diff --git a/src/app.tsx b/src/app.tsx index 0f7e2605..94420fcf 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -3,295 +3,26 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { lazy, Suspense, useEffect, useMemo } from 'react'; +import React from 'react'; -import { ModalManager, useSnackbar } from '@zextras/carbonio-design-system'; -import { - ACTION_TYPES, - addBoard, - addBoardView, - addRoute, - addSearchView, - addSettingsView, - NewAction, - registerActions, - registerComponents, - registerFunctions, - SearchViewProps, - SecondaryBarComponentProps -} from '@zextras/carbonio-shell-ui'; -import { useTranslation } from 'react-i18next'; - -import { FOLDER_VIEW } from './carbonio-ui-commons/constants'; -import { useInitializeFolders } from './carbonio-ui-commons/hooks/use-initialize-folders'; +import { AuthGuard } from './app/auth-guard'; +import { FoldersSynchronizator } from './app/folders-syncronization'; +import { IntegrationsRegistration } from './app/integrations-registration'; +import { ViewsRegistration } from './app/views-registration'; import { InitializeTags } from './components/initialize-tags'; -import { Spinner } from './components/Spinner'; -import { - CONTACTS_APP_ID, - CONTACTS_ROUTE, - NEW_CONTACT_GROUP_BOARD_ID, - GROUPS_ROUTE, - EDIT_CONTACT_GROUP_BOARD_ID, - EDIT_DL_BOARD_ID, - CONTACT_BOARD_ID -} from './constants'; -import { ContactInput } from './legacy/integrations/contact-input'; -import createContactIntegration from './legacy/integrations/create-contact'; import { StoreProvider } from './legacy/store/redux'; import { SyncDataHandler } from './legacy/views/secondary-bar/sync-data-handler'; -const LazyContactsView = lazy( - () => import(/* webpackChunkName: "contacts-view" */ './views/contacts-view') -); -const LazySecondaryBarView = lazy( - () => - import(/* webpackChunkName: "secondaryBarView" */ './views/distribution-list/SecondaryBarView') -); -const LazyLegacySecondaryBarView = lazy( - () => - import( - /* webpackChunkName: "legacySecondaryBarView" */ './legacy/views/secondary-bar/secondary-bar-view' - ) -); - -const LazyDistributionListAppView = lazy( - () => import(/* webpackChunkName: "groupsAppView" */ './views/distribution-list-view') -); -const LazySettingsView = lazy( - () => import(/* webpackChunkName: "settings-view" */ './legacy/views/settings/settings-view') -); -const LazySearchView = lazy( - () => import(/* webpackChunkName: "search-view" */ './legacy/views/search/search-view') -); - -const LazyBoardView = lazy( - () => import(/* webpackChunkName: "edit-view" */ './legacy/views/edit/edit-view-board-wrapper') -); - -const LazyNewContactGroupBoardView = lazy( - () => - import( - /* webpackChunkName: "newContactGroupView" */ './views/contact-groups/board/new-contact-group-board' - ) -); - -const LazyEditContactGroupBoardView = lazy( - () => - import( - /* webpackChunkName: "editContactGroupView" */ './views/contact-groups/board/edit-contact-group-board' - ) -); - -const LazyEditDLBoardView = lazy( - () => import(/* webpackChunkName: "edit-dl-view" */ './views/board/edit-dl-board') -); - -const ContactsAppView = (): React.JSX.Element => ( - }> - - - - - - -); - -const SecondaryBarView = (props: SecondaryBarComponentProps): React.JSX.Element => ( - }> - - - - -); - -const DistributionListAppView = (): React.JSX.Element => ( - }> - - -); - -const BoardView = (): React.JSX.Element => ( - }> +const App = (): React.JSX.Element => ( + + + + + - - - - - -); - -const NewContactGroupBoardView = (): React.JSX.Element => ( - }> - - - - -); - -const EditContactGroupBoardView = (): React.JSX.Element => ( - }> - - - - -); - -const EditDLBoardView = (): React.JSX.Element => ( - }> - - -); - -const SettingsView = (): React.JSX.Element => ( - }> - - - - - - -); - -const SearchView = (props: SearchViewProps): React.JSX.Element => ( - }> - - - - - - -); - -const LegacySecondaryBarView = (props: SecondaryBarComponentProps): React.JSX.Element => ( - }> - - - - - - -); - -const App = (): React.JSX.Element => { - const [t] = useTranslation(); - const createSnackbar = useSnackbar(); - - useInitializeFolders(FOLDER_VIEW.contact); - - const newContactAction = useMemo( - (): NewAction => ({ - id: 'new-contact', - label: t('label.new_contact', 'New Contact'), - icon: 'ContactsModOutline', - execute: (ev): void => { - ev?.preventDefault?.(); - addBoard({ - boardViewId: CONTACT_BOARD_ID, - title: t('label.new_contact', 'New Contact') - }); - }, - disabled: false, - group: CONTACTS_APP_ID, - primary: true - }), - [t] - ); - - const newContactGroupAction = useMemo( - (): NewAction => ({ - id: 'new-contact-group', - label: t('label.newContactGroup', 'New contact group'), - icon: 'PeopleOutline', - execute: (): void => { - addBoard({ - boardViewId: NEW_CONTACT_GROUP_BOARD_ID, - title: t('board.newContactGroup.title', 'New Group') - }); - }, - disabled: false, - primary: false, - group: CONTACTS_APP_ID - }), - [t] - ); - - useEffect(() => { - addRoute({ - route: CONTACTS_ROUTE, - position: 300, - visible: true, - label: t('label.app_name', 'Contacts'), - primaryBar: 'ContactsModOutline', - secondaryBar: LegacySecondaryBarView, - appView: ContactsAppView - }); - addRoute({ - route: GROUPS_ROUTE, - position: 310, - visible: true, - label: t('label.distribution_list_app_name', 'Distribution Lists'), - primaryBar: 'ListOutline', - secondaryBar: SecondaryBarView, - appView: DistributionListAppView - }); - addSettingsView({ - route: CONTACTS_ROUTE, - label: t('label.app_name', 'Contacts'), - component: SettingsView - }); - addSearchView({ - route: CONTACTS_ROUTE, - label: t('label.app_name', 'Contacts'), - component: SearchView - }); - addBoardView({ - id: CONTACT_BOARD_ID, - component: BoardView - }); - addBoardView({ - id: NEW_CONTACT_GROUP_BOARD_ID, - component: NewContactGroupBoardView - }); - addBoardView({ - id: EDIT_CONTACT_GROUP_BOARD_ID, - component: EditContactGroupBoardView - }); - addBoardView({ - id: EDIT_DL_BOARD_ID, - component: EditDLBoardView - }); - }, [t]); - - useEffect(() => { - registerComponents({ - id: 'contact-input', - component: ContactInput - }); - - registerActions( - { - action: () => newContactAction, - id: 'new-contact', - type: ACTION_TYPES.NEW - }, - { - id: 'new-contact-group', - type: ACTION_TYPES.NEW, - action: () => newContactGroupAction - } - ); - registerFunctions({ - id: 'create_contact_from_vcard', - fn: createContactIntegration(createSnackbar, t) - }); - }, [createSnackbar, newContactAction, newContactGroupAction, t]); - - return ( - - - ); -}; + +); export default App; diff --git a/src/app/auth-guard.tsx b/src/app/auth-guard.tsx new file mode 100644 index 00000000..0a6c1a49 --- /dev/null +++ b/src/app/auth-guard.tsx @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React, { ReactNode } from 'react'; + +import { useAuthenticated } from '@zextras/carbonio-shell-ui'; + +type AuthGuardProps = { + children: ReactNode; +}; + +export const AuthGuard: React.FC = ({ children }) => { + const isAuthenticated = useAuthenticated(); + + return isAuthenticated ? <>{children} : null; +}; diff --git a/src/app/folders-syncronization.tsx b/src/app/folders-syncronization.tsx new file mode 100644 index 00000000..f9edd019 --- /dev/null +++ b/src/app/folders-syncronization.tsx @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React from 'react'; + +import { ModalManager } from '@zextras/carbonio-design-system'; + +import { FOLDER_VIEW } from '../carbonio-ui-commons/constants'; +import { useInitializeFolders } from '../carbonio-ui-commons/hooks/use-initialize-folders'; + +const FoldersSynchronizatorLogic: React.FC = () => { + useInitializeFolders(FOLDER_VIEW.contact); + + return null; +}; + +export const FoldersSynchronizator: React.FC = () => ( + + + +); diff --git a/src/app/integrations-registration.tsx b/src/app/integrations-registration.tsx new file mode 100644 index 00000000..00fbe4af --- /dev/null +++ b/src/app/integrations-registration.tsx @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { FC, useEffect, useMemo } from 'react'; + +import { useSnackbar } from '@zextras/carbonio-design-system'; +import { + ACTION_TYPES, + addBoard, + NewAction, + registerActions, + registerComponents, + registerFunctions +} from '@zextras/carbonio-shell-ui'; +import { useTranslation } from 'react-i18next'; + +import { CONTACT_BOARD_ID, CONTACTS_APP_ID, NEW_CONTACT_GROUP_BOARD_ID } from '../constants'; +import { ContactInput } from '../legacy/integrations/contact-input'; +import createContactIntegration from '../legacy/integrations/create-contact'; + +export const IntegrationsRegistration: FC = () => { + const [t] = useTranslation(); + const createSnackbar = useSnackbar(); + + const newContactAction = useMemo( + (): NewAction => ({ + id: 'new-contact', + label: t('label.new_contact', 'New Contact'), + icon: 'ContactsModOutline', + execute: (ev): void => { + ev?.preventDefault?.(); + addBoard({ + boardViewId: CONTACT_BOARD_ID, + title: t('label.new_contact', 'New Contact') + }); + }, + disabled: false, + group: CONTACTS_APP_ID, + primary: true + }), + [t] + ); + + const newContactGroupAction = useMemo( + (): NewAction => ({ + id: 'new-contact-group', + label: t('label.newContactGroup', 'New contact group'), + icon: 'PeopleOutline', + execute: (): void => { + addBoard({ + boardViewId: NEW_CONTACT_GROUP_BOARD_ID, + title: t('board.newContactGroup.title', 'New Group') + }); + }, + disabled: false, + primary: false, + group: CONTACTS_APP_ID + }), + [t] + ); + + useEffect(() => { + registerComponents({ + id: 'contact-input', + component: ContactInput + }); + + registerActions( + { + action: () => newContactAction, + id: 'new-contact', + type: ACTION_TYPES.NEW + }, + { + id: 'new-contact-group', + type: ACTION_TYPES.NEW, + action: () => newContactGroupAction + } + ); + registerFunctions({ + id: 'create_contact_from_vcard', + fn: createContactIntegration(createSnackbar, t) + }); + }, [createSnackbar, newContactAction, newContactGroupAction, t]); + + return null; +}; diff --git a/src/app/tests/auth-guard.test.tsx b/src/app/tests/auth-guard.test.tsx new file mode 100644 index 00000000..ee406b2b --- /dev/null +++ b/src/app/tests/auth-guard.test.tsx @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React from 'react'; + +import { useAuthenticated } from '../../../__mocks__/@zextras/carbonio-shell-ui'; +import { setupTest, screen } from '../../carbonio-ui-commons/test/test-setup'; +import { AuthGuard } from '../auth-guard'; + +describe('AuthGuard', () => { + it('should render the child component when the user is authenticated', () => { + useAuthenticated.mockReturnValue(true); + + setupTest( + +

Authenticated

+
+ ); + + expect(screen.getByText('Authenticated')).toBeInTheDocument(); + }); + + it('should not render the child component when the user is authenticated', () => { + useAuthenticated.mockReturnValue(false); + + setupTest( + +

Authenticated

+
+ ); + + expect(screen.queryByText('Authenticated')).not.toBeInTheDocument(); + }); +}); diff --git a/src/app/tests/folders-synchronizator.test.tsx b/src/app/tests/folders-synchronizator.test.tsx new file mode 100644 index 00000000..660d0cb6 --- /dev/null +++ b/src/app/tests/folders-synchronizator.test.tsx @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React from 'react'; + +import { FOLDER_VIEW } from '../../carbonio-ui-commons/constants'; +import * as commonsHook from '../../carbonio-ui-commons/hooks/use-initialize-folders'; +import { setupTest } from '../../carbonio-ui-commons/test/test-setup'; +import { FoldersSynchronizator } from '../folders-syncronization'; + +describe('FoldersSynchronizator', () => { + it('should call the useInitializeFolders hook with the contact folder view', () => { + const useInitializeFoldersSpy = jest.spyOn(commonsHook, 'useInitializeFolders'); + + setupTest(); + + expect(useInitializeFoldersSpy).toHaveBeenCalledWith(FOLDER_VIEW.contact); + }); +}); diff --git a/src/app/tests/integrations-registration.test.tsx b/src/app/tests/integrations-registration.test.tsx new file mode 100644 index 00000000..b04bf5c3 --- /dev/null +++ b/src/app/tests/integrations-registration.test.tsx @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React from 'react'; + +import { + ACTION_TYPES, + registerActions, + registerComponents, + registerFunctions +} from '@zextras/carbonio-shell-ui'; + +import { setupTest } from '../../carbonio-ui-commons/test/test-setup'; +import { IntegrationsRegistration } from '../integrations-registration'; + +describe('IntegrationsRegistration', () => { + it('should register the contact-input component', () => { + setupTest(); + + expect(registerComponents).toHaveBeenCalledWith({ + id: 'contact-input', + component: expect.any(Function) + }); + }); + + it('should register the contact creation from vCard function', () => { + setupTest(); + + expect(registerFunctions).toHaveBeenCalledWith({ + id: 'create_contact_from_vcard', + fn: expect.any(Function) + }); + }); + + it('should register the actions', () => { + setupTest(); + + expect(registerActions).toHaveBeenCalledWith( + { + id: 'new-contact', + type: ACTION_TYPES.NEW, + action: expect.any(Function) + }, + { + id: 'new-contact-group', + type: ACTION_TYPES.NEW, + action: expect.any(Function) + } + ); + }); +}); diff --git a/src/app/tests/views-registration.test.tsx b/src/app/tests/views-registration.test.tsx new file mode 100644 index 00000000..af02a339 --- /dev/null +++ b/src/app/tests/views-registration.test.tsx @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React from 'react'; + +import { addBoardView, addRoute, addSearchView, addSettingsView } from '@zextras/carbonio-shell-ui'; + +import { setupTest } from '../../carbonio-ui-commons/test/test-setup'; +import { + CONTACT_BOARD_ID, + CONTACTS_ROUTE, + EDIT_CONTACT_GROUP_BOARD_ID, + EDIT_DL_BOARD_ID, + GROUPS_ROUTE, + NEW_CONTACT_GROUP_BOARD_ID +} from '../../constants'; +import { ViewsRegistration } from '../views-registration'; + +describe('ViewsRegistration', () => { + it('should register the main route', () => { + setupTest(); + + expect(addRoute).toHaveBeenCalledWith({ + route: CONTACTS_ROUTE, + position: 300, + visible: true, + label: 'Contacts', + primaryBar: 'ContactsModOutline', + secondaryBar: expect.any(Function), + appView: expect.any(Function) + }); + }); + + it('should register the distribution lists route', () => { + setupTest(); + + expect(addRoute).toHaveBeenCalledWith({ + route: GROUPS_ROUTE, + position: 310, + visible: true, + label: 'Distribution Lists', + primaryBar: 'ListOutline', + secondaryBar: expect.any(Function), + appView: expect.any(Function) + }); + }); + + it('should register the settings view', () => { + setupTest(); + + expect(addSettingsView).toHaveBeenCalledWith({ + route: CONTACTS_ROUTE, + label: 'Contacts', + component: expect.any(Function) + }); + }); + + it('should register the search view', () => { + setupTest(); + + expect(addSearchView).toHaveBeenCalledWith({ + route: CONTACTS_ROUTE, + label: 'Contacts', + component: expect.any(Function) + }); + }); + + it('should register the contact board view', () => { + setupTest(); + + expect(addBoardView).toHaveBeenCalledWith({ + id: CONTACT_BOARD_ID, + component: expect.any(Function) + }); + }); + + it('should register the contact group creation board views', () => { + setupTest(); + + expect(addBoardView).toHaveBeenCalledWith({ + id: NEW_CONTACT_GROUP_BOARD_ID, + component: expect.any(Function) + }); + }); + + it('should register the contact group editing board views', () => { + setupTest(); + + expect(addBoardView).toHaveBeenCalledWith({ + id: EDIT_CONTACT_GROUP_BOARD_ID, + component: expect.any(Function) + }); + }); + + it('should register the distribution list editing board views', () => { + setupTest(); + + expect(addBoardView).toHaveBeenCalledWith({ + id: EDIT_DL_BOARD_ID, + component: expect.any(Function) + }); + }); +}); diff --git a/src/app/views-registration.tsx b/src/app/views-registration.tsx new file mode 100644 index 00000000..bc3f026f --- /dev/null +++ b/src/app/views-registration.tsx @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: 2024 Zextras + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import React, { FC, lazy, Suspense, useEffect } from 'react'; + +import { ModalManager } from '@zextras/carbonio-design-system'; +import { + addBoardView, + addRoute, + addSearchView, + addSettingsView, + SearchViewProps, + SecondaryBarComponentProps +} from '@zextras/carbonio-shell-ui'; +import { useTranslation } from 'react-i18next'; + +import { Spinner } from '../components/Spinner'; +import { + CONTACT_BOARD_ID, + CONTACTS_ROUTE, + EDIT_CONTACT_GROUP_BOARD_ID, + EDIT_DL_BOARD_ID, + GROUPS_ROUTE, + NEW_CONTACT_GROUP_BOARD_ID +} from '../constants'; +import { StoreProvider } from '../legacy/store/redux'; + +const LazyContactsView = lazy( + () => import(/* webpackChunkName: "contacts-view" */ '../views/contacts-view') +); +const LazySecondaryBarView = lazy( + () => + import(/* webpackChunkName: "secondaryBarView" */ '../views/distribution-list/SecondaryBarView') +); +const LazyLegacySecondaryBarView = lazy( + () => + import( + /* webpackChunkName: "legacySecondaryBarView" */ '../legacy/views/secondary-bar/secondary-bar-view' + ) +); + +const LazyDistributionListAppView = lazy( + () => import(/* webpackChunkName: "groupsAppView" */ '../views/distribution-list-view') +); +const LazySettingsView = lazy( + () => import(/* webpackChunkName: "settings-view" */ '../legacy/views/settings/settings-view') +); +const LazySearchView = lazy( + () => import(/* webpackChunkName: "search-view" */ '../legacy/views/search/search-view') +); + +const LazyBoardView = lazy( + () => import(/* webpackChunkName: "edit-view" */ '../legacy/views/edit/edit-view-board-wrapper') +); + +const LazyNewContactGroupBoardView = lazy( + () => + import( + /* webpackChunkName: "newContactGroupView" */ '../views/contact-groups/board/new-contact-group-board' + ) +); + +const LazyEditContactGroupBoardView = lazy( + () => + import( + /* webpackChunkName: "editContactGroupView" */ '../views/contact-groups/board/edit-contact-group-board' + ) +); + +const LazyEditDLBoardView = lazy( + () => import(/* webpackChunkName: "edit-dl-view" */ '../views/board/edit-dl-board') +); + +const ContactsAppView = (): React.JSX.Element => ( + }> + + + + + + +); + +const SecondaryBarView = (props: SecondaryBarComponentProps): React.JSX.Element => ( + }> + + + + +); + +const DistributionListAppView = (): React.JSX.Element => ( + }> + + +); + +const BoardView = (): React.JSX.Element => ( + }> + + + + + + +); + +const NewContactGroupBoardView = (): React.JSX.Element => ( + }> + + + + +); + +const EditContactGroupBoardView = (): React.JSX.Element => ( + }> + + + + +); + +const EditDLBoardView = (): React.JSX.Element => ( + }> + + +); + +const SettingsView = (): React.JSX.Element => ( + }> + + + + + + +); + +const SearchView = (props: SearchViewProps): React.JSX.Element => ( + }> + + + + + + +); + +const LegacySecondaryBarView = (props: SecondaryBarComponentProps): React.JSX.Element => ( + }> + + + + + + +); + +export const ViewsRegistration: FC = () => { + const [t] = useTranslation(); + + useEffect(() => { + addRoute({ + route: CONTACTS_ROUTE, + position: 300, + visible: true, + label: t('label.app_name', 'Contacts'), + primaryBar: 'ContactsModOutline', + secondaryBar: LegacySecondaryBarView, + appView: ContactsAppView + }); + addRoute({ + route: GROUPS_ROUTE, + position: 310, + visible: true, + label: t('label.distribution_list_app_name', 'Distribution Lists'), + primaryBar: 'ListOutline', + secondaryBar: SecondaryBarView, + appView: DistributionListAppView + }); + addSettingsView({ + route: CONTACTS_ROUTE, + label: t('label.app_name', 'Contacts'), + component: SettingsView + }); + addSearchView({ + route: CONTACTS_ROUTE, + label: t('label.app_name', 'Contacts'), + component: SearchView + }); + addBoardView({ + id: CONTACT_BOARD_ID, + component: BoardView + }); + addBoardView({ + id: NEW_CONTACT_GROUP_BOARD_ID, + component: NewContactGroupBoardView + }); + addBoardView({ + id: EDIT_CONTACT_GROUP_BOARD_ID, + component: EditContactGroupBoardView + }); + addBoardView({ + id: EDIT_DL_BOARD_ID, + component: EditDLBoardView + }); + }, [t]); + + return null; +};