Skip to content

Commit

Permalink
refactor!: remove ModalManager
Browse files Browse the repository at this point in the history
BREAKING CHANGE: remove shell ModalManager
BREAKING CHANGE: remove useModal in context-bridge
BREAKING CHANGE: remove useSnackbar in context-bridge
BREAKING CHANGE: remove CreateModalProps type
Refs: SHELL-111 (#411)
  • Loading branch information
CataldoMazzilli authored May 3, 2024
1 parent 7669fca commit c288690
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 75 deletions.
10 changes: 5 additions & 5 deletions api-extractor/carbonio-shell-ui.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1684,11 +1684,11 @@ interface ZimletProp {
// lib/types/account/index.d.ts:132:9 - (ae-forgotten-export) The symbol "AccountRightName" needs to be exported by the entry point lib.d.ts
// lib/types/account/index.d.ts:133:9 - (ae-forgotten-export) The symbol "AccountRightTarget" needs to be exported by the entry point lib.d.ts
// lib/types/apps/index.d.ts:68:5 - (ae-forgotten-export) The symbol "PanelMode" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:109:9 - (ae-forgotten-export) The symbol "SoapPolicy" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:128:5 - (ae-forgotten-export) The symbol "FolderView" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:144:5 - (ae-forgotten-export) The symbol "Meta" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:148:5 - (ae-forgotten-export) The symbol "SoapRetentionPolicy" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:162:5 - (ae-forgotten-export) The symbol "SortBy" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:85:9 - (ae-forgotten-export) The symbol "SoapPolicy" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:104:5 - (ae-forgotten-export) The symbol "FolderView" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:120:5 - (ae-forgotten-export) The symbol "Meta" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:124:5 - (ae-forgotten-export) The symbol "SoapRetentionPolicy" needs to be exported by the entry point lib.d.ts
// lib/types/misc/index.d.ts:138:5 - (ae-forgotten-export) The symbol "SortBy" needs to be exported by the entry point lib.d.ts
// lib/types/network/index.d.ts:107:5 - (ae-forgotten-export) The symbol "AccountACEInfo" needs to be exported by the entry point lib.d.ts
// lib/types/network/soap.d.ts:11:5 - (ae-forgotten-export) The symbol "NameSpace" needs to be exported by the entry point lib.d.ts
// lib/types/network/soap.d.ts:33:5 - (ae-forgotten-export) The symbol "SoapFault" needs to be exported by the entry point lib.d.ts
Expand Down
25 changes: 11 additions & 14 deletions src/boot/app/app-context-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,20 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import type { FC } from 'react';
import React from 'react';

import { I18nextProvider } from 'react-i18next';

import AppErrorCatcher from './app-error-catcher';
import { SHELL_APP_ID } from '../../constants';
import { useI18nStore } from '../../store/i18n/store';
import { ModuleI18nextProvider } from '../module-i18next-provider';

interface AppContextProviderProps {
pkg: string;
children: React.ReactNode | React.ReactNode[];
}

const AppContextProvider: FC<{ pkg: string }> = ({ pkg, children }) => {
const { instances, defaultI18n } = useI18nStore.getState();
const i18n = instances[pkg] ?? instances[SHELL_APP_ID] ?? defaultI18n;
return (
<I18nextProvider i18n={i18n}>
<AppErrorCatcher>{children}</AppErrorCatcher>
</I18nextProvider>
);
};
const AppContextProvider = ({ pkg, children }: AppContextProviderProps): React.JSX.Element => (
<ModuleI18nextProvider pkg={pkg}>
<AppErrorCatcher>{children}</AppErrorCatcher>
</ModuleI18nextProvider>
);

export default AppContextProvider;
4 changes: 2 additions & 2 deletions src/boot/app/app-loader-mounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import type { FC } from 'react';
import React, { Suspense, useMemo } from 'react';
import React, { useMemo } from 'react';

import { map } from 'lodash';

Expand All @@ -15,7 +15,7 @@ import { useAppStore } from '../../store/app';
const Mounter: FC<{ appId: string }> = ({ children, appId }) => (
<div key={appId} id={appId}>
<AppContextProvider key={appId} pkg={appId}>
<Suspense fallback={''}>{children}</Suspense>
{children}
</AppContextProvider>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/boot/app/default-views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { size } from 'lodash';

import { SEARCH_APP_ID, SETTINGS_APP_ID, SHELL_APP_ID } from '../../constants';
import { SearchAppView } from '../../search/search-app-view';
import { AccountsSettings } from '../../settings/accounts-settings';
import { WrappedAccountsSettings } from '../../settings/accounts-settings';
import GeneralSettings from '../../settings/general-settings';
import { settingsSubSections } from '../../settings/general-settings-sub-sections';
import { SettingsAppView } from '../../settings/settings-app-view';
Expand All @@ -34,7 +34,7 @@ const settingsAccountsView = (t: TFunction): SettingsView => ({
id: 'accounts',
route: 'accounts',
app: SHELL_APP_ID,
component: AccountsSettings,
component: WrappedAccountsSettings,
icon: 'PersonOutline',
label: t('settings.accounts', 'Accounts'),
position: 1
Expand Down
30 changes: 14 additions & 16 deletions src/boot/bootstrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import type { FC } from 'react';
import React, { useEffect } from 'react';

import { ModalManager, SnackbarManager } from '@zextras/carbonio-design-system';
import { SnackbarManager } from '@zextras/carbonio-design-system';
import { useTranslation } from 'react-i18next';
import { BrowserRouter, Route, Switch, useParams } from 'react-router-dom';

Expand Down Expand Up @@ -43,21 +43,19 @@ const Bootstrapper: FC = () => (
<ShellI18nextProvider>
<BrowserRouter basename={BASENAME}>
<SnackbarManager>
<ModalManager>
<Loader />
{IS_FOCUS_MODE && (
<Switch>
<Route path={'/:route'}>
<FocusModeListener />
</Route>
</Switch>
)}
<DefaultViewsRegister />
<NotificationPermissionChecker />
<ContextBridge />
<AppLoaderMounter />
<ShellView />
</ModalManager>
<Loader />
{IS_FOCUS_MODE && (
<Switch>
<Route path={'/:route'}>
<FocusModeListener />
</Route>
</Switch>
)}
<DefaultViewsRegister />
<NotificationPermissionChecker />
<ContextBridge />
<AppLoaderMounter />
<ShellView />
</SnackbarManager>
</BrowserRouter>
</ShellI18nextProvider>
Expand Down
7 changes: 1 addition & 6 deletions src/boot/context-bridge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,15 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useModal, useSnackbar } from '@zextras/carbonio-design-system';
import { useHistory } from 'react-router-dom';

import { useBridge } from '../store/context-bridge';

export const ContextBridge = (): null => {
const history = useHistory();
const createSnackbar = useSnackbar();
const createModal = useModal();
useBridge({
functions: {
getHistory: () => history,
createSnackbar,
createModal
getHistory: () => history
}
});
return null;
Expand Down
26 changes: 26 additions & 0 deletions src/boot/module-i18next-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React from 'react';

import { I18nextProvider } from 'react-i18next';

import { SHELL_APP_ID } from '../constants';
import { useI18nStore } from '../store/i18n/store';

interface ModuleI18nextProviderProps {
pkg: string;
children: React.ReactNode | React.ReactNode[];
}
export const ModuleI18nextProvider = ({
pkg,
children
}: ModuleI18nextProviderProps): React.JSX.Element => {
const { instances, defaultI18n } = useI18nStore.getState();
const i18n = instances[pkg] ?? instances[SHELL_APP_ID] ?? defaultI18n;

return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
};
8 changes: 7 additions & 1 deletion src/settings/accounts-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import React, { useCallback, useMemo, useState, useEffect, useRef } from 'react';

import { Container, useSnackbar } from '@zextras/carbonio-design-system';
import { Container, ModalManager, useSnackbar } from '@zextras/carbonio-design-system';
import type { TFunction } from 'i18next';
import { produce } from 'immer';
import { map, find, isEmpty, reduce, findIndex, filter, size } from 'lodash';
Expand Down Expand Up @@ -480,3 +480,9 @@ export const AccountsSettings = (): React.JSX.Element => {
</>
);
};

export const WrappedAccountsSettings = (): React.JSX.Element => (
<ModalManager>
<AccountsSettings />
</ModalManager>
);
60 changes: 60 additions & 0 deletions src/shell/remove-modal-manager.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2023 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useEffect } from 'react';

import { act } from '@testing-library/react';
import { useModal } from '@zextras/carbonio-design-system';

import AppViewContainer from './app-view-container';
import { useAppStore } from '../store/app';
import { setup } from '../test/utils';

const WithUseModalHookView = (): null => {
const createModal = useModal();
useEffect(() => {
createModal({ id: 'modal-1', title: 'modal test title' });
}, [createModal]);

return null;
};

test('Using useModal hook without a ModalManager, log a Modal manager context not initialized console error', async () => {
const mockedError = jest.fn();
console.error = mockedError;

setup(<AppViewContainer />, { withoutModalManager: true });

act(() => {
useAppStore.getState().setApps([
{
commit: '',
description: 'Mails module',
display: 'Mails',
icon: 'MailModOutline',
js_entrypoint: '',
name: 'carbonio-mails-ui',
priority: 1,
type: 'carbonio',
version: '0.0.1'
}
]);
useAppStore.getState().addRoute({
id: 'mails',
route: 'mails',
position: 1,
visible: true,
label: 'Mails',
primaryBar: 'MailModOutline',
appView: WithUseModalHookView,
badge: { show: false },
app: 'carbonio-mails-ui'
});
});

expect(mockedError).toBeCalled();

expect(mockedError).toBeCalledWith('Modal manager context not initialized');
});
20 changes: 16 additions & 4 deletions src/test/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const getAppI18n = (): i18n => {

interface WrapperProps {
children?: React.ReactNode;
withoutModalManager?: boolean;
initialRouterEntries?: string[];
}

Expand All @@ -121,15 +122,19 @@ export const I18NextTestProvider = ({
return <I18nextProvider i18n={i18nInstance}>{children}</I18nextProvider>;
};

const Wrapper = ({ initialRouterEntries, children }: WrapperProps): React.JSX.Element => (
const Wrapper = ({
initialRouterEntries,
withoutModalManager,
children
}: WrapperProps): React.JSX.Element => (
<MemoryRouter
initialEntries={initialRouterEntries}
initialIndex={(initialRouterEntries?.length || 1) - 1}
>
<I18NextTestProvider>
<ThemeProvider>
<SnackbarManager>
<ModalManager>{children}</ModalManager>
{withoutModalManager ? children : <ModalManager>{children}</ModalManager>}
</SnackbarManager>
</ThemeProvider>
</I18NextTestProvider>
Expand All @@ -140,21 +145,27 @@ function customRender(
ui: React.ReactElement,
{
initialRouterEntries = ['/'],
withoutModalManager = false,
...options
}: WrapperProps & {
options?: Omit<RenderOptions, 'queries' | 'wrapper'>;
} = {}
): RenderResult<ExtendedQueries> {
return render(ui, {
wrapper: ({ children }: Pick<WrapperProps, 'children'>) => (
<Wrapper initialRouterEntries={initialRouterEntries}>{children}</Wrapper>
<Wrapper
initialRouterEntries={initialRouterEntries}
withoutModalManager={withoutModalManager}
>
{children}
</Wrapper>
),
queries: extendedQueries,
...options
});
}

type SetupOptions = Pick<WrapperProps, 'initialRouterEntries'> & {
type SetupOptions = Pick<WrapperProps, 'initialRouterEntries' | 'withoutModalManager'> & {
renderOptions?: Omit<RenderOptions, 'queries'>;
setupOptions?: Parameters<(typeof userEvent)['setup']>[0];
};
Expand All @@ -177,6 +188,7 @@ export const setup = (
user: setupUserEvent({ advanceTimers: jest.advanceTimersByTime, ...options?.setupOptions }),
...customRender(ui, {
initialRouterEntries: options?.initialRouterEntries,
withoutModalManager: options?.withoutModalManager,
...options?.renderOptions
})
});
Expand Down
25 changes: 0 additions & 25 deletions src/types/misc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type React from 'react';
import type { ComponentType } from 'react';

import type { Store } from '@reduxjs/toolkit';
Expand Down Expand Up @@ -84,30 +83,6 @@ export type IdentityProps = {
whenSentToAddresses?: string;
};

export type CreateModalProps = {
background: string;
centered: boolean;
children: React.ReactElement;
confirmColor: string;
confirmLabel: string;
copyLabel: string;
customFooter: React.ReactElement;
disablePortal: boolean;
dismissLabel: string;
hideFooter: boolean;
maxHeight: string;
onClose: () => void;
onConfirm: () => void;
onSecondaryAction: () => void;
optionalFooter: React.ReactElement;
secondaryActionLabel: string;
showCloseIcon: boolean;
size: string;
title: string;
type: string;
zIndex: number;
};

// Custom metadata
type Meta<T extends Record<string, unknown>> = {
// Section. Normally present. If absent this indicates that CustomMetadata info is present but there are no sections to report on.
Expand Down

0 comments on commit c288690

Please sign in to comment.