Changeset View
Standalone View
web/modals/threads/create/compose-subchannel-modal.react.js
// @flow | // @flow | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { useDispatch, useSelector } from 'react-redux'; | |||||
import { newThread, newThreadActionTypes } from 'lib/actions/thread-actions'; | |||||
import type { ThreadInfo } from 'lib/types/thread-types'; | 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 { trimText } from 'lib/utils/text-utils'; | ||||
import Stepper from '../../../components/stepper.react'; | import Stepper from '../../../components/stepper.react'; | ||||
import { updateNavInfoActionType } from '../../../redux/action-types'; | |||||
import { nonThreadCalendarQuery } from '../../../selectors/nav-selectors'; | |||||
import Modal from '../../modal.react'; | import Modal from '../../modal.react'; | ||||
import css from './compose-subchannel-modal.css'; | import css from './compose-subchannel-modal.css'; | ||||
import SubchannelMembers from './steps/subchannel-members.react'; | import SubchannelMembers from './steps/subchannel-members.react'; | ||||
import SubchannelSettings from './steps/subchannel-settings.react'; | import SubchannelSettings from './steps/subchannel-settings.react'; | ||||
type Props = { | type Props = { | ||||
+onClose: () => any, | +onClose: () => any, | ||||
+parentThreadInfo: ThreadInfo, | +parentThreadInfo: ThreadInfo, | ||||
}; | }; | ||||
const threadTypesMatrix = { | |||||
tomek: `matrix` doesn't explain the purpose of this constant. Maybe `threadTypesPerVisibility` would… | |||||
open: [ | |||||
threadTypes.COMMUNITY_OPEN_SUBTHREAD, | |||||
threadTypes.COMMUNITY_OPEN_ANNOUNCEMENT_SUBTHREAD, | |||||
], | |||||
closed: [ | |||||
threadTypes.COMMUNITY_SECRET_SUBTHREAD, | |||||
threadTypes.COMMUNITY_SECRET_ANNOUNCEMENT_SUBTHREAD, | |||||
], | |||||
}; | |||||
type Pages = 'settings' | 'members'; | type Pages = 'settings' | 'members'; | ||||
type VisibilityType = 'open' | 'closed'; | type VisibilityType = 'open' | 'closed'; | ||||
type HeaderProps = { | type HeaderProps = { | ||||
+parentThreadName: string, | +parentThreadName: string, | ||||
}; | }; | ||||
function ComposeSubchannelHeader(props: HeaderProps): React.Node { | function ComposeSubchannelHeader(props: HeaderProps): React.Node { | ||||
Show All 19 Lines | const [visibilityType, setVisibilityType] = React.useState<VisibilityType>( | ||||
'open', | 'open', | ||||
); | ); | ||||
const [announcement, setAnnouncement] = React.useState<boolean>(false); | const [announcement, setAnnouncement] = React.useState<boolean>(false); | ||||
const [selectedUsers, setSelectedUsers] = React.useState<Set<string>>( | const [selectedUsers, setSelectedUsers] = React.useState<Set<string>>( | ||||
new Set(), | new Set(), | ||||
); | ); | ||||
const [searchUserText, setSearchUserText] = React.useState<string>(''); | const [searchUserText, setSearchUserText] = React.useState<string>(''); | ||||
const [isLoading, setIsLoading] = React.useState<boolean>(false); | |||||
tomekUnsubmitted Done Inline ActionsI don't think it is necessary to create a state for loading. You should use createLoadingStatusSelector instead. tomek: I don't think it is necessary to create a state for loading. You should use… | |||||
const [errorMessage, setErrorMessage] = React.useState<string>(''); | |||||
const calendarQuery = useSelector(nonThreadCalendarQuery); | |||||
const callNewThread = useServerCall(newThread); | |||||
const dispatchActionPromise = useDispatchActionPromise(); | |||||
const dispatch = useDispatch(); | |||||
const createSubchannel = React.useCallback(async () => { | |||||
try { | |||||
const threadType = threadTypesMatrix[visibilityType][+announcement]; | |||||
atulUnsubmitted Not Done Inline ActionsWhat's the + here for? atul: What's the `+` here for? | |||||
jakubAuthorUnsubmitted Done Inline Actionsjakub: It's an alternative for `parseInt` function. By default, `announcement` is boolean value thus… | |||||
tomekUnsubmitted Not Done Inline ActionsThis is really fragile. JS conversions are terrible and relying on them is extremely risky. Also, it isn't obvious that the position in threadTypesPerVisibility matters and it's easy to break this. I think it would be better to have an object instead of an array and use announcement value in ternary expression. But still, this complication isn't necessary and I think the best solution here is to have a simple function that takes visibilityType and announcement and returns threadType as a result. (just like pendingThreadType in thread-utils does). tomek: This is really fragile. JS conversions are terrible and relying on them is extremely risky. | |||||
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 setIsLoading(false); | |||||
tomekUnsubmitted Not Done Inline ActionsThis is strange that we're setting loading to false only when there's an error. How the loading indicator behaves when a thread is created successfully? tomek: This is strange that we're setting loading to false only when there's an error. How the loading… | |||||
jakubAuthorUnsubmitted Done Inline ActionsAfter successfully thread creation, modal disappear, so we don't need to return loading state to false. jakub: After successfully thread creation, modal disappear, so we don't need to return loading state… | |||||
tomekUnsubmitted Not Done Inline ActionsI don't see createLoadingStatusIndicator being used here. Is it in a different diff or this diff will be updated? tomek: I don't see `createLoadingStatusIndicator` being used here. Is it in a different diff or this… | |||||
jakubAuthorUnsubmitted Done Inline ActionsYes, I used it in the latest diff update jakub: Yes, I used it in the latest diff update | |||||
await setErrorMessage( | |||||
e.message === 'invalid_parameters' | |||||
? 'annoucement channels currently not available' | |||||
atulUnsubmitted Done Inline Actionsminor typo: should be "announcement" atul: minor typo: should be "announcement" | |||||
: 'unknown error', | |||||
); | |||||
return null; | |||||
} | |||||
}, [ | |||||
parentThreadInfo, | |||||
selectedUsers, | |||||
visibilityType, | |||||
announcement, | |||||
callNewThread, | |||||
calendarQuery, | |||||
channelName, | |||||
]); | |||||
const dispatchCreateSubchannel = React.useCallback(async () => { | |||||
await setErrorMessage(''); | |||||
await setIsLoading(true); | |||||
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 handleChanges = React.useCallback( | const handleChanges = React.useCallback( | ||||
( | ( | ||||
event: SyntheticEvent<HTMLInputElement>, | event: SyntheticEvent<HTMLInputElement>, | ||||
setValue: (newValue: any) => any, | setValue: (newValue: any) => any, | ||||
) => { | ) => { | ||||
const target = event.currentTarget; | const target = event.currentTarget; | ||||
setValue(target.value); | setValue(target.value); | ||||
}, | }, | ||||
▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | function ComposeSubchannelModal(props: Props): React.Node { | ||||
const stepperButtons = React.useMemo( | const stepperButtons = React.useMemo( | ||||
() => ({ | () => ({ | ||||
settings: { | settings: { | ||||
nextProps: { | nextProps: { | ||||
content: 'Next', | content: 'Next', | ||||
disabled: !channelName.trim(), | disabled: !channelName.trim(), | ||||
onClick: () => { | onClick: () => { | ||||
setErrorMessage(''); | |||||
setChannelName(channelName.trim()); | setChannelName(channelName.trim()); | ||||
setActiveStep('members'); | setActiveStep('members'); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
members: { | members: { | ||||
prevProps: { | prevProps: { | ||||
content: 'Back', | content: 'Back', | ||||
onClick: () => setActiveStep('settings'), | onClick: () => setActiveStep('settings'), | ||||
}, | }, | ||||
nextProps: { | nextProps: { | ||||
content: 'Create', | content: 'Create', | ||||
loading: isLoading, | |||||
disabled: selectedUsers.size === 0, | |||||
onClick: () => { | onClick: () => { | ||||
/// TODO: make form logic | dispatchCreateSubchannel(); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
}), | }), | ||||
[channelName], | [channelName, dispatchCreateSubchannel, isLoading, selectedUsers], | ||||
); | ); | ||||
const subchannelMembers = React.useMemo( | const subchannelMembers = React.useMemo( | ||||
() => ( | () => ( | ||||
<SubchannelMembers | <SubchannelMembers | ||||
parentThreadInfo={parentThreadInfo} | parentThreadInfo={parentThreadInfo} | ||||
selectedUsers={selectedUsers} | selectedUsers={selectedUsers} | ||||
searchText={searchUserText} | searchText={searchUserText} | ||||
Show All 18 Lines | steps.push( | ||||
/>, | />, | ||||
); | ); | ||||
steps.push( | steps.push( | ||||
<Stepper.Item | <Stepper.Item | ||||
content={subchannelMembers} | content={subchannelMembers} | ||||
key="members" | key="members" | ||||
prevProps={stepperButtons.members.prevProps} | prevProps={stepperButtons.members.prevProps} | ||||
nextProps={stepperButtons.members.nextProps} | nextProps={stepperButtons.members.nextProps} | ||||
errorMessage={errorMessage} | |||||
/>, | />, | ||||
); | ); | ||||
const modalName = React.useMemo(() => { | const modalName = React.useMemo(() => { | ||||
if (activeStep !== 'settings') { | if (activeStep !== 'settings') { | ||||
return `Create channel - ${trimText(channelName || '', 11)}`; | return `Create channel - ${trimText(channelName || '', 11)}`; | ||||
} else { | } else { | ||||
return 'Create channel'; | return 'Create channel'; | ||||
Show All 21 Lines |
matrix doesn't explain the purpose of this constant. Maybe threadTypesPerVisibility would be more descriptive?