From ee3ad5255b31404279716caed4b79f89da594568 Mon Sep 17 00:00:00 2001 From: Ayush Thakur <100013900+ayusht2810@users.noreply.github.com> Date: Fri, 15 Dec 2023 18:49:18 +0530 Subject: [PATCH] [MI-3828] Add feature to show linked channels and search the linked channels (#41) * [MI-3828] Add feature to show linked channels and search the linked channels * [MI-3828] Fix issue of view not displaying correctly on disconnecting user * [MI-3828] Fix ci * [MI-3828] Remove coverage folder * [MI-3828] Update util classes and refactor some code * [MI-3828] Update style and minor review comments * [MI-3828] Remove extra unit test case and make a common function * [MI-3828] Update breaking ui * [MI-3828] Update snapshot * [MI-3828] Review fixes for Phase 2 UI given by QA (#49) * [MI-3828] Review fixes for Phase 2 UI given by QA * [MI-3828] Fix warning card title --- server/api.go | 2 +- webapp/src/App.tsx | 3 - webapp/src/components/Icon/Icon.map.tsx | 15 + webapp/src/components/Icon/Icon.types.ts | 2 +- .../LinkedChannelCard.component.tsx | 42 +- .../LinkedChannelCard.styles.scss | 12 + .../LinkedChannelCard.test.tsx | 1 + .../LinkedChannelCard.types.ts | 2 +- .../LinkedChannelCard.test.tsx.snap | 362 ++++++------- .../WarningCard/WarningCard.component.tsx | 4 +- .../__snapshots__/WarningCard.test.tsx.snap | 8 +- .../enforceConnectedAccountModal.tsx | 4 - webapp/src/constants/common.constants.ts | 1 + .../src/constants/linkedChannels.constants.ts | 2 + webapp/src/containers/Rhs/Rhs.container.tsx | 335 ++++++++++-- ...edChannels.styles.scss => Rhs.styles.scss} | 0 webapp/src/containers/Rhs/Rhs.test.tsx | 22 - .../Rhs/__snapshots__/Rhs.test.tsx.snap | 258 --------- .../ConnectAccount.container.tsx | 58 -- .../ConnectAccount/ConnectAccount.test.tsx | 34 -- .../ConnectAccount.test.tsx.snap | 506 ------------------ .../Rhs/views/ConnectAccount/index.ts | 1 - .../ConnectedAccount.container.tsx | 110 ---- .../ConnectedAccount.test.tsx | 39 -- .../ConnectedAccount.test.tsx.snap | 146 ----- .../Rhs/views/ConnectedAccount/index.ts | 1 - .../LinkedChannels.container.tsx | 94 ---- .../LinkedChannels/LinkedChannels.mock.ts | 219 -------- .../LinkedChannels/LinkedChannels.test.tsx | 34 -- .../LinkedChannels.test.tsx.snap | 234 -------- .../Rhs/views/LinkedChannels/index.ts | 1 - webapp/src/hooks/usePreviousState.ts | 12 + webapp/src/styles/_utils.scss | 16 +- 33 files changed, 576 insertions(+), 2004 deletions(-) rename webapp/src/containers/Rhs/{views/LinkedChannels/LinkedChannels.styles.scss => Rhs.styles.scss} (100%) delete mode 100644 webapp/src/containers/Rhs/Rhs.test.tsx delete mode 100644 webapp/src/containers/Rhs/__snapshots__/Rhs.test.tsx.snap delete mode 100644 webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.container.tsx delete mode 100644 webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.test.tsx delete mode 100644 webapp/src/containers/Rhs/views/ConnectAccount/__snapshots__/ConnectAccount.test.tsx.snap delete mode 100644 webapp/src/containers/Rhs/views/ConnectAccount/index.ts delete mode 100644 webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.container.tsx delete mode 100644 webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.test.tsx delete mode 100644 webapp/src/containers/Rhs/views/ConnectedAccount/__snapshots__/ConnectedAccount.test.tsx.snap delete mode 100644 webapp/src/containers/Rhs/views/ConnectedAccount/index.ts delete mode 100644 webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.container.tsx delete mode 100644 webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.mock.ts delete mode 100644 webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.test.tsx delete mode 100644 webapp/src/containers/Rhs/views/LinkedChannels/__snapshots__/LinkedChannels.test.tsx.snap delete mode 100644 webapp/src/containers/Rhs/views/LinkedChannels/index.ts create mode 100644 webapp/src/hooks/usePreviousState.ts diff --git a/server/api.go b/server/api.go index a45956cb2..f87329a36 100644 --- a/server/api.go +++ b/server/api.go @@ -507,7 +507,7 @@ func (a *API) getLinkedChannels(w http.ResponseWriter, r *http.Request) { return } - searchTerm := strings.ToLower(r.URL.Query().Get(QueryParamSearchTerm)) + searchTerm := strings.ToLower(strings.TrimSpace(r.URL.Query().Get(QueryParamSearchTerm))) offset, limit := a.p.GetOffsetAndLimit(r.URL.Query()) matchCount := 0 for _, link := range links { diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index 88c83c2c9..db49a962e 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -30,11 +30,8 @@ const App = ({registry, store}:{registry: PluginRegistry, store: Store { - const linkedChannelsParams: SearchLinkedChannelParams = {page: defaultPage, per_page: defaultPerPage}; - makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.getConfig.apiServiceName); makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.needsConnect.apiServiceName); - makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.getLinkedChannels.apiServiceName, linkedChannelsParams); }, []); const {data: needsConnectData, isLoading} = useMemo(() => getApiState(pluginApiServiceConfigs.needsConnect.apiServiceName), [getApiState]); diff --git a/webapp/src/components/Icon/Icon.map.tsx b/webapp/src/components/Icon/Icon.map.tsx index 610034619..c77a389f8 100644 --- a/webapp/src/components/Icon/Icon.map.tsx +++ b/webapp/src/components/Icon/Icon.map.tsx @@ -447,4 +447,19 @@ export const IconMap : Record = { /> ), + lock: ( + + + + ), }; diff --git a/webapp/src/components/Icon/Icon.types.ts b/webapp/src/components/Icon/Icon.types.ts index a7ec642e2..f6407fdfb 100644 --- a/webapp/src/components/Icon/Icon.types.ts +++ b/webapp/src/components/Icon/Icon.types.ts @@ -1,4 +1,4 @@ -export type IconName = 'user' | 'message' | 'connectAccount' | 'warning' | 'close' | 'globe' | 'msTeams' | 'link' | 'noChannels' | 'tick' +export type IconName = 'user' | 'message' | 'connectAccount' | 'warning' | 'close' | 'globe' | 'msTeams' | 'link' | 'noChannels' | 'tick' | 'lock' export type IconProps = { iconName: IconName; diff --git a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.component.tsx b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.component.tsx index 1a2ae474c..a88b785ab 100644 --- a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.component.tsx +++ b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.component.tsx @@ -1,30 +1,50 @@ import React from 'react'; +import {Tooltip} from '@brightscout/mattermost-ui-library'; + +import {General as MMConstants} from 'mattermost-redux/constants'; + import {Icon} from 'components/Icon'; import {LinkedChannelCardProps} from './LinkedChannelCard.types'; import './LinkedChannelCard.styles.scss'; -export const LinkedChannelCard = ({msTeamsChannelName, msTeamsTeamName, mattermostChannelName, mattermostTeamName}: LinkedChannelCardProps) => ( -
+export const LinkedChannelCard = ({msTeamsChannelName, msTeamsTeamName, mattermostChannelName, mattermostTeamName, mattermostChannelType}: LinkedChannelCardProps) => { + const getData = (channelName: string, teamName: string) => { + return ( + <> + +
{channelName}
+
+ +
{teamName}
+
+ + ); + }; + + return (
-
+
- {/* TODO: Update icon on basis of channel type */} - -
{mattermostChannelName}
-
{mattermostTeamName}
+ + {getData(mattermostChannelName, mattermostTeamName)}
-
{msTeamsChannelName}
-
{msTeamsTeamName}
+ {getData(msTeamsChannelName, msTeamsTeamName)}
-
-); + ); +}; diff --git a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.styles.scss b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.styles.scss index 0191fcf5e..2ae8d80dd 100644 --- a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.styles.scss +++ b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.styles.scss @@ -26,5 +26,17 @@ width: 10px; } } + + &__body { + width: 95%; + } + + &__entity-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 45%; + line-height: normal; + } } } diff --git a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.test.tsx b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.test.tsx index efc83df78..2166188c7 100644 --- a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.test.tsx +++ b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.test.tsx @@ -11,6 +11,7 @@ const linkedChannelCardProps: LinkedChannelCardProps = { mattermostTeamName: 'mockMattermostTeamName', msTeamsChannelName: 'mockMSTeamsChannelName', msTeamsTeamName: 'mockMSTeamsTeamName', + mattermostChannelType: 'mattermostChannelType', }; let tree: RenderResult; diff --git a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.types.ts b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.types.ts index f1ec1ff95..7366df2a7 100644 --- a/webapp/src/components/LinkedChannelCard/LinkedChannelCard.types.ts +++ b/webapp/src/components/LinkedChannelCard/LinkedChannelCard.types.ts @@ -1,3 +1,3 @@ -export type LinkedChannelCardProps = Pick & { +export type LinkedChannelCardProps = Pick & { channelId: string } diff --git a/webapp/src/components/LinkedChannelCard/__snapshots__/LinkedChannelCard.test.tsx.snap b/webapp/src/components/LinkedChannelCard/__snapshots__/LinkedChannelCard.test.tsx.snap index adee45fc5..ad1eebfa6 100644 --- a/webapp/src/components/LinkedChannelCard/__snapshots__/LinkedChannelCard.test.tsx.snap +++ b/webapp/src/components/LinkedChannelCard/__snapshots__/LinkedChannelCard.test.tsx.snap @@ -5,186 +5,6 @@ exports[`Linked Channel Card component Should render correctly 1`] = ` "asFragment": [Function], "baseElement":
-
-
- -
-
- - - - - -
- mockMattermostChannelName -
-
- mockMattermostTeamName -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- mockMSTeamsChannelName -
-
- mockMSTeamsTeamName -
-
-
-
-
-
- , - "container":
-
@@ -216,7 +36,7 @@ exports[`Linked Channel Card component Should render correctly 1`] = `
mockMattermostChannelName
mockMattermostTeamName
@@ -343,12 +163,12 @@ exports[`Linked Channel Card component Should render correctly 1`] = `
mockMSTeamsChannelName
mockMSTeamsTeamName
@@ -356,6 +176,178 @@ exports[`Linked Channel Card component Should render correctly 1`] = `
+ , + "container":
+
+ +
+
+ + + + + +
+ mockMattermostChannelName +
+
+ mockMattermostTeamName +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ mockMSTeamsChannelName +
+
+ mockMSTeamsTeamName +
+
+
+
, "debug": [Function], "findAllByAltText": [Function], diff --git a/webapp/src/components/WarningCard/WarningCard.component.tsx b/webapp/src/components/WarningCard/WarningCard.component.tsx index ad79167ea..c94406390 100644 --- a/webapp/src/components/WarningCard/WarningCard.component.tsx +++ b/webapp/src/components/WarningCard/WarningCard.component.tsx @@ -23,9 +23,9 @@ export const WarningCard = ({onConnect}: WarningCardProps) => {
-
{'Please Connect your MS Teams account.'}
+
{'Please connect your account'}
-

{'You are not connected to your MS Teams account yet, please connect to your account to continue using MS Teams sync.'} +

{'Your Microsoft Teams account is not connected. Please connect your account to continue with Microsoft Teams sync.'}

- You are not connected to your MS Teams account yet, please connect to your account to continue using MS Teams sync. + Your Microsoft Teams account is not connected. Please connect your account to continue with Microsoft Teams sync.

- You are not connected to your MS Teams account yet, please connect to your account to continue using MS Teams sync. + Your Microsoft Teams account is not connected. Please connect your account to continue with Microsoft Teams sync.

+
+
+
+
{Constants.listTitle}
+
    + {Constants.connectAccountFeatures.map(({icon, text}) => ( +
  • + +
    {text}
    +
  • + ))} +
+
+
+ ); } - if (connected && !isAnyChannelLinked) { - return ; - } + /** + * Rhs state for the following views: + * user is disconnected and linked channels are present + * user is connected and no linked channels are present + * user is connected and linked channels are present + */ + return ( +
+ {connected ? ( +
+ {/* TODO: Refactor user Avatar */} +
+ +
- return <>; - }, [linkedChannels, connected, isRhsLoading]); +
+
{'Connected as '}{username}
+ +
+
+ ) : ( +
+ +
+ )} + {/* Show spinner during the first load of the linked channels. */} + {isLinkedChannelsLoading && firstRender && ( + + )} + {/* State when user is connected, but no linked channels are present. */} + {!totalLinkedChannels.length && !isLinkedChannelsLoading && !searchLinkedChannelsText && !previousState?.searchLinkedChannelsText && ( +
+ {<> + +

{'There are no linked channels yet'}

+ } +
+ )} + {/* State when user is connected and linked channels are present. */} + {((Boolean(totalLinkedChannels.length) || isLinkedChannelsLoading || searchLinkedChannelsText || previousState?.searchLinkedChannelsText) && !firstRender) && ( + <> +
+

{channelListTitle}

+
+ ) => setSearchLinkedChannelsText(e.target.value)} + onClose={() => setSearchLinkedChannelsText('')} + /> +
+
+ {/* Show a spinner while searching for a specific linked channel. */} + {isLinkedChannelsLoading && !paginationQueryParams.page ? ( + + ) : ( +
+ } + endMessage={ +

+ {(searchLinkedChannelsText || previousState?.searchLinkedChannelsText) && !totalLinkedChannels.length ? noResultsFoundText : noMoreChannelsText} +

+ } + scrollableTarget='scrollableArea' + > + {totalLinkedChannels.map(({msTeamsChannelID, ...rest}) => ( + + )) + } +
+
+ )} + + )} + setShowDisconnectDialog(false)} + > + {'Are you sure you want to disconnect your Microsoft Teams Account? You will no longer be able to send and receive messages to Microsoft Teams users from Mattermost.'} + +
+ ); + }, [connected, isRhsLoading, isLinkedChannelsLoading, totalLinkedChannels, firstRender, searchLinkedChannelsText, showDisconnectDialog]); return ( <> - {getRhsView()} +
+ {getRhsView()} +
{isOpen && } ); diff --git a/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.styles.scss b/webapp/src/containers/Rhs/Rhs.styles.scss similarity index 100% rename from webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.styles.scss rename to webapp/src/containers/Rhs/Rhs.styles.scss diff --git a/webapp/src/containers/Rhs/Rhs.test.tsx b/webapp/src/containers/Rhs/Rhs.test.tsx deleted file mode 100644 index 8c267a316..000000000 --- a/webapp/src/containers/Rhs/Rhs.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -import {RenderResult, render} from '@testing-library/react'; - -import {Rhs} from './Rhs.container'; - -let tree: RenderResult; - -describe('RHS view', () => { - beforeEach(() => { - tree = render(); - }); - - it('Should render correctly', () => { - expect(tree).toMatchSnapshot(); - }); - - it('Should render disconnect account button', () => { - const disconnectButton = tree.getByText('Disconnect'); - expect(disconnectButton).toBeVisible(); - }); -}); diff --git a/webapp/src/containers/Rhs/__snapshots__/Rhs.test.tsx.snap b/webapp/src/containers/Rhs/__snapshots__/Rhs.test.tsx.snap deleted file mode 100644 index 43fc31223..000000000 --- a/webapp/src/containers/Rhs/__snapshots__/Rhs.test.tsx.snap +++ /dev/null @@ -1,258 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RHS view Should render correctly 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
-
-
- -
-
-
- Connected as - - John Doe - -
- -
-
-
-
-
-
-
-
- - - - - -
- mockMessage -
-
- -
-
-
- , - "container":
-
-
-
-
- -
-
-
- Connected as - - John Doe - -
- -
-
-
-
-
-
-
-
- - - - - -
- mockMessage -
-
- -
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.container.tsx b/webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.container.tsx deleted file mode 100644 index ac3ec1982..000000000 --- a/webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.container.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, {useCallback} from 'react'; - -import {Button} from '@brightscout/mattermost-ui-library'; - -import Constants from 'constants/connectAccount.constants'; -import {Icon, IconName} from 'components'; -import usePluginApi from 'hooks/usePluginApi'; -import {pluginApiServiceConfigs} from 'constants/apiService.constant'; -import useApiRequestCompletionState from 'hooks/useApiRequestCompletionState'; -import useAlert from 'hooks/useAlert'; - -export const ConnectAccount = () => { - const showAlert = useAlert(); - const {makeApiRequestWithCompletionStatus, getApiState} = usePluginApi(); - - const connectAccount = useCallback(() => { - makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.connect.apiServiceName); - }, []); - - useApiRequestCompletionState({ - serviceName: pluginApiServiceConfigs.connect.apiServiceName, - handleError: () => { - showAlert({message: Constants.connectAccountUnsuccessfulMsg, severity: 'error'}); - }, - }); - - return ( -
-
-
-
- -

{Constants.connectAccountMsg}

-
- -
-
-
-
{Constants.listTitle}
-
    - {Constants.connectAccountFeatures.map(({icon, text}) => ( -
  • - -
    {text}
    -
  • - )) } -
-
-
-
- ); -}; diff --git a/webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.test.tsx b/webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.test.tsx deleted file mode 100644 index 813b78578..000000000 --- a/webapp/src/containers/Rhs/views/ConnectAccount/ConnectAccount.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -import userEvent from '@testing-library/user-event'; -import {RenderResult, render} from '@testing-library/react'; - -import {mockDispatch} from 'tests/setup'; - -import {ConnectAccount} from './ConnectAccount.container'; - -let tree: RenderResult; - -describe('Connect Account View', () => { - beforeEach(() => { - tree = render(); - }); - - it('Should render correctly', () => { - expect(tree).toMatchSnapshot(); - }); - - it('Should render connect account button', () => { - const connectButton = tree.getByText('Connect Account'); - - expect(connectButton).toBeVisible(); - }); - - it('Should dispatch an action when button is clicked', async () => { - const connectButton = tree.getByText('Connect Account'); - - await userEvent.click(connectButton); - - expect(mockDispatch).toHaveBeenCalledTimes(1); - }); -}); diff --git a/webapp/src/containers/Rhs/views/ConnectAccount/__snapshots__/ConnectAccount.test.tsx.snap b/webapp/src/containers/Rhs/views/ConnectAccount/__snapshots__/ConnectAccount.test.tsx.snap deleted file mode 100644 index 69d96fcac..000000000 --- a/webapp/src/containers/Rhs/views/ConnectAccount/__snapshots__/ConnectAccount.test.tsx.snap +++ /dev/null @@ -1,506 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Connect Account View Should render correctly 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- Connect your Microsoft Teams account -

