Skip to content

Commit

Permalink
[MI-3831]: Link channels modal
Browse files Browse the repository at this point in the history
  • Loading branch information
SaurabhSharma-884 committed Dec 11, 2023
1 parent 1baf888 commit 13bb9a6
Show file tree
Hide file tree
Showing 30 changed files with 531 additions and 15 deletions.
2 changes: 1 addition & 1 deletion webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"webpack-cli": "5.0.1"
},
"dependencies": {
"@brightscout/mattermost-ui-library": "2.3.3",
"@brightscout/mattermost-ui-library": "file:../../mm-ui-library",
"react-infinite-scroll-component": "6.1.0",
"@reduxjs/toolkit": "1.8.2",
"core-js": "3.29.1",
Expand Down
16 changes: 16 additions & 0 deletions webapp/src/components/Icon/Icon.map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -447,4 +447,20 @@ export const IconMap : Record<IconName, JSX.Element> = {
/>
</svg>
),
mattermost: (
<svg
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M15.4248 10.1717C15.4248 10.1717 15.4559 11.5363 14.5096 12.4634C13.5632 13.3904 12.4008 13.3057 11.6446 13.0497C10.8884 12.7936 9.91359 12.1549 9.72489 10.8437C9.53632 9.53238 10.3898 8.46713 10.3898 8.46713L12.2498 6.15412L13.3331 4.83309L14.2629 3.68188C14.2629 3.68188 14.6896 3.11029 14.8113 2.99232C14.8354 2.96895 14.8601 2.95361 14.8843 2.9418L14.902 2.93279L14.9051 2.93157C14.9563 2.90954 15.0152 2.90479 15.072 2.92403C15.1277 2.9429 15.1708 2.98112 15.198 3.02824L15.2038 3.03737L15.2088 3.04784C15.222 3.07292 15.2331 3.10153 15.2383 3.13683C15.2632 3.30447 15.255 4.01777 15.255 4.01777L15.2943 5.49695L15.3524 7.2044L15.4248 10.1717ZM17.8325 3.90867C21.2725 6.41232 22.8502 10.9559 21.4157 15.2055C19.6501 20.4353 13.9909 23.2398 8.77554 21.4694C3.56021 19.6989 0.763636 14.0241 2.52911 8.79424C3.96607 4.53772 7.98216 1.8881 12.239 2.00363L10.8745 3.62026C8.34917 4.07814 6.16873 5.8075 5.31172 8.34622C4.0366 12.1233 6.17382 16.2617 10.0854 17.5895C13.9968 18.9173 18.2015 16.9318 19.4766 13.1547C20.3308 10.6244 19.6535 7.93217 17.9375 6.03115L17.8325 3.90867Z'
fill='#1E325C'
/>
</svg>
),
};
2 changes: 1 addition & 1 deletion webapp/src/components/Icon/Icon.types.ts
Original file line number Diff line number Diff line change
@@ -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' | 'mattermost'

