import { Button, Dialog, ModalContext } from '@8base/boost'; import { ApolloClient, ApolloQueryResult, FetchPolicy } from 'apollo-client'; import debounce from 'lodash/debounce'; import React from 'react'; import { compose, withApollo } from 'react-apollo'; import { ChatContext, IChatContext, withContext } from 'context'; import { DialogForm, UsersBrowser } from 'shared/components'; import { ChannelMembersDocument, ChannelUpdateDocument, ChannelUpdateMutation, UserChannelsPreviewDocument, UsersSearchDocument, UsersSearchQuery, UsersSearchQueryVariables, } from 'shared/graphql/__generated__'; import { makeCancelable } from 'shared/utils'; import { IOption } from 'types'; import { getOperationName } from 'apollo-link'; import cssUtils from '../../../styles/utilities.css'; // -- TYPES interface IAddToChannelDialogProps { user: IChatContext['user']; client: ApolloClient; usersFilter: IChatContext['usersFilter']; } interface IDialogContentProps extends IAddToChannelDialogProps { channelId: string; channelName: string; } interface IDialogContentState { data: { users: IOption[]; }; userSearchText: string; usersList: UsersSearchQuery['usersList']; loadingOptions: boolean; submitting: boolean; } // -- CONSTANTS export const ADD_TO_CHANNEL_DIALOG_ID = 'ADD_TO_CHANNEL_DIALOG_ID'; // -- COMPONENTS const cnUsersBrowser = { list: cssUtils['u-dialog-browser-list'], }; class DialogContent extends React.Component { static contextType = ModalContext; private onSearchDebounced: DialogContent['onSearch']; private currentRequest?: ReturnType; constructor(props: IDialogContentProps) { super(props); this.state = { data: { users: [], }, userSearchText: '', usersList: { items: [], }, loadingOptions: false, submitting: false, }; this.onSearchDebounced = debounce(this.onSearch, 200); } componentDidMount() { this.fetchUsers(this.state.userSearchText, 'network-only'); } componentWillUnmount() { this.cancelCurrentRequest(); } render() { const { channelName } = this.props; const { data, userSearchText, usersList, loadingOptions, submitting } = this.state; const disabled = data.users.length === 0; const availableUsers = usersList.items.filter( user => data.users.find(el => el.value === user.id) === undefined, ); return ( el.value), onChange: this.onSelectChange, }} options={data.users} loading={loadingOptions} onInputChange={this.onInputChange} inputValue={userSearchText} users={availableUsers} onItemSelect={this.onItemSelect} /> ); } private onSubmit = async (e: any) => { e.preventDefault(); if (this.state.submitting) { return; } const membersCreate = this.state.data.users.map(el => ({ user: { connect: { id: el.value, }, }, })); this.setState({ submitting: true, }); await this.props.client.mutate({ mutation: ChannelUpdateDocument, refetchQueries: [ getOperationName(UserChannelsPreviewDocument) as string, { query: ChannelMembersDocument, variables: { id: this.props.channelId, first: 3 }, }, ], variables: { data: { id: this.props.channelId, members: { create: membersCreate, }, }, }, }); this.setState({ submitting: false, }); this.closeDialog(); }; private closeDialog = () => { this.context.closeModal(ADD_TO_CHANNEL_DIALOG_ID); }; private onItemSelect = (option: IOption) => { this.setState(s => { if (s.data.users.find(el => el.value === option.value)) { return null; } return { data: { ...s.data, users: [...s.data.users, option], }, }; }); }; private onSelectChange = (values: string[]) => { this.setState(s => ({ data: { ...s.data, users: s.data.users.filter(el => values.includes(el.value)), }, })); }; private onInputChange = (val: string, { action }: { action: string }) => { if (action !== 'input-change') { return; } this.setState({ userSearchText: val, }); this.onSearchDebounced(val); }; private onSearch = (text: string) => { this.cancelCurrentRequest(); this.fetchUsers(text); }; private fetchUsers(text: string, fetchPolicy?: FetchPolicy) { this.setState({ loadingOptions: true, }); const usersFilter = this.props.usersFilter || { id: { not_in: [this.props.user ? this.props.user.id : ''], }, }; const queryVariables: UsersSearchQueryVariables = { first: 4, searchText: text, channelId: this.props.channelId, usersFilter, }; this.currentRequest = makeCancelable( this.props.client.query({ query: UsersSearchDocument, variables: queryVariables, fetchPolicy, }), ); (this.currentRequest.promise as Promise>) .then(({ data }) => { this.setState({ usersList: data.usersList, loadingOptions: false, }); }) .catch(() => {}); } private cancelCurrentRequest() { if (this.currentRequest) { this.currentRequest.cancel(); } } } // -- MAIN function AddToChannelDialog(props: IAddToChannelDialogProps) { return ( {({ args }: { args: { channelId: string; channelName: string } }) => ( )} ); } AddToChannelDialog.id = ADD_TO_CHANNEL_DIALOG_ID; export default compose( withApollo, withContext({ context: ChatContext, keys: ['user', 'usersFilter'] }), )(AddToChannelDialog);