creating and retrieving links successfully

This commit is contained in:
Arjun Patel
2022-02-13 11:25:12 -08:00
parent bfc539671f
commit 244bdba9e1
7 changed files with 276 additions and 67 deletions
+3 -3
View File
@@ -12,7 +12,7 @@ export default class Conversation {
membersInLiveRoom: string[] = [] as string[]; // all members in a live call right now for this convo
cachedAudioClip?: AudioClip; // last message pretty much
cachedDrawerItem?: Link; // last link pretty much
cachedLink?: Link; // last link pretty much
tldr?: string; // description almost that people keep up to day in the convo
@@ -92,7 +92,7 @@ export class Link {
url: string;
name: string;
type: LinkType;
// type: LinkType;
createdByUserId: string;
createdDate: Timestamp = Timestamp.now();
@@ -101,7 +101,7 @@ export class Link {
this.createdByUserId = _senderUserId;
this.url = _linkUrl;
this.name = _name;
this.type = Link.getLinkType(_linkUrl);
// this.type = Link.getLinkType(_linkUrl);
}
static getLinkType(url: string): LinkType {
+1
View File
@@ -15,6 +15,7 @@ enum Collections {
conversations = "conversations",
conversationMembers = "members",
conversationAudioClips = "audioClips",
conversationLinks = "links",
}
export default Collections;
@@ -15,6 +15,7 @@ import Conversation, {
AudioClip,
ConversationMember,
ConversationMemberState,
Link,
} from "../models/conversation";
import Collections from "./collections";
import { cloudStorageService } from "./index";
@@ -210,4 +211,51 @@ export default class ConversationService {
{ merge: true }
);
}
async shareLink(createdByUserId: string, link: Link, convoId: string) {
const linkRef = doc(
this.db,
Collections.conversations,
convoId,
Collections.conversationLinks,
link.id
);
const conversationDoc = doc(this.db, Collections.conversations, convoId);
const userConvoAssocDoc = doc(
this.db,
Collections.conversations,
convoId,
Collections.conversationMembers,
createdByUserId
);
await runTransaction(this.db, async (transaction) => {
// want to add to the long term collection of all links for a conversation
transaction.set(
linkRef,
{ ...link, createdDate: serverTimestamp() },
{ merge: true }
);
// want to make updates to the conversation document itself: set the last item for just the basic information to be
// published to users
transaction.set(
conversationDoc,
{
lastActivityDate: serverTimestamp(),
cachedLink: { ...link, createdDate: serverTimestamp() },
},
{ merge: true }
);
// update the user's convo assoc so that their last interaction was set properly
transaction.set(
userConvoAssocDoc,
{
lastInteractionDate: serverTimestamp(),
},
{ merge: true }
);
});
}
}
@@ -1,16 +1,27 @@
import { Link } from "@nirvana/common/models/conversation";
import { conversationService } from "@nirvana/common/services";
import Modal from "antd/lib/modal/Modal";
import { useState, useEffect } from "react";
import Image from "next/image";
import { useState, useEffect, useRef } from "react";
import toast from "react-hot-toast";
import LinkIcon from "./LinkIcon";
import { useRecoilValue } from "recoil";
import { useAuth } from "../../contexts/authContext";
import { selectedConvoAtom } from "../../recoil/main";
import LinkIcon, { getFavicon } from "./LinkIcon";
export default function CreateItemModal(props: {
pastedLink: string;
show: boolean;
handleClose: () => void;
}) {
const { currUser } = useAuth();
const [linkVal, setLinkVal] = useState<string>("");
const [linkDesc, setLinkDesc] = useState<string>("");
const linkInput = useRef<HTMLInputElement>();
const [loading, setLoading] = useState<boolean>(false);
const selectedConvoId = useRecoilValue(selectedConvoAtom);
useEffect(() => {
if (props.pastedLink) {
@@ -18,9 +29,55 @@ export default function CreateItemModal(props: {
}
}, [props.pastedLink]);
const handleCreate = () => {
console.log("added drawer item");
toast.success("added drawer item to conversation");
useEffect(() => {
linkInput.current?.focus();
}, [props.show]);
const handleCreate = async () => {
if (loading) {
toast("in the process of sharing...");
return;
}
setLoading(true);
try {
// validations
if (!linkVal) {
toast.error("must put a link");
setLoading(false);
return;
}
if (!linkDesc) {
toast.error("must put a name of some sort");
setLoading(false);
return;
}
// service to add link to the collection
// change the cacheddraweritem on the convo doc
const newLink = new Link(linkDesc, linkVal, currUser!.uid);
await conversationService.shareLink(
currUser!.uid,
newLink,
selectedConvoId!
);
toast.success("shared");
} catch (error) {
console.error(error);
toast.error("problem in sharing link");
}
setLoading(false);
resetForm();
props.handleClose();
};
const resetForm = () => {
setLinkDesc("");
setLinkVal("");
};
const handleCancel = () => {
@@ -28,15 +85,44 @@ export default function CreateItemModal(props: {
props.handleClose();
};
const handleChangeLinkInput = (e) => {
setLinkVal(e.target.value);
// todo: find the tab name of the url
};
function addDefaultSrc(ev) {
ev.target.src = getFavicon("www.com");
}
return (
<Modal
title="Share"
onOk={handleCreate}
onCancel={handleCancel}
footer={
<span className="flex flex-row justify-end space-x-2">
<button
onClick={handleCancel}
className="rounded-lg p-2 border space-x-2
text-slate-400 text-xs hover:bg-slate-50"
>
<span>Cancel</span>
</button>
<button
onClick={handleCreate}
type="submit"
className="rounded-lg font-semibold bg-teal-600 p-2 text-white shadow-lg"
>
<span>Share</span>
</button>
</span>
}
visible={props.show}
{...props}
>
<span className="flex flex-col space-y-5">
<form onSubmit={handleCreate} className="flex flex-col space-y-5">
<span className="flex flex-col items-start">
<span className="text-lg font-semibold">Link</span>
<span className="text-gray-300 text-sm mb-2">
@@ -44,17 +130,22 @@ export default function CreateItemModal(props: {
</span>
{/* icon and link input */}
<span className="flex flex-row items-center space-x-2 w-full">
<LinkIcon
className="text-3xl"
linkType={Link.getLinkType(linkVal)}
/>
{linkVal && (
<img
height="30"
width="30"
src={getFavicon(linkVal)}
onError={addDefaultSrc}
/>
)}
<input
ref={linkInput}
autoFocus
className="flex-1 rounded-lg bg-gray-50 p-3"
value={linkVal}
placeholder="https://jira.atlassian.com/team/xxx"
onChange={(e) => setLinkVal(e.target.value)}
onChange={handleChangeLinkInput}
/>
</span>
</span>
@@ -72,7 +163,7 @@ export default function CreateItemModal(props: {
onChange={(e) => setLinkDesc(e.target.value)}
/>
</span>
</span>
</form>
</Modal>
);
}
@@ -12,6 +12,11 @@ import Link, { LinkType } from "@nirvana/common/models/conversation";
import { SiGooglemeet, SiZoom } from "react-icons/si";
const faviconGrabberAPI = "https://api.statvoo.com/favicon/?url=";
export function getFavicon(url: string): string {
return faviconGrabberAPI + url;
}
export default function LinkIcon(props: {
className?: string;
linkType: LinkType;
@@ -0,0 +1,57 @@
import { Link } from "@nirvana/common/models/conversation";
import { Tooltip } from "antd";
import { linkWithCredential } from "firebase/auth";
import moment from "moment";
import link from "next/link";
import { FaImages, FaExternalLinkAlt } from "react-icons/fa";
import { getFavicon } from "./LinkIcon";
export default function SharedItemsRow(props: { link: Link }) {
const { link } = props;
function addDefaultSrc(ev) {
ev.target.src = getFavicon("www.com");
}
return (
<span
className="group flex flex-row items-center border-t
p-2 hover:bg-slate-50 transition-all shrink-0 text-ellipsis last:border-b w-[18rem]"
>
{/* <span className="rounded-lg bg-slate-200 p-2 hover:cursor-pointer"></span> */}
<img
height="30"
width="30"
src={getFavicon(link.url)}
onError={addDefaultSrc}
/>
<Tooltip title={link.name}>
<span className="flex flex-col ml-2 max-w-[10rem]">
<span className="text-slate-400 text-sm truncate">{link.name}</span>
<span className="text-slate-300 text-xs">
{moment(link.createdDate.toDate()).fromNow()}
</span>
</span>
</Tooltip>
<span className="flex flex-row ml-auto space-x-1 group-hover:visible invisible">
<Tooltip title={"Add to personal drawer."}>
<span className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200 ">
<FaImages className="text-xl text-slate-400" />
</span>
</Tooltip>
<Tooltip title={link.url}>
<span
onClick={() => window.open(link.url, "_blank")}
className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200 "
>
<FaExternalLinkAlt className="text-lg text-slate-400" />
</span>
</Tooltip>
</span>
</span>
);
}
@@ -1,4 +1,4 @@
import { LinkType } from "@nirvana/common/models/conversation";
import { Link, LinkType } from "@nirvana/common/models/conversation";
import { UserStatus } from "@nirvana/common/models/user";
import { Tooltip } from "antd";
import { duration } from "moment";
@@ -49,6 +49,7 @@ import {
onSnapshot,
orderBy,
query,
Unsubscribe,
where,
} from "firebase/firestore";
@@ -59,6 +60,8 @@ import Modal from "antd/lib/modal/Modal";
import { configure, GlobalHotKeys, KeyMap } from "react-hotkeys";
import CreateItemModal from "../Drawer/CreateItemModal";
import isValidHttpUrl from "../../helpers/urlHelper";
import { FaPlus } from "react-icons/fa";
import SharedItemsRow from "../Drawer/SharedItemsRow";
const testDrawerItems: {
linkType: LinkType;
@@ -88,6 +91,7 @@ const testDrawerItems: {
];
const AUDIO_CLIP_FETCH_LIMIT = 50;
const LINK_FETCH_LIMIT = 5;
export default function ViewConvo(props: { conversationId: string }) {
const { currUser } = useAuth();
@@ -114,11 +118,15 @@ export default function ViewConvo(props: { conversationId: string }) {
const setSelectedConvoId = useSetRecoilState(selectedConvoAtom);
const [sharedItems, setSharedItems] = useState<Link[]>([] as Link[]);
useEffect(() => {
endOfTimeline.current?.scrollIntoView({ behavior: "smooth" });
}, [audioClips]);
useEffect(() => {
const unsubs: Unsubscribe[] = [] as Unsubscribe[];
// should have this convo, otherwise unauthorized
if (!convo || !convo.activeMembers.includes(currUser!.uid)) {
toast.error("Not authorized for this conversation");
@@ -147,7 +155,7 @@ export default function ViewConvo(props: { conversationId: string }) {
orderBy("createdDate", "desc"),
limit(AUDIO_CLIP_FETCH_LIMIT)
);
const unsubscribe = onSnapshot(audioClipsQuery, (querySnapshot) => {
const unsubscribeAudClips = onSnapshot(audioClipsQuery, (querySnapshot) => {
const audioClips: AudioClip[] = [] as AudioClip[];
querySnapshot.forEach((doc) => {
@@ -162,7 +170,40 @@ export default function ViewConvo(props: { conversationId: string }) {
console.log(audioClips);
});
return unsubscribe;
unsubs.push(unsubscribeAudClips);
// links retrieval real time
const linksQuery = query(
collection(
db,
Collections.conversations,
props.conversationId,
Collections.conversationLinks
),
orderBy("createdDate", "desc"),
limit(LINK_FETCH_LIMIT)
);
const unsubscribeLinks = onSnapshot(linksQuery, (querySnapshot) => {
const links: Link[] = [] as Link[];
querySnapshot.forEach((doc) => {
const link = doc.data() as Link;
link.id = doc.id;
// appending all messages in sort order
links.push(link);
});
setSharedItems(links);
});
unsubs.push(unsubscribeLinks);
return () => {
unsubs.forEach((unsub) => {
unsub();
});
};
}, []);
const handleOrganizeConversation = async (
@@ -454,50 +495,25 @@ export default function ViewConvo(props: { conversationId: string }) {
{/* drawer items */}
<div className="flex flex-col">
<span className="text-md tracking-widest font-semibold text-slate-300 uppercase mb-2">
Shared
<span className="flex flex-row justify-between items-center mb-2">
<span className="text-md tracking-widest font-semibold text-slate-300 uppercase">
Shared
</span>
<Tooltip title={"CTRL+V to share a link."}>
<span
onClick={handleOpenDrawerItemModal}
className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200 "
>
<FaPlus className="ml-auto text-md text-slate-400" />
</span>
</Tooltip>
</span>
{/* row of cards drawer items */}
<span className="flex flex-col items-stretch">
{testDrawerItems.map((link) => (
<span
key={link.linkName}
className="group flex flex-row items-center border-t
p-2 hover:bg-slate-50 transition-all shrink-0 text-ellipsis last:border-b"
>
{/* <span className="rounded-lg bg-slate-200 p-2 hover:cursor-pointer"></span> */}
<LinkIcon
linkType={link.linkType}
className="text-3xl shrink-0"
/>
<Tooltip title={link.linkName}>
<span className="flex flex-col ml-2 max-w-[10rem]">
<span className="text-slate-400 text-sm truncate">
{link.linkName}
</span>
<span className="text-slate-300 text-xs">
{link.relativeSentTime}
</span>
</span>
</Tooltip>
<span className="flex flex-row ml-auto space-x-1 group-hover:visible invisible">
<Tooltip title={"Add to personal drawer."}>
<span className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200 ">
<FaImages className="text-xl text-slate-400" />
</span>
</Tooltip>
<Tooltip title={"https://usenirvana.com"}>
<span className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200 ">
<FaExternalLinkAlt className="text-lg text-slate-400" />
</span>
</Tooltip>
</span>
</span>
{sharedItems.map((link) => (
<SharedItemsRow key={link.id} link={link} />
))}
</span>
</div>
@@ -531,15 +547,6 @@ export default function ViewConvo(props: { conversationId: string }) {
<FaPlay className="text-xl" />
</span>
</Tooltip>
<Tooltip title={"CTRL+V to share a link."}>
<span
className={`shadow-lg flex flex-row items-center p-5 justify-center
rounded-lg font-bold bg-slate-50 text-purple-600`}
>
<FaLink className="text-xl" />
</span>
</Tooltip>
</span>
</>
);