export type IconProps = {
iconName: IconName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, {useEffect, useState} from 'react';

import {LinearProgress, Modal} from '@brightscout/mattermost-ui-library';

import usePluginApi from 'hooks/usePluginApi';

import {getCurrentTeam, getLinkModalState} from 'selectors';

import {SearchMMChannels} from './SearchMMChannels';
import {SearchMSTeams} from './SearchMSTeams';
import {SearchMSChannels} from './SearchMSChannels';

export const LinkChannelModal = ({onClose}: {onClose: () => void}) => {
const {state} = usePluginApi();
const {show = false, isLoading} = getLinkModalState(state);
const currentTeam = getCurrentTeam(state);
const [mMChannel, setMmChannel] = useState<Channel | null>(null);
const [mSTeam, setMsTeam] = useState<MSTeamOrChannel | null>(null);
const [mSChannel, setMsChannel] = useState<MSTeamOrChannel | null>(null);

const handleModalClose = () => {
setMmChannel(null);
setMsTeam(null);
setMsChannel(null);
onClose();
};

return (
<Modal
show={show}
title='Link a channel'
subtitle='Link a channel in Mattermost with a channel in Microsoft Teams.'
primaryActionText='Link Channels'
secondaryActionText='Cancel'
onFooterCloseHandler={handleModalClose}
onHeaderCloseHandler={handleModalClose}
isPrimaryButtonDisabled={!mMChannel || !mSChannel || !mSTeam}
onSubmitHandler={() => {
// TODO: handle channel linking
}}
>
{isLoading && <LinearProgress className='fixed w-full left-0 top-100'/>}
<SearchMMChannels
setChannel={setMmChannel}
teamId={currentTeam}
/>
<hr className='w-full my-32'/>
<SearchMSTeams setMsTeam={setMsTeam}/>
<SearchMSChannels
setChannel={setMsChannel}
teamId={mSTeam?.ID}
/>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, {useCallback, useEffect, useState} from 'react';

import {Client4} from 'mattermost-redux/client';

import {ListItemType, MMSearch} from '@brightscout/mattermost-ui-library';

import {useDispatch} from 'react-redux';

import utils from 'utils';

import {Icon} from 'components/Icon';

import {debounceFunctionTimeLimit} from 'constants/common.constants';

import {setLinkModalLoading} from 'reducers/linkModal';

import {SearchMMChannelProps} from './SearchMMChannels.types';

export const SearchMMChannels = ({
setChannel,
teamId,
}: SearchMMChannelProps) => {
const dispatch = useDispatch();
const [searchTerm, setSearchTerm] = useState<string>('');

const [searchSuggestions, setSearchSuggestions] = useState<ListItemType[]>([]);
const [suggestionsLoading, setSuggestionsLoading] = useState<boolean>(false);

useEffect(() => {
handleClearInput();
}, [teamId]);

const searchChannels = ({searchFor}: {searchFor?: string}) => {
if (searchFor && teamId) {
setSuggestionsLoading(true);
dispatch(setLinkModalLoading(true));
Client4.autocompleteChannelsForSearch(teamId, searchFor).
then((channels) => {
const suggestions = [];
for (const channel of channels) {
suggestions.push({
label: channel.display_name,
value: channel.id,
});
}
setSearchSuggestions(suggestions);
setSuggestionsLoading(false);
dispatch(setLinkModalLoading(false));
}).catch((err) => {
// TODO: Handle error here
setSuggestionsLoading(false);
dispatch(setLinkModalLoading(true));
});
}
};

const debouncedSearchChannels = useCallback(utils.debounce(searchChannels, debounceFunctionTimeLimit), [searchChannels]);

const handleSearch = (val: string) => {
if (!val) {
setSearchSuggestions([]);
setChannel(null);
}
setSearchTerm(val);
debouncedSearchChannels({searchFor: val});
};

const handleChannelSelect = (_: any, option: ListItemType) => {
setChannel({
id: option.value,
displayName: option.label as string,
});
setSearchTerm(option.label as string);
};

const handleClearInput = () => {
setSearchTerm('');
setSearchSuggestions([]);
setChannel(null);
};

return (
<div className='d-flex flex-column gap-24'>
<div className='d-flex gap-8 align-items-center'>
<Icon iconName='mattermost'/>
<h5 className='my-0 lh-20 wt-600'>{'Select a Mattermost channel'}</h5>
</div>
<MMSearch
autoFocus={true}
fullWidth={true}
label='Search Mattermost channels'
items={searchSuggestions}
onSelect={handleChannelSelect}
searchValue={searchTerm}
setSearchValue={handleSearch}
onClearInput={handleClearInput}
optionsLoading={suggestionsLoading}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type SearchMMChannelProps = {
setChannel: React.Dispatch<React.SetStateAction<Channel | null>>;
teamId: string | null,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {SearchMMChannels} from './SearchMMChannels.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, {useCallback, useEffect, useState} from 'react';

import {ListItemType, MMSearch} from '@brightscout/mattermost-ui-library';

import {useDispatch} from 'react-redux';

import usePluginApi from 'hooks/usePluginApi';
import utils from 'utils';
import {debounceFunctionTimeLimit, defaultPage, defaultPerPage} from 'constants/common.constants';
import {pluginApiServiceConfigs} from 'constants/apiService.constant';
import useApiRequestCompletionState from 'hooks/useApiRequestCompletionState';

import {setLinkModalLoading} from 'reducers/linkModal';

import {SearchMSChannelProps} from './SearchMSChannels.types';

export const SearchMSChannels = ({setChannel, teamId}: SearchMSChannelProps) => {
const dispatch = useDispatch();
const {makeApiRequestWithCompletionStatus, getApiState} = usePluginApi();
const [searchTerm, setSearchTerm] = useState<string>('');
const [searchChannelsPayload, setSearchChannelsPayload] = useState<SearchMSChannelsParams | null>(null);
const [searchSuggestions, setSearchSuggestions] = useState<ListItemType[]>([]);

useEffect(() => {
handleClearInput();
}, [teamId]);

const searchChannels = ({searchFor}: {searchFor?: string}) => {
if (searchFor && teamId) {
const payload = {
search: searchFor,
page: defaultPage,
per_page: defaultPerPage,
teamId,
};
setSearchChannelsPayload(payload);
makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.searchMSChannels.apiServiceName, payload);
dispatch(setLinkModalLoading(true));
}
};

const debouncedSearchChannels = useCallback(utils.debounce(searchChannels, debounceFunctionTimeLimit), [searchChannels]);

const handleSearch = (val: string) => {
if (!val) {
setSearchSuggestions([]);
setChannel(null);
}
setSearchTerm(val);
debouncedSearchChannels({searchFor: val});
};

const {data: searchedChannels, isLoading: searchSuggestionsLoading} = getApiState(pluginApiServiceConfigs.searchMSChannels.apiServiceName, searchChannelsPayload as SearchMSChannelsParams);
const handleChannelSelect = (_: any, option: ListItemType) => {
setChannel({
ID: option.value,
DisplayName: option.label as string,
});
setSearchTerm(option.label as string);
};

const handleClearInput = () => {
setSearchTerm('');
setChannel(null);
setSearchSuggestions([]);
};

useApiRequestCompletionState({
serviceName: pluginApiServiceConfigs.searchMSChannels.apiServiceName,
payload: searchChannelsPayload as SearchMSChannelsParams,
handleSuccess: () => {
if (searchedChannels) {
const suggestions: ListItemType[] = [];
for (const channel of searchedChannels as MSTeamsSearchResponse) {
suggestions.push({
label: channel.DisplayName,
value: channel.ID,
});
}
setSearchSuggestions(suggestions);
}
dispatch(setLinkModalLoading(false));
},
handleError: () => {
dispatch(setLinkModalLoading(false));

// TODO: Handle this error
},
});

return (
<div className='mt-24'>
<MMSearch
fullWidth={true}
label='Select a channel in Microsoft Teams'
items={searchSuggestions}
onSelect={handleChannelSelect}
searchValue={searchTerm}
setSearchValue={handleSearch}
onClearInput={handleClearInput}
optionsLoading={searchSuggestionsLoading}
disabled={!teamId}
/>
</div>);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type SearchMSChannelProps = {
setChannel: React.Dispatch<React.SetStateAction<MSTeamOrChannel | null>>;
teamId?: string | null,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {SearchMSChannels} from './SearchMSChannels.component';
Loading

0 comments on commit 13bb9a6

Please sign in to comment.