whole flow with keyboard shortcuts looks good

This commit is contained in:
talksik
2022-05-18 05:39:22 -05:00
parent 1222976f65
commit 7f2da8f43e
2 changed files with 79 additions and 15 deletions
@@ -1,26 +1,34 @@
import React, { useState, useCallback, useRef, useEffect } from 'react'; import React, { useState, useCallback, useRef, useEffect } from 'react';
import { FiPlusSquare, FiUsers, FiXSquare } from 'react-icons/fi'; import { FiPlusSquare, FiUsers, FiX, FiXSquare } from 'react-icons/fi';
import { useAsyncFn, useDebounce } from 'react-use'; import { useAsyncFn, useDebounce, useKeyPressEvent } from 'react-use';
import { userSearch } from '../../../../api/NirvanaApi'; import { createLine, userSearch } from '../../../../api/NirvanaApi';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { Avatar, Skeleton, Spin } from 'antd'; import { Avatar, Divider, Skeleton, Spin } from 'antd';
import { User } from '@nirvana/core/models/user.model'; import { User } from '@nirvana/core/models/user.model';
import CreateLineRequest from '@nirvana/core/requests/createLine.request';
export default function NewChannelForm() { export default function NewChannelForm({ handleClose }: { handleClose: () => void }) {
const [peopleSearchQuery, setPeopleSearchQuery] = useState<string>(''); const [peopleSearchQuery, setPeopleSearchQuery] = useState<string>('');
const [userSearchRes, fetchUsers] = useAsyncFn(userSearch); const [userSearchRes, fetchUsers] = useAsyncFn(userSearch);
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
const [isSearchingUsers, setSearchingUsers] = useState<boolean>(false); const [isSearchingUsers, setSearchingUsers] = useState<boolean>(false);
const searchInputRef = useRef<HTMLInputElement>(null); const searchInputRef = useRef<HTMLInputElement>(null);
const [createChannelRes, triggerCreateChannel] = useAsyncFn(createLine);
useEffect(() => { useEffect(() => {
if (searchInputRef) searchInputRef.current.focus(); if (searchInputRef) searchInputRef.current.focus();
}, [searchInputRef]); }, [searchInputRef]);
const [_, cancel] = useDebounce( const [_, cancel] = useDebounce(
async () => { async () => {
if (!peopleSearchQuery) {
return;
}
try { try {
await fetchUsers(peopleSearchQuery); await fetchUsers(peopleSearchQuery);
@@ -42,17 +50,15 @@ export default function NewChannelForm() {
[setPeopleSearchQuery, setSearchingUsers], [setPeopleSearchQuery, setSearchingUsers],
); );
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
// ensuring that we haven't already selected this user // ensuring that we haven't already selected this user
// changing search results so that we don't see the selected user in the search results anymore // changing search results so that we don't see the selected user in the search results anymore
const addUser = useCallback( const addUser = useCallback(
(newUser: User) => { (newUser: User) => {
setSelectedUsers((prevUsers) => { setSelectedUsers((prevUsers) => {
if (prevUsers.find((currUser) => currUser.email === newUser.email)) { if (prevUsers.find((currUser) => currUser.email === newUser.email)) {
return [...prevUsers, newUser]; return prevUsers;
} }
return prevUsers; return [...prevUsers, newUser];
}); });
if (userSearchRes.value?.users) { if (userSearchRes.value?.users) {
@@ -70,8 +76,49 @@ export default function NewChannelForm() {
); );
}, []); }, []);
// TODO: prevent creating one-on-one line if already exists with x person?
// ensure that we don't have a one on one chat already with x person if it's one person selected
// upon success,
// make sure that the list of lines updates for this client and others so that it shows this new line
// select the line so that it shows up in the line details for this client
const handleCreateChannel = useCallback(async () => {
console.log('trying to create line now!');
try {
if (!selectedUsers?.length) {
toast.error('you must select at least one person');
return;
}
const selectedMemberIds = selectedUsers.map((selectedPerson) =>
selectedPerson._id.toString(),
);
await triggerCreateChannel(new CreateLineRequest(selectedMemberIds));
toast.success('created channel!');
// handle close once the new line is created
handleClose();
} catch (error) {
toast.error(error);
console.error(error);
} finally {
console.log('done');
}
}, [handleClose, selectedUsers, triggerCreateChannel]);
useKeyPressEvent('Enter', handleCreateChannel);
useKeyPressEvent('Escape', handleClose);
return ( return (
<div className="flex flex-col flex-1 items-center pt-10 bg-white"> <div className="flex flex-col flex-1 items-center pt-10 bg-white relative">
<span className="flex flex-col items-center gap-2 absolute top-5 right-5 cursor-pointer">
<FiX onClick={handleClose} className="text-gray-300 text-xl" />
<span className="text-gray-300 text-xs p-1 bg-gray-100">`esc`</span>
</span>
<div className="flex flex-col gap-2 max-w-lg w-full"> <div className="flex flex-col gap-2 max-w-lg w-full">
{/* people search */} {/* people search */}
<span className="text-gray-500">People</span> <span className="text-gray-500">People</span>
@@ -89,9 +136,10 @@ export default function NewChannelForm() {
{/* dropdown search results */} {/* dropdown search results */}
<div className="flex flex-col shadow-lg max-h-[500px] overflow-auto"> <div className="flex flex-col shadow-lg max-h-[500px] overflow-auto">
{(userSearchRes.loading || isSearchingUsers) && <Spin />} {(userSearchRes.loading || isSearchingUsers) && <Spin />}
{(!userSearchRes.value || userSearchRes.value?.users.length === 0) && ( {(!userSearchRes.value || userSearchRes.value?.users.length === 0) &&
<span className="p-10 text-gray-300">{`Can't find someone? Invite them and tell them the secret passcode!`}</span> selectedUsers?.length === 0 && (
)} <span className="p-10 text-gray-300">{`Can't find someone? Invite them and tell them the secret passcode!`}</span>
)}
{userSearchRes.value?.users.map((searchedUser) => { {userSearchRes.value?.users.map((searchedUser) => {
return ( return (
<div <div
@@ -139,6 +187,21 @@ export default function NewChannelForm() {
</div> </div>
); );
})} })}
<Divider />
<div className="flex flex-row justify-end items-start gap-2">
<button onClick={handleClose} className="p-2 text-gray-300 hover:bg-gray-100">
Cancel
</button>
<span className="flex flex-col items-center gap-2">
<button onClick={handleCreateChannel} className="p-2 bg-gray-800 text-white">
Tune In
</button>
<span className="text-gray-300 text-xs p-1 bg-gray-100">`enter`</span>
</span>
</div>
</div> </div>
</div> </div>
); );
@@ -23,13 +23,14 @@ import NewChannelForm from '../compose/NewChannelForm';
export default function MainPanel() { export default function MainPanel() {
const { user } = useAuth(); const { user } = useAuth();
const { selectedLineId, showNewChannelForm } = useRealTimeRooms(); const { selectedLineId, showNewChannelForm, handleShowNewChannelForm } = useRealTimeRooms();
// already won't see stuff for overlay only mode as per parent configuration // already won't see stuff for overlay only mode as per parent configuration
// TODO: if there is stuff in search, show that first // TODO: if there is stuff in search, show that first
if (showNewChannelForm) return <NewChannelForm />; if (showNewChannelForm)
return <NewChannelForm handleClose={() => handleShowNewChannelForm('hide')} />;
// if selected line, show line details // if selected line, show line details
if (selectedLineId) return <LineDetails />; if (selectedLineId) return <LineDetails />;