nice flow and quick working without creating a new conversation
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { HTMLAttributes, useCallback, useState } from 'react';
|
||||
import React, { HTMLAttributes, useCallback, useState, useEffect } from 'react';
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteChangeReason,
|
||||
@@ -20,18 +20,21 @@ import {
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import { blueGrey } from '@mui/material/colors';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { useDebounce, useKeyPressEvent, useToggle } from 'react-use';
|
||||
import { searchUsers } from '../firebase/firestore';
|
||||
import { User } from '@nirvana/core/src/models/user.model';
|
||||
import useAuth from '../providers/AuthProvider';
|
||||
import UserDetailRow from '../subcomponents/UserDetailRow';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
|
||||
export default function NewConversationDialog({
|
||||
open,
|
||||
handleClose,
|
||||
handleSubmit,
|
||||
}: {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleSubmit: (selectedUsers: User[], conversationName?: string) => void;
|
||||
}) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { user } = useAuth();
|
||||
@@ -75,16 +78,6 @@ export default function NewConversationDialog({
|
||||
[setSearching, setSearchVal],
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
try {
|
||||
//
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
|
||||
handleClose();
|
||||
}, [handleClose]);
|
||||
|
||||
const renderOption = useCallback(
|
||||
(props: HTMLAttributes<HTMLLIElement>, option: User, state: AutocompleteRenderOptionState) => (
|
||||
<ListItem {...props}>
|
||||
@@ -94,6 +87,48 @@ export default function NewConversationDialog({
|
||||
[],
|
||||
);
|
||||
|
||||
const [conversationName, setConversationName] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedUsers.length < 2) {
|
||||
setConversationName('');
|
||||
}
|
||||
}, [selectedUsers, setConversationName]);
|
||||
|
||||
const handleChangeName = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConversationName(e.target.value);
|
||||
},
|
||||
[setConversationName],
|
||||
);
|
||||
|
||||
const [isSubmitting, toggleIsSubmitting] = useToggle(false);
|
||||
|
||||
const handleSubmitLocal = useCallback(async () => {
|
||||
if (selectedUsers.length === 0) {
|
||||
enqueueSnackbar('Must a person!', { variant: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
toggleIsSubmitting();
|
||||
|
||||
handleSubmit(selectedUsers, conversationName);
|
||||
|
||||
// clear form for next time
|
||||
setSelectedUsers([]);
|
||||
setConversationName('');
|
||||
|
||||
toggleIsSubmitting();
|
||||
}, [
|
||||
selectedUsers,
|
||||
setConversationName,
|
||||
conversationName,
|
||||
enqueueSnackbar,
|
||||
handleSubmit,
|
||||
setSelectedUsers,
|
||||
toggleIsSubmitting,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dialog fullScreen open={open} onClose={handleClose}>
|
||||
<Container
|
||||
@@ -113,53 +148,67 @@ export default function NewConversationDialog({
|
||||
<FiX />
|
||||
</IconButton>
|
||||
|
||||
<Container
|
||||
maxWidth="sm"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Autocomplete
|
||||
multiple
|
||||
loading={searching}
|
||||
includeInputInList
|
||||
id="tags-outlined"
|
||||
autoHighlight
|
||||
onInputChange={handleChangeSearchInput}
|
||||
options={searchUsersResults}
|
||||
renderOption={renderOption}
|
||||
getOptionLabel={(option) => (typeof option === 'string' ? option : option.displayName)}
|
||||
value={selectedUsers}
|
||||
onChange={handleChangeSelections}
|
||||
filterSelectedOptions
|
||||
isOptionEqualToValue={(optionUser, valueUser) => optionUser.id === valueUser.id}
|
||||
filterOptions={(options) => options}
|
||||
inputValue={searchVal}
|
||||
renderInput={(params) => (
|
||||
{isSubmitting ? (
|
||||
<CircularProgress />
|
||||
) : (
|
||||
<Container
|
||||
maxWidth="sm"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 5,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4">Start a Conversation</Typography>
|
||||
|
||||
<Autocomplete
|
||||
multiple
|
||||
loading={searching}
|
||||
includeInputInList
|
||||
id="tags-outlined"
|
||||
autoHighlight
|
||||
onInputChange={handleChangeSearchInput}
|
||||
options={searchUsersResults}
|
||||
renderOption={renderOption}
|
||||
getOptionLabel={(option) =>
|
||||
typeof option === 'string' ? option : option.displayName
|
||||
}
|
||||
value={selectedUsers}
|
||||
onChange={handleChangeSelections}
|
||||
filterSelectedOptions
|
||||
isOptionEqualToValue={(optionUser, valueUser) => optionUser.id === valueUser.id}
|
||||
filterOptions={(options) => options}
|
||||
inputValue={searchVal}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
fullWidth
|
||||
label="People"
|
||||
{...params}
|
||||
placeholder="Search by name or email"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{selectedUsers.length > 1 && (
|
||||
<TextField
|
||||
value={conversationName}
|
||||
onChange={handleChangeName}
|
||||
fullWidth
|
||||
label="People"
|
||||
{...params}
|
||||
placeholder="Search by name or email"
|
||||
label="Name (optional)"
|
||||
placeholder="Channel, subject line, whatever..."
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Name (optional)"
|
||||
placeholder="Channel, subject line, whatever..."
|
||||
/>
|
||||
|
||||
<Stack justifyContent={'flex-end'} direction={'row'} spacing={2}>
|
||||
<Button variant={'text'}>Cancel</Button>
|
||||
<Button variant={'contained'} color="primary">
|
||||
Connect
|
||||
</Button>
|
||||
</Stack>
|
||||
</Container>
|
||||
<Stack justifyContent={'flex-end'} direction={'row'} spacing={2}>
|
||||
<Button variant={'text'}>Cancel</Button>
|
||||
<Button disabled={selectedUsers.length === 0} variant={'contained'} color="primary">
|
||||
Connect
|
||||
</Button>
|
||||
</Stack>
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -50,6 +50,7 @@ import Navbar from './Navbar';
|
||||
import MainPanel from './MainPanel';
|
||||
import { ConversationList } from './ConversationList';
|
||||
import NewConversationDialog from './NewConversationDialog';
|
||||
import { createGroupConversation } from '../firebase/firestore';
|
||||
type ConversationMap = {
|
||||
[conversationId: string]: Conversation;
|
||||
};
|
||||
@@ -69,7 +70,7 @@ interface ITerminalContext {
|
||||
selectedConversation?: Conversation;
|
||||
selectConversation?: (conversationId: string) => void;
|
||||
|
||||
handleQuickDial?: (otherUserId: string) => void;
|
||||
handleQuickDial?: (otherUser: User) => void;
|
||||
|
||||
getUser?: (userId: string) => Promise<User | undefined>;
|
||||
|
||||
@@ -159,7 +160,7 @@ const handleOnStartRecording = (e: BlobEvent) => {
|
||||
// todo: extract each use effect to custom hook and will be clean
|
||||
export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { user, logout } = useAuth();
|
||||
const { user, logout, nirvanaUser } = useAuth();
|
||||
|
||||
const [selectedConversationId, setSelectedConversationId] = useState<string>(undefined);
|
||||
|
||||
@@ -282,7 +283,7 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
||||
// handle create or open existing conversation
|
||||
// not 100% consistent to the second, but still works...don't need atomicity
|
||||
const handleQuickDial = useCallback(
|
||||
async (otherUserId: string) => {
|
||||
async (otherUser: User) => {
|
||||
setSearchVal('');
|
||||
|
||||
try {
|
||||
@@ -292,8 +293,8 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
||||
const findExistingConversation = Object.values(conversationMap).find((convo) => {
|
||||
if (
|
||||
convo.memberIdsList?.length === 2 &&
|
||||
convo.memberIdsList.includes(user.uid) &&
|
||||
convo.memberIdsList.includes(otherUserId)
|
||||
convo.memberIdsList.includes(nirvanaUser.id) &&
|
||||
convo.memberIdsList.includes(otherUser.id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -307,14 +308,14 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
||||
}
|
||||
|
||||
// create conversation in this case
|
||||
const newConversationId = await createOneOnOneConversation(otherUserId, user.uid);
|
||||
const newConversationId = await createOneOnOneConversation(otherUser, nirvanaUser);
|
||||
enqueueSnackbar('started conversation!', { variant: 'success' });
|
||||
setSelectedConversationId(newConversationId);
|
||||
} catch (error) {
|
||||
enqueueSnackbar('Something went wrong, please try again', { variant: 'error' });
|
||||
}
|
||||
},
|
||||
[conversationMap, enqueueSnackbar, user],
|
||||
[conversationMap, enqueueSnackbar, nirvanaUser],
|
||||
);
|
||||
|
||||
const getUser = useCallback(
|
||||
@@ -496,12 +497,42 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
// const handleCreateConversation = useCallback(() => {
|
||||
|
||||
// }, [])
|
||||
|
||||
const [createConversationMode, setCreateConversationMode] = useState<boolean>(true);
|
||||
|
||||
const handleStartConversation = useCallback(
|
||||
async (selectedUsers: User[], conversationName?: string) => {
|
||||
if (selectedUsers.length === 0) {
|
||||
enqueueSnackbar('Must select more than one person');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// one on one, handle quick dial to prevent another conversation
|
||||
if (selectedUsers.length === 1) {
|
||||
await handleQuickDial(selectedUsers[0]);
|
||||
|
||||
setCreateConversationMode(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// create group chat
|
||||
// create conversation in this case
|
||||
const newConversationId = await createGroupConversation(
|
||||
selectedUsers,
|
||||
nirvanaUser,
|
||||
conversationName ?? null,
|
||||
);
|
||||
enqueueSnackbar('started group conversation!', { variant: 'success' });
|
||||
setSelectedConversationId(newConversationId);
|
||||
|
||||
setCreateConversationMode(false);
|
||||
} catch (error) {
|
||||
enqueueSnackbar('Something went wrong, please try again', { variant: 'error' });
|
||||
}
|
||||
},
|
||||
[nirvanaUser, handleQuickDial, enqueueSnackbar, setCreateConversationMode],
|
||||
);
|
||||
|
||||
const handleShowCreateConvoForm = useCallback(() => {
|
||||
setSelectedConversationId(undefined);
|
||||
setCreateConversationMode(true);
|
||||
@@ -693,7 +724,11 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
||||
{children}
|
||||
|
||||
{/* create chat dialog */}
|
||||
<NewConversationDialog open={createConversationMode} handleClose={handleEscape} />
|
||||
<NewConversationDialog
|
||||
open={createConversationMode}
|
||||
handleSubmit={handleStartConversation}
|
||||
handleClose={handleEscape}
|
||||
/>
|
||||
</TerminalContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -739,7 +774,7 @@ function ListPeople({ people }: { people: User[] }) {
|
||||
{people.map((person) => (
|
||||
<Tooltip key={`${person.uid}-searchUsers`} title={'quick dial'}>
|
||||
<ListItem>
|
||||
<ListItemButton onClick={() => handleQuickDial(person.uid)}>
|
||||
<ListItemButton onClick={() => handleQuickDial(person)}>
|
||||
<ListItemAvatar>
|
||||
<Avatar alt={person.displayName} src={person.photoUrl} />
|
||||
</ListItemAvatar>
|
||||
|
||||
Reference in New Issue
Block a user