-
- -
-
-
-
- With your connected Microsoft Teams account, you will be able to: -
-
    -
  • - - - - - -
    - Send and receive direct and group messages with your colleagues on Microsoft Teams -
    -
  • -
  • - - - - - -
    - Send and receive messages in channels that are linked between the two platforms. -
    -
  • -
-
-
-
-
- , - "container":
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- Connect your Microsoft Teams account -

-
- -
-
-
-
- With your connected Microsoft Teams account, you will be able to: -
-
    -
  • - - - - - -
    - Send and receive direct and group messages with your colleagues on Microsoft Teams -
    -
  • -
  • - - - - - -
    - Send and receive messages in channels that are linked between the two platforms. -
    -
  • -
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/webapp/src/containers/Rhs/views/ConnectAccount/index.ts b/webapp/src/containers/Rhs/views/ConnectAccount/index.ts deleted file mode 100644 index 180022dab..000000000 --- a/webapp/src/containers/Rhs/views/ConnectAccount/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {ConnectAccount} from './ConnectAccount.container'; diff --git a/webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.container.tsx b/webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.container.tsx deleted file mode 100644 index f1de83041..000000000 --- a/webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.container.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import {useDispatch, useSelector} from 'react-redux'; - -import {Button} from '@brightscout/mattermost-ui-library'; - -import {pluginApiServiceConfigs} from 'constants/apiService.constant'; - -import {Dialog} from 'components'; - -import usePluginApi from 'hooks/usePluginApi'; -import useAlert from 'hooks/useAlert'; -import useApiRequestCompletionState from 'hooks/useApiRequestCompletionState'; - -import {setConnected} from 'reducers/connectedState'; - -import {getConnectedState} from 'selectors'; - -import {ReduxState} from 'types/common/store.d'; - -import utils from 'utils'; - -export const ConnectedAccount = () => { - const dispatch = useDispatch(); - const [showDialog, setShowDialog] = useState(false); - const {makeApiRequestWithCompletionStatus, state} = usePluginApi(); - const {username, isAlreadyConnected, msteamsUserId, connected} = getConnectedState(state); - const globalState = useSelector((reduxState: ReduxState) => reduxState); - - const showAlert = useAlert(); - - const disconnectUser = useCallback(() => { - makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.disconnectUser.apiServiceName); - }, []); - - useApiRequestCompletionState({ - serviceName: pluginApiServiceConfigs.disconnectUser.apiServiceName, - handleSuccess: () => { - dispatch(setConnected({connected: false, username: '', msteamsUserId: '', isAlreadyConnected: false})); - setShowDialog(false); - showAlert({ - message: 'Your account has been disconnected.', - }); - }, - handleError: () => { - showAlert({ - message: 'Error occurred while disconnecting the user.', - severity: 'error', - }); - setShowDialog(false); - }, - }); - - useEffect(() => { - if (connected && !isAlreadyConnected) { - showAlert({message: 'Your account is connected successfully.'}); - dispatch(setConnected({connected, msteamsUserId, username, isAlreadyConnected: true})); - } - }, [connected, isAlreadyConnected]); - - return ( -
-
-
- {/* TODO: Refactor user Avatar */} -
- -
-
-
{'Connected as '}{username}
- -
-
-
- {/* NOTE: Part of Phase-II */} - {/* -

{'There are no linked channels yet'}

*/} -
- setShowDialog(false)} - onSubmitHandler={disconnectUser} - > - {'Are you sure you want to disconnect your Microsoft Teams Account? You will no longer be able to send and receive messages to Microsoft Teams users from Mattermost.'} - -
-
- ); -}; diff --git a/webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.test.tsx b/webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.test.tsx deleted file mode 100644 index aa0ed2d26..000000000 --- a/webapp/src/containers/Rhs/views/ConnectedAccount/ConnectedAccount.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -import {RenderResult, render} from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import {getConnectedState} from 'selectors'; - -import {mockDispatch} from 'tests/setup'; - -import {mockTestState} from 'tests/mockState'; - -import {ReduxState} from 'types/common/store.d'; - -import {ConnectedAccount} from './ConnectedAccount.container'; - -let tree: RenderResult; - -describe('Connected Account View', () => { - beforeEach(() => { - tree = render(); - }); - - it('Should render correctly', () => { - expect(tree).toMatchSnapshot(); - }); - - it('Should render disconnect account button', () => { - const disconnectButton = tree.getByText('Disconnect'); - expect(disconnectButton).toBeVisible(); - }); - - it('Should display the name of connected user correctly', () => { - const usernameContainer = tree.getByText('Connected as'); - const username = tree.getByText(getConnectedState(mockTestState['plugins-com.mattermost.msteams-sync'] as ReduxState).username); - - expect(usernameContainer).toContainElement(username); - expect(username).toBeVisible(); - }); -}); diff --git a/webapp/src/containers/Rhs/views/ConnectedAccount/__snapshots__/ConnectedAccount.test.tsx.snap b/webapp/src/containers/Rhs/views/ConnectedAccount/__snapshots__/ConnectedAccount.test.tsx.snap deleted file mode 100644 index 91dd716cd..000000000 --- a/webapp/src/containers/Rhs/views/ConnectedAccount/__snapshots__/ConnectedAccount.test.tsx.snap +++ /dev/null @@ -1,146 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Connected Account View Should render correctly 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
-
-
- -
-
-
- Connected as - - John Doe - -
- -
-
-
-
-
-
- , - "container":
-
-
-
-
- -
-
-
- Connected as - - John Doe - -
- -
-
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/webapp/src/containers/Rhs/views/ConnectedAccount/index.ts b/webapp/src/containers/Rhs/views/ConnectedAccount/index.ts deleted file mode 100644 index 690b72e57..000000000 --- a/webapp/src/containers/Rhs/views/ConnectedAccount/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {ConnectedAccount} from './ConnectedAccount.container'; diff --git a/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.container.tsx b/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.container.tsx deleted file mode 100644 index 8e52de42c..000000000 --- a/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.container.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import InfiniteScroll from 'react-infinite-scroll-component'; - -import {Spinner} from '@brightscout/mattermost-ui-library'; - -import {WarningCard, LinkedChannelCard} from 'components'; -import usePluginApi from 'hooks/usePluginApi'; -import {pluginApiServiceConfigs} from 'constants/apiService.constant'; - -import {defaultPage, defaultPerPage} from 'constants/common.constants'; - -import {channelListTitle, noMoreChannelsText} from 'constants/linkedChannels.constants'; - -import {mockLinkedChannels} from './LinkedChannels.mock'; - -import './LinkedChannels.styles.scss'; - -export const LinkedChannels = () => { - // TODO: Add Linked channel list - const {makeApiRequestWithCompletionStatus} = usePluginApi(); - const [totalLinkedChannels, setTotalLinkedChannels] = useState([]); - const [paginationQueryParams, setPaginationQueryParams] = useState({ - page: defaultPage, - per_page: defaultPerPage, - }); - - // TODO: Remove this part used for mocking API call for infinite scroll. - const getChannels = () => new Promise((res) => { - setTimeout(() => res(), 2000); - }); - - // TODO: Remove this part used for mocking API call for infinite scroll. - useEffect(() => { - getChannels().then(() => { - const linkedChannels = mockLinkedChannels.slice((paginationQueryParams.page * paginationQueryParams.per_page), (paginationQueryParams.page + 1) * paginationQueryParams.per_page); - setTotalLinkedChannels([...totalLinkedChannels, ...(linkedChannels as ChannelLinkData[])]); - }); - }, [paginationQueryParams]); - - const connectAccount = useCallback(() => { - makeApiRequestWithCompletionStatus( - pluginApiServiceConfigs.connect.apiServiceName, - ); - }, []); - - // Increase the page number by 1 - const handlePagination = useCallback(() => { - setPaginationQueryParams((prev) => ({...prev, page: paginationQueryParams.page + 1, - })); - }, [paginationQueryParams]); - - const hasMoreLinkedChannels = useMemo(() => ( - (totalLinkedChannels.length - (paginationQueryParams.page * defaultPerPage) === defaultPerPage) - ), [totalLinkedChannels]); - - return ( -
-
-
- -
-

{channelListTitle}

-
- } - endMessage={ -

- {noMoreChannelsText} -

- } - scrollableTarget='scrollableArea' - > - {totalLinkedChannels.map(({msTeamsChannelID, ...rest}) => ( - - )) - } -
-
-
-
- ); -}; diff --git a/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.mock.ts b/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.mock.ts deleted file mode 100644 index 7ab17db05..000000000 --- a/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.mock.ts +++ /dev/null @@ -1,219 +0,0 @@ -export const mockLinkedChannels = [ - - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - { - msTeamsTeamID: 'string', - msTeamsTeamName: 'string', - msTeamsChannelID: 'string', - msTeamsChannelName: 'string', - mattermostTeamID: 'string', - mattermostTeamName: 'string', - mattermostChannelID: 'string', - mattermostChannelName: 'string', - mattermostChannelType: 'string', - msTeamsChannelType: 'string', - }, - -]; diff --git a/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.test.tsx b/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.test.tsx deleted file mode 100644 index 0fc42aebe..000000000 --- a/webapp/src/containers/Rhs/views/LinkedChannels/LinkedChannels.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -import userEvent from '@testing-library/user-event'; -import {RenderResult, render} from '@testing-library/react'; - -import {mockDispatch} from 'tests/setup'; - -import {LinkedChannels} from './LinkedChannels.container'; - -let tree: RenderResult; - -describe('Linked Channels View', () => { - beforeEach(() => { - tree = render(); - }); - - it('Should render correctly', () => { - expect(tree).toMatchSnapshot(); - }); - - it('Should render connect account button', () => { - const connectButton = tree.getByText('Connect Account'); - - expect(connectButton).toBeVisible(); - }); - - it('Should dispatch an action when button is clicked', async () => { - const connectButton = tree.getByText('Connect Account'); - - await userEvent.click(connectButton); - - expect(mockDispatch).toHaveBeenCalledTimes(1); - }); -}); diff --git a/webapp/src/containers/Rhs/views/LinkedChannels/__snapshots__/LinkedChannels.test.tsx.snap b/webapp/src/containers/Rhs/views/LinkedChannels/__snapshots__/LinkedChannels.test.tsx.snap deleted file mode 100644 index 001c673d7..000000000 --- a/webapp/src/containers/Rhs/views/LinkedChannels/__snapshots__/LinkedChannels.test.tsx.snap +++ /dev/null @@ -1,234 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Linked Channels View Should render correctly 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
-
-
-
- - - - - -
-
-
- Please Connect your MS Teams account. -
-
-

