creating and retrieving links successfully
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user