From 4073d526bd7edc87ddd1ef5e19de81efece2e9b3 Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Tue, 1 Feb 2022 10:38:45 -0800 Subject: [PATCH] cleaning, prolly broke a lot but whatever --- packages/common/models/conversation.ts | 10 +- packages/web/.eslintrc.json | 2 +- packages/web/components/AnnouncementCard.tsx | 108 --- .../components/Dashboard/Announcements.tsx | 185 ----- packages/web/components/Dashboard/Header.tsx | 351 ---------- packages/web/components/Dashboard/Links.tsx | 260 ------- packages/web/components/Dashboard/Office.tsx | 191 ------ packages/web/components/Dashboard/Rooms.tsx | 451 ------------ .../components/Dashboard/TeamVoiceLine.tsx | 245 ------- .../CreateConversation.tsx.tsx} | 56 +- .../SearchResults.tsx | 0 .../components/Layouts/LandingPageLayout.tsx | 2 +- packages/web/components/LinkCard.tsx | 195 ------ .../web/components/{ => Logo}/MainLogo.tsx | 0 .../{v2 => MainTabsOrPages}/Conversations.tsx | 0 .../{v2 => MainTabsOrPages}/Done.tsx | 0 .../{v2 => MainTabsOrPages}/Drawer.tsx | 0 .../{v2 => MainTabsOrPages}/Header.tsx | 4 +- .../KeyboardShortcutHandler.tsx | 0 .../{v2 => MainTabsOrPages}/Later.tsx | 0 .../{v2 => MainTabsOrPages}/Priority.tsx | 0 .../{v2 => MainTabsOrPages}/Sidebar.tsx | 0 packages/web/components/OfficeCard.tsx | 179 ----- packages/web/components/RoomCard.tsx | 473 ------------- packages/web/components/RoomTypeTag.tsx | 82 --- packages/web/components/RoomsRowsByType.tsx | 38 -- .../UserDetails/SimpleUserDetailsRow.tsx | 61 ++ .../{ => UserDetails}/UserStatusBubble.tsx | 2 +- packages/web/contexts/keyboardContext.tsx | 646 ------------------ packages/web/models/announcement.ts | 25 - packages/web/models/link.ts | 87 --- packages/web/models/message.ts | 15 - packages/web/models/officeRoom.ts | 37 - packages/web/models/room.ts | 50 -- packages/web/models/team.ts | 30 - packages/web/models/teamMember.ts | 26 - packages/web/pages/index.js | 2 +- packages/web/pages/s/index.tsx | 18 +- packages/web/pages/teams/[teamid].tsx | 134 ---- packages/web/pages/teams/[teamid]/admin.tsx | 396 ----------- packages/web/pages/teams/create.tsx | 142 ---- packages/web/pages/teams/index.tsx | 240 ------- packages/web/pages/teams/login.tsx | 2 +- packages/web/services/IService.tsx | 5 - packages/web/services/agoraService.ts | 94 --- packages/web/services/announcementService.tsx | 27 - packages/web/services/cloudStorageService.tsx | 75 -- packages/web/services/collections.tsx | 13 - packages/web/services/firebaseAdminService.js | 15 - packages/web/services/linkService.tsx | 24 - packages/web/services/officeRoomService.tsx | 93 --- packages/web/services/roomService.tsx | 56 -- packages/web/services/sendService.tsx | 42 -- packages/web/services/teamService.tsx | 297 -------- 54 files changed, 84 insertions(+), 5402 deletions(-) delete mode 100644 packages/web/components/AnnouncementCard.tsx delete mode 100644 packages/web/components/Dashboard/Announcements.tsx delete mode 100644 packages/web/components/Dashboard/Header.tsx delete mode 100644 packages/web/components/Dashboard/Links.tsx delete mode 100644 packages/web/components/Dashboard/Office.tsx delete mode 100644 packages/web/components/Dashboard/Rooms.tsx delete mode 100644 packages/web/components/Dashboard/TeamVoiceLine.tsx rename packages/web/components/{v2/CreateConversation.tsx => FullPageExperiences/CreateConversation.tsx.tsx} (56%) rename packages/web/components/{v2 => FullPageExperiences}/SearchResults.tsx (100%) delete mode 100644 packages/web/components/LinkCard.tsx rename packages/web/components/{ => Logo}/MainLogo.tsx (100%) rename packages/web/components/{v2 => MainTabsOrPages}/Conversations.tsx (100%) rename packages/web/components/{v2 => MainTabsOrPages}/Done.tsx (100%) rename packages/web/components/{v2 => MainTabsOrPages}/Drawer.tsx (100%) rename packages/web/components/{v2 => MainTabsOrPages}/Header.tsx (97%) rename packages/web/components/{v2 => MainTabsOrPages}/KeyboardShortcutHandler.tsx (100%) rename packages/web/components/{v2 => MainTabsOrPages}/Later.tsx (100%) rename packages/web/components/{v2 => MainTabsOrPages}/Priority.tsx (100%) rename packages/web/components/{v2 => MainTabsOrPages}/Sidebar.tsx (100%) delete mode 100644 packages/web/components/OfficeCard.tsx delete mode 100644 packages/web/components/RoomCard.tsx delete mode 100644 packages/web/components/RoomTypeTag.tsx delete mode 100644 packages/web/components/RoomsRowsByType.tsx create mode 100644 packages/web/components/UserDetails/SimpleUserDetailsRow.tsx rename packages/web/components/{ => UserDetails}/UserStatusBubble.tsx (96%) delete mode 100644 packages/web/contexts/keyboardContext.tsx delete mode 100644 packages/web/models/announcement.ts delete mode 100644 packages/web/models/link.ts delete mode 100644 packages/web/models/message.ts delete mode 100644 packages/web/models/officeRoom.ts delete mode 100644 packages/web/models/room.ts delete mode 100644 packages/web/models/team.ts delete mode 100644 packages/web/models/teamMember.ts delete mode 100644 packages/web/pages/teams/[teamid].tsx delete mode 100644 packages/web/pages/teams/[teamid]/admin.tsx delete mode 100644 packages/web/pages/teams/create.tsx delete mode 100644 packages/web/pages/teams/index.tsx delete mode 100644 packages/web/services/IService.tsx delete mode 100644 packages/web/services/agoraService.ts delete mode 100644 packages/web/services/announcementService.tsx delete mode 100644 packages/web/services/cloudStorageService.tsx delete mode 100644 packages/web/services/collections.tsx delete mode 100644 packages/web/services/firebaseAdminService.js delete mode 100644 packages/web/services/linkService.tsx delete mode 100644 packages/web/services/officeRoomService.tsx delete mode 100644 packages/web/services/roomService.tsx delete mode 100644 packages/web/services/sendService.tsx delete mode 100644 packages/web/services/teamService.tsx diff --git a/packages/common/models/conversation.ts b/packages/common/models/conversation.ts index ec20d4c..993c59c 100644 --- a/packages/common/models/conversation.ts +++ b/packages/common/models/conversation.ts @@ -5,15 +5,15 @@ export default class Conversation { id: string = uuid(); type: ConversationType; - name: string; // engineering, general, arjun and jacob... + name?: string; // engineering, general, arjun, jacob and rachel... createdDate: Timestamp = Timestamp.now(); createdByUserId: string; constructor( - _name: string, _createdByUserId: string, - _type: ConversationType + _type: ConversationType, + _name?: string ) { this.name = _name; this.createdByUserId = _createdByUserId; @@ -22,6 +22,6 @@ export default class Conversation { } export enum ConversationType { - personal = "personal", - group = "group", + personal = "personal", // no conversation name then + group = "group", // must have conversation name } diff --git a/packages/web/.eslintrc.json b/packages/web/.eslintrc.json index d5c054b..b9ab2c9 100644 --- a/packages/web/.eslintrc.json +++ b/packages/web/.eslintrc.json @@ -8,7 +8,7 @@ ], "rules": { // I suggest you add those two rules: - "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-explicit-any": "error", "prefer-const": "error", "react-hooks/rules-of-hooks": "error", diff --git a/packages/web/components/AnnouncementCard.tsx b/packages/web/components/AnnouncementCard.tsx deleted file mode 100644 index 81e6d5a..0000000 --- a/packages/web/components/AnnouncementCard.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import moment from "moment"; -import Image from "next/image"; -import { useState } from "react"; -import toast from "react-hot-toast"; -import { FaCheck, FaPlay } from "react-icons/fa"; -import { useAuth } from "../contexts/authContext"; -import { useKeyboardContext } from "../contexts/keyboardContext"; -import { useTeamDashboardContext } from "../contexts/teamDashboardContext"; -import Announcement, { AnnouncementState } from "../models/announcement"; -import { User } from "../models/user"; -import { AnnouncementService } from "../services/announcementService"; -import SkeletonLoader from "./Loading/skeletonLoader"; - -interface IAnnouncementCardProps { - announcement: Announcement; -} - -const announcementService = new AnnouncementService(); - -export default function AnnouncementCard(props: IAnnouncementCardProps) { - const { currUser } = useAuth(); - const { teamUsersMap, user } = useTeamDashboardContext(); - const { handleAddAudioToQueue } = useKeyboardContext(); - const [loading, setLoading] = useState(false); - - function playAnnouncement() { - handleAddAudioToQueue([props.announcement.audioDataUrl], true); - } - - async function resolveAnnouncement() { - setLoading(true); - - try { - await announcementService.updateAnnouncementState( - props.announcement.id, - AnnouncementState.resolved - ); - } catch (error) { - toast.error("unable to resolve announcement"); - } - - setLoading(false); - } - - async function deleteAnnouncement() { - try { - await announcementService.updateAnnouncementState( - props.announcement.id, - AnnouncementState.deleted - ); - } catch (error) { - toast.error("unable to resolve announcement"); - } - } - - var announcementUser: User = null; - if (props.announcement.createdByUserId in teamUsersMap) { - announcementUser = teamUsersMap[props.announcement.createdByUserId]; - } else if (currUser.uid == props.announcement.createdByUserId) { - announcementUser = user; - } - - const relativeCreatedDate = moment( - props.announcement.createdDate.toDate() - ).fromNow(); - - if (loading) { - return ; - } - - return ( - - - - - profile - - - - - {announcementUser?.nickName} - - {relativeCreatedDate} - - - {props.announcement.state == AnnouncementState.active && ( - - )} - - - - ); -} diff --git a/packages/web/components/Dashboard/Announcements.tsx b/packages/web/components/Dashboard/Announcements.tsx deleted file mode 100644 index db6d9fd..0000000 --- a/packages/web/components/Dashboard/Announcements.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { FaAngleDown, FaCheck, FaMicrophoneAlt, FaPlay } from "react-icons/fa"; -import { UserStatus } from "../../models/user"; - -import Image from "next/image"; -import { useEffect, useState } from "react"; -import { - collection, - getFirestore, - onSnapshot, - query, - where, -} from "firebase/firestore"; -import { Collections } from "../../services/collections"; -import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; -import { useAuth } from "../../contexts/authContext"; -import Announcement, { AnnouncementState } from "../../models/announcement"; -import { getTime } from "../../helpers/dateTime"; -import AnnouncementCard from "../AnnouncementCard"; -import { Dropdown, Menu, Radio, Tooltip } from "antd"; -import { useKeyboardContext } from "../../contexts/keyboardContext"; - -// for query -const yesterday = new Date(); -yesterday.setDate(yesterday.getDate() - 1); - -const db = getFirestore(); - -export default function Announcements() { - const { currUser } = useAuth(); - const { team } = useTeamDashboardContext(); - const { isRecordingAnnouncement } = useKeyboardContext(); - - const [annMap, setAnnMap] = useState>( - new Map() - ); - - // SECTION: LISTENER for Announcements - useEffect(() => { - /** - * QUERY: - * all announcements for the team in the past 24 hours for now - * order createdDate desc - * - */ - const q = query( - collection(db, Collections.announcements), - where("teamId", "==", team.id), - where("createdDate", ">", yesterday) - ); - - // return unsubscribe - return onSnapshot(q, (snapshot) => { - snapshot.docChanges().forEach((change) => { - let updatedOrNewAnn = change.doc.data() as Announcement; - updatedOrNewAnn.id = change.doc.id; - - if (change.type === "added" || change.type === "modified") { - // update rooms map - setAnnMap((prevMap) => { - return new Map(prevMap.set(updatedOrNewAnn.id, updatedOrNewAnn)); - }); - } - if (change.type === "removed") { - // going to more so be resolved or deleted - console.log("Removed annoncement: ", updatedOrNewAnn); - } - }); - }); - }, []); - - const allAnn = Array.from(annMap.values()); - // sort the announcements - allAnn.sort(function (a, b) { - return getTime(b.createdDate.toDate()) - getTime(a.createdDate.toDate()); - }); - - const activeAnn = allAnn.filter( - (ann) => ann.state == AnnouncementState.active - ); - const resolvedAnn = allAnn.filter( - (ann) => ann.state == AnnouncementState.resolved - ); - - const [selectedTabPane, setSelectedTabPane] = useState( - AnnouncementState.active - ); - - function getTabContent() { - switch (selectedTabPane) { - case AnnouncementState.active: - return ( - <> - {activeAnn.map((ann) => { - return ; - })} - - ); - case AnnouncementState.resolved: - return ( - <> - {resolvedAnn.map((ann) => { - return ; - })} - - ); - } - } - - const TimePeriodFilterMenu = ( - - - Past Week (Coming Soon) - - - This Month (Coming Soon) - - - ); - - return ( -
- - - - ANNOUNCEMENTS - - - - - - updates, pep talks, blockers, reminders - - - - {/* tab pane */} - - setSelectedTabPane(e.target.value)} - > - - {AnnouncementState.active}{" "} - - {activeAnn.length > 0 ? activeAnn.length : ""} - - - - {AnnouncementState.resolved}{" "} - - {activeAnn.length > 0 ? activeAnn.length : ""} - - - - - - - 24 HRS - - - - - - - {/* */} - - -
- {getTabContent()} -
-
- ); -} diff --git a/packages/web/components/Dashboard/Header.tsx b/packages/web/components/Dashboard/Header.tsx deleted file mode 100644 index 5eaf342..0000000 --- a/packages/web/components/Dashboard/Header.tsx +++ /dev/null @@ -1,351 +0,0 @@ -import { useAuth } from "../../contexts/authContext"; -import Image from "next/image"; -import { - FaMicrophoneAlt, - FaHeadphonesAlt, - FaTh, - FaBell, - FaSearch, - FaBuilding, - FaCalendarDay, - FaClock, - FaPeopleCarry, - FaAngleDown, - FaCheck, - FaDatabase, - FaMicrophoneAltSlash, - FaVolumeMute, - FaLayerGroup, - FaUser, -} from "react-icons/fa"; -import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; -import router from "next/router"; -import Moment from "react-moment"; -import { User } from "../../models/user"; -import { generateGreetings } from "../../helpers/dateTime"; -import { Dropdown, Menu, Tooltip } from "antd"; -import { useRouter } from "next/router"; -import { TeamMemberRole, TeamMemberStatus } from "../../models/teamMember"; -import { toast } from "react-hot-toast"; -import SubMenu from "antd/lib/menu/SubMenu"; -import { useEffect, useState } from "react"; -import TeamService from "../../services/teamService"; -import { Team } from "../../models/team"; -import Loading from "../Loading"; -import { useKeyboardContext } from "../../contexts/keyboardContext"; -import UserStatusBubble from "../UserStatusBubble"; - -const teamService = new TeamService(); - -const today = new Date(); - -export default function Header() { - const { currUser, logOut } = useAuth(); - const { team, user, userTeamMember } = useTeamDashboardContext(); - const router = useRouter(); - const { teamid } = router.query; - const { - hasRecPermit, - - audioInputDeviceId, - audioOutputDeviceId, - selectAudioOutput, - selectAudioInput, - inputDevices, - outputDevices, - - isMuted, - isSilenceMode, - muteOrUnmute, - silenceOrLivenMode, - } = useKeyboardContext(); - - const [usersTeams, setUserTeams] = useState(null); - - // get team data for the team dropdown - useEffect(() => { - (async function () { - try { - // get all the teams that this user is part of - const newTeams: Team[] = - await teamService.getActiveOrInvitedTeamsbyUser( - currUser.uid, - currUser.email - ); - - setUserTeams(newTeams); - } catch (error) { - console.log("problem getting teams of this user"); - toast.error("problem on load"); - } - })(); - }, []); - - async function handleSignOut() { - console.log("clicked log out button"); - - try { - await logOut(); - } catch (error) { - console.log(error); - toast.error("Unable to log out!"); - } - } - - async function handleAdminRoute() { - if (userTeamMember.role == TeamMemberRole.admin) { - router.push("/teams/" + teamid + "/admin"); - return; - } - - toast.error("You are not a team admin!"); - } - - const searchBar = ( -
- - - - - -
- ); - - const TeamsMenu = ( - - window.open("/teams", "_self")} - key={"team hub"} - icon={} - > - - - }> - - - - {/* all current teams that this person is a part of */} - {usersTeams && - usersTeams.map((uteam, i) => { - return ( - : <>} - onClick={() => { - console.log("going to " + uteam.id); - window.location.href = "/teams/" + uteam.id; - }} - > - - - ); - })} - - ); - const UserMenu = ( - - router.push("/teams/profile")} - icon={} - > - - - - window.open("/teams", "_self")} - key={"team hub"} - icon={} - > - - - - - - - - - - ); - - const handleSilenceOrLivenMode = (event) => { - event.preventDefault(); - - silenceOrLivenMode(); - }; - - const handleMuteUnmute = (event) => { - event.preventDefault(); - - muteOrUnmute(); - }; - - const audioInputDropMenu = ( - - - - - - - - {/* device */} - {inputDevices.map((device, i) => { - return ( - : <>} - onClick={() => selectAudioInput(device.deviceId)} - > - - - ); - })} - - ); - - const audioOutputDropMenu = ( - - - - - - - - {/* devices */} - {outputDevices.map((device, i) => { - return ( - : <>} - onClick={() => selectAudioOutput(device.deviceId)} - > - - - ); - })} - - ); - - function renderAudioInput() { - if (!hasRecPermit) { - return ( - - - - - - ); - } - - if (isMuted) { - return ( - - - - ); - } - - return ( - - - - - - ); - } - - function renderAudioOutput() { - if (isSilenceMode) { - return ( - - - - ); - } - - return ( - - - - - - ); - } - - const periodOfDay = generateGreetings(); - - return ( -
- {/* welcome message */} - - - 👋Hey {user.firstName}, {periodOfDay}! - - - {/* Date and time */} - - - - - - - - - - - - - - {/* header control section */} - - {/* search bar */} - - - - {renderAudioOutput()} - - {renderAudioInput()} - - {/* teams menu */} - - - - - - - {/* avatar */} - - - - - asdf - - -
- ); -} diff --git a/packages/web/components/Dashboard/Links.tsx b/packages/web/components/Dashboard/Links.tsx deleted file mode 100644 index 409a319..0000000 --- a/packages/web/components/Dashboard/Links.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import { BsThreeDots } from "react-icons/bs"; -import { FaAngleDown, FaCode, FaFileImage, FaPlus } from "react-icons/fa"; - -import Image from "next/image"; -import { Dropdown, Menu, Radio, Tooltip } from "antd"; -import { - ShowModalType, - useKeyboardContext, -} from "../../contexts/keyboardContext"; -import CreateOrUpdateLink from "../Modals/CreateOrUpdateLink"; -import { useEffect, useState } from "react"; -import LinkCard from "../LinkCard"; -import { Collections } from "../../services/collections"; -import { - collection, - getFirestore, - onSnapshot, - orderBy, - query, - where, -} from "firebase/firestore"; -import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; -import Link, { LinkState } from "../../models/link"; -import { useAuth } from "../../contexts/authContext"; - -const lastWeek = new Date(); -lastWeek.setDate(lastWeek.getDate() - 7); - -const db = getFirestore(); - -export default function Links() { - const { currUser } = useAuth(); - const { handleModalType } = useKeyboardContext(); - - const { team } = useTeamDashboardContext(); - - const [linksMap, setLinksMap] = useState>( - new Map() - ); - - // SECTION: LISTENER for LINKS - useEffect(() => { - /** - * QUERY: - * all links for the team in the past 7 days - * order createdDate desc - * - */ - const q = query( - collection(db, Collections.links), - where("teamId", "==", team.id), - where("createdDate", ">", lastWeek), - orderBy("createdDate", "desc") - ); - - // return unsubscribe - return onSnapshot(q, (snapshot) => { - snapshot.docChanges().forEach((change) => { - let updatedOrNewLink = change.doc.data() as Link; - updatedOrNewLink.id = change.doc.id; - - if (change.type === "added" || change.type === "modified") { - setLinksMap((prevMap) => { - return new Map(prevMap.set(updatedOrNewLink.id, updatedOrNewLink)); - }); - } - if (change.type === "removed") { - console.log("Removed link: ", updatedOrNewLink); - } - }); - }); - }, []); - - const TimePeriodFilterMenu = ( - - - Past 24 Hours (Coming Soon) - - - This Month (Coming Soon) - - - ); - - const [selectedTabPane, setSelectedTabPane] = useState( - LinkTypeFilter.me - ); - - const allLinks = Array.from(linksMap.values()); - - const meLinks = allLinks.filter((link) => { - // if archived or deleted, don't show - if (link.state != LinkState.active) { - return false; - } - - // if I am the sender, then also show, but only if it's me sending to a specific person - if (link.createdByUserId == currUser.uid && link.recipients?.length > 0) { - return true; - } - - // if I am a receiver - if (link.recipients?.includes(currUser.uid)) { - return true; - } - - return false; - }); - const teamLinks = allLinks.filter((link) => { - // not archived and does not have a recipients list - if (link.state == LinkState.active && !link.recipients) { - return true; - } - return false; - }); - const archivedLinks = allLinks.filter( - (link) => link.state == LinkState.archived - ); - - function getFilteredContent() { - switch (selectedTabPane) { - case LinkTypeFilter.me: - return meLinks; - case LinkTypeFilter.team: - return teamLinks; - case LinkTypeFilter.archived: - return archivedLinks; - default: - return allLinks; - } - } - - return ( -
- {/* header row */} - - {/* modal */} - - - - - - DRAWER - - - - - links: jira tickets, drive files/folders, powerpoints... - - - {meLinks.length > 6 || teamLinks.length > 6 ? ( - - Make sure to{" "} - - {"clear out your drawer and your team's"} - {" "} - so that you are focused on this week. - - ) : ( - <> - )} - - -
- setSelectedTabPane(e.target.value)} - > - - {LinkTypeFilter.me}{" "} - - {meLinks.length > 0 ? meLinks.length : ""} - - - - {LinkTypeFilter.team}{" "} - - {teamLinks.length} - - - - - {LinkTypeFilter.favorites}{" "} - {/* - {liveRooms.length > 0 ? fa.length : ""} - */} - - - - {LinkTypeFilter.archived}{" "} - - {archivedLinks.length > 0 ? archivedLinks.length : ""} - - - -
- - - - Week - - - - - - - - - - - - -
-
- - {/* table of links */} -
- {getFilteredContent().map((link) => ( - - ))} -
-
- ); -} - -enum LinkTypeFilter { - team = "team", - me = "me", - favorites = "favorites", - archived = "archived", -} diff --git a/packages/web/components/Dashboard/Office.tsx b/packages/web/components/Dashboard/Office.tsx deleted file mode 100644 index 24039f0..0000000 --- a/packages/web/components/Dashboard/Office.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { - ClientConfig, - IAgoraRTC, - IAgoraRTCClient, - IMicrophoneAudioTrack, -} from "agora-rtc-sdk-ng"; -import { Tooltip } from "antd"; -import { - collection, - getFirestore, - onSnapshot, - query, - where, -} from "firebase/firestore"; -import { useEffect, useState } from "react"; -import toast from "react-hot-toast"; -import { useAuth } from "../../contexts/authContext"; -import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; -import OfficeRoom from "../../models/officeRoom"; -import { appId } from "../../services/agoraService"; -import { Collections } from "../../services/collections"; -import OfficeRoomService from "../../services/officeRoomService"; -import OfficeCard from "../OfficeCard"; - -const db = getFirestore(); - -const officeRoomService = new OfficeRoomService(); - -const config: ClientConfig = { - mode: "rtc", - codec: "vp8", -}; - -export default function Office() { - const { currUser } = useAuth(); - const { team } = useTeamDashboardContext(); - const [agoraRtc, setAgoraRtc] = useState(null); - const [agoraRtcClient, setAgoraRtcClient] = useState(null); - const [localAudioTrack, setLocalAudioTrack] = - useState(null); - - const [officeRoomsMap, setOfficeRoomsMap] = useState>( - new Map() - ); - - // get all offices for this team : realtime - useEffect(() => { - /** - * QUERY: - * all offices for this team - * - */ - const q = query( - collection(db, Collections.officeRooms), - where("teamId", "==", team.id) - ); - - // return unsubscribe - return onSnapshot(q, (snapshot) => { - if (snapshot.empty) { - // seed data and create the initial rooms - console.log("seeding office rooms for this team"); - officeRoomService.createInitialOfficeRooms(currUser.uid, team.id); - return; - } - - snapshot.docChanges().forEach((change) => { - let updatedOfficeRoom = change.doc.data() as OfficeRoom; - updatedOfficeRoom.id = change.doc.id; - - if (change.type === "added" || change.type === "modified") { - // update office rooms map on change of the room - setOfficeRoomsMap((prevMap) => { - return new Map( - prevMap.set(updatedOfficeRoom.id, updatedOfficeRoom) - ); - }); - } - if (change.type === "removed") { - console.log("Removed office room: ", updatedOfficeRoom); - } - }); - }); - }, []); - - // set up agora stuff - useEffect(() => { - (async function () { - // dynamic import as the server side import doesn't work - const AgoraRTC = (await import("agora-rtc-sdk-ng")).default; - const agoraClient = AgoraRTC.createClient(config); - - setAgoraRtcClient(agoraClient); - setAgoraRtc(AgoraRTC); - })(); - }, []); - - async function handleJoinChannel(channelName: string, agoraToken: string) { - const localTrack: IMicrophoneAudioTrack = - await agoraRtc.createMicrophoneAudioTrack(); - - setLocalAudioTrack(localTrack); - - let init = async (chanName: string) => { - agoraRtcClient.on("user-published", async (user, mediaType) => { - await agoraRtcClient.subscribe(user, mediaType); - console.log("subscribe success"); - - if (mediaType === "audio") { - user.audioTrack?.play(); - } - }); - - agoraRtcClient.on("user-unpublished", async (user, type) => { - console.log("unpublished", user, type); - if (type === "audio") { - user.audioTrack?.stop(); - } - - await agoraRtcClient.unsubscribe(user); - }); - - agoraRtcClient.on("user-left", (user) => { - console.log("user left", user); - }); - - await agoraRtcClient.join(appId, chanName, agoraToken, null); - if (localTrack) await agoraRtcClient.publish(localTrack); - }; - - if (localTrack) { - console.log("init ready"); - init(channelName); - } else { - toast.error("Not ready for joining call"); - return; - } - } - - async function handleLeaveChannel() { - // destroy local track - localAudioTrack?.close(); - - // leave all channels - await agoraRtcClient.leave(); - } - - const allOfficeRooms = Array.from(officeRoomsMap.values()); - - allOfficeRooms.sort((a, b) => { - if (a.name < b.name) { - return 1; - } - - if (a.name > b.name) { - return 1; - } - return 0; - }); - - // if there are none, then show user button to create initial office rooms and then create them - - return ( -
- - - - Office - - - - - {/* all office rooms */} - - {allOfficeRooms.map((officeRoom) => ( - - ))} - {/* */} - -
- ); -} diff --git a/packages/web/components/Dashboard/Rooms.tsx b/packages/web/components/Dashboard/Rooms.tsx deleted file mode 100644 index 03e8c17..0000000 --- a/packages/web/components/Dashboard/Rooms.tsx +++ /dev/null @@ -1,451 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import { FaAngleDown, FaBell, FaClock, FaPlus, FaLink } from "react-icons/fa"; -import { IoTimer } from "react-icons/io5"; -import { BsThreeDots } from "react-icons/bs"; -import Image from "next/image"; -import CreateOrUpdateRoom from "../Modals/CreateOrUpdateRoom"; -import { - ShowModalType, - useKeyboardContext, -} from "../../contexts/keyboardContext"; -import { - collection, - getFirestore, - onSnapshot, - orderBy, - query, - where, -} from "firebase/firestore"; -import { Collections } from "../../services/collections"; -import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; -import Room, { RoomStatus, RoomType } from "../../models/room"; -import { useAuth } from "../../contexts/authContext"; -import { Divider, Dropdown, Menu, Radio, Tooltip } from "antd"; -import RoomCard from "../RoomCard"; -import toast from "react-hot-toast"; -import RoomTypeTag from "../RoomTypeTag"; -import moment from "moment"; -import { getTime } from "../../helpers/dateTime"; - -enum RoomTypeFilter { - team = "team", - me = "me", - archived = "archived", -} - -enum RoomTimeFilter { - today = "today", - week = "week", - month = "month", -} - -const db = getFirestore(); - -const today = new Date(); -const lastWeek = new Date(); -lastWeek.setDate(lastWeek.getDate() - 7); -const nextWeek = new Date(); -nextWeek.setDate(nextWeek.getDate() + 7); - -export default function DashboardRoom() { - const { currUser } = useAuth(); - const { handleModalType, showModalType } = useKeyboardContext(); - const { team } = useTeamDashboardContext(); - - const [roomsMap, setRoomsMap] = useState>( - new Map() - ); - - // get recurring rooms data realtime: ALL of them - useEffect(() => { - /** - * QUERY: - * all recurring rooms, as the created date can be a year ago - * - */ - const q = query( - collection(db, Collections.rooms), - where("teamId", "==", team.id), - where("type", "==", RoomType.recurring) - ); - - // return unsubscribe - return onSnapshot(q, (snapshot) => { - snapshot.docChanges().forEach(handleDocChange); - }); - }, []); - - // get scheduled rooms data realtime: the ones with the date range - useEffect(() => { - /** - * QUERY: - * all scheduled rooms in the past 7 days or the next 7 days - * - */ - const q = query( - collection(db, Collections.rooms), - where("teamId", "==", team.id), - where("type", "==", RoomType.scheduled), - where("scheduledDateTime", ">", lastWeek), - where("scheduledDateTime", "<", nextWeek), - orderBy("scheduledDateTime", "asc") - ); - - // return unsubscribe - return onSnapshot(q, (snapshot) => { - snapshot.docChanges().forEach(handleDocChange); - }); - }, []); - - // get now rooms data realtime - useEffect(() => { - /** - * QUERY: - * all live/now rooms going on right now - * - */ - const q = query( - collection(db, Collections.rooms), - where("teamId", "==", team.id), - where("type", "==", RoomType.now), - where("createdDate", ">", lastWeek), - orderBy("createdDate", "asc") - ); - - // return unsubscribe - return onSnapshot(q, (snapshot) => { - snapshot.docChanges().forEach(handleDocChange); - }); - }, []); - - function handleDocChange(change) { - let updatedRoom = change.doc.data() as Room; - updatedRoom.id = change.doc.id; - - if (change.type === "added" || change.type === "modified") { - // update rooms map - setRoomsMap((prevMap) => { - return new Map(prevMap.set(updatedRoom.id, updatedRoom)); - }); - } - if (change.type === "removed") { - console.log("Removed room: ", updatedRoom); - } - } - - // on CTRL + Q, new tab to google meet - // on CTRL + V, show modal to create meeting with the link - - const [selectedTabPane, setSelectedTabPane] = useState( - RoomTypeFilter.me - ); - - // sort this mega array - const allRooms = Array.from(roomsMap.values()); - - const teamRooms = allRooms.filter( - (room) => room.status != RoomStatus.archived - ); - const meRooms = allRooms.filter( - (room) => - room.members.includes(currUser.uid) && room.status != RoomStatus.archived - ); - - const archivedRooms = allRooms.filter( - (room) => room.status == RoomStatus.archived - ); - - // data for different meRooms - const recurring = meRooms.filter( - (room) => room.type == RoomType.recurring && room.status == RoomStatus.empty - ); - const now = meRooms.filter( - (room) => - (room.type == RoomType.now && room.status != RoomStatus.archived) || - room.status == RoomStatus.live - ); - const scheduled = meRooms.filter( - (room) => room.type == RoomType.scheduled && room.status == RoomStatus.empty - ); - - // sorting array of scheduled rooms - scheduled.sort(function (a, b) { - // Turn your strings into dates, and then subtract them - // to get a value that is either negative, positive, or zero. - if (!b.scheduledDateTime) { - return -1; - } - - return ( - getTime(a.scheduledDateTime.toDate()) - - getTime(b.scheduledDateTime.toDate()) - ); - }); - - var relativeTimeNextMeeting = ""; - - // go through scheduled and find the first one coming up - if (scheduled && scheduled.length > 0) { - const firstOneInFuture = scheduled.find( - (room) => moment(room.scheduledDateTime.toDate()).diff(today) > 0 - ); - - if (firstOneInFuture) { - const meetingDatetime: Date = firstOneInFuture.scheduledDateTime.toDate(); - relativeTimeNextMeeting = moment(meetingDatetime).fromNow(); - } - } - - function getRoomContent() { - // return data based on the selected filters - switch (selectedTabPane) { - case RoomTypeFilter.team: - // render sections for different types - - const recurringTeam = teamRooms.filter( - (room) => - room.type == RoomType.recurring && room.status == RoomStatus.empty - ); - - const nowTeam = teamRooms.filter( - (room) => - (room.type == RoomType.now && room.status != RoomStatus.archived) || - room.status == RoomStatus.live - ); - - const scheduledTeam = teamRooms.filter( - (room) => - room.type == RoomType.scheduled && room.status == RoomStatus.empty - ); - - // sorting array of scheduled rooms - scheduledTeam.sort(function (a, b) { - // Turn your strings into dates, and then subtract them - // to get a value that is either negative, positive, or zero. - if (!b.scheduledDateTime) { - return -1; - } - - return ( - getTime(a.scheduledDateTime.toDate()) - - getTime(b.scheduledDateTime.toDate()) - ); - }); - - return ( - <> -
- {nowTeam.map((room) => ( - - ))} - - {scheduledTeam.map((room) => ( - - ))} - - {recurringTeam.map((room) => ( - - ))} -
- - ); - - case RoomTypeFilter.me: - return ( - <> -
- {now.map((room) => ( - - ))} - - {scheduled.map((room) => ( - - ))} - - {recurring.map((room) => ( - - ))} -
- - ); - case RoomTypeFilter.archived: - return ( - - {archivedRooms.map((room) => { - // if the room is - return ( - - ); - })} - - ); - default: - return allRooms.map((room) => { - // if the room is - return ( - - ); - }); - } - } - - const TimePeriodFilterMenu = ( - - - Past 24 Hours (Coming Soon) - - - This Month (Coming Soon) - - - ); - - const [selectedUpdateRoom, setSelectedUpdateRoom] = useState(null); - - async function handleUpdateRoom(roomId: string) { - console.log("going to update room"); - - // get the room details and pass it into the modal - if (!roomsMap.has(roomId)) { - toast.error("not a valid room to edit"); - return; - } - - // pass this to the modal for use - setSelectedUpdateRoom(roomsMap.get(roomId)); - - // call the keyboard context to show the modal - handleModalType(ShowModalType.createRoom); - } - - async function handleCloseModal() { - console.log("closing modal"); - - // to make sure that the next time the props changes, the modal has the updated room object - setSelectedUpdateRoom(null); - - // close the modal - handleModalType(ShowModalType.na); - } - - return ( -
- {/* modal for creating room */} - - - {/* header */} - - - - - ROOMS - - - - - {relativeTimeNextMeeting ? ( - - {"You have your next scheduled one "}{" "} - - {relativeTimeNextMeeting} - {"."} - - - ) : ( - <> - )} - - - setSelectedTabPane(e.target.value)} - > - - {RoomTypeFilter.me}{" "} - - {meRooms.length > 0 ? meRooms.length : ""} - - - - {RoomTypeFilter.team}{" "} - - {teamRooms.length} - - - - {RoomTypeFilter.archived}{" "} - - {archivedRooms.length > 0 ? archivedRooms.length : ""} - - - - - - - Week - - - - - - {/* */} - - - - {/* all rooms */} - {getRoomContent()} -
- ); -} diff --git a/packages/web/components/Dashboard/TeamVoiceLine.tsx b/packages/web/components/Dashboard/TeamVoiceLine.tsx deleted file mode 100644 index 5294839..0000000 --- a/packages/web/components/Dashboard/TeamVoiceLine.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { useRouter } from "next/router"; -import { FaPlus, FaBackward, FaArrowCircleDown } from "react-icons/fa"; -import { BsThreeDots } from "react-icons/bs"; -import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; -import { useEffect, useState } from "react"; -import { toast } from "react-hot-toast"; -import { TeamMemberRole, TeamMemberStatus } from "../../models/teamMember"; -import { getFirestore } from "firebase/firestore"; -import { Tooltip } from "antd"; -import { - ShowModalType, - useKeyboardContext, -} from "../../contexts/keyboardContext"; -import UserStatusBubble, { UserPulse } from "../UserStatusBubble"; -import { useAuth } from "../../contexts/authContext"; -import PowerPlayer from "../Modals/PowerPlayer"; - -const maxNumberOfKeyboardMappings: number = 9; - -export default function TeamVoiceLine() { - const { currUser } = useAuth(); - const { - addTeamShortcutBinding, - selectTeamMember, - selectedTeammate, - isRecording, - handleModalType, - } = useKeyboardContext(); - const router = useRouter(); - const { teamid } = router.query; - const { userTeamMember, teamUsers, messagesByTeamMate } = - useTeamDashboardContext(); - - // todo use a global is loading - const [loading, setLoading] = useState(false); - - // set up shortcuts for each teammate - useEffect(() => { - // create shortcuts - if (!teamUsers) { - console.log("no team members to map to shortcuts"); - return; - } - - teamUsers.forEach((tmUser, i) => { - // make sure we don't map a user if they are past the max allowed - if (i < maxNumberOfKeyboardMappings) { - let shortcut: number = i + 49; // 49 is what number 1 is on the keyboard - addTeamShortcutBinding(shortcut, tmUser.id); - } - }); - }, [teamUsers]); - - async function handleAdminRoute() { - if (userTeamMember.role == TeamMemberRole.admin) { - router.push("/teams/" + teamid + "/admin"); - return; - } - - toast.error("You are not a team admin!"); - } - - useEffect(() => { - // todo move all variable stuff like shortcut mappings here - }, [teamUsers]); - - function renderTeamMemberList() { - // show loading skeleton if not yet got friend info - if (loading) { - return ( -
-
-
-
-
-
-
- ); - } - - // if not teammates, stale state message to tell admin to add people - if (!teamUsers) { - return Please add team members.; - } - - return teamUsers.map((tmember, i) => { - var isMessageIncoming: boolean = false; - if ( - messagesByTeamMate.has(tmember.id) && - messagesByTeamMate.get(tmember.id)[0]?.receiverUserId == currUser.uid - ) { - isMessageIncoming = true; - } - - var customSelectClasses: string = ""; - if (tmember.id == selectedTeammate) { - customSelectClasses = "bg-white scale-100 z-20 "; - } else if (isMessageIncoming) { - customSelectClasses = "bg-orange-500 bg-opacity-20"; - } - - return ( - selectTeamMember(tmember.id)} - key={i} - className={`rounded flex flex-row items-center py-2 px-2 justify-items-start - ease-in-out duration-300 hover:cursor-pointer ${customSelectClasses} hover:bg-gray-200 hover:bg-opacity-20`} - > - - - - - - asdf - - - - {tmember.id == selectedTeammate ? ( - <> - - - {tmember.nickName} - - - - - {tmember.teamRole}{" "} - - - ) : ( - <> - - - {tmember.nickName} - - - - {/* keyboard shortcuts start from 1 */} - {i + 1} - - - - - {tmember.teamRole} - - - )} - - - {tmember.id == selectedTeammate ? ( - <> - - {isRecording ? ( - - ) : ( - - )} - - - - - - - - - ) : ( - - {isMessageIncoming ? ( - - ) : ( - "" - )} - - - - )} - - ); - }); - } - - const [showPowerPlayer, setShowPowerPlayer] = useState(false); - - function handleCloseModal() { - handleModalType(ShowModalType.na); - setShowPowerPlayer(false); - } - - function handleShowModal() { - handleModalType(ShowModalType.powerPlayer); // disables controls - - setShowPowerPlayer(true); - } - - return ( -
- - - TEAM - - - - - - - - - - - - - - {/* list of team members */} -
- {renderTeamMemberList()} -
-
- ); -} diff --git a/packages/web/components/v2/CreateConversation.tsx b/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx similarity index 56% rename from packages/web/components/v2/CreateConversation.tsx rename to packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx index 7dc8f23..4402e50 100644 --- a/packages/web/components/v2/CreateConversation.tsx +++ b/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx @@ -1,7 +1,6 @@ import { Select, Tag } from "antd"; -import Avatar from "antd/lib/avatar/avatar"; import { useEffect, useRef } from "react"; -import { FaPaperPlane, FaRegTimesCircle } from "react-icons/fa"; +import SimpleUserDetailsRow from "../UserDetails/SimpleUserDetailsRow"; const options = [ { value: "gold" }, @@ -45,58 +44,7 @@ export default function CreateConversation() { {/* list of selected people who are nirvana users */} - - - A - - - - - {"ariel@microsoft.com"} - - - Not a valid Nirvana user. - - - - - - - - - - {"Joe Smoe"} - - {"joe@microsoft.com"} - - - - - - - - - - {"Elon Musk"} - {"musk@tesla.com"} - - - - + diff --git a/packages/web/components/v2/SearchResults.tsx b/packages/web/components/FullPageExperiences/SearchResults.tsx similarity index 100% rename from packages/web/components/v2/SearchResults.tsx rename to packages/web/components/FullPageExperiences/SearchResults.tsx diff --git a/packages/web/components/Layouts/LandingPageLayout.tsx b/packages/web/components/Layouts/LandingPageLayout.tsx index 4e83fba..3735298 100644 --- a/packages/web/components/Layouts/LandingPageLayout.tsx +++ b/packages/web/components/Layouts/LandingPageLayout.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { useState } from "react"; import { FaAngleRight, FaGripHorizontal } from "react-icons/fa"; import { useAuth } from "../../contexts/authContext"; -import MainLogo from "../MainLogo"; +import MainLogo from "../Logo/MainLogo"; enum LandingPageNavigation { product = "/", diff --git a/packages/web/components/LinkCard.tsx b/packages/web/components/LinkCard.tsx deleted file mode 100644 index fdda9b9..0000000 --- a/packages/web/components/LinkCard.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { - FaAngleDoubleRight, - FaArchive, - FaExternalLinkAlt, - FaFilePdf, - FaTrash, -} from "react-icons/fa"; -import Link, { LinkState, LinkType } from "../models/link"; -import Image from "next/image"; -import { BsThreeDots } from "react-icons/bs"; -import LinkIcon from "./LinkIcon"; -import { useTeamDashboardContext } from "../contexts/teamDashboardContext"; -import { useAuth } from "../contexts/authContext"; -import { User } from "../models/user"; -import { Avatar, Dropdown, Menu, Tooltip } from "antd"; -import { useState } from "react"; -import SkeletonLoader from "./Loading/skeletonLoader"; -import moment from "moment"; -import { LinkService } from "../services/linkService"; - -interface ILinkCardProps { - link: Link; -} - -const linkService = new LinkService(); - -export default function LinkCard(props: ILinkCardProps) { - const { currUser } = useAuth(); - const { teamUsersMap, user } = useTeamDashboardContext(); - const [loading, setLoading] = useState(false); - - var sender: User; - - if (props.link.createdByUserId == currUser.uid) { - sender = user; - } else { - sender = teamUsersMap[props.link.createdByUserId]; - } - - var receivers: User[] = props.link.recipients?.reduce((results, userId) => { - if (userId in teamUsersMap) { - results.push(teamUsersMap[userId]); - } - - // if I am the receiver still add me to the receivers - if (userId == currUser.uid) { - results.push(user); - } - - return results; - }, [] as User[]); - - async function handleDeleteLink() { - setLoading(true); - if ( - confirm( - "Are you sure you want to delete link? It will disappear for all recipients." - ) - ) { - console.log("deleting link"); - - await linkService.updateLinkState(props.link.id, LinkState.deleted); - - try { - } catch (error) {} - } - - setLoading(false); - } - - async function handleArchivingLink() { - setLoading(true); - - if ( - confirm( - "Are you sure you want to archive link? It will be in the archive tab." - ) - ) { - await linkService.updateLinkState(props.link.id, LinkState.archived); - try { - } catch (error) {} - } - - setLoading(false); - } - - if (loading) { - return ; - } - - const LinkOptionsMenu = ( - - } - > - - - } - > - - - - ); - - const relativeDateTime: string = moment( - props.link.createdDate.toDate() - ).fromNow(); - - var receiversNames = "team"; - if (receivers) { - receiversNames = receivers.map((receiver) => receiver.firstName).join(", "); - } - - return ( - - {/* attmnt header */} - - window.open(props.link.link, "_blank")} - className="flex flex-row bg-gray-300 bg-opacity-25 py-5 px-3 items-center justify-start hover:cursor-pointer h-full" - > - - - - - {props.link.name} - - - - {props.link.description} - - - - {/* attachment actions */} - - - - - {/* attmnt footer */} - " + receiversNames + ": " + relativeDateTime - } - > - - - - - - - {receivers ? ( - receivers.map((receiverUser, i) => { - return ( - - ); - }) - ) : ( - - team - - )} - - - - - - - - - - - ); -} diff --git a/packages/web/components/MainLogo.tsx b/packages/web/components/Logo/MainLogo.tsx similarity index 100% rename from packages/web/components/MainLogo.tsx rename to packages/web/components/Logo/MainLogo.tsx diff --git a/packages/web/components/v2/Conversations.tsx b/packages/web/components/MainTabsOrPages/Conversations.tsx similarity index 100% rename from packages/web/components/v2/Conversations.tsx rename to packages/web/components/MainTabsOrPages/Conversations.tsx diff --git a/packages/web/components/v2/Done.tsx b/packages/web/components/MainTabsOrPages/Done.tsx similarity index 100% rename from packages/web/components/v2/Done.tsx rename to packages/web/components/MainTabsOrPages/Done.tsx diff --git a/packages/web/components/v2/Drawer.tsx b/packages/web/components/MainTabsOrPages/Drawer.tsx similarity index 100% rename from packages/web/components/v2/Drawer.tsx rename to packages/web/components/MainTabsOrPages/Drawer.tsx diff --git a/packages/web/components/v2/Header.tsx b/packages/web/components/MainTabsOrPages/Header.tsx similarity index 97% rename from packages/web/components/v2/Header.tsx rename to packages/web/components/MainTabsOrPages/Header.tsx index d5678c3..ebadf8e 100644 --- a/packages/web/components/v2/Header.tsx +++ b/packages/web/components/MainTabsOrPages/Header.tsx @@ -11,8 +11,8 @@ import { FaMicrophoneAlt, } from "react-icons/fa"; import { UserStatus } from "../../models/user"; -import MainLogo from "../MainLogo"; -import UserStatusBubble from "../UserStatusBubble"; +import MainLogo from "../Logo/MainLogo"; +import UserStatusBubble from "../UserDetails/UserStatusBubble"; export default function Header() { const router = useRouter(); diff --git a/packages/web/components/v2/KeyboardShortcutHandler.tsx b/packages/web/components/MainTabsOrPages/KeyboardShortcutHandler.tsx similarity index 100% rename from packages/web/components/v2/KeyboardShortcutHandler.tsx rename to packages/web/components/MainTabsOrPages/KeyboardShortcutHandler.tsx diff --git a/packages/web/components/v2/Later.tsx b/packages/web/components/MainTabsOrPages/Later.tsx similarity index 100% rename from packages/web/components/v2/Later.tsx rename to packages/web/components/MainTabsOrPages/Later.tsx diff --git a/packages/web/components/v2/Priority.tsx b/packages/web/components/MainTabsOrPages/Priority.tsx similarity index 100% rename from packages/web/components/v2/Priority.tsx rename to packages/web/components/MainTabsOrPages/Priority.tsx diff --git a/packages/web/components/v2/Sidebar.tsx b/packages/web/components/MainTabsOrPages/Sidebar.tsx similarity index 100% rename from packages/web/components/v2/Sidebar.tsx rename to packages/web/components/MainTabsOrPages/Sidebar.tsx diff --git a/packages/web/components/OfficeCard.tsx b/packages/web/components/OfficeCard.tsx deleted file mode 100644 index 34ada9d..0000000 --- a/packages/web/components/OfficeCard.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { Avatar, Tooltip } from "antd"; -import toast from "react-hot-toast"; -import { FaPhoneSlash, FaPlus, FaWalking } from "react-icons/fa"; -import { HiSpeakerphone } from "react-icons/hi"; -import { useAuth } from "../contexts/authContext"; -import { useTeamDashboardContext } from "../contexts/teamDashboardContext"; -import OfficeRoom, { OfficeRoomState } from "../models/officeRoom"; -import { User } from "../models/user"; -import OfficeRoomService from "../services/officeRoomService"; -import { VscDebugDisconnect } from "react-icons/vsc"; -import AgoraService from "../services/agoraService"; - -interface IOfficeCard { - officeRoom: OfficeRoom; - handleJoinChannel: Function; - handleLeaveChannel: Function; -} - -const officeRoomService = new OfficeRoomService(); -const agoraService = new AgoraService(); - -export default function OfficeCard(props: IOfficeCard) { - const { currUser } = useAuth(); - const { teamUsersMap, user } = useTeamDashboardContext(); - - var allMembersInRoom = props.officeRoom.members?.reduce((results, userId) => { - if (userId in teamUsersMap) { - results.push(teamUsersMap[userId]); - } - - if (userId == currUser.uid) { - results.push(user); - } - - return results; - }, [] as User[]); - - async function handleJoinOfficeRoom() { - toast.loading("Connecting"); - - // make sure user is not in another room - // get agora token from cloud function - // join channel for agora - - if (props.officeRoom.members.includes(currUser.uid)) { - toast.error("You are already in this office room!"); - return; - } - - try { - // agora token from CF - const agoraToken = await agoraService.getAgoraToken(props.officeRoom.id); - - // // function to do it all - // await officeRoomService.joinOfficeRoom(props.officeRoom, currUser.uid); - - // handle joining agora channel - await props.handleJoinChannel(props.officeRoom.id, agoraToken); - - // update firestore to add ourselves in the office room - const newMembers = [...props.officeRoom.members, currUser.uid]; - await officeRoomService.updateMembersInOfficeRoom( - props.officeRoom.id, - newMembers - ); - } catch (error) { - console.error(error); - toast.error("problem joining office room"); - } - - toast.dismiss(); - toast.success("joined office room"); - } - - async function handleLeaveOfficeRoom() { - toast.loading("leaving"); - - if (!props.officeRoom.members.includes(currUser.uid)) { - toast.error("You are not in this office room!"); - return; - } - - try { - // leave agora channel - await props.handleLeaveChannel(); - - // leave from firestore database - const newMembersInRoom = props.officeRoom.members.filter( - (memberId) => memberId != currUser.uid - ); - - await officeRoomService.updateMembersInOfficeRoom( - props.officeRoom.id, - newMembersInRoom - ); - } catch (error) { - console.error(error); - toast.error("problem leaving office room"); - } - toast.dismiss(); - toast.success("left office room"); - } - - // check if user is in the office room - var isUserInRoom: boolean = false; - if (props.officeRoom.members?.includes(currUser.uid)) { - isUserInRoom = true; - } - - return ( - - {renderOfficePulse(props.officeRoom.state)} - - {/* office location name */} - - {props.officeRoom.name} - - - {/* all members in room */} - - - {allMembersInRoom.map((member) => { - return ( - - - {member.nickName[0]} - - - ); - })} - - - - {isUserInRoom ? ( - - ) : ( - - - - )} - - ); -} - -function renderOfficePulse(officeRoomState: OfficeRoomState) { - switch (officeRoomState) { - case OfficeRoomState.active: - return ( - - ); - case OfficeRoomState.idle: - return ( - - ); - default: - return ( - - ); - } -} diff --git a/packages/web/components/RoomCard.tsx b/packages/web/components/RoomCard.tsx deleted file mode 100644 index 53c2d90..0000000 --- a/packages/web/components/RoomCard.tsx +++ /dev/null @@ -1,473 +0,0 @@ -import { BsThreeDots } from "react-icons/bs"; -import { - FaArchive, - FaBell, - FaCalendarDay, - FaClock, - FaLink, - FaPlus, -} from "react-icons/fa"; -import { IoTimer } from "react-icons/io5"; -import Room, { RoomStatus, RoomType } from "../models/room"; -import RoomTypeTag from "./RoomTypeTag"; -import Image from "next/image"; -import { useAuth } from "../contexts/authContext"; -import { Avatar, Divider, Dropdown, Menu, Popover, Tooltip } from "antd"; -import { UserOutlined, AntDesignOutlined } from "@ant-design/icons"; -import { useTeamDashboardContext } from "../contexts/teamDashboardContext"; -import { User, UserStatus } from "../models/user"; -import RoomService from "../services/roomService"; -import toast from "react-hot-toast"; -import { useState } from "react"; -import SkeletonLoader from "./Loading/skeletonLoader"; -import { useKeyboardContext } from "../contexts/keyboardContext"; -import UserService from "../services/userService"; -import Moment from "react-moment"; -import moment from "moment"; - -interface IRoomCardProps { - room: Room; - updateRoomHandler: Function; -} - -const roomService = new RoomService(); -const userService = new UserService(); - -const today = new Date(); - -export default function RoomCard(props: IRoomCardProps) { - const { currUser } = useAuth(); - const { teamUsersMap, user } = useTeamDashboardContext(); - const [loading, setLoading] = useState(false); - - var isUserInRoom: boolean; - if (props.room.membersInRoom?.includes(currUser.uid)) { - isUserInRoom = true; - } - - // first array of all members invited - var allMembersInvited = props.room.members?.reduce((results, userId) => { - if (userId in teamUsersMap) { - results.push(teamUsersMap[userId]); - } - - if (userId == currUser.uid) { - results.push(user); - } - - return results; - }, [] as User[]); - - // second array of all members in room - var allMembersInRoom = props.room.membersInRoom?.reduce((results, userId) => { - if (userId in teamUsersMap) { - results.push(teamUsersMap[userId]); - } - - if (userId == currUser.uid) { - results.push(user); - } - - return results; - }, [] as User[]); - - // third array all members invited and not in the array of users in room - const allMembersInvitedButNotInRoom = allMembersInvited.filter( - (invitedMember) => { - if (allMembersInRoom.some((user) => user.id === invitedMember.id)) { - /* vendors contains the element we're looking for */ - return false; - } - - return true; - } - ); - - // show members who are in the invite list but not already in the room - const membersInvited = () => { - if (allMembersInvited?.length == 0) { - return; - } - - const listOfNames = allMembersInvited - .map((user) => user.nickName) - .join(", "); - - return ( - - - - {allMembersInvitedButNotInRoom.map((user, i) => { - return ( - - ); - })} - - - - ); - }; - - const membersInRoom = () => { - if (allMembersInRoom?.length == 0) { - return; - } - - const listOfNames = allMembersInRoom - .map((user) => user.nickName) - .join(", "); - - return ( - - - - {allMembersInRoom.map((user, i) => { - return ( - - ); - })} - - - - ); - }; - - async function handleLeavingRoom() { - setLoading(true); - - try { - // filter current membersInRoom and return new array - const newMembersInRoom = props.room.membersInRoom.filter( - (memberId) => memberId != currUser.uid - ); - - // update the room with room id to have a new array of userIds - await roomService.updateMembersInRoom(props.room.id, newMembersInRoom); - - // change curr user status to free now - await userService.updateUserStatus(currUser.uid, UserStatus.online); - } catch (error) { - console.error(error); - - toast.error("unable to leave room...something went wrong"); - } - - setLoading(false); - } - - async function handleJoiningRoom() { - setLoading(true); - - try { - // update members in room - const newMembersInRoom = [...props.room.membersInRoom, currUser.uid]; - - // update the room with room id to have a new array of userIds - await roomService.updateMembersInRoom(props.room.id, newMembersInRoom); - - // change curr user status to free now - await userService.updateUserStatus(currUser.uid, UserStatus.busy); - - // then window.open to room's link - window.open(props.room.link, "_blank"); - } catch (error) { - console.error(error); - - toast.error("unable to leave room...something went wrong"); - } - - setLoading(false); - } - - async function handleArchivingRoom() { - if ( - confirm( - "Are you sure you want to archive room? It will be added to the archived tab." - ) - ) { - console.log("archiving room"); - - try { - const newRoom: Room = { ...props.room }; - - newRoom.status = RoomStatus.archived; - newRoom.membersInRoom = []; - await roomService.updateRoom(newRoom); - } catch (error) {} - } - } - - if (loading) { - return ; - } - - async function handleUpdateRoom() { - props.updateRoomHandler(props.room.id); - } - - const RoomOptionsMenu = ( - - - - - - Archive Room - - - ); - - const AgendaContent = ( -
- - {props.room.description} - -
- ); - - // handle specifics for a scheduled room - var dateTimeScheduled = null; - var isPastRoom = false; // signal user to archive this room if it's past - if (props.room.type == RoomType.scheduled) { - dateTimeScheduled = props.room.scheduledDateTime.toDate(); - - // check if the moment date scheduled is future or past - - if (moment(today).diff(moment(dateTimeScheduled)) > 0) { - isPastRoom = true; - } - } - - function renderTopRightCardInfo() { - if (props.room.type == RoomType.scheduled && props.room.scheduledDateTime) { - return ( - <> - - - - - - - - - - - ); - } - } - - if (props.room.status == RoomStatus.archived) { - return ( - <> - - {membersInvited()} - - {/* header */} - - {/* meeting details */} - - - {props.room.name} - - - - {props.room.description} - - - - {/* all invited members who are not in the room already */} - - - - - - {/* room status and link(s) */} - - {renderTopRightCardInfo()} - - - {props.room.approximateDateTime} - - - - {/* room attachments */} - - {props.room.attachments && props.room.attachments.length > 0 ? ( - - ) : ( - <> - )} - - - {/* footer */} - - - - - - ); - } - - return ( - - {/* header */} - - {/* meeting details */} - - - {props.room.name} - - - - {props.room.description} - - - - {/* badges and tags */} - {/* - - scrum - - */} - - {/* all invited members who are not in the room already */} - {membersInvited()} - - - {/* room status and link(s) */} - - - - {renderTopRightCardInfo()} - - - {props.room.approximateDateTime} - - - {/* room attachments */} - - {props.room.attachments && props.room.attachments.length > 0 ? ( - - ) : ( - - )} - - - - - {/* footer */} - - {membersInRoom()} - - {/* tell them to archive if it's past */} - {isPastRoom ? ( - - - - ) : ( - <> - )} - - {isUserInRoom ? ( - - ) : ( - - )} - - - - - - - ); -} diff --git a/packages/web/components/RoomTypeTag.tsx b/packages/web/components/RoomTypeTag.tsx deleted file mode 100644 index 85f879b..0000000 --- a/packages/web/components/RoomTypeTag.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { FaClock } from "react-icons/fa"; -import { IoTimer } from "react-icons/io5"; -import { RoomStatus, RoomType } from "../models/room"; - -interface RoomTypeTagProps { - roomType: RoomType; - roomStatus: RoomStatus; -} - -export default function RoomTypeTag(props: RoomTypeTagProps) { - const universalClassNames = - "text-xs font-bold p-1 rounded-md flex flex-row space-x-2 items-center shadow-lg"; - - // show live even if it's some other type because people are in it - if (props.roomStatus == RoomStatus.live) { - return ( - - - live - - ); - } else if (props.roomStatus == RoomStatus.archived) { - if (props.roomType == RoomType.now) { - return ( - // - // archived - // - - - - now room - - ); - } else if (props.roomType == RoomType.scheduled) { - return ( - - - scheduled room - - ); - } else if (props.roomType == RoomType.recurring) { - return ( - - - recurring room - - ); - } - } - - // if it's just empty and not empty, then go ahead and show the right type - switch (props.roomType) { - case RoomType.now: - return ( - - - live - - ); - - case RoomType.scheduled: - return ( - <> - // - // - // scheduled - // - ); - - case RoomType.recurring: - return ( - - - recurring - - ); - } -} diff --git a/packages/web/components/RoomsRowsByType.tsx b/packages/web/components/RoomsRowsByType.tsx deleted file mode 100644 index f5b0b64..0000000 --- a/packages/web/components/RoomsRowsByType.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Room, { RoomType } from "../models/room"; -import RoomCard from "./RoomCard"; - -interface IRoomsData { - rooms: Room[]; -} - -// different rows for the types -export function RoomsRowsByType(props: IRoomsData) { - // got the top level content/rooms - // now let's drill down and filter - const recurringRooms = props.rooms.filter( - (room) => room.type == RoomType.recurring - ); - - const scheduledRooms = props.rooms.filter( - (room) => room.type == RoomType.scheduled - ); - - const nowRooms = props.rooms.filter( - (room) => room.type == RoomType.scheduled - ); - - return; - // return props.rooms.map((room) => { - // // if the room is - // return ( - // - // ); - // }) -} - -// simple table for the archived rooms -export function ArchivedRoomsTable(props: IRoomsData) {} diff --git a/packages/web/components/UserDetails/SimpleUserDetailsRow.tsx b/packages/web/components/UserDetails/SimpleUserDetailsRow.tsx new file mode 100644 index 0000000..17e6b2f --- /dev/null +++ b/packages/web/components/UserDetails/SimpleUserDetailsRow.tsx @@ -0,0 +1,61 @@ +import { Avatar } from "antd"; +import { FaPaperPlane, FaRegTimesCircle } from "react-icons/fa"; +import User from "@nirvana/common/models/user"; + +export default function SelectedUserRow(props: { user: User }) { + console.log(props); + return ( + <> + + + A + + + + + {"ariel@microsoft.com"} + + + Not a valid Nirvana user. + + + + + + + + + + {"Joe Smoe"} + {"joe@microsoft.com"} + + + + + + + + + {"Elon Musk"} + {"musk@tesla.com"} + + + + + + ); +} diff --git a/packages/web/components/UserStatusBubble.tsx b/packages/web/components/UserDetails/UserStatusBubble.tsx similarity index 96% rename from packages/web/components/UserStatusBubble.tsx rename to packages/web/components/UserDetails/UserStatusBubble.tsx index c34e0cf..990f2fe 100644 --- a/packages/web/components/UserStatusBubble.tsx +++ b/packages/web/components/UserDetails/UserStatusBubble.tsx @@ -1,5 +1,5 @@ import { IoPulseOutline, IoRemoveOutline } from "react-icons/io5"; -import { UserStatus } from "../models/user"; +import { UserStatus } from "../../models/user"; interface UserStatusPropsInterface { status: UserStatus; diff --git a/packages/web/contexts/keyboardContext.tsx b/packages/web/contexts/keyboardContext.tsx deleted file mode 100644 index 6c2677a..0000000 --- a/packages/web/contexts/keyboardContext.tsx +++ /dev/null @@ -1,646 +0,0 @@ -import React, { useCallback, useContext, useEffect, useState } from "react"; -import toast from "react-hot-toast"; -import { KeyCode } from "../globals/keycode"; -import MicRecorder from "mic-recorder-to-mp3"; -import { v4 as uuidv4 } from "uuid"; - -import AudioPlayer from "react-h5-audio-player"; -import "react-h5-audio-player/lib/styles.css"; -import CloudStorageService from "../services/cloudStorageService"; -import { Message } from "../models/message"; -import { useAuth } from "./authContext"; -import { SendService } from "../services/sendService"; -import { useTeamDashboardContext } from "./teamDashboardContext"; -import isValidHttpUrl from "../helpers/urlHelper"; -import { GlobalHotKeys, KeyMap } from "react-hotkeys"; -import Announcement from "../models/announcement"; - -interface KeyboardContextInterface { - selectedTeammate: string; // can only have one selected - selectTeamMember: Function; - - teamShortcutMappings: {}; - addTeamShortcutBinding: Function; - - isRecording: Boolean; // can only record if someone is selected or maybe for an announcement - isRecordingAnnouncement: boolean; - - isMuted: Boolean; - isSilenceMode: Boolean; // won't automatically listen to notifications or sounds - muteOrUnmute: Function; - silenceOrLivenMode: Function; - - hasRecPermit: Boolean; // permission to record or not - - audioInputDeviceId: string; - audioOutputDeviceId: string; - - selectAudioOutput: Function; - selectAudioInput: Function; - - inputDevices: MediaDeviceInfo[]; - outputDevices: MediaDeviceInfo[]; - - ctrlDown: boolean; - - showModalType: ShowModalType; - handleModalType: Function; - pastedLink: string; - - handleAddAudioToQueue: Function; -} - -export enum ShowModalType { - createLink = "link", - createRoom = "room", - powerPlayer = "powerPlayer", - na = "none", -} - -const KeyboardContext = React.createContext( - null -); - -function stopBothVideoAndAudio(stream) { - stream.getTracks().forEach(function (track) { - if (track.readyState == "live") { - track.stop(); - - console.log("stopped playing anything"); - } - }); -} - -const cloudStorageService = new CloudStorageService(); -const sendService = new SendService(); - -export default function KeyboardContextProvider({ children }) { - const { currUser } = useAuth(); - - // SECTION: set up for shortcuts and recording and such - const [selectedTeammate, setSelectedTeamMember] = useState(null); // id of selected teammate - const [isRecording, setIsRecording] = useState(false); - const [isRecordingAnnouncement, setIsRecordingAnnouncement] = - useState(false); - const [hasRecPermit, setHasRecPermit] = useState(false); - - const [ctrlDown, setCtrlDown] = useState(false); - - const [teamShortcutMappings, setTeamShortcutMappings] = useState<{}>({}); - const [audioInputDeviceId, setAudioInputDevice] = useState(null); // device id - const [audioOutputDeviceId, setAudioOutputDevice] = useState(null); // device id - - const [inputDevices, setInputDevices] = useState([]); - const [outputDevices, setOutputDevices] = useState([]); - - const [isMuted, setIsMuted] = useState(false); - const [isSilenceMode, setIseSilenceMode] = useState(false); - - const [pastedLink, setPastedLink] = useState(null); - const [showModalType, setShowModalType] = useState( - ShowModalType.na - ); - - const handleModalType = (modalType: ShowModalType) => { - handleResetView(); - setShowModalType(modalType); - }; - - const [recorder, setRecorder] = useState( - new MicRecorder({ bitRate: 128 }) - ); - - const [audioQueue, setAudioQueue] = useState([]); // queue for the player to keep playing - - // playing incoming messages - const { allMessages, messagesByTeamMate, team } = useTeamDashboardContext(); - - useEffect(() => { - if (!allMessages.length) { - toast("no message to play"); - return; - } - - // only play if it's incoming, as I listened to my message before sending it - if (allMessages[0].receiverUserId == currUser.uid) { - console.log("playing message"); - console.log(allMessages[0]); - - // select the user - setSelectedTeamMember(allMessages[0].senderUserId); - - // start player on the bottom - // autoplay message - // add to the queue - handleAddAudioToQueue([allMessages[0].audioDataUrl]); - } - }, [allMessages]); - - // manage audio queue - useEffect(() => { - // every time queue changes, - // setPlayerSrc as the next item if there is any to play it - - if (audioQueue && audioQueue.length > 0) { - toast("Adding Message to Queue"); - - // todo: edge case, if it's the same link again, then still play it somehow - if (playerSrc == audioQueue[0]) { - // play empty one and then add the next audio file - setPlayerSrc(""); - - setTimeout(() => { - setPlayerSrc(audioQueue[0]); - }, 1000); - } else { - setPlayerSrc(audioQueue[0]); - } - } - - if (!audioQueue || audioQueue.length == 0) { - setPlayerSrc(null); - } - }, [audioQueue]); - - // show toast when recording - useEffect(() => { - if (isRecordingAnnouncement || isRecording) { - toast.loading("Recording..."); - } else { - toast.dismiss(); - } - }, [isRecording, isRecordingAnnouncement]); - - const value: KeyboardContextInterface = { - selectedTeammate, - selectTeamMember, - addTeamShortcutBinding, - isRecording, - isRecordingAnnouncement, - teamShortcutMappings, - - isMuted, - isSilenceMode, - muteOrUnmute, - silenceOrLivenMode, - - hasRecPermit, - - audioInputDeviceId, - audioOutputDeviceId, - selectAudioOutput, - selectAudioInput, - - inputDevices, - outputDevices, - - ctrlDown, - - pastedLink, - handleModalType, - showModalType, - - handleAddAudioToQueue, - }; - - function muteOrUnmute() { - toast.success(isMuted ? "Unmuted" : "Muted"); - - setIsMuted((prevVal) => !prevVal); - } - - function silenceOrLivenMode() { - toast.success(isSilenceMode ? "Unsilenced" : "Auto listen mode disabled"); - - setIseSilenceMode((prevVal) => !prevVal); - } - - function selectAudioOutput(deviceId: string) { - toast.success("Changed output device"); - setAudioOutputDevice(deviceId); - } - - function selectAudioInput(deviceId: string) { - toast.success("Changed input device"); - setAudioInputDevice(deviceId); - } - - // set up audio - useEffect(() => { - (async function () { - try { - const devices = await navigator.mediaDevices.enumerateDevices(); - - const inputDevices: MediaDeviceInfo[] = devices.filter( - (d) => d.kind == "audioinput" - ); - - setAudioInputDevice(inputDevices ? inputDevices[0].deviceId : null); - setInputDevices(inputDevices); - - const outputDevices: MediaDeviceInfo[] = devices.filter( - (d) => d.kind == "audiooutput" - ); - - setAudioOutputDevice(outputDevices ? outputDevices[0].deviceId : null); - setOutputDevices(outputDevices); - } catch (e) { - console.log(e); - toast.error("Problem in setting up audio devices"); - } - })(); - }, [hasRecPermit]); - - // first load just force check if permissions enabled - // through fake stream - // todo: only call this when starting to record and do it with a usecallback - useEffect(() => { - try { - // won't work in https!!! - navigator.mediaDevices - .getUserMedia({ audio: { deviceId: audioInputDeviceId } }) - .then((stream) => { - // stop playing anything - stopBothVideoAndAudio(stream); - - console.log("Permission Granted"); - setHasRecPermit(true); - }) - .catch((e) => { - console.log("Permission Denied"); - toast.error( - "Please make sure that you have connected a microphone and given permissions." - ); - setHasRecPermit(false); - }); - } catch (error) { - console.log(error); - toast.error("something went wrong"); - } - }, []); - - // set recording device - useEffect(() => { - setRecorder( - new MicRecorder({ bitRate: 128, deviceId: audioInputDeviceId }) - ); - }, [audioInputDeviceId]); // change it everytime we change the input device - - // SECTION: recording - async function startRecording() { - recorder - .start() - .then(() => { - // toast.success("started recording"); - console.log("recording started"); - }) - .catch((e) => - toast.error("there was a problem in starting your recording") - ); - } - - async function stopRecording(): Promise { - return new Promise((resolve, reject) => { - recorder - .stop() - .getMp3() - .then(([buffer, blob]) => { - const blobURL = URL.createObjectURL(blob); - - const file = new File(buffer, uuidv4() + ".mp3", { - type: blob.type, - lastModified: Date.now(), - }); - - const player = new Audio(URL.createObjectURL(file)); - player.onended = onEndedPlaying; - player.play(); - - resolve(file); - }) - .catch((error) => { - reject(error.message); - }); - }); - } - - function onEndedPlaying(e) { - // toast.success("finished playing"); - - // remove from queue and the queue manager will handle the rest - setAudioQueue((prevQueue) => { - let newQueue: string[] = [...prevQueue]; - newQueue.shift(); - return newQueue; - }); - } - - const handleKeyUp = useCallback( - (event) => { - // if was recording and released R, then stop recording and send message - if (event.keyCode == KeyCode.R && selectedTeammate && isRecording) { - console.log("stopped recording"); - setIsRecording(false); - - const currReceiverUserId = selectedTeammate; - - stopRecording() - .then((file) => { - console.log(file); - - // upload to cloud storage - return cloudStorageService.uploadMessageAudioFile(file); - }) - .then((downloadUrl) => { - console.log("file is stored: " + downloadUrl); - - // send message to firestore - const message = new Message(); - message.audioDataUrl = downloadUrl; - message.senderUserId = currUser.uid; - message.receiverUserId = currReceiverUserId; - - return sendService.sendMessage(message); - }) - .then(() => { - toast.success("clip sent"); - }) - .catch((error) => { - toast.error("Problem in sending clip"); - }); - - console.log("sending message to " + selectedTeammate); - - handleResetView(); - } else if (event.keyCode == KeyCode.Ctrl) { - setCtrlDown(false); - } - }, - [isRecording, selectedTeammate] - ); - - const handleKeyboardShortcut = useCallback( - (event) => { - // no need to monitor repeats - if (event.repeat) { - return; - } - - // handle for when in modals, don't want any of this crap - if (showModalType != ShowModalType.na) { - return; - } - - // recording - if (event.keyCode == KeyCode.R && !ctrlDown) { - if (!audioInputDeviceId) { - toast.error("No microphone selected"); - } else if (!hasRecPermit) { - toast.error( - "You did not allow microphone permissions in your browser!" - ); - } else if (!selectedTeammate) { - toast.error("Please select a team member or announcements first"); - } else if (isMuted) { - toast.error("You are muted!"); - } else { - // alright now you are good to go - console.log("started recording"); - setIsRecording(true); - startRecording(); - } - } - // if we have a valid user for such a shortcut, then go ahead...otherwise - else if (teamShortcutMappings[event.keyCode]) { - setSelectedTeamMember(teamShortcutMappings[event.keyCode]); - } else if (event.keyCode == KeyCode.Escape) { - handleResetView(); - } else if (event.keyCode == KeyCode.Space) { - // listen to last message in convo - if (isSilenceMode) { - toast.error("you are in silence mode, please disable it first"); - } else if (!selectedTeammate) { - toast.error("select a team member first to play"); - } else { - // alright now you are good to play last message chunk in conversation with selected user - - const arrayMessagesforUser: Message[] = - messagesByTeamMate.get(selectedTeammate); - if ( - messagesByTeamMate.has(selectedTeammate) && - arrayMessagesforUser && - arrayMessagesforUser.length > 0 - ) { - var convoChunk: string[] = []; - console.log("playing from this cache"); - - // wait for x changes, and then break adding to the queue...I want to hear the past loop of conversation maybe - const maxConvoChanges: number = 1; - var convoChangeCount: number = 0; - - var currTalkerId = arrayMessagesforUser[0].senderUserId; - for (const audioMessage of arrayMessagesforUser) { - if (audioMessage.senderUserId != currTalkerId) { - convoChangeCount += 1; - } - - // heard enough of this convo - if (convoChangeCount == maxConvoChanges) { - break; - } - - convoChunk.push(audioMessage.audioDataUrl); - currTalkerId = audioMessage.senderUserId; - } - - // reverse the chunk so that I listen to the messages in order - convoChunk.reverse(); - - // add convo chunk to the queue player - handleAddAudioToQueue(convoChunk); - } else { - toast("nothing to play"); - } - } - } else if (event.keyCode == KeyCode.Ctrl) { - handleResetView(); - - setCtrlDown(true); - } else if (event.keyCode == KeyCode.Q && ctrlDown) { - toast("navigating to create google meet"); - - window.open(new URL("https://meet.google.com/"), "_blank"); - } else if (event.keyCode == KeyCode.V && ctrlDown) { - toast("pasting link"); - - navigator.clipboard - .readText() - .then((text) => { - console.log("Pasted content: ", text); - - // check that the link is valid - if (!isValidHttpUrl(text)) { - toast.error("Not a valid link"); - return; - } - - // set it as we should show modal regardless now - setPastedLink(text); - - // see which modal to go to based on room vs. attachment - // https://meet.google.com/soc-ebwc-rkt - if (text.includes("meet.google.com")) { - setShowModalType(ShowModalType.createRoom); - } else { - setShowModalType(ShowModalType.createLink); - } - }) - .catch((err) => { - console.error("Failed to read clipboard contents: ", err); - toast.error("Please enable permissions for clipboard"); - }); - } else { - // toast("Invalid keyboard shortcut."); - console.log("no shortcut logged"); - } - - // todo play message if pressing space - - // todo if we press the same shortcut twice, deactive selected user - }, - [ - selectedTeammate, - audioInputDeviceId, - hasRecPermit, - isMuted, - teamShortcutMappings, - ctrlDown, - showModalType, - ] - ); - - function handleResetView() { - setSelectedTeamMember(null); - setShowModalType(ShowModalType.na); - - setAudioQueue([]); - } - - // IMPORTANT: shortcut handlers need to be updated as the function has to have the fresh state - useEffect(() => { - console.log("updating event listeners"); - document.addEventListener("keydown", handleKeyboardShortcut); - document.addEventListener("keyup", handleKeyUp); - - return () => { - document.removeEventListener("keydown", handleKeyboardShortcut); - document.removeEventListener("keyup", handleKeyUp); - }; - }, [handleKeyboardShortcut, handleKeyUp]); - - function addTeamShortcutBinding(keyCode: number, userId: string) { - setTeamShortcutMappings((prevMap) => ({ ...prevMap, [keyCode]: userId })); - } - - function selectTeamMember(userId: string) { - setSelectedTeamMember(userId); - } - - const [playerSrc, setPlayerSrc] = useState(null); - - function handleAddAudioToQueue( - urls: string[], - clearEverythingElse: boolean = false - ) { - if (clearEverythingElse) { - setAudioQueue(urls); - } else { - setAudioQueue((prevQueue) => [...prevQueue, ...urls]); - } - } - - function startAnnouncement() { - setIsRecordingAnnouncement(true); - - startRecording(); - } - - async function sendAnnouncement() { - setIsRecordingAnnouncement(false); - - try { - const file = await stopRecording(); - - // confirm with user that he wants to make the announcement - // before sending and all - // show this 2 seconds after playing entire message - setTimeout(async () => { - if ( - confirm( - "Send this announcement to team? OR just cancel and create a new one to restate something." - ) - ) { - const downloadUrl = await cloudStorageService.uploadMessageAudioFile( - file - ); - - console.log("file is stored: " + downloadUrl); - - // create announcement object and send to firestore - const newAnnounce = new Announcement( - downloadUrl, - team.id, - currUser.uid - ); - - await sendService.sendAnnouncement(newAnnounce); - - toast.success("clip sent"); - } else { - toast.error("cancelled announcement"); - - return; - } - }, 2000); - } catch (error) { - toast.error("problem in sending"); - } - } - - const keyMap: KeyMap = { - RECORD_ANNOUNCEMENT: "a", - STOP_RECORDING_ANNOUNCEMENT: { - name: "Stop recording", - sequence: "a", - action: "keyup", - }, - }; - const handlers = { - RECORD_ANNOUNCEMENT: startAnnouncement, - STOP_RECORDING_ANNOUNCEMENT: sendAnnouncement, - }; - - return ( - - - - {children} - - {/* player for audio messages */} - {playerSrc && ( - console.log("onPlay")} - showSkipControls={true} - onEnded={onEndedPlaying} - className="w-screen flex flex-row" - /> - )} - - ); -} - -export function useKeyboardContext() { - return useContext(KeyboardContext); -} diff --git a/packages/web/models/announcement.ts b/packages/web/models/announcement.ts deleted file mode 100644 index 79120ef..0000000 --- a/packages/web/models/announcement.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Timestamp } from "firebase/firestore"; - -export default class Announcement { - id: string; - teamId: string; - audioDataUrl: string; // link to cloud storage file - - state: AnnouncementState = AnnouncementState.active; - - createdByUserId: string; - createdDate: Timestamp; - lastUpdatedDate: Timestamp; - - constructor(_audioUrl: string, _teamId: string, _createdByUserId: string) { - this.audioDataUrl = _audioUrl; - this.createdByUserId = _createdByUserId; - this.teamId = _teamId; - } -} - -export enum AnnouncementState { - active = "active", - resolved = "resolved", - deleted = "deleted", -} diff --git a/packages/web/models/link.ts b/packages/web/models/link.ts deleted file mode 100644 index 3aae505..0000000 --- a/packages/web/models/link.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Timestamp } from "firebase/firestore"; - -export default class Link { - id: string; - name: string; - description: string; - link: string; //url for file - - state: LinkState = LinkState.active; - type: LinkType; - - teamId: string; - // if it's not a teamAttachment, then have a list of members who it's for - recipients: string[]; // userIds - - createdByUserId: string; - createdDate: Timestamp; - - constructor( - _name: string, - _description: string, - _link: string, - _teamId: string, - recipientsArr: string[], - _createdByUserId: string - ) { - this.name = _name; - this.description = _description; - this.link = _link; - this.teamId = _teamId; - this.recipients = recipientsArr; - this.createdByUserId = _createdByUserId; - - if (!recipientsArr || recipientsArr?.length == 0) { - this.recipients = null; - } else { - this.recipients = recipientsArr; - } - - this.type = Link.getLinkType(_link); - } - - static getLinkType(url: string): LinkType { - if (url.includes(LinkType.github)) { - return LinkType.github; - } else if (url.includes(LinkType.atlassian)) { - return LinkType.atlassian; - } else if ( - url.includes(LinkType.googleDrive) || - url.includes("docs.google") - ) { - return LinkType.googleDrive; - } else if ( - url.includes(".png") || - url.includes(".jpg") || - url.includes(".svg") || - url.includes(".gif") || - url.includes(LinkType.pastePics) - ) { - return LinkType.image; - } else if (url.includes(LinkType.pdf)) { - return LinkType.pdf; - } else if (url.includes(LinkType.codePile)) { - return LinkType.codePile; - } else { - return LinkType.default; - } - } -} - -export enum LinkState { - active = "active", - archived = "archived", - deleted = "deleted", -} - -export enum LinkType { - default = "default", - github = "github", - atlassian = "atlassian", - googleDrive = "drive.google", - onedrive = "onedrive", - image = "image", - pdf = "pdf", - codePile = "codepile", - pastePics = "paste.pics", -} diff --git a/packages/web/models/message.ts b/packages/web/models/message.ts deleted file mode 100644 index f76a7d7..0000000 --- a/packages/web/models/message.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Timestamp } from "firebase/firestore"; - -export class Message { - id: string; - audioDataUrl: string; - - senderUserId: string; - receiverUserId: string; - - senderReceiver: string[]; // composite to make querying easier in the future - - createdDate: Timestamp; - - // firstListenDate: Timestamp; -} diff --git a/packages/web/models/officeRoom.ts b/packages/web/models/officeRoom.ts deleted file mode 100644 index 2a26d5c..0000000 --- a/packages/web/models/officeRoom.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Timestamp } from "firebase/firestore"; -import { v4 as uuidv4 } from "uuid"; - -export default class OfficeRoom { - id: string = uuidv4(); - - teamId: string; - - name: string; // entrance, kitchen, etc. - - createdDate: Timestamp; - createdByUserId: string; - - lastUpdatedDate: Timestamp; - - members: string[] = []; // id's of users in the office room - - state: OfficeRoomState; - - constructor( - _name: string, - _teamId: string, - _createdBy: string, - _state: OfficeRoomState = OfficeRoomState.idle - ) { - this.name = _name; - this.teamId = _teamId; - this.createdByUserId = _createdBy; - this.state = _state; - } -} - -export enum OfficeRoomState { - active = "active", - idle = "idle", - archived = "archived", -} diff --git a/packages/web/models/room.ts b/packages/web/models/room.ts deleted file mode 100644 index ce76e23..0000000 --- a/packages/web/models/room.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Timestamp } from "firebase/firestore"; - -export default class Room { - id: string; - - name: string; - description: string; - - link: string; // google meet link for now - - members: string[] = []; //userIds of "mandatory"/invited people including the person who created it - - membersInRoom: string[] = []; - - attachments: string[] = []; // the links themselves (NOT Ids)...there will be duplicate entries in the attachments table which will be created - - type: RoomType; - status: RoomStatus = RoomStatus.empty; - - approximateDateTime: string; // vaguely say when the meeting should be...give user pointers - scheduledDateTime: Timestamp; - - // scheduledJsDateTime(): Date { - // return this.scheduledDateTime.toDate(); - // } - - createdDate: Timestamp; - - // createdJsDate(): Date { - // return this.createdDate.toDate(); - // } - - createdByUserId: string; - - teamId: string; - - lastUpdatedDate: Timestamp; -} - -export enum RoomType { - now = "now", - scheduled = "scheduled", // one time sort of standard meeting - recurring = "recurring", //daily standup -} - -export enum RoomStatus { - live = "live", - empty = "empty", - archived = "archived", // user marks it over -} diff --git a/packages/web/models/team.ts b/packages/web/models/team.ts deleted file mode 100644 index 555196b..0000000 --- a/packages/web/models/team.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Timestamp } from "firebase/firestore"; - -export class Team { - id: string; - name: string; - - status: TeamStatus; - - allowedUserCount: number = 2; - // subscriptionPlan: TeamSubscriptionPlan = TeamSubscriptionPlan.basic - - companySite: string; - - createdByUserId: string; - createdDate: Timestamp; - - lastUpdatedDate: Timestamp; -} - -export enum TeamStatus { - created = "created", - deactivated = "deactivated", - deleted = "deleted", -} - -export enum TeamSubscriptionPlan { - free = "free", - basic = "basic", - pro = "pro", -} diff --git a/packages/web/models/teamMember.ts b/packages/web/models/teamMember.ts deleted file mode 100644 index 106a4d7..0000000 --- a/packages/web/models/teamMember.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Timestamp } from "firebase/firestore"; - -export class TeamMember { - id: string; - userId: string; - teamId: string; - - inviteEmailAddress: string; - invitedByUserId: string; - - role: TeamMemberRole - status: TeamMemberStatus - - createdDate: Timestamp - lastUpdatedDate: Timestamp -} - -export enum TeamMemberRole { - admin = "admin" -} - -export enum TeamMemberStatus { - invited = "invited", - activated = "activated", - deleted = "deleted" -} \ No newline at end of file diff --git a/packages/web/pages/index.js b/packages/web/pages/index.js index 9fc7191..3723197 100644 --- a/packages/web/pages/index.js +++ b/packages/web/pages/index.js @@ -19,7 +19,7 @@ import Announcements from "../components/demo/Announcements"; import Rooms from "../components/demo/Rooms"; import VoiceLineConceptDemo from "../components/demo/VoiceLineConceptDemo"; -import MainLogo from "../components/MainLogo"; +import MainLogo from "../components/Logo/MainLogo"; import { Divider, Tooltip, Image as AntDImage } from "antd"; import LangingPageLayout from "../components/Layouts/LandingPageLayout"; import LandingPageActionBar from "../components/demo/LandingPageActionBar"; diff --git a/packages/web/pages/s/index.tsx b/packages/web/pages/s/index.tsx index e6bdfba..178ec33 100644 --- a/packages/web/pages/s/index.tsx +++ b/packages/web/pages/s/index.tsx @@ -1,16 +1,16 @@ import { useRouter } from "next/router"; import React, { ReactElement } from "react"; -import Conversations from "../../components/v2/Conversations"; -import Done from "../../components/v2/Done"; -import Drawer from "../../components/v2/Drawer"; -import Header from "../../components/v2/Header"; -import Later from "../../components/v2/Later"; -import Sidebar from "../../components/v2/Sidebar"; +import Conversations from "../../components/MainTabsOrPages/Conversations"; +import Done from "../../components/MainTabsOrPages/Done"; +import Drawer from "../../components/MainTabsOrPages/Drawer"; +import Header from "../../components/MainTabsOrPages/Header"; +import Later from "../../components/MainTabsOrPages/Later"; +import Sidebar from "../../components/MainTabsOrPages/Sidebar"; import { CSSTransition, SwitchTransition } from "react-transition-group"; -import CreateConversation from "../../components/v2/CreateConversation"; +import CreateConversation from "../../components/FullPageExperiences/CreateConversation.tsx"; import { FaArrowLeft } from "react-icons/fa"; -import KeyboardShortcutHandler from "../../components/v2/KeyboardShortcutHandler"; -import SearchResults from "../../components/v2/SearchResults"; +import KeyboardShortcutHandler from "../../components/MainTabsOrPages/KeyboardShortcutHandler"; +import SearchResults from "../../components/FullPageExperiences/SearchResults"; import { QueryRoutes, Routes } from "@nirvana/common/helpers/routes"; export default function Me() { diff --git a/packages/web/pages/teams/[teamid].tsx b/packages/web/pages/teams/[teamid].tsx deleted file mode 100644 index 31fd330..0000000 --- a/packages/web/pages/teams/[teamid].tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { GetServerSidePropsContext, GetServerSidePropsResult } from "next"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import Header from "../../components/Dashboard/Header"; -import Rooms from "../../components/Dashboard/Rooms"; -import TeamVoiceLine from "../../components/Dashboard/TeamVoiceLine"; -import BackgroundLayout from "../../components/Layouts/BackgroundLayout"; -import Loading from "../../components/Loading"; -import AudioContextProvider from "../../contexts/keyboardContext"; -import { useAuth } from "../../contexts/authContext"; -import { - TeamDashboardContextProvider, - useTeamDashboardContext, -} from "../../contexts/teamDashboardContext"; -import { Team } from "../../models/team"; -import { TeamMemberStatus } from "../../models/teamMember"; -import { UserStatus } from "../../models/user"; -import TeamService from "../../services/teamService"; -import UserService from "../../services/userService"; -import Announcements from "../../components/Dashboard/Announcements"; -import Links from "../../components/Dashboard/Links"; -import Office from "../../components/Dashboard/Office"; -import ShortcutHelpModal from "../../components/Modals/ShortcutHelpModal"; - -// User has switched back to the tab -const onFocus = () => { - console.log("Tab is in focus"); -}; - -// User has switched away from the tab (AKA tab is hidden) -const onBlur = () => { - console.log("Tab is blurred"); -}; - -const alertUserAboutClosing = (ev) => { - // change user status to offline - - ev.preventDefault(); - return (ev.returnValue = "are you sure?"); -}; - -const userService = new UserService(); - -function TeamDashboard() { - const { currUser } = useAuth(); - const router = useRouter(); - const teamDashboardContext = useTeamDashboardContext(); - - // user goes offline - const handleTabClosing = () => { - // change status of user - userService.updateUserStatus(currUser.uid, UserStatus.offline); - }; - - // shows browser alert to warn user of exiting - const alertUserAboutClosing = (event: any) => { - event.preventDefault(); - event.returnValue = ""; - }; - - useEffect(() => { - // set the document title based on the team - // todo: update based on who sent you a message - document.title = teamDashboardContext.team.name; - - window.addEventListener("focus", onFocus); - window.addEventListener("blur", onBlur); - // Calls onFocus when the window first loads - onFocus(); - - // set user to online until he closes tab/window - userService.updateUserStatus(currUser.uid, UserStatus.online); - - window.addEventListener("beforeunload", alertUserAboutClosing); - window.addEventListener("unload", handleTabClosing); - return () => { - // Specify how to clean up after this effect: - window.removeEventListener("focus", onFocus); - window.removeEventListener("blur", onBlur); - window.removeEventListener("beforeunload", alertUserAboutClosing); - window.removeEventListener("unload", handleTabClosing); - }; - }, []); - - return ( - <> - - -
-
- -
-
- - - -
- -
- - - -
-
- - -
- - ); -} - -export default function TeamDashboardWrapper() { - return ( - - - - - - - - ); -} - -export async function getServerSideProps(context: GetServerSidePropsContext) { - // basic check if this team exists - - // todo check if authenticated - - // todo check if this user is in this team - - return { - props: {}, - }; -} diff --git a/packages/web/pages/teams/[teamid]/admin.tsx b/packages/web/pages/teams/[teamid]/admin.tsx deleted file mode 100644 index 954a951..0000000 --- a/packages/web/pages/teams/[teamid]/admin.tsx +++ /dev/null @@ -1,396 +0,0 @@ -import { Divider } from "antd"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import Loading from "../../../components/Loading"; -import { - TeamDashboardContextProvider, - useTeamDashboardContext, -} from "../../../contexts/teamDashboardContext"; -import { - TeamMember, - TeamMemberRole, - TeamMemberStatus, -} from "../../../models/teamMember"; -import { toast } from "react-hot-toast"; -import { Team, TeamStatus } from "../../../models/team"; - -import { - FaMoneyBillWave, - FaTrash, - FaPaperPlane, - FaArrowLeft, -} from "react-icons/fa"; -import TeamService from "../../../services/teamService"; -import Router from "next/router"; -import Moment from "react-moment"; - -export default function TeamAdminWrapper() { - return ( - - - - ); -} - -const initialSite: string = "https://"; - -function renderTeamMemberStatus(status: TeamMemberStatus) { - switch (status) { - case TeamMemberStatus.invited: - return ( - - invited - - ); - case TeamMemberStatus.activated: - return ( - - active - - ); - case TeamMemberStatus.deleted: - return ( - - deleted - - ); - } -} - -const teamService = new TeamService(); - -function TeamAdmin() { - const router = useRouter(); - const { teamid } = router.query; - const { team, userTeamMember, teamMembers, user } = useTeamDashboardContext(); - - const [error, setError] = useState(""); - const [teamName, setTeamName] = useState(team.name); - const [companySite, setcompanySite] = useState(team.companySite); - const [inviteEmail, setInviteEmail] = useState(""); - const [loading, setLoading] = useState(true); - - useEffect(() => { - (async function () { - // have checks for team and part of team or not in context - try { - // if not admin, I shouldn't be here - if (userTeamMember.role != TeamMemberRole.admin) { - toast.error("You are not allowed here!"); - router.push("/teams"); - } - } catch (error) { - console.log("something went wrong in admin team page"); - router.push("/teams"); - } - - setLoading(false); - })(); - }, []); - - async function handleSubmitTeamUpdate(e) { - e.preventDefault(); - - setLoading(true); - - if (!teamName) { - setError("You must input a team name!"); - return; - } - - try { - const updatedTeam = new Team(); - updatedTeam.id = team.id; - updatedTeam.name = teamName; - updatedTeam.createdByUserId = user.id; - updatedTeam.status = TeamStatus.created; - - if (companySite != initialSite) { - // make sure to put null in database for the team - updatedTeam.companySite = companySite; - } - - // update team name and company site if it changed - await teamService.updateTeam(updatedTeam); - - // take user back to team dashboard - router.push("/teams/" + team.id); - } catch (error) { - setError(error.message); - console.log(error); - } - - setLoading(false); - } - - async function handleSubmitNewTeamMember(e) { - e.preventDefault(); - - setLoading(true); - - if (!inviteEmail) { - setError("You must input a valid email!"); - return; - } - - try { - const newInviteTeamMember = new TeamMember(); - newInviteTeamMember.inviteEmailAddress = inviteEmail; - newInviteTeamMember.invitedByUserId = user.id; - newInviteTeamMember.status = TeamMemberStatus.invited; - newInviteTeamMember.teamId = team.id; - - await teamService.createTeamInvite(newInviteTeamMember); - - Router.reload(); - } catch (error) { - toast.error("something went wrong"); - } - } - - async function handleDeleteTeamMember(e, teamMemberId: string) { - e.preventDefault(); - - setLoading(true); - - try { - await teamService.updateTeamMemberStatus( - teamMemberId, - TeamMemberStatus.deleted - ); - - Router.reload(); - } catch (error) { - toast.error("unable to delete team member"); - } - } - - if (loading) { - return ; - } - - async function handleInviteTeamMember(e, teamMemberId: string) { - e.preventDefault(); - setLoading(true); - - try { - await teamService.updateTeamMemberStatus( - teamMemberId, - TeamMemberStatus.invited - ); - - Router.reload(); - } catch (error) { - toast.error("unable to delete team member"); - } - } - - if (loading) { - return ; - } - - // get count of all active and invited users... - const activeOrInvited = teamMembers.filter( - (tmember) => - tmember.status == TeamMemberStatus.invited || - tmember.status == TeamMemberStatus.activated - ); - const teamSpotsRemaining = - (team.allowedUserCount || 0) - activeOrInvited.length - 1; - - return ( -
-
- {/* header */} -
- router.push("/teams/" + teamid)} - />{" "} - Manage Team -
- - {error && {error}} - - - - - Team Name - - Everyone in your team will see this. - - setTeamName(e.target.value)} - /> - - - - Company Site - optional - setcompanySite(e.target.value)} - /> - - - - - - - - - - -
- - - - - - - Billing - - Please email{" "} - {" "} - to open more spots for your team. - - - - - {teamSpotsRemaining} spots remaining - - - - - - Team - - {/* current user */} -
- asdf - - - {user.firstName + " " + user.lastName} - - - admin - - - {renderTeamMemberStatus(userTeamMember.status)} - - {user.emailAddress} - -
- - {/* mapping all team members */} - {teamMembers.map((tmember, i) => { - return ( -
-
- - - - {tmember.inviteEmailAddress} - - {renderTeamMemberStatus(tmember.status)} - - - {/* todo: date joined */} - {/* */} - - - {tmember.status == TeamMemberStatus.deleted && - teamSpotsRemaining > 0 ? ( - - ) : ( - <> - )} - {tmember.status == TeamMemberStatus.invited || - tmember.status == TeamMemberStatus.activated ? ( - - ) : ( - <> - )} -
- ); - })} - - {/* input for adding people */} - {teamSpotsRemaining > 0 ? ( - <> - - Add Members - - IMPORTANT: Tell them to sign up at{" "} - - usenirvana.com{" "} - {" "} - using this same email address. - - setInviteEmail(e.target.value)} - /> - - - - {inviteEmail ? ( - - ) : ( - - )} - - - ) : ( - <> - )} - -
- ); -} diff --git a/packages/web/pages/teams/create.tsx b/packages/web/pages/teams/create.tsx deleted file mode 100644 index 16908c4..0000000 --- a/packages/web/pages/teams/create.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Divider } from "antd"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import { useAuth } from "../../contexts/authContext"; -import { Team, TeamStatus } from "../../models/team"; -import { User, UserStatus } from "../../models/user"; -import TeamService from "../../services/teamService"; - -const initialSite: string = "https://"; - -const teamService = new TeamService(); - -export default function CreateTeam() { - const { currUser } = useAuth(); - const [error, setError] = useState(""); - const [loading, setLoading] = useState(true); - const router = useRouter(); - - useEffect(() => { - (async function () { - try { - // if not authenticated, take user to the login - if (!currUser) { - console.log( - "not authenticated...routing from dashboard to teams home" - ); - router.push("/teams/login"); - } - - // todo if user is in a team already, notify them to make sure - // buttt most likely will already be routed to the correct place from the router...don't do the router's job - } catch (error) { - console.log(error); - router.push("/teams/login"); - } - - setLoading(false); - })(); - }, []); - - async function handleSubmit(e) { - e.preventDefault(); - - setLoading(true); - - if (!teamName) { - setError("You must input a team name!"); - return; - } - - try { - const team = new Team(); - team.name = teamName; - team.createdByUserId = currUser.uid; - team.status = TeamStatus.created; - team.allowedUserCount = 2; - - if (companySite != initialSite) { - // make sure to put null in database for the team - team.companySite = companySite; - } - - // provision a team with state of initiated - const createdTeamId = await teamService.createTeam(team); - router.push("/teams/" + createdTeamId); - } catch (error) { - setError(error.message); - } - - setLoading(false); - } - - const [teamName, setTeamName] = useState(""); - const [companySite, setcompanySite] = useState(initialSite); - - return ( -
- {/* header */} -
🙌Create a Team
- - {error && {error}} - - - - - Team Name - - Everyone in your team will see this. You can always change this later. - - setTeamName(e.target.value)} - /> - - - - Company Site - optional - setcompanySite(e.target.value)} - /> - - - - - - - {loading ? ( -
- Loading... -
- ) : ( - - )} -
- - ); -} diff --git a/packages/web/pages/teams/index.tsx b/packages/web/pages/teams/index.tsx deleted file mode 100644 index 60b724a..0000000 --- a/packages/web/pages/teams/index.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { useAuth } from "../../contexts/authContext"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import UserService from "../../services/userService"; -import { User } from "../../models/user"; -import { GetServerSidePropsContext } from "next"; -import firebaseAdmin from "firebase-admin"; -import { - FaPeopleCarry, - FaArrowRight, - FaBullhorn, - FaPlus, -} from "react-icons/fa"; -import BackgroundLayout from "../../components/Layouts/BackgroundLayout"; -import TeamService from "../../services/teamService"; -import Loading from "../../components/Loading"; -import { TeamMemberStatus } from "../../models/teamMember"; -import { Divider, Tooltip } from "antd"; -import { Team } from "../../models/team"; -import moment from "moment"; - -/** - * figure out where to take the user based on everything - */ - -const userService: UserService = new UserService(); -const teamService: TeamService = new TeamService(); - -function RouteHandler() { - const { currUser } = useAuth(); - const router = useRouter(); - - const [loading, setLoading] = useState(true); - const [user, setUser] = useState(null); - const [teams, setTeams] = useState([]); - - useEffect(() => { - (async function () { - try { - // if not authenticated, take user to the login - if (!currUser) { - console.log( - "not authenticated...routing from dashboard to teams home" - ); - router.push("/teams/login"); - return; - } - - // get user - const returnedUser: User | null = await userService.getUser( - currUser.uid - ); - - // if user has no profile, then go to create profile - if ( - !returnedUser || - !returnedUser.firstName || - !returnedUser.lastName || - !returnedUser.nickName - ) { - console.log("no profile for the user, routing him/her there"); - router.push("/teams/profile"); - return; - } - setUser(returnedUser); - - // get all teams that this user id is active in - // get all of the teams that this user's email is invited to - const teams: Team[] = await teamService.getActiveOrInvitedTeamsbyUser( - currUser.uid, - returnedUser.emailAddress - ); - - setTeams(teams); - - setLoading(false); - } catch (error) { - console.log(error); - router.push("/teams/login"); - } - })(); - }, []); - - if (loading || !user) { - return ; - } - - // not in a team yet, and have not created a team yet - return ( -
- {/* header */} -
- -
👋Hey, {user.firstName}
- - Let's get you started. - -
- - - - -
- -
- Your Teams - - - -
- - {/* all teams part of */} -
- {teams.map((team) => { - console.log(team); - return ( - - - - - {team.name} - - - - {team.allowedUserCount || 2} - - - - - {team.companySite && ( - window.open(team.companySite, "_blank")} - className="text-xs hover:cursor-pointer font-semibold text-gray-500" - > - {team.companySite} - - )} - - - {"created: " + moment(team.createdDate.toDate()).fromNow()} - - - - - window.open("/teams/" + team.id, "_blank")} - className="ml-auto hover:cursor-pointer transition-all bg-gray-500 bg-opacity-25 p-2 rounded group-hover:bg-opacity-40 group-hover:bg-teal-500" - > - - - - - ); - })} - - {teams.length == 0 ? ( - - ) : ( - "" - )} -
- - - or learn more about{" "} - - -
- ); -} - -export async function getServerSideProps(context: GetServerSidePropsContext) { - // const cookies = nookies.get(context); - - // var user: User | null = null - - // if (cookies.token) { - // try { - // const headers: HeadersInit = { - // 'Content-Type': 'application/json', - // Authorization: JSON.stringify({ token: cookies.token }) - // }; - // const result = await unfetch('/api/auth/validateToken', { headers }); - // console.log(result) - - // // get user data - // const userService: UserService = new UserService() - - // // the user is authenticated! - - // // FETCH STUFF HERE!! 🚀 - - // // user = await userService.getUser("asdf") - // } catch (e) { - // // let exceptions fail silently - // // could be invalid token, just let client-side deal with that - - // console.log(e) - // } - // } - - // // Pass data to the page via props - // return { props: { - // user - // } - // } - - return { props: {} }; -} - -export default RouteHandler; diff --git a/packages/web/pages/teams/login.tsx b/packages/web/pages/teams/login.tsx index 524efd7..4ba4867 100644 --- a/packages/web/pages/teams/login.tsx +++ b/packages/web/pages/teams/login.tsx @@ -2,7 +2,7 @@ import { useRouter } from "next/router"; import { useEffect } from "react"; import { FaArrowLeft } from "react-icons/fa"; import { FcGoogle } from "react-icons/fc"; -import MainLogo from "../../components/MainLogo"; +import MainLogo from "../../components/Logo/MainLogo"; import { useAuth } from "../../contexts/authContext"; export default function Login() { diff --git a/packages/web/services/IService.tsx b/packages/web/services/IService.tsx deleted file mode 100644 index e8d7ef2..0000000 --- a/packages/web/services/IService.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Firestore } from "firebase/firestore"; - -export default interface IService { - db: Firestore; -} \ No newline at end of file diff --git a/packages/web/services/agoraService.ts b/packages/web/services/agoraService.ts deleted file mode 100644 index f0f5a96..0000000 --- a/packages/web/services/agoraService.ts +++ /dev/null @@ -1,94 +0,0 @@ -import AgoraRTC, { - ClientConfig, - IAgoraRTCRemoteUser, - ICameraVideoTrack, - IMicrophoneAudioTrack, -} from "agora-rtc-sdk-ng"; -// import { -// AgoraVideoPlayer, -// createClient, -// createMicrophoneAndCameraTracks, -// createMicrophoneAudioTrack, -// } from "agora-rtc-react"; -import { getFunctions, httpsCallable } from "firebase/functions"; -import toast from "react-hot-toast"; - -const config: ClientConfig = { - mode: "rtc", - codec: "vp8", -}; - -// const useClient = createClient(config); -// const useMicrophoneTracks = createMicrophoneAudioTrack(); - -// TODO: move this to env file -const appId: string = "c8dfd65deb5c4741bd564085627139d0"; //ENTER APP ID HERE - -export default class AgoraService { - private functions = getFunctions(); - // private rtcClient = useClient(); - - async getAgoraToken(channelName: string): Promise { - const cfResult = httpsCallable(this.functions, "agoraToken"); - - return await cfResult({ channelName }) - .then((result: { data: { token: string } }) => { - const data = result.data; - - if (!data.token) { - throw new Error("No token retrieved from agora"); - } - - return data.token; - }) - .catch((error) => { - // Getting the Error details. - const code = error.code; - const message = error.message; - const details = error.details; - throw error; - }); - } - - // async handleJoinChannel(channelName: string, agoraToken: string) { - // const localTrack: IMicrophoneAudioTrack = - // await AgoraRTC.createMicrophoneAudioTrack(); - - // let init = async (name: string) => { - // this.rtcClient.on("user-published", async (user, mediaType) => { - // await this.rtcClient.subscribe(user, mediaType); - // console.log("subscribe success"); - - // if (mediaType === "audio") { - // user.audioTrack?.play(); - // } - // }); - - // this.rtcClient.on("user-unpublished", async (user, type) => { - // console.log("unpublished", user, type); - // if (type === "audio") { - // user.audioTrack?.stop(); - // } - - // await this.rtcClient.unsubscribe(user); - // }); - - // this.rtcClient.on("user-left", (user) => { - // console.log("user left", user); - // }); - - // await this.rtcClient.join(appId, name, agoraToken, null); - // if (localTrack) await this.rtcClient.publish(localTrack); - // }; - - // if (localTrack) { - // console.log("init ready"); - // init(channelName); - // } else { - // toast.error("Not ready for joining call"); - // return; - // } - // } -} - -export { appId }; diff --git a/packages/web/services/announcementService.tsx b/packages/web/services/announcementService.tsx deleted file mode 100644 index f962086..0000000 --- a/packages/web/services/announcementService.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { - addDoc, - collection, - doc, - Firestore, - getFirestore, - serverTimestamp, - setDoc, -} from "firebase/firestore"; -import { AnnouncementState } from "../models/announcement"; -import { Collections } from "./collections"; - -export class AnnouncementService { - private db: Firestore = getFirestore(); - - async updateAnnouncementState( - announcementId: string, - newState: AnnouncementState - ) { - const docRef = doc(this.db, Collections.announcements, announcementId); - await setDoc( - docRef, - { state: newState, lastUpdatedDate: serverTimestamp() }, - { merge: true } - ); - } -} diff --git a/packages/web/services/cloudStorageService.tsx b/packages/web/services/cloudStorageService.tsx deleted file mode 100644 index 1ae8a92..0000000 --- a/packages/web/services/cloudStorageService.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { - getDownloadURL, - getStorage, - ref, - uploadBytesResumable, -} from "firebase/storage"; - -export default class CloudStorageService { - private storage = getStorage(); - - /** - * return the - */ - async uploadMessageAudioFile(audioFile: File): Promise { - // Create the file metadata - /** @type {any} */ - const metadata = { - contentType: "audio/mpeg", - }; - - // Upload file and metadata to the object 'images/mountains.jpg' - const storageRef = ref(this.storage, "messages/" + audioFile.name); - const uploadTask = uploadBytesResumable(storageRef, audioFile, metadata); - - // Listen for state changes, errors, and completion of the upload. - return new Promise((resolve, reject) => { - uploadTask.on( - "state_changed", - (snapshot) => { - // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded - const progress = - (snapshot.bytesTransferred / snapshot.totalBytes) * 100; - console.log("Upload is " + progress + "% done"); - switch (snapshot.state) { - case "paused": - console.log("Upload is paused"); - break; - case "running": - console.log("Upload is running"); - break; - } - }, - (error) => { - // A full list of error codes is available at - // https://firebase.google.com/docs/storage/web/handle-errors - - reject(new Error(error.message)); - - switch (error.code) { - case "storage/unauthorized": - // User doesn't have permission to access the object - break; - case "storage/canceled": - // User canceled the upload - break; - - // ... - - case "storage/unknown": - // Unknown error occurred, inspect error.serverResponse - break; - } - }, - () => { - // Upload completed successfully, now we can get the download URL - return getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { - console.log("File available at", downloadURL); - - return resolve(downloadURL); - }); - } - ); - }); - } -} diff --git a/packages/web/services/collections.tsx b/packages/web/services/collections.tsx deleted file mode 100644 index ff4015a..0000000 --- a/packages/web/services/collections.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export enum Collections { - users = "users", - teams = "teams", - teamMembers = "teamMembers", - teamSubscriptions = "teamSubscriptions", - audioMessages = "audioMessages", - - rooms = "rooms", - announcements = "announcements", - links = "links", - - officeRooms = "officeRooms", -} diff --git a/packages/web/services/firebaseAdminService.js b/packages/web/services/firebaseAdminService.js deleted file mode 100644 index 5020e4e..0000000 --- a/packages/web/services/firebaseAdminService.js +++ /dev/null @@ -1,15 +0,0 @@ -import firebaseAdmin from "firebase-admin/app"; - -import serviceAccount from "../nirvana-for-business-firebase-adminsdk"; - -const adminApp = firebaseAdmin.initializeApp({ - credential: credential.cert({ - privateKey: serviceAccount.private_key, - clientEmail: serviceAccount.client_email, - projectId: serviceAccount.project_id, - }), -}); - -console.log("initialized firebase admin"); - -export { adminApp }; diff --git a/packages/web/services/linkService.tsx b/packages/web/services/linkService.tsx deleted file mode 100644 index c79ae34..0000000 --- a/packages/web/services/linkService.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { - addDoc, - collection, - doc, - Firestore, - getFirestore, - serverTimestamp, - setDoc, -} from "firebase/firestore"; -import Link, { LinkState } from "../models/link"; -import { Collections } from "./collections"; - -export class LinkService { - private db: Firestore = getFirestore(); - - async updateLinkState(linkId: string, newState: LinkState) { - const docRef = doc(this.db, Collections.links, linkId); - await setDoc( - docRef, - { state: newState, lastUpdatedDate: serverTimestamp() }, - { merge: true } - ); - } -} diff --git a/packages/web/services/officeRoomService.tsx b/packages/web/services/officeRoomService.tsx deleted file mode 100644 index 1f8d658..0000000 --- a/packages/web/services/officeRoomService.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { - addDoc, - collection, - doc, - FieldValue, - Firestore, - getFirestore, - serverTimestamp, - setDoc, - writeBatch, -} from "firebase/firestore"; -import OfficeRoom, { OfficeRoomState } from "../models/officeRoom"; -// import AgoraService from "./agoraService"; -import { Collections } from "./collections"; - -export default class OfficeRoomService { - private db: Firestore = getFirestore(); - private batch = writeBatch(this.db); - - // private agoraService = new AgoraService(); - - async createInitialOfficeRooms(createdByUserId: string, teamId: string) { - const entrance = new OfficeRoom("Entrance", teamId, createdByUserId); - const kitchen = new OfficeRoom("Kitchen", teamId, createdByUserId); - const hallway = new OfficeRoom("Hallway", teamId, createdByUserId); - const corner = new OfficeRoom("Corner", teamId, createdByUserId); - const main = new OfficeRoom("Team Hub", teamId, createdByUserId); - const handsOnDeck = new OfficeRoom( - "All Hands On Deck", - teamId, - createdByUserId - ); - - const initialORs: OfficeRoom[] = [ - entrance, - kitchen, - hallway, - corner, - main, - handsOnDeck, - ]; - - initialORs.forEach((oR) => { - const oRRef = doc(this.db, Collections.officeRooms, oR.id); - this.batch.set(oRRef, { ...oR, createdDate: serverTimestamp() }); - }); - - await this.batch.commit(); - } - - async joinOfficeRoom(officeRoom: OfficeRoom, userId: string) { - // get agoraToken - // const agoraToken = await this.agoraService.getAgoraToken(); - - // join channel - // await this.agoraService.handleJoinChannel(officeRoom.id, agoraToken); - - // update members list in firestore - const newMembers = [...officeRoom.members, userId]; - await this.updateMembersInOfficeRoom(officeRoom.id, newMembers); - } - - async updateMembersInOfficeRoom( - officeRoomId: string, - newMembersInRoom: string[] - ) { - // if the room is going to be empty, then change status accordingly - var state: OfficeRoomState = OfficeRoomState.active; - if (newMembersInRoom.length == 0) { - state = OfficeRoomState.idle; - } - - const docRef = doc(this.db, Collections.officeRooms, officeRoomId); - await setDoc( - docRef, - { - state, - members: newMembersInRoom, - lastUpdatedDate: serverTimestamp(), - }, - { merge: true } - ); - } - - // async updateRoom(room: Room) { - // const docRef = doc(this.db, Collections.rooms, room.id); - // await setDoc( - // docRef, - // { ...room, lastUpdatedDate: serverTimestamp() }, - // { merge: true } - // ); - // } -} diff --git a/packages/web/services/roomService.tsx b/packages/web/services/roomService.tsx deleted file mode 100644 index f5e6888..0000000 --- a/packages/web/services/roomService.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { - addDoc, - collection, - doc, - Firestore, - getFirestore, - serverTimestamp, - setDoc, -} from "firebase/firestore"; -import Room, { RoomStatus } from "../models/room"; -import { Collections } from "./collections"; - -export default class RoomService { - private db: Firestore = getFirestore(); - - async createOrUpdateRoom(room: Room) { - if (room.id) { - //update - await this.updateRoom(room); - } else { - //create - const roomDocRef = await addDoc(collection(this.db, Collections.rooms), { - ...room, - createdDate: serverTimestamp(), - }); - } - } - - async updateMembersInRoom(roomId: string, newMembersInRoom: string[]) { - // if the room is going to be empty, then change status accordingly - var status: RoomStatus = RoomStatus.live; - if (newMembersInRoom.length == 0) { - status = RoomStatus.empty; - } - - const docRef = doc(this.db, Collections.rooms, roomId); - await setDoc( - docRef, - { - status, - membersInRoom: newMembersInRoom, - lastUpdatedDate: serverTimestamp(), - }, - { merge: true } - ); - } - - async updateRoom(room: Room) { - const docRef = doc(this.db, Collections.rooms, room.id); - await setDoc( - docRef, - { ...room, lastUpdatedDate: serverTimestamp() }, - { merge: true } - ); - } -} diff --git a/packages/web/services/sendService.tsx b/packages/web/services/sendService.tsx deleted file mode 100644 index 8298e75..0000000 --- a/packages/web/services/sendService.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { - addDoc, - collection, - Firestore, - getFirestore, - serverTimestamp, -} from "firebase/firestore"; -import Announcement from "../models/announcement"; -import Link from "../models/link"; -import { Message } from "../models/message"; -import { Collections } from "./collections"; - -export class SendService { - private db: Firestore = getFirestore(); - - async sendMessage(message: Message) { - // do quick stuff to create the composite for easier future querying - message.senderReceiver = [message.senderUserId, message.receiverUserId]; - - const teamDocRef = await addDoc( - collection(this.db, Collections.audioMessages), - { - ...message, - createdDate: serverTimestamp(), - } - ); - } - - async sendLink(link: Link) { - await addDoc(collection(this.db, Collections.links), { - ...link, - createdDate: serverTimestamp(), - }); - } - - async sendAnnouncement(announcement: Announcement) { - await addDoc(collection(this.db, Collections.announcements), { - ...announcement, - createdDate: serverTimestamp(), - }); - } -} diff --git a/packages/web/services/teamService.tsx b/packages/web/services/teamService.tsx deleted file mode 100644 index 2c90cfd..0000000 --- a/packages/web/services/teamService.tsx +++ /dev/null @@ -1,297 +0,0 @@ -import { - addDoc, - collection, - doc, - DocumentReference, - Firestore, - getDoc, - getDocs, - getFirestore, - orderBy, - query, - serverTimestamp, - setDoc, - where, -} from "firebase/firestore"; -import { Team } from "../models/team"; -import { - TeamMember, - TeamMemberRole, - TeamMemberStatus, -} from "../models/teamMember"; -import { Collections } from "./collections"; -import IService from "./IService"; - -export default class TeamService implements IService { - db: Firestore = getFirestore(); - - async createTeam(team: Team): Promise { - // create team - const teamDocRef = await addDoc(collection(this.db, Collections.teams), { - ...team, - createdDate: serverTimestamp(), - }); - - const teamMember = new TeamMember(); - teamMember.role = TeamMemberRole.admin; - teamMember.teamId = teamDocRef.id; - teamMember.userId = team.createdByUserId; - teamMember.status = TeamMemberStatus.activated; - - // create team member as admin who created the team - const teamMemberRef = await addDoc( - collection(this.db, Collections.teamMembers), - { - ...teamMember, - createdDate: serverTimestamp(), - } - ); - - console.log("created team"); - - return teamDocRef.id; - } - - async updateTeam(team: Team) { - const docRef = doc(this.db, Collections.teams, team.id); - await setDoc( - docRef, - { ...team, lastUpdatedDate: serverTimestamp() }, - { merge: true } - ); - } - - async getTeam(teamId: string): Promise { - const docRef = doc(this.db, Collections.teams, teamId); - const docSnap = await getDoc(docRef); - - if (docSnap.exists()) { - console.log("got team data"); - let team: Team = docSnap.data() as Team; - team.id = docSnap.id; - return team; - } else { - // doc.data() will be undefined in this case - console.log("team not found!"); - - return null; - } - } - - async getTeamMemberByUserId( - teamId: string, - userId: string - ): Promise { - const q = query( - collection(this.db, Collections.teamMembers), - where("teamId", "==", teamId), - where("userId", "==", userId) - ); - - const querySnapshot = await getDocs(q); - - if (querySnapshot.size > 1) { - console.log( - "there are multiple teammembers for this user...error in teamservice" - ); - } - - var teamMember: TeamMember | null = null; - querySnapshot.forEach((doc) => { - // doc.data() is never undefined for query doc snapshots - // get the first one and just return...shouldn't be more - console.log("got teammember data"); - teamMember = doc.data() as TeamMember; - - teamMember.id = doc.id; - }); - - return teamMember; - } - - async getTeamMemberByEmailInvite( - teamId: string, - emailAddress: string - ): Promise { - const q = query( - collection(this.db, Collections.teamMembers), - where("teamId", "==", teamId), - where("inviteEmailAddress", "==", emailAddress) - ); - - const querySnapshot = await getDocs(q); - - if (querySnapshot.size > 1) { - console.log( - "there are multiple teammembers for this user...error in teamservice" - ); - } - - var teamMember: TeamMember | null = null; - querySnapshot.forEach((doc) => { - // doc.data() is never undefined for query doc snapshots - // get the first one and just return...shouldn't be more - console.log("got teammember data"); - teamMember = doc.data() as TeamMember; - - teamMember.id = doc.id; - }); - - return teamMember; - } - - async getTeamMembersByEmailInvite( - emailAddress: string - ): Promise { - const q = query( - collection(this.db, Collections.teamMembers), - where("inviteEmailAddress", "==", emailAddress) - ); - - const querySnapshot = await getDocs(q); - - var teamMembers: TeamMember[] = []; - querySnapshot.forEach((doc) => { - // doc.data() is never undefined for query doc snapshots - // get the first one and just return...shouldn't be more - console.log("got teammember data"); - let teamMember: TeamMember = doc.data() as TeamMember; - - teamMember.id = doc.id; - - teamMembers.push(teamMember); - }); - - return teamMembers; - } - - async getTeamMembersByUserId(userId: string): Promise { - const q = query( - collection(this.db, Collections.teamMembers), - where("userId", "==", userId) - ); - - const querySnapshot = await getDocs(q); - - if (querySnapshot.size > 1) { - console.log("this user is part of multiple teams"); - } - - var teamMembers: TeamMember[] = []; - - querySnapshot.forEach((doc) => { - // doc.data() is never undefined for query doc snapshots - // get the first one and just return...shouldn't be more - console.log("got teammember data"); - let teamMember: TeamMember = doc.data() as TeamMember; - teamMember.id = doc.id; - - teamMembers.push(teamMember); - }); - - return teamMembers; - } - - async getTeamMembersByTeamId(teamId: string): Promise { - const q = query( - collection(this.db, Collections.teamMembers), - where("teamId", "==", teamId) - ); - - const querySnapshot = await getDocs(q); - - var teamMembers: TeamMember[] = []; - - querySnapshot.forEach((doc) => { - // doc.data() is never undefined for query doc snapshots - // get the first one and just return...shouldn't be more - let teamMember: TeamMember = doc.data() as TeamMember; - teamMember.id = doc.id; - - teamMembers.push(teamMember); - }); - - return teamMembers; - } - - async updateTeamMember(teamMember: TeamMember) { - const docRef = doc(this.db, Collections.teamMembers, teamMember.id); - await setDoc( - docRef, - { ...teamMember, lastUpdatedDate: serverTimestamp() }, - { merge: true } - ); - } - - async createTeamInvite(teamMember: TeamMember) { - if (teamMember.status == TeamMemberStatus.activated) { - console.log('cannot invite this user, already active.') - return - } - - // if a person was already invited, then update that record, otherwise create a new one - const existingTeamMember = await this.getTeamMemberByEmailInvite( - teamMember.teamId, - teamMember.inviteEmailAddress - ); - - if (!existingTeamMember) { - const teamMemberRef = await addDoc( - collection(this.db, Collections.teamMembers), - { - ...teamMember, - createdDate: serverTimestamp(), - } - ); - - return; - } - - // just update the current user to active since we are activating again - await this.updateTeamMemberStatus( - existingTeamMember.id, - TeamMemberStatus.invited - ); - } - - async updateTeamMemberStatus(teamMemberId: string, status: TeamMemberStatus) { - const docRef = doc(this.db, Collections.teamMembers, teamMemberId); - await setDoc( - docRef, - { status, lastUpdatedDate: serverTimestamp() }, - { merge: true } - ); - } - - async getActiveOrInvitedTeamsbyUser( - userId: string, - email: string - ): Promise { - // get all of the teammember entries for the user - const userTeamMembers = await this.getTeamMembersByUserId(userId); - const userTeamMembersByEmail = await this.getTeamMembersByEmailInvite( - email - ); - - // traverse through and get the teams for each - var teams: Promise[]; - if (userTeamMembers) { - teams = userTeamMembers.map(async (tm) => { - if (tm.status != TeamMemberStatus.deleted) { - let team = await this.getTeam(tm.teamId); - return team; - } - }); - } - - var invitedToTeams: Promise[] = []; - invitedToTeams = userTeamMembersByEmail - .filter((tm) => tm.status == TeamMemberStatus.invited) - .map(async (tm) => { - let team = await this.getTeam(tm.teamId); - return team; - }); - - return Promise.all([...teams, ...invitedToTeams]); - } -}