cleaning out legacy and making it work
This commit is contained in:
@@ -0,0 +1,513 @@
|
||||
import { $desktopMode, $selectedLineId } from '../../controller/recoil';
|
||||
import { Avatar, Skeleton, Tooltip } from 'antd';
|
||||
import { FiActivity, FiHeadphones, FiSettings, FiSun } from 'react-icons/fi';
|
||||
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useGetUserDetails, useUserLines } from '../../controller/index';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
import LineIcon from '../../components/lines/lineIcon/index';
|
||||
import { LineMemberState } from '@nirvana/core/models/line.model';
|
||||
import LineRow from '../../components/lines/lineRow.tsx/index';
|
||||
import MasterLineData from '@nirvana/core/models/masterLineData.model';
|
||||
import NewLineModal from './newLine';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useLineDataProvider } from '../../controller/lineDataProvider';
|
||||
|
||||
/**
|
||||
* Socket Provider
|
||||
* Line Data Provider
|
||||
*/
|
||||
export default function NirvanaTerminal({ overlayOnly }: { overlayOnly: boolean }) {
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
||||
|
||||
const { data: userDetails } = useGetUserDetails();
|
||||
const [selectedLineId, setSelectedLineId] = useRecoilState($selectedLineId);
|
||||
const [desktopMode, setDesktopMode] = useRecoilState($desktopMode);
|
||||
|
||||
// simply using this query for specific data on loading
|
||||
// todo: add these properties in context provider value although more work down the line for control
|
||||
const { isLoading: isLoadingInitialLines } = useUserLines();
|
||||
const { linesMap, handleTuneToLine, handleUnTuneToLine } = useLineDataProvider();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("change/update in lines map");
|
||||
// console.log(linesMap);
|
||||
}, [linesMap]);
|
||||
|
||||
// ! remounting when toggle tuning and untoggle tuning a line
|
||||
// because it changes the list that it's in...solution 1: put everything in one list and use one sort to handle toggle tuned items to be clean
|
||||
// the change in the object or the property which is specifically currentUserMember of the masterLineData sent in to line row
|
||||
// causes a re-render but not a unmount and remount
|
||||
|
||||
const allLines: MasterLineData[] = useMemo(() => {
|
||||
const masterLines: MasterLineData[] = Object.values(linesMap);
|
||||
|
||||
// TODO: sort based on the audio blocks and currentMember lastActiveDate
|
||||
|
||||
return masterLines.filter(
|
||||
(masterLine) => masterLine.currentUserMember.state === LineMemberState.INBOX,
|
||||
);
|
||||
}, [linesMap]);
|
||||
|
||||
const toggleTunedLines = useMemo(() => {
|
||||
const masterLines: MasterLineData[] = Object.values(linesMap);
|
||||
|
||||
return masterLines.filter(
|
||||
(masterLine) => masterLine.currentUserMember.state === LineMemberState.TUNED,
|
||||
);
|
||||
}, [linesMap]);
|
||||
|
||||
const selectedLine: MasterLineData | undefined = useMemo(() => {
|
||||
if (!selectedLineId) return undefined;
|
||||
|
||||
// find the line from the data provider
|
||||
if (linesMap[selectedLineId]) {
|
||||
// console.log("looking for selected Line in map for details section");
|
||||
|
||||
const foundSelectedLine = linesMap[selectedLineId];
|
||||
|
||||
// on mount of this, we want to temporarily tune into the line if we are not already tuned in...which would happen if we toggle tuned in
|
||||
if (!foundSelectedLine.tunedInMemberIds?.includes(userDetails?.user?._id.toString())) {
|
||||
handleTuneToLine(selectedLineId, false);
|
||||
}
|
||||
|
||||
return { ...foundSelectedLine };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [selectedLineId, linesMap, userDetails]);
|
||||
|
||||
// todo: sort/order based on activity and activity date and currently broadcasting/live
|
||||
|
||||
const handleEscape = useCallback(() => {
|
||||
console.log('deselecting line');
|
||||
|
||||
setSelectedLineId((prevSelectedLineId) => {
|
||||
// ! only want to untune if it's a temporarily tuned line
|
||||
if (selectedLine?.currentUserMember?.state === LineMemberState.INBOX)
|
||||
handleUnTuneToLine(prevSelectedLineId);
|
||||
|
||||
return null;
|
||||
});
|
||||
}, [setSelectedLineId, selectedLine]);
|
||||
|
||||
/** show user line details on click of one line */
|
||||
const handleSelectLine = useCallback(
|
||||
(newLineIdToSelect: string) => {
|
||||
if (newLineIdToSelect !== selectedLine?.lineDetails._id.toString()) {
|
||||
setSelectedLineId((prevSelectedLineId) => {
|
||||
// untune myself from the previously selected if it was a temporary one/inbox
|
||||
// todo: p3: consolidate this logic as it's used in escape but different scenarios sort of so p3
|
||||
if (prevSelectedLineId && selectedLine.currentUserMember?.state === LineMemberState.INBOX)
|
||||
handleUnTuneToLine(prevSelectedLineId);
|
||||
|
||||
return newLineIdToSelect;
|
||||
});
|
||||
}
|
||||
},
|
||||
[setSelectedLineId, selectedLine],
|
||||
);
|
||||
|
||||
const handleToggleTuneToLine = useCallback(
|
||||
(lineId: string, turnToggleOn: boolean) => {
|
||||
// inhibit if they are trying to turn on and already have 3 toggle tuned
|
||||
if (toggleTunedLines?.length >= 3 && turnToggleOn) {
|
||||
toast.error('You cannot toggle more than 3 lines!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handleTuneToLine(lineId, turnToggleOn);
|
||||
},
|
||||
[toggleTunedLines, handleTuneToLine],
|
||||
);
|
||||
|
||||
const handleStartBroadcast = useCallback(
|
||||
(lineId: string) => () => {
|
||||
console.log(`starting broadcast for ${lineId}!!!`);
|
||||
|
||||
// todo: enable stream in this tuned in channel
|
||||
|
||||
// if (lineId) handleUserBroadcast(lineId, true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleStopBroadcast = useCallback(
|
||||
(lineId: string) => () => {
|
||||
console.log(`stopping broadcast for ${lineId}!!!`);
|
||||
|
||||
// todo: disable stream in this tuned in channel
|
||||
|
||||
// if (lineId) handleUserBroadcast(lineId, false);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const keyMap: KeyMap = useMemo(
|
||||
() => ({
|
||||
DESELECT_LINE: 'esc',
|
||||
START_BROADCAST: {
|
||||
sequence: '`',
|
||||
action: 'keydown',
|
||||
},
|
||||
STOP_BROADCAST: {
|
||||
sequence: '`',
|
||||
action: 'keyup',
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const handlers = useMemo(
|
||||
() => ({
|
||||
DESELECT_LINE: handleEscape,
|
||||
START_BROADCAST: handleStartBroadcast(selectedLineId),
|
||||
STOP_BROADCAST: handleStopBroadcast(selectedLineId),
|
||||
}),
|
||||
[selectedLineId],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalHotKeys handlers={handlers} keyMap={keyMap} allowChanges />
|
||||
|
||||
<div className="flex flex-row flex-1">
|
||||
<div className="flex flex-col bg-white w-[400px] relative group">
|
||||
{/* modal for creating new line */}
|
||||
<NewLineModal open={isModalVisible} handleClose={() => setIsModalVisible(false)} />
|
||||
|
||||
{/* tuned in lines block */}
|
||||
<div className="bg-gray-100 flex flex-col shadow-lg">
|
||||
{/* tuned in header + general controls */}
|
||||
|
||||
<Tooltip placement="right" title={'These are your active rooms...'}>
|
||||
<div className="flex flex-row items-center py-3 px-4 pb-0">
|
||||
<span className="flex flex-row gap-2 items-center justify-start text-gray-400 animate-pulse">
|
||||
<FiActivity className="text-sm" />
|
||||
|
||||
<h2 className="text-inherit text-sm">Rooms</h2>
|
||||
|
||||
<p className="text-slate-300 text-xs">{`${toggleTunedLines?.length || 0}/3`}</p>
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* list of toggle tuned lines */}
|
||||
<div className="flex flex-col mt-2">
|
||||
{toggleTunedLines.map((masterLineData) => (
|
||||
<LineRow
|
||||
key={`terminalListLines-${masterLineData.lineDetails._id.toString()}`}
|
||||
masterLineData={masterLineData}
|
||||
handleSelectLine={handleSelectLine}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!(allLines.length > 0) && !(toggleTunedLines.length > 0) && (
|
||||
<span className="text-gray-300 text-sm my-5 text-center">
|
||||
You have no lines! <br /> Create one to connect to your team instantly.
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* rest of the lines */}
|
||||
<div className={'flex flex-col'}>
|
||||
{isLoadingInitialLines ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
allLines.map((masterLineData) => (
|
||||
<LineRow
|
||||
key={`terminalListLines-${masterLineData.lineDetails._id.toString()}`}
|
||||
masterLineData={masterLineData}
|
||||
handleSelectLine={handleSelectLine}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => setIsModalVisible(true)}
|
||||
className="absolute bottom-3 right-3 z-10 scale-0
|
||||
group-hover:scale-100 ease-in-out hover:transition group-hover:transition delay-100 duration-200"
|
||||
>
|
||||
<button
|
||||
className="flex flex-row gap-2 items-center justify-evenly
|
||||
shadow-xl bg-gray-800 p-2 text-white text-xs"
|
||||
>
|
||||
<FaPlus />
|
||||
<span>New line</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedLine ? (
|
||||
<LineDetailsTerminal
|
||||
handleToggleTuneToLine={handleToggleTuneToLine}
|
||||
selectedLine={selectedLine}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="flex flex-col flex-1 justify-center items-center bg-gray-100
|
||||
border-l border-l-gray-200"
|
||||
>
|
||||
<span className="text-xl text-gray-800">{`Hi ${userDetails?.user?.givenName}!`}</span>
|
||||
<span className="text-md text-gray-400">You're all set!</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function LineDetailsTerminal({
|
||||
selectedLine,
|
||||
handleToggleTuneToLine,
|
||||
}: {
|
||||
selectedLine: MasterLineData;
|
||||
handleToggleTuneToLine: (lineId: string, turnToggleOn: boolean) => void;
|
||||
}) {
|
||||
const { data: userDetails } = useGetUserDetails();
|
||||
|
||||
console.log('selected line', selectedLine);
|
||||
|
||||
const isUserToggleTuned = useMemo(
|
||||
() => selectedLine?.currentUserMember?.state === LineMemberState.TUNED,
|
||||
[selectedLine],
|
||||
);
|
||||
|
||||
// seeing if I am in the list of broadcasters
|
||||
// the source of truth from the socket connections telling me if my clicking actually made a round trip
|
||||
const isUserBroadcasting = useMemo(
|
||||
() => selectedLine?.currentBroadcastersUserIds?.includes(userDetails?.user._id.toString()),
|
||||
[userDetails, selectedLine],
|
||||
);
|
||||
|
||||
// showing all tuned in members...they may not hear me since they might be doing something else
|
||||
// but feeling of presentness
|
||||
const tunedProfiles = useMemo(() => {
|
||||
const pictureSources: { name: string; pictureSrc: string }[] = [];
|
||||
|
||||
selectedLine.tunedInMemberIds?.forEach((tunedInMemberUserId) => {
|
||||
// don't want to see my own picture
|
||||
// TODO: don't show myself!?
|
||||
if (tunedInMemberUserId === userDetails.user._id.toString()) {
|
||||
pictureSources.push();
|
||||
|
||||
pictureSources.push({
|
||||
name: userDetails.user?.givenName,
|
||||
pictureSrc: userDetails.user?.picture,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const otherUserObject = selectedLine.otherUserObjects?.find(
|
||||
(userObj) => userObj._id.toString() === tunedInMemberUserId,
|
||||
);
|
||||
if (otherUserObject?.picture)
|
||||
pictureSources.push({
|
||||
name: otherUserObject.givenName,
|
||||
pictureSrc: otherUserObject.picture,
|
||||
});
|
||||
});
|
||||
|
||||
return pictureSources;
|
||||
}, [selectedLine, userDetails]);
|
||||
|
||||
// pics for the line icons
|
||||
const profilePictures = useMemo(() => {
|
||||
const pictureSources: string[] = [];
|
||||
|
||||
// ?don't add in my image as that's useless contextually?
|
||||
// if (userData?.user?.picture) pictureSources.push(userData.user.picture);
|
||||
|
||||
selectedLine.otherUserObjects?.forEach((otherUser) => {
|
||||
if (otherUser.picture) pictureSources.push(otherUser.picture);
|
||||
});
|
||||
|
||||
return pictureSources;
|
||||
}, [selectedLine, userDetails]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col flex-1 bg-gray-100
|
||||
border-l border-l-gray-200 relative"
|
||||
>
|
||||
{/* line details */}
|
||||
<div
|
||||
className="p-4
|
||||
flex flex-row items-center gap-2 justify-end border-b-gray-200 border-b"
|
||||
>
|
||||
{profilePictures && <LineIcon grayscale={false} sourceImages={profilePictures} />}
|
||||
|
||||
<div className="flex flex-col items-start mr-auto group">
|
||||
<span className="flex flex-row gap-2 items-center">
|
||||
<h2 className={`text-lg text-slate-800 font-semibold`}>
|
||||
{selectedLine.lineDetails.name || selectedLine.otherUserObjects[0].givenName}
|
||||
</h2>
|
||||
|
||||
<button
|
||||
className={`p-1 hidden group-hover:flex justify-center items-center hover:bg-gray-300
|
||||
transition-all hover:scale-105`}
|
||||
>
|
||||
<FiSettings className="text-gray-400 text-xs" />
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span className="flex flex-row gap-2 items-center">
|
||||
<span className="text-gray-300 text-xs">{`${
|
||||
selectedLine.otherMembers?.length + 1 ?? 0
|
||||
} members`}</span>
|
||||
<span className="h-1 w-1 bg-gray-800 rounded-full"></span>
|
||||
<span className="text-teal-500 text-xs">{`${
|
||||
selectedLine.tunedInMemberIds?.length ?? 0
|
||||
} in this room`}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* TODO: move to on hover of line row */}
|
||||
<Tooltip
|
||||
placement="left"
|
||||
title={`${isUserToggleTuned ? 'click to untoggle' : 'click to stay tuned in'}`}
|
||||
>
|
||||
<button
|
||||
className={`p-2 flex justify-center items-center shadow-lg
|
||||
hover:scale-105 transition-all animate-pulse ${
|
||||
isUserToggleTuned ? 'bg-gray-800 text-white' : 'text-black'
|
||||
}`}
|
||||
onClick={() =>
|
||||
isUserToggleTuned
|
||||
? handleToggleTuneToLine(selectedLine.lineDetails._id.toString(), false)
|
||||
: handleToggleTuneToLine(selectedLine.lineDetails._id.toString(), true)
|
||||
}
|
||||
>
|
||||
<FiActivity className="text-md" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* line timeline */}
|
||||
<div className="flex flex-col items-center gap-2 my-2 mx-auto max-w-lg w-full">
|
||||
<span className={'text-gray-300 text-sm cursor-pointer hover:underline'}>load more</span>
|
||||
|
||||
<span className={'text-gray-300 text-sm'}>yesterday</span>
|
||||
|
||||
<div className={'rounded border border-gray-200 flex flex-col w-full'}>
|
||||
{selectedLine.otherUserObjects.map((otherUser) => (
|
||||
<div
|
||||
className="flex flex-row p-2 gap-2 items-center bg-transparent
|
||||
border-b border-b-gray-200 last:border-b-0"
|
||||
>
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={otherUser.picture}
|
||||
shape="square"
|
||||
size={'default'}
|
||||
// grayscale if not playing?
|
||||
className={`${true && 'grayscale'}`}
|
||||
/>
|
||||
<span className="text-gray-500">{otherUser.givenName}</span>
|
||||
|
||||
<span className="ml-auto text-xs text-gray-300">{`${
|
||||
Math.floor(Math.random() * 10) + 1
|
||||
}:${Math.floor(Math.random() * 100) + 10}pm |`}</span>
|
||||
|
||||
<span className="text-gray-400 text-md">{`${
|
||||
Math.floor(Math.random() * 60) + 1
|
||||
} seconds`}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div
|
||||
className="flex flex-row p-2 gap-2 items-center bg-transparent
|
||||
border-b border-b-gray-200 last:border-b-0"
|
||||
>
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={userDetails.user.picture}
|
||||
shape="square"
|
||||
size={'default'}
|
||||
// grayscale if not playing?
|
||||
className={`${true && 'grayscale'}`}
|
||||
/>
|
||||
<span className="text-gray-500">{'Arjun Patel'}</span>
|
||||
|
||||
<span className="ml-auto text-xs text-gray-300">{`${
|
||||
Math.floor(Math.random() * 10) + 1
|
||||
}:${Math.floor(Math.random() * 100) + 10}pm |`}</span>
|
||||
|
||||
<span className="text-gray-400 text-md">{`${
|
||||
Math.floor(Math.random() * 60) + 1
|
||||
} seconds`}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className={'text-gray-300 text-sm'}>today</span>
|
||||
|
||||
<div className={'rounded border border-gray-400 flex flex-col w-full shadow-xl'}>
|
||||
{selectedLine.otherUserObjects.map((otherUser) => (
|
||||
// TODO: show the shadow if it's unheard
|
||||
<div
|
||||
className="flex flex-row p-2 gap-2 items-center bg-transparent
|
||||
border-b border-b-gray-400 last:border-b-0"
|
||||
>
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={otherUser.picture}
|
||||
shape="square"
|
||||
size={'default'}
|
||||
// grayscale if not playing?
|
||||
className={`${false && 'grayscale'}`}
|
||||
/>
|
||||
<span className="text-gray-600 font-semibold">{otherUser.name}</span>
|
||||
|
||||
<span className="ml-auto text-xs text-gray-400">{`${
|
||||
Math.floor(Math.random() * 10) + 1
|
||||
}:${Math.floor(Math.random() * 100) + 10}pm |`}</span>
|
||||
|
||||
<span className="text-gray-500 text-md">{`${
|
||||
Math.floor(Math.random() * 60) + 1
|
||||
} seconds`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* live broadcasters */}
|
||||
<span className={'flex flex-row gap-2 items-center text-teal-500 text-sm'}>
|
||||
<FiSun className="animate-ping" />
|
||||
<span>right now</span>
|
||||
</span>
|
||||
<div className={'rounded border border-teal-500 flex flex-col w-full shadow-2xl'}>
|
||||
{selectedLine.otherUserObjects.map((otherUser) => (
|
||||
<div className="flex flex-row p-2 gap-2 items-center bg-transparent">
|
||||
<Avatar
|
||||
key={`linehistory-${1}`}
|
||||
src={otherUser.picture}
|
||||
shape="square"
|
||||
size={'large'}
|
||||
// grayscale if not playing?
|
||||
className={`shadow-lg`}
|
||||
/>
|
||||
<span className="text-gray-600 font-semibold">{otherUser.name}</span>
|
||||
|
||||
<Tooltip title={'audio only'}>
|
||||
<FiHeadphones className="text-gray-600 text-md ml-auto" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`p-3 absolute right-3 bottom-3 flex justify-center items-center shadow-2xl
|
||||
hover:scale-105 transition-all ${
|
||||
isUserBroadcasting ? 'bg-teal-800 text-white' : 'text-teal-800 border-teal-800 border'
|
||||
}`}
|
||||
>
|
||||
<FiSun className="text-lg" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import { Avatar, Modal } from "antd";
|
||||
import { HotKeys, KeyMap } from "react-hotkeys";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCreateLine, useUserSearch } from "../../../controller/index";
|
||||
|
||||
import BasicUserRow from "../../../components/User/basicUserDetailsRow";
|
||||
import { FiSearch } from "react-icons/fi";
|
||||
import { User } from "@nirvana/core/models";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export default function NewLineModal({
|
||||
open,
|
||||
handleClose,
|
||||
}: {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
}) {
|
||||
const [selectedPeople, setSelectedPeople] = useState<User[]>([]);
|
||||
|
||||
const [peopleSearchValue, setPeopleSearchValue] = useState<string>("");
|
||||
|
||||
// the actual state that goes to query...the debounced one so to speak
|
||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||
const { refetch, data: searchRes } = useUserSearch(searchQuery);
|
||||
|
||||
const { mutateAsync, isLoading } = useCreateLine();
|
||||
|
||||
const [lineName, setLineName] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery) refetch();
|
||||
}, [searchQuery]);
|
||||
|
||||
const onSearch = useCallback(() => {
|
||||
console.log("enter key pressed");
|
||||
|
||||
if (peopleSearchValue) {
|
||||
console.log("searching for people in database");
|
||||
setSearchQuery(peopleSearchValue);
|
||||
}
|
||||
}, [setSearchQuery, peopleSearchValue]);
|
||||
|
||||
const selectUser = useCallback(
|
||||
(userToAdd: User) => {
|
||||
setSelectedPeople((prevSelectedUsers) => {
|
||||
// if user is not already in the selected user
|
||||
const foundUser = prevSelectedUsers.find(
|
||||
(currentUser) =>
|
||||
currentUser._id.toString() === userToAdd._id.toString()
|
||||
);
|
||||
if (!foundUser) {
|
||||
return [...prevSelectedUsers, userToAdd];
|
||||
}
|
||||
|
||||
toast.error("you already selected this person below!");
|
||||
return prevSelectedUsers;
|
||||
});
|
||||
},
|
||||
[setSelectedPeople]
|
||||
);
|
||||
|
||||
const unSelectUser = useCallback(
|
||||
(userIdToRemove: string) => {
|
||||
setSelectedPeople((prevSelectedUsers) => {
|
||||
return prevSelectedUsers.filter(
|
||||
(prevUser) => prevUser._id.toString() !== userIdToRemove
|
||||
);
|
||||
});
|
||||
},
|
||||
[setSelectedPeople]
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
// 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
|
||||
|
||||
console.log("trying to create line now!");
|
||||
|
||||
try {
|
||||
if (!selectedPeople?.length) {
|
||||
toast.error("you must select at least one person");
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedMemberIds = selectedPeople.map((selectedPerson) =>
|
||||
selectedPerson._id.toString()
|
||||
);
|
||||
|
||||
const res = await mutateAsync({
|
||||
lineName,
|
||||
otherMemberIds: selectedMemberIds,
|
||||
});
|
||||
|
||||
toast.success("created line!");
|
||||
|
||||
// handle close once the new line is created
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
toast.error(error);
|
||||
console.error(error);
|
||||
} finally {
|
||||
console.log("done");
|
||||
}
|
||||
}, [lineName, selectedPeople]);
|
||||
|
||||
const handleCancel = () => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const keyMap: KeyMap = {
|
||||
HANDLE_SEARCH: "enter",
|
||||
};
|
||||
const handlers = {
|
||||
HANDLE_SEARCH: onSearch,
|
||||
};
|
||||
|
||||
if (isLoading) return <span>one second while we make magic</span>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title="Create a Line"
|
||||
visible={open}
|
||||
onCancel={handleCancel}
|
||||
footer={
|
||||
<div className="flex flex-row text-white">
|
||||
<button
|
||||
className="flex-1 bg-gray-500 pb-3 pt-2 text-left pl-2"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="flex-1 bg-teal-500 pb-3 pt-2 text-left pl-2"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
className={"flex flex-col gap-5"}
|
||||
>
|
||||
<HotKeys handlers={handlers} keyMap={keyMap} allowChanges={true}>
|
||||
<div className="flex flex-col items-start gap-2 mb-5">
|
||||
<p className="text-gray-300 text-sm">People</p>
|
||||
|
||||
<span className="flex flex-row gap-1 w-full items-center border border-gray-200 p-2 shadow">
|
||||
<FiSearch className="text-gray-300" />
|
||||
|
||||
<input
|
||||
className="placeholder:text-gray-300 outline-none placeholder:text-sm border-0 flex-1"
|
||||
value={peopleSearchValue}
|
||||
onChange={(e) => setPeopleSearchValue(e.target.value)}
|
||||
placeholder="search by name or email"
|
||||
/>
|
||||
|
||||
<span className="text-xs text-gray-200 ml-auto">
|
||||
enter to search
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{/* search results */}
|
||||
{searchRes?.users?.length > 0 && (
|
||||
<div className="flex flex-col border border-gray-200 shadow-md max-h-[500px] w-full overflow-y-auto">
|
||||
{searchRes?.users?.map((searchedUser) => (
|
||||
<BasicUserRow
|
||||
key={`searchResUser-${searchedUser.googleId}`}
|
||||
user={searchedUser}
|
||||
rightJsx={
|
||||
<button onClick={() => selectUser(searchedUser)}>
|
||||
Add
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* selected people */}
|
||||
{selectedPeople?.length > 0 && (
|
||||
<div className="flex flex-col gap-2 mb-5">
|
||||
<p className="text-gray-300 text-sm">Selected People</p>
|
||||
|
||||
<div className="flex flex-col w-full">
|
||||
{selectedPeople.map((selectedUser) => (
|
||||
<BasicUserRow
|
||||
key={`selectedUser-${selectedUser.googleId}`}
|
||||
user={selectedUser}
|
||||
rightJsx={
|
||||
<button
|
||||
onClick={() =>
|
||||
unSelectUser(selectedUser._id.toString())
|
||||
}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-gray-300 text-sm">Line Name (optional)</p>
|
||||
<input
|
||||
value={lineName}
|
||||
onChange={(e) => setLineName(e.target.value)}
|
||||
className="placeholder:text-gray-300 outline-none placeholder:text-sm flex-1
|
||||
border p-2 border-gray-200"
|
||||
placeholder={"ex. Engineering, Sprint 7, Follow up on present..."}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user