- You are not connected to your MS Teams account yet, please connect to your account to continue using MS Teams sync. -

-
- -
-
-
-
-
-

- Linked Channels -

-
-
-
-

- - No more linked channels present. - -

-
-
-
-
-
-
- , - "container":
-
-
-
-
-
- - - - - -
-
-
- Please Connect your MS Teams account. -
-
-

- You are not connected to your MS Teams account yet, please connect to your account to continue using MS Teams sync. -

-
- -
-
-
-
-
-

- Linked Channels -

-
-
-
-

- - No more linked channels present. - -

-
-
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/webapp/src/containers/Rhs/views/LinkedChannels/index.ts b/webapp/src/containers/Rhs/views/LinkedChannels/index.ts deleted file mode 100644 index 5f7fef6ec..000000000 --- a/webapp/src/containers/Rhs/views/LinkedChannels/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {LinkedChannels} from './LinkedChannels.container'; diff --git a/webapp/src/hooks/usePreviousState.ts b/webapp/src/hooks/usePreviousState.ts new file mode 100644 index 000000000..64c64ce5c --- /dev/null +++ b/webapp/src/hooks/usePreviousState.ts @@ -0,0 +1,12 @@ +import {useEffect, useRef} from 'react'; + +// usePreviousState stores the previous state for a current state +function usePreviousState(value: Record) { + const ref = useRef>(); + useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; +} + +export default usePreviousState; diff --git a/webapp/src/styles/_utils.scss b/webapp/src/styles/_utils.scss index 1add83c65..0c0e8d967 100644 --- a/webapp/src/styles/_utils.scss +++ b/webapp/src/styles/_utils.scss @@ -39,6 +39,10 @@ .p-#{$size} { padding: #{$size}px !important; } + + .pt-#{$size} { + padding-top: #{$size}px !important; + } } // margin @@ -46,6 +50,14 @@ .m-#{$size} { margin: #{$size}px !important; } + + .mt-#{$size} { + margin-top: #{$size}px !important; + } + + .mb-#{$size} { + margin-bottom: #{$size}px !important; + } } // margin block @@ -101,8 +113,8 @@ } } - .mt-0 { - margin-top: 0; + .align-items-center { + align-items: center; } .align-items-center {