adding full search functionality and other things
This commit is contained in:
@@ -1,11 +1,21 @@
|
|||||||
import { User, UserStatus } from "@nirvana/common/models/user";
|
import { User, UserStatus } from "@nirvana/common/models/user";
|
||||||
import { Select, Tag } from "antd";
|
import { Divider, Select, Tag } from "antd";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { ChangeEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { FaPlus, FaRegTimesCircle, FaSearch } from "react-icons/fa";
|
||||||
|
import searchClient, { usersIndex } from "../../services/algoliaSearchService";
|
||||||
|
import SimpleLoadingDot from "../Loading/SimpleLoadingDot";
|
||||||
import SimpleUserDetailsRow from "../UserDetails/SimpleUserDetailsRow";
|
import SimpleUserDetailsRow from "../UserDetails/SimpleUserDetailsRow";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { SearchResponse } from "@algolia/client-search";
|
||||||
|
|
||||||
|
const searchDebounceMsTime = 3000;
|
||||||
|
|
||||||
export default function CreateConversation() {
|
export default function CreateConversation() {
|
||||||
const input = useRef<HTMLInputElement>();
|
const input = useRef<HTMLInputElement>();
|
||||||
const [selectedUsers, setSelectedUsers] = useState<User[]>([] as User[]);
|
const [selectedUsers, setSelectedUsers] = useState<User[]>([] as User[]);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [userSearchInput, setUserSearchInput] = useState<string>("");
|
||||||
|
|
||||||
// recommendations list: algolio results + cached "relevant users" from all cached conversations currently
|
// recommendations list: algolio results + cached "relevant users" from all cached conversations currently
|
||||||
// options for the select
|
// options for the select
|
||||||
@@ -14,10 +24,7 @@ export default function CreateConversation() {
|
|||||||
[] as User[]
|
[] as User[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = recommendedUsers.map((recUser) => {
|
// on loading this page always focus the select field
|
||||||
value: recUser.firstName + " " + recUser.lastName;
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
input.current?.focus();
|
input.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -26,9 +33,61 @@ export default function CreateConversation() {
|
|||||||
// if it's an email of a non-nirvana user, then hit invite button to email them with a voice clip along with it
|
// if it's an email of a non-nirvana user, then hit invite button to email them with a voice clip along with it
|
||||||
// if it's an existing user, then show a nice chip of that user's profile picture and their name
|
// if it's an existing user, then show a nice chip of that user's profile picture and their name
|
||||||
|
|
||||||
|
const handleChangeUserSearchInput = async (event) => {
|
||||||
|
const newInput = event?.target?.value;
|
||||||
|
console.log("searching for : " + newInput);
|
||||||
|
setUserSearchInput(newInput);
|
||||||
|
|
||||||
|
// no search if empty input
|
||||||
|
if (!newInput) {
|
||||||
|
setIsLoading(false);
|
||||||
|
console.warn("no valid input to search...no point searching");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// do the search on the search value
|
||||||
|
const searchedUsers: SearchResponse = await usersIndex.search(newInput);
|
||||||
|
console.log(searchedUsers.hits);
|
||||||
|
|
||||||
|
if (searchedUsers.hits) {
|
||||||
|
const userHits = searchedUsers.hits.map((returnedUser) => {
|
||||||
|
const recUser = returnedUser as unknown as User;
|
||||||
|
recUser.id = returnedUser.objectID;
|
||||||
|
return recUser;
|
||||||
|
});
|
||||||
|
|
||||||
|
setRecommendedUsers(userHits);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("Problem in searching");
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop showing loading to say that we got results
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const debounceHandler = useMemo(
|
||||||
|
() => debounce(handleChangeUserSearchInput, searchDebounceMsTime),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Stop the invocation of the debounced function
|
||||||
|
// after unmounting
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
debounceHandler.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectUser = (user) => {
|
||||||
|
setSelectedUsers((prevUsers) => [user, ...prevUsers]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto my-auto w-96 flex flex-col">
|
<div className="mx-auto my-auto w-96 flex flex-col">
|
||||||
<Select
|
{/* <Select
|
||||||
placeholder={"Look up emails, names, etc."}
|
placeholder={"Look up emails, names, etc."}
|
||||||
tagRender={tagRender}
|
tagRender={tagRender}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
@@ -38,75 +97,109 @@ export default function CreateConversation() {
|
|||||||
allowClear
|
allowClear
|
||||||
// defaultValue={['a10', 'c12']}
|
// defaultValue={['a10', 'c12']}
|
||||||
ref={input}
|
ref={input}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
<span className="flex flex-row items-center mt-10 mb-2">
|
{/* search bar that debounces and has a loading button while debouncing */}
|
||||||
<span className="uppercase tracking-widest text-slate-400 font-semibold">
|
|
||||||
Selected
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button className="ml-auto text-slate-300 text-xs">clear</button>
|
<span className="flex flex-row items-center space-x-2 mb-5">
|
||||||
</span>
|
<FaSearch className="text-slate-300 text-lg" />
|
||||||
|
|
||||||
{/* list of selected people who are nirvana users */}
|
|
||||||
|
|
||||||
<span className="flex flex-col">
|
|
||||||
<SimpleUserDetailsRow
|
|
||||||
user={
|
|
||||||
new User(
|
|
||||||
undefined,
|
|
||||||
"[email protected]",
|
|
||||||
"Johnny",
|
|
||||||
"Smith",
|
|
||||||
"https://joeschmoe.io/api/v1/random",
|
|
||||||
UserStatus.online
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="flex flex-col mt-10">
|
|
||||||
<span className="uppercase tracking-widest text-slate-400 font-semibold">
|
|
||||||
Name
|
|
||||||
</span>
|
|
||||||
<span className="text-slate-300 text-xs">
|
|
||||||
Name this conversation: engineering, hangout, lunch time, or leave it
|
|
||||||
as is.
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="p-3 rounded mt-2 bg-slate-50 border placeholder-slate-300"
|
className="p-2 flex-1 rounded bg-slate-50 border placeholder-slate-300"
|
||||||
placeholder="coding and chilling room, birthdays, sprint 7"
|
placeholder="look up an email address or name"
|
||||||
|
ref={input}
|
||||||
|
onChange={(e) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
debounceHandler(e);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{isLoading && <SimpleLoadingDot />}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<button
|
{/* show search results nicely like tailwind website */}
|
||||||
className="rounded p-2 border flex flex-row items-center space-x-2
|
|
||||||
text-white font-semibold text-xs ml-auto mt-5 bg-teal-600 hover:scale-110 transition-all"
|
|
||||||
>
|
|
||||||
<span>Create</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* if it's a one on one, and i already have a conversation with them, then block creating a new one? */}
|
{recommendedUsers && recommendedUsers.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<span className="text-slate-400 text-xs">
|
||||||
|
showing {recommendedUsers.length} results
|
||||||
|
</span>
|
||||||
|
{recommendedUsers.map((recUser) => {
|
||||||
|
return (
|
||||||
|
<SimpleUserDetailsRow
|
||||||
|
key={recUser.id}
|
||||||
|
user={recUser}
|
||||||
|
actionButton={
|
||||||
|
<span
|
||||||
|
onClick={() => selectUser(recUser)}
|
||||||
|
className="p-2 rounded-full hover:cursor-pointer
|
||||||
|
text-teal-500 hover:bg-slate-200 ml-auto"
|
||||||
|
>
|
||||||
|
<FaPlus className="text-lg" />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedUsers?.length ? (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<span className="flex flex-row items-center mb-2">
|
||||||
|
<span className="uppercase tracking-widest text-slate-400 font-semibold">
|
||||||
|
Selected
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button className="ml-auto text-slate-300 text-xs">clear</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* list of selected people who are nirvana users */}
|
||||||
|
<span className="flex flex-col">
|
||||||
|
{selectedUsers.map((selectUser) => (
|
||||||
|
<SimpleUserDetailsRow
|
||||||
|
key={selectUser.id}
|
||||||
|
user={selectUser}
|
||||||
|
actionButton={
|
||||||
|
<button className="p-2 rounded-full hover:cursor-pointer text-orange-500 ml-auto">
|
||||||
|
<FaRegTimesCircle className="ml-auto text-lg" />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="flex flex-col mt-10">
|
||||||
|
<span className="uppercase tracking-widest text-slate-400 font-semibold">
|
||||||
|
Name
|
||||||
|
</span>
|
||||||
|
<span className="text-slate-300 text-xs">
|
||||||
|
Name this conversation: engineering, hangout, lunch time, or leave
|
||||||
|
it as is.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="p-3 rounded mt-2 bg-slate-50 border placeholder-slate-300"
|
||||||
|
placeholder="coding and chilling room, birthdays, sprint 7"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="rounded p-2 border flex flex-row items-center space-x-2
|
||||||
|
text-white font-semibold text-xs ml-auto mt-5 bg-teal-600 hover:scale-110 transition-all"
|
||||||
|
>
|
||||||
|
<span>Create</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* show conversations already with these people */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function tagRender(props) {
|
|
||||||
const { label, value, closable, onClose } = props;
|
|
||||||
const onPreventMouseDown = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
color={value}
|
|
||||||
onMouseDown={onPreventMouseDown}
|
|
||||||
closable={closable}
|
|
||||||
onClose={onClose}
|
|
||||||
style={{ marginRight: 3 }}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { FaRegDotCircle } from "react-icons/fa";
|
||||||
|
|
||||||
|
export default function SimpleLoadingDot() {
|
||||||
|
return <FaRegDotCircle className="animate-pulse text-teal-500" />;
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Avatar } from "antd";
|
|
||||||
import { FaPaperPlane, FaRegTimesCircle } from "react-icons/fa";
|
|
||||||
import { User } from "@nirvana/common/models/user";
|
import { User } from "@nirvana/common/models/user";
|
||||||
import UserAvatar from "./UserAvatar";
|
import UserAvatar from "./UserAvatar";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
export default function SelectedUserRow(props: { user: User }) {
|
export default function SelectedUserRow(props: {
|
||||||
|
user: User;
|
||||||
|
actionButton: ReactElement;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<span className="flex flex-row items-center py-1 border-t">
|
<span className="flex flex-row items-center py-1 border-t">
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
@@ -17,9 +19,7 @@ export default function SelectedUserRow(props: { user: User }) {
|
|||||||
<span className="text-slate-400 text-xs">{"[email protected]"}</span>
|
<span className="text-slate-400 text-xs">{"[email protected]"}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<button className="p-2 rounded-full hover:cursor-pointer text-orange-500 ml-auto">
|
{props.actionButton}
|
||||||
<FaRegTimesCircle className="ml-auto text-lg" />
|
|
||||||
</button>
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nirvana/common": "*",
|
"@nirvana/common": "*",
|
||||||
|
"@types/lodash": "^4.14.178",
|
||||||
"agora-rtc-sdk-ng": "^4.8.1",
|
"agora-rtc-sdk-ng": "^4.8.1",
|
||||||
|
"algoliasearch": "^4.12.1",
|
||||||
"antd": "^4.18.3",
|
"antd": "^4.18.3",
|
||||||
"firebase": "^9.6.2",
|
"firebase": "^9.6.2",
|
||||||
"firebase-admin": "^10.0.1",
|
"firebase-admin": "^10.0.1",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"mic-recorder-to-mp3": "^2.2.2",
|
"mic-recorder-to-mp3": "^2.2.2",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"next": "12.0.7",
|
"next": "12.0.7",
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ export default function Me() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const currPage = router.query.page;
|
const currPage = router.query.page;
|
||||||
|
|
||||||
console.log(currPage);
|
|
||||||
|
|
||||||
function getCurrentContent() {
|
function getCurrentContent() {
|
||||||
switch (currPage) {
|
switch (currPage) {
|
||||||
case QueryRoutes.convos:
|
case QueryRoutes.convos:
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import algoliaSearch, { SearchClient } from "algoliasearch";
|
||||||
|
|
||||||
|
var searchClient: SearchClient = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID &&
|
||||||
|
process.env.NEXT_PUBLIC_ALGOLIA_API_KEY
|
||||||
|
) {
|
||||||
|
searchClient = algoliaSearch(
|
||||||
|
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
|
||||||
|
process.env.NEXT_PUBLIC_ALGOLIA_API_KEY
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("set up algolia search successfully");
|
||||||
|
} else {
|
||||||
|
console.error("unable to set up aloglia");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usersIndex = searchClient.initIndex(
|
||||||
|
process.env.NEXT_PUBLIC_ALGOLIA_USERS_INDEX_NAME || ""
|
||||||
|
);
|
||||||
|
|
||||||
|
export default searchClient;
|
||||||
@@ -1123,7 +1123,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||||
|
|
||||||
"@types/lodash@^4.14.104":
|
"@types/lodash@^4.14.104", "@types/lodash@^4.14.178":
|
||||||
version "4.14.178"
|
version "4.14.178"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
||||||
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
|
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
|
||||||
|
|||||||
Reference in New Issue
Block a user