diff --git a/web/modals/threads/create/compose-subchannel-modal.react.js b/web/modals/threads/create/compose-subchannel-modal.react.js
index e0dae6554..0bc2cf5f5 100644
--- a/web/modals/threads/create/compose-subchannel-modal.react.js
+++ b/web/modals/threads/create/compose-subchannel-modal.react.js
@@ -1,190 +1,284 @@
// @flow
import * as React from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { newThread, newThreadActionTypes } from 'lib/actions/thread-actions';
+import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors';
import type { ThreadInfo } from 'lib/types/thread-types';
+import { threadTypes } from 'lib/types/thread-types';
+import {
+ useDispatchActionPromise,
+ useServerCall,
+} from 'lib/utils/action-utils';
import { trimText } from 'lib/utils/text-utils';
import Stepper from '../../../components/stepper.react';
+import { updateNavInfoActionType } from '../../../redux/action-types';
+import { nonThreadCalendarQuery } from '../../../selectors/nav-selectors';
import Modal from '../../modal.react';
import css from './compose-subchannel-modal.css';
import SubchannelMembers from './steps/subchannel-members.react';
import SubchannelSettings from './steps/subchannel-settings.react';
import type { VisibilityType } from './steps/subchannel-settings.react';
type Props = {
+onClose: () => void,
+parentThreadInfo: ThreadInfo,
};
+const getThreadType = (visibility: VisibilityType, announcement: boolean) => {
+ if (visibility === 'open') {
+ return announcement
+ ? threadTypes.COMMUNITY_OPEN_ANNOUNCEMENT_SUBTHREAD
+ : threadTypes.COMMUNITY_OPEN_SUBTHREAD;
+ } else {
+ return announcement
+ ? threadTypes.COMMUNITY_SECRET_ANNOUNCEMENT_SUBTHREAD
+ : threadTypes.COMMUNITY_SECRET_SUBTHREAD;
+ }
+};
+
type Steps = 'settings' | 'members';
type HeaderProps = {
+parentThreadName: string,
};
function ComposeSubchannelHeader(props: HeaderProps): React.Node {
const { parentThreadName } = props;
return (
{'within '}
{parentThreadName}
);
}
+const createSubchannelLoadingStatusSelector = createLoadingStatusSelector(
+ newThreadActionTypes,
+);
+
function ComposeSubchannelModal(props: Props): React.Node {
const { parentThreadInfo, onClose } = props;
const { uiName: parentThreadName } = parentThreadInfo;
const [activeStep, setActiveStep] = React.useState('settings');
const [channelName, setChannelName] = React.useState('');
const [visibilityType, setVisibilityType] = React.useState(
'open',
);
const [announcement, setAnnouncement] = React.useState(false);
const [selectedUsers, setSelectedUsers] = React.useState<
$ReadOnlySet,
>(new Set());
const [searchUserText, setSearchUserText] = React.useState('');
+ const loadingState = useSelector(createSubchannelLoadingStatusSelector);
+
+ const [errorMessage, setErrorMessage] = React.useState('');
+
+ const calendarQuery = useSelector(nonThreadCalendarQuery);
+ const callNewThread = useServerCall(newThread);
+
+ const dispatchActionPromise = useDispatchActionPromise();
+ const dispatch = useDispatch();
+
+ const createSubchannel = React.useCallback(async () => {
+ try {
+ const threadType = getThreadType(visibilityType, announcement);
+
+ const query = calendarQuery();
+ const result = await callNewThread({
+ name: channelName,
+ type: threadType,
+ parentThreadID: parentThreadInfo.id,
+ initialMemberIDs: Array.from(selectedUsers),
+ calendarQuery: query,
+ color: parentThreadInfo.color,
+ });
+
+ return result;
+ } catch (e) {
+ await setErrorMessage(
+ e.message === 'invalid_parameters' && announcement
+ ? 'announcement channels currently not available'
+ : 'unknown error',
+ );
+
+ return null;
+ }
+ }, [
+ parentThreadInfo,
+ selectedUsers,
+ visibilityType,
+ announcement,
+ callNewThread,
+ calendarQuery,
+ channelName,
+ ]);
+
+ const dispatchCreateSubchannel = React.useCallback(async () => {
+ await setErrorMessage('');
+
+ const response = createSubchannel();
+ await dispatchActionPromise(newThreadActionTypes, response);
+ const result = await response;
+
+ if (result) {
+ const { newThreadID } = result;
+ await dispatch({
+ type: updateNavInfoActionType,
+ payload: {
+ activeChatThreadID: newThreadID,
+ },
+ });
+
+ props.onClose();
+ }
+ }, [dispatchActionPromise, createSubchannel, props, dispatch]);
+
const onChangeChannelName = React.useCallback(
(event: SyntheticEvent) => {
const target = event.currentTarget;
setChannelName(target.value);
},
[],
);
const onOpenVisibilityTypeSelected = React.useCallback(
() => setVisibilityType('open'),
[],
);
const onSecretVisibilityTypeSelected = React.useCallback(
() => setVisibilityType('secret'),
[],
);
const onAnnouncementSelected = React.useCallback(
() => setAnnouncement(!announcement),
[announcement],
);
const toggleUserSelection = React.useCallback((userID: string) => {
setSelectedUsers((users: $ReadOnlySet) => {
const newUsers = new Set(users);
if (newUsers.has(userID)) {
newUsers.delete(userID);
} else {
newUsers.add(userID);
}
return newUsers;
});
}, []);
const subchannelSettings = React.useMemo(
() => (
),
[
channelName,
visibilityType,
announcement,
onChangeChannelName,
onOpenVisibilityTypeSelected,
onSecretVisibilityTypeSelected,
onAnnouncementSelected,
],
);
const stepperButtons = React.useMemo(
() => ({
settings: {
nextProps: {
content: 'Next',
disabled: !channelName.trim(),
onClick: () => {
+ setErrorMessage('');
setChannelName(channelName.trim());
setActiveStep('members');
},
},
},
members: {
prevProps: {
content: 'Back',
onClick: () => setActiveStep('settings'),
},
nextProps: {
content: 'Create',
+ loading: loadingState === 'loading',
+ disabled: selectedUsers.size === 0,
onClick: () => {
- // TODO: make form logic
+ dispatchCreateSubchannel();
},
},
},
}),
- [channelName],
+ [channelName, dispatchCreateSubchannel, loadingState, selectedUsers],
);
const subchannelMembers = React.useMemo(
() => (
),
[
selectedUsers,
toggleUserSelection,
parentThreadInfo,
searchUserText,
setSearchUserText,
],
);
const modalName =
activeStep === 'members'
? `Create channel - ${trimText(channelName, 11)}`
: 'Create channel';
return (
);
}
export default ComposeSubchannelModal;