From 6a6f0f4aef1245e47f4b86becc2a25d301da453f Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Thu, 3 Feb 2022 17:14:22 -0800 Subject: [PATCH] adding data listening, user getching and using more of recoil --- packages/common/models/conversation.ts | 20 +- .../common/services/conversationService.ts | 4 +- packages/web/.eslintrc.json | 3 +- .../web/components/Conversations/LiveRoom.tsx | 37 ++ .../CreateConversation.tsx.tsx | 18 +- .../components/Layouts/LandingPageLayout.tsx | 3 +- .../MainTabsOrPages/Conversations.tsx | 32 +- .../web/components/MainTabsOrPages/Header.tsx | 3 +- packages/web/contexts/authContext.js | 105 ------ packages/web/contexts/authContext.tsx | 65 ++++ .../web/contexts/teamDashboardContext.tsx | 333 ------------------ packages/web/pages/s/index.tsx | 11 +- packages/web/pages/teams/dashboard.tsx | 7 +- packages/web/pages/teams/login.tsx | 37 +- packages/web/pages/teams/profile.tsx | 6 +- packages/web/recoil/MainRecoilDataHandler.tsx | 99 ++++-- packages/web/recoil/main.tsx | 23 +- packages/web/services/firebaseService.js | 2 + packages/web/tsconfig.json | 3 +- workspace.code-workspace | 8 + 20 files changed, 302 insertions(+), 517 deletions(-) create mode 100644 packages/web/components/Conversations/LiveRoom.tsx delete mode 100644 packages/web/contexts/authContext.js create mode 100644 packages/web/contexts/authContext.tsx delete mode 100644 packages/web/contexts/teamDashboardContext.tsx create mode 100644 workspace.code-workspace diff --git a/packages/common/models/conversation.ts b/packages/common/models/conversation.ts index 563cf35..890f596 100644 --- a/packages/common/models/conversation.ts +++ b/packages/common/models/conversation.ts @@ -6,16 +6,26 @@ export default class Conversation { name: string; // engineering, general, arjun, jacob and rachel... + lastActivityDate?: Timestamp; // caching this for purpose of saving on listeners + + activeMembers: string[]; // userIds + membersInLiveRoom: string[] = [] as string[]; // all members in a live call right now for this convo + + cachedAudioClips: AudioClip[] = [] as AudioClip[]; + cachedDrawerItems: Link[] = [] as Link[]; + tldrClips: AudioClip[] = [] as AudioClip[]; + createdDate: Timestamp = Timestamp.now(); createdByUserId: string; - lastActivityDate?: Timestamp; // caching this for purpose of saving on listeners - - membersInLiveRoom: string[] = [] as string[]; // all members in a live call right now for this convo - - constructor(_createdByUserId: string, _name: string) { + constructor( + _createdByUserId: string, + _name: string, + _initialUsers: string[] = [] as string[] + ) { this.name = _name; this.createdByUserId = _createdByUserId; + this.activeMembers = _initialUsers; } } diff --git a/packages/common/services/conversationService.ts b/packages/common/services/conversationService.ts index 5be04ad..0a87b6e 100644 --- a/packages/common/services/conversationService.ts +++ b/packages/common/services/conversationService.ts @@ -15,13 +15,15 @@ import Collections from "./collections"; export default class ConversationService { private db: Firestore = getFirestore(); - async test() { + async test(): Promise { const docRef = doc(this.db, "test", "test"); await setDoc( docRef, { hi: " hiii", lastUpdatedDate: serverTimestamp() }, { merge: true } ); + + return false; } async createConversation( diff --git a/packages/web/.eslintrc.json b/packages/web/.eslintrc.json index b9ab2c9..cf83a2c 100644 --- a/packages/web/.eslintrc.json +++ b/packages/web/.eslintrc.json @@ -8,7 +8,8 @@ ], "rules": { // I suggest you add those two rules: - "@typescript-eslint/no-unused-vars": "warn", + "no-unused-vars": "off", + "@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/Conversations/LiveRoom.tsx b/packages/web/components/Conversations/LiveRoom.tsx new file mode 100644 index 0000000..94bee12 --- /dev/null +++ b/packages/web/components/Conversations/LiveRoom.tsx @@ -0,0 +1,37 @@ +import Conversation from "@nirvana/common/models/conversation"; +import { Avatar } from "antd"; +import { FaWalking } from "react-icons/fa"; + +export default function LiveRoom(props: { conversation: Conversation }) { + return ( + + + + + + + + + + Engineering + + + {"01:20"} + + + + + + + ); +} diff --git a/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx b/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx index cb3aa8c..c4376c8 100644 --- a/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx +++ b/packages/web/components/FullPageExperiences/CreateConversation.tsx.tsx @@ -12,10 +12,11 @@ import Conversation, { ConversationMemberRole, ConversationMemberState, } from "@nirvana/common/models/conversation"; -import { useAuth } from "../../contexts/authContext"; import { conversationService } from "@nirvana/common/services"; import { QueryRoutes, Routes } from "@nirvana/common/helpers/routes"; import { useRouter } from "next/router"; +import { useRecoilState } from "recoil"; +import { useAuth } from "../../contexts/authContext"; const searchDebounceMsTime = 3000; @@ -91,7 +92,7 @@ export default function CreateConversation() { }, []); const selectUser = (addUser: User) => { - if (addUser.id == currUser.uid) { + if (addUser.id == currUser!.uid) { toast.error("You cannot add yourself, silly!"); return; } @@ -137,25 +138,34 @@ export default function CreateConversation() { } try { - const newConversation = new Conversation(currUser.uid, conversationName); + const arrActiveMembers: string[] = [] as string[]; // create each conversation member based on the selected people const members = selectedUsers.map((selUser) => { + arrActiveMembers.push(selUser.id); + return new ConversationMember( selUser.id, ConversationMemberState.default, ConversationMemberRole.member ); }); + // add myself to this collection of members members.push( new ConversationMember( - currUser.uid, + currUser!.uid, ConversationMemberState.default, ConversationMemberRole.admin ) ); + const newConversation = new Conversation( + currUser!.uid, + conversationName, + arrActiveMembers + ); + console.log(members); console.log(newConversation); diff --git a/packages/web/components/Layouts/LandingPageLayout.tsx b/packages/web/components/Layouts/LandingPageLayout.tsx index 3735298..ba53326 100644 --- a/packages/web/components/Layouts/LandingPageLayout.tsx +++ b/packages/web/components/Layouts/LandingPageLayout.tsx @@ -3,8 +3,9 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { useState } from "react"; import { FaAngleRight, FaGripHorizontal } from "react-icons/fa"; -import { useAuth } from "../../contexts/authContext"; +import { useRecoilState } from "recoil"; import MainLogo from "../Logo/MainLogo"; +import { useAuth } from "../../contexts/authContext"; enum LandingPageNavigation { product = "/", diff --git a/packages/web/components/MainTabsOrPages/Conversations.tsx b/packages/web/components/MainTabsOrPages/Conversations.tsx index b4273e8..00a228a 100644 --- a/packages/web/components/MainTabsOrPages/Conversations.tsx +++ b/packages/web/components/MainTabsOrPages/Conversations.tsx @@ -14,6 +14,8 @@ import { FaAtlassian, } from "react-icons/fa"; import { Routes } from "@nirvana/common/helpers/routes"; +import LiveRoom from "../Conversations/LiveRoom"; +import Conversation from "@nirvana/common/models/conversation"; export default function Conversations() { const router = useRouter(); @@ -37,35 +39,7 @@ export default function Conversations() { {/* row of live room cards */} {/* engineering liveroom */} - - - - - - - - - - Engineering - - - {"01:20"} - - - - - - + {/* */} {/* Today */} diff --git a/packages/web/components/MainTabsOrPages/Header.tsx b/packages/web/components/MainTabsOrPages/Header.tsx index 7d07cb8..392916d 100644 --- a/packages/web/components/MainTabsOrPages/Header.tsx +++ b/packages/web/components/MainTabsOrPages/Header.tsx @@ -10,10 +10,11 @@ import { FaHeadphones, FaMicrophoneAlt, } from "react-icons/fa"; -import { useAuth } from "../../contexts/authContext"; +import { useRecoilState } from "recoil"; import { UserStatus } from "../../models/user"; import MainLogo from "../Logo/MainLogo"; import UserStatusBubble from "../UserDetails/UserStatusBubble"; +import { useAuth } from "../../contexts/authContext"; export default function Header() { const { currUser } = useAuth(); diff --git a/packages/web/contexts/authContext.js b/packages/web/contexts/authContext.js deleted file mode 100644 index 468ea03..0000000 --- a/packages/web/contexts/authContext.js +++ /dev/null @@ -1,105 +0,0 @@ -import { - getAuth, - GoogleAuthProvider, - onAuthStateChanged, - signInWithPopup, - signOut, - User, -} from "firebase/auth"; -import { useRouter } from "next/router"; -import React, { useContext, useEffect, useState } from "react"; -import toast from "react-hot-toast"; -import Loading from "../components/Loading"; -import firebase from "../services/firebaseService"; -import cookie from "js-cookie"; - -const AuthContext = React.createContext(null); -const googleProvider = new GoogleAuthProvider(); - -const auth = getAuth(); - -export function AuthProvider({ children }) { - const [currUser, setCurrUser] = useState(); - const [loading, setLoading] = useState(true); - - const router = useRouter(); - - useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, async (user) => { - setLoading(true); - - console.log("change in auth"); - if (user) { - setCurrUser(user); - - // set the token in the cookies for ssr verification - const token = await user.getIdToken(); - // nookies.set(undefined, "token", token, { path: "/" }); - cookie.set("auth", token); - } else { - // nookies.set(undefined, "token", "", { path: "/" }); - cookie.remove("auth"); - - setCurrUser(null); - } - - setLoading(false); - }); - - return unsubscribe; - }, []); - - const signInGoogle = () => { - return signInWithPopup(auth, googleProvider) - .then((res) => { - // This gives you a Google Access Token. You can use it to access the Google API. - const credential = GoogleAuthProvider.credentialFromResult(res); - - const token = credential.accessToken; - // The signed-in user info. - const user = res.user; - - setCurrUser(user); - - console.log(user); - }) - .catch((error) => { - // Handle Errors here. - const errorCode = error.code; - const errorMessage = error.message; - // The email of the user's account used. - const email = error.email; - // The AuthCredential type that was used. - const credential = GoogleAuthProvider.credentialFromError(error); - console.log(error); - toast.error("something went wrong"); - }); - }; - - const logOut = async () => { - setLoading(true); - - console.log("logging user out"); - - Promise.resolve(await signOut(auth)); - - router.push("/"); - }; - - const value = { - currUser, - signInGoogle, - logOut, - }; - - return ( - - {!loading && children} - - ); -} - -// custom hook to use the authUserContext and access currUser -export function useAuth() { - return useContext(AuthContext); -} diff --git a/packages/web/contexts/authContext.tsx b/packages/web/contexts/authContext.tsx new file mode 100644 index 0000000..7f64491 --- /dev/null +++ b/packages/web/contexts/authContext.tsx @@ -0,0 +1,65 @@ +import { + getAuth, + GoogleAuthProvider, + onAuthStateChanged, + User as FirebaseUser, +} from "firebase/auth"; +import { useRouter } from "next/router"; +import React, { useContext, useEffect, useState } from "react"; +import firebaseApp from "../services/firebaseService"; +import cookie from "js-cookie"; +import { useSetRecoilState } from "recoil"; + +interface ContextInterface { + currUser: FirebaseUser | null; +} + +const AuthContext = React.createContext( + {} as ContextInterface +); +const auth = getAuth(firebaseApp); + +export function AuthProvider({ children }) { + const [currUser, setCurrUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, async (user: FirebaseUser) => { + setLoading(true); + + console.log("change in auth"); + + if (user) { + setCurrUser(user); + + // set the token in the cookies for ssr verification + const token = await user.getIdToken(); + // nookies.set(undefined, "token", token, { path: "/" }); + cookie.set("auth", token); + } else { + // nookies.set(undefined, "token", "", { path: "/" }); + cookie.remove("auth"); + + setCurrUser(null); + } + + setLoading(false); + }); + + return unsubscribe; + }, []); + + const value: ContextInterface = { + currUser, + }; + + return ( + + {!loading && children} + + ); +} + +export function useAuth() { + return useContext(AuthContext); +} diff --git a/packages/web/contexts/teamDashboardContext.tsx b/packages/web/contexts/teamDashboardContext.tsx deleted file mode 100644 index 5e9a592..0000000 --- a/packages/web/contexts/teamDashboardContext.tsx +++ /dev/null @@ -1,333 +0,0 @@ -import { - collection, - doc, - getFirestore, - onSnapshot, - orderBy, - query, - Unsubscribe, - where, -} from "firebase/firestore"; -import { useRouter } from "next/router"; -import React, { useContext, useEffect, useState } from "react"; -import toast from "react-hot-toast"; -import Loading from "../components/Loading"; -import { compareStatus } from "../helpers/userHelper"; -import { Message } from "../models/message"; -import Room from "../models/room"; -import { Team } from "../models/team"; -import { TeamMember, TeamMemberStatus } from "../models/teamMember"; -import { User } from "../models/user"; -import { Collections } from "../services/collections"; -import TeamService from "../services/teamService"; -import UserService from "../services/userService"; -import { useAuth } from "./authContext"; - -interface TeamDashboardContextInterface { - team: Team; - teamMembers: TeamMember[]; - userTeamMember: TeamMember; - - user: User; // REALTIME - 1 - - messagesByTeamMate: Map; // REALTIME: string of teammate userid and array of messages - 1 - allMessages: Message[]; // REALTIME: 1 - - teamUsers: User[]; // REALTIME teammates - n team members => n listeners - teamUsersMap: {}; // map for easier getting teammate data -} - -const TeamDashboardContext = - React.createContext(null); - -// date of yesterday to check if messages are after yesterday -const yesterday = new Date(); -yesterday.setDate(yesterday.getDate() - 1); - -const teamService = new TeamService(); -const userService = new UserService(); -const db = getFirestore(); - -export function TeamDashboardContextProvider({ children }) { - const { currUser } = useAuth(); - - const router = useRouter(); - - const [loading, setLoading] = useState(true); - - const { teamid } = router.query; - - // this context can only be used if it's a specific route: '/teams' - if (!teamid) { - router.push("/teams"); - } - - // update this "global" object to send to children - const [value, setValue] = useState( - {} as TeamDashboardContextInterface - ); - - // all main data - useEffect(() => { - var userListener: Unsubscribe; - var unsubs: Unsubscribe[] = [] as Unsubscribe[]; - - (async function () { - try { - // SECTION: authentication - if (!currUser) { - console.log( - "not authenticated...routing from dashboard to teams home" - ); - router.push("/teams/login"); - return; - } - - // SECTION: get user details : realtime - const returnedUser = await userService.getUser(currUser.uid); - - const docRef = doc(db, Collections.users, currUser.uid); - - userListener = onSnapshot(docRef, (doc) => { - const updatedUser = doc.data() as User; - - setValue((prevValue) => ({ ...prevValue, user: updatedUser })); - }); - - setValue((prevValue) => ({ ...prevValue, user: returnedUser })); - - // SECTION: getting team data and team member information - if (typeof teamid !== "string") { - console.log("not a proper query with teamid"); - router.push("/teams"); - return; - } - - // check if the team is a valid team and that user is in it - const returnedTeam = await teamService.getTeam(teamid); - if (!returnedTeam) { - console.log("team doesnt exist"); - router.push("/teams"); - return; - } - setValue((prevValue) => ({ ...prevValue, team: returnedTeam })); - - const returnedTeamMember = await teamService.getTeamMemberByUserId( - teamid, - currUser.uid - ); - - // if you are not a member of this team, then get out of here - // but have to check through email invite and userid - if ( - returnedTeamMember && - returnedTeamMember.status == TeamMemberStatus.deleted - ) { - console.log("user was deleted from team"); - router.push("/teams"); - return; - } - - // if there is no team member through user id, - // give him last chance and see if he was invited - if (!returnedTeamMember) { - const invitedTeamMember = - await teamService.getTeamMemberByEmailInvite( - teamid, - currUser.email - ); - - if (!invitedTeamMember) { - console.log("not invited to team either"); - router.push("/teams"); - return; - } - - // if I am new to the team but I was invited, and this is my first time, then activate me into the team - // and proceed with showing the dashboard stuff - - invitedTeamMember.status = TeamMemberStatus.activated; - invitedTeamMember.userId = currUser.uid; - await teamService.updateTeamMember(invitedTeamMember); - - setValue((prevValue) => ({ - ...prevValue, - userTeamMember: invitedTeamMember, - })); - } else { - setValue((prevValue) => ({ - ...prevValue, - userTeamMember: returnedTeamMember, - })); - } - - // SECTION: get all teammembers for team, will have listeners in other subcomponents - var teamMembers: TeamMember[] = - await teamService.getTeamMembersByTeamId(returnedTeam.id); - - teamMembers = teamMembers.filter( - (element) => element.userId != currUser.uid - ); - - // listeners for all teammates' status - if (teamMembers) { - teamMembers.map((tmember) => { - if (tmember.status == TeamMemberStatus.activated) { - const docRef = doc(db, Collections.users, tmember.userId); - - const unsub = onSnapshot(docRef, (doc) => { - const updatedteamMateUser = doc.data() as User; - - // update map of team member users - setTeamUsersMap((prevMap) => ({ - ...prevMap, - [updatedteamMateUser.id]: updatedteamMateUser, - })); - - // update array of team member users - setTeamUsers((prevTeamUsers) => { - const newTeamUsers = prevTeamUsers.filter( - (tm) => tm.id != updatedteamMateUser.id - ); - newTeamUsers.push(updatedteamMateUser); - - // order users by status - setTeamUsers(newTeamUsers.sort(compareStatus)); - - return newTeamUsers; - }); - }); - - unsubs.push(unsub); - } - - return; - }); - } - - setValue((prevValue) => ({ ...prevValue, teamMembers })); - } catch (error) { - console.log(error); - router.push("/"); - } - - setLoading(false); - })(); - - return () => { - if (userListener) { - userListener(); - } - - unsubs.forEach((unsub) => { - unsub(); - }); - }; - }, []); - - const [teamUsers, setTeamUsers] = useState([]); - const [teamUsersMap, setTeamUsersMap] = useState<{}>({}); - - const [allMessages, setAllMessages] = useState([]); - const [messagesByTeamMate, setMessagesByTeamMate] = useState< - Map - >(new Map()); - - // SECTION: REALTIME listener for all incoming messages - useEffect(() => { - // todo for new messages, change document.title - - if (!currUser) { - console.log("not authenticated...routing from dashboard to teams home"); - router.push("/teams/login"); - return; - } - - /** - * QUERY: - * - last 24 hours only - * - any message that I have sent or received: relevant messages - * - order by: date desc - */ - const q = query( - collection(db, Collections.audioMessages), - where("senderReceiver", "array-contains", currUser.uid), - where("createdDate", ">", yesterday), - orderBy("createdDate", "asc") - ); - - // return unsubscribe - return onSnapshot(q, (snapshot) => { - snapshot.docChanges().forEach((change) => { - // no need to process results again, just append to arrays instead - // messages won't be deleted or updated really - if (change.type === "added") { - let newMessage = change.doc.data() as Message; - - // update all messages array - setAllMessages((prevMessages) => [newMessage, ...prevMessages]); - - // update map of teammate to relevant messages - setMessagesByTeamMate((prevMap) => { - // if the map contains the teammate userid already, then cool, just unshift to that array - var newMap: Map = new Map( - prevMap.set("dummy", [] as Message[]) - ); - if (prevMap.has(newMessage.receiverUserId)) { - newMap.set(newMessage.receiverUserId, [ - newMessage, - ...prevMap.get(newMessage.receiverUserId), - ]); - } // if this is the first relevant message linked to this receiver, - //then create a new array - else { - newMap.set(newMessage.receiverUserId, [newMessage]); - } - - // todo: if I am the receiver, still want to put it in the right conversation - if (newMessage.receiverUserId == currUser.uid) { - if (prevMap.has(newMessage.senderUserId)) { - newMap.set(newMessage.senderUserId, [ - newMessage, - ...prevMap.get(newMessage.senderUserId), - ]); - } else { - newMap.set(newMessage.senderUserId, [newMessage]); - } - } - - return newMap; - }); - } - if (change.type === "modified") { - console.log("Modified message: ", change.doc.data()); - } - if (change.type === "removed") { - console.log("Removed message: ", change.doc.data()); - } - }); - }); - }, []); - - if (loading) { - return ; - } - - const transformedValue = { - ...value, - allMessages, - messagesByTeamMate, - teamUsers, - teamUsersMap, - }; - - return ( - - {children} - - ); -} - -export function useTeamDashboardContext() { - return useContext(TeamDashboardContext); -} diff --git a/packages/web/pages/s/index.tsx b/packages/web/pages/s/index.tsx index 664ba8b..47fa407 100644 --- a/packages/web/pages/s/index.tsx +++ b/packages/web/pages/s/index.tsx @@ -1,5 +1,5 @@ import { useRouter } from "next/router"; -import React, { ReactElement } from "react"; +import React, { ReactElement, useEffect } from "react"; import Conversations from "../../components/MainTabsOrPages/Conversations"; import Done from "../../components/MainTabsOrPages/Done"; import Drawer from "../../components/MainTabsOrPages/Drawer"; @@ -14,9 +14,18 @@ import SearchResults from "../../components/FullPageExperiences/SearchResults"; import { QueryRoutes, Routes } from "@nirvana/common/helpers/routes"; import ViewConvo from "../../components/MainTabsOrPages/ViewConvo"; import MainRecoilDataHandler from "../../recoil/MainRecoilDataHandler"; +import { useAuth } from "../../contexts/authContext"; export default function Me() { // TODO: if not authenticated, take user away + const { currUser } = useAuth(); + useEffect(() => { + if (!currUser) { + console.log("not authenticated...routing from dashboard to teams home"); + router.push("/teams/login"); + return; + } + }, []); // figure out what content to render from here const router = useRouter(); diff --git a/packages/web/pages/teams/dashboard.tsx b/packages/web/pages/teams/dashboard.tsx index b6176d4..0731dbb 100644 --- a/packages/web/pages/teams/dashboard.tsx +++ b/packages/web/pages/teams/dashboard.tsx @@ -29,10 +29,10 @@ import { KeyCode } from "../../globals/keycode"; import { useRouter } from "next/router"; import { useEffect } from "react"; - +import { useRecoilState } from "recoil"; import { useAuth } from "../../contexts/authContext"; -let testFriends = [ +const testFriends = [ { name: "Liam", role: "engineer", @@ -72,7 +72,8 @@ let testFriends = [ ]; export default function Dashboard() { - const { currUser, logOut } = useAuth(); + const { currUser } = useAuth(); + const router = useRouter(); useEffect(() => { diff --git a/packages/web/pages/teams/login.tsx b/packages/web/pages/teams/login.tsx index 691a929..a89f34a 100644 --- a/packages/web/pages/teams/login.tsx +++ b/packages/web/pages/teams/login.tsx @@ -1,14 +1,49 @@ +import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth"; import { useRouter } from "next/router"; import { useEffect } from "react"; +import toast from "react-hot-toast"; import { FaArrowLeft } from "react-icons/fa"; import { FcGoogle } from "react-icons/fc"; +import { useRecoilState } from "recoil"; import MainLogo from "../../components/Logo/MainLogo"; import { useAuth } from "../../contexts/authContext"; +const googleProvider = new GoogleAuthProvider(); +const auth = getAuth(); + export default function Login() { - const { currUser, signInGoogle } = useAuth(); + const { currUser } = useAuth(); + const router = useRouter(); + const signInGoogle = () => { + return signInWithPopup(auth, googleProvider) + .then((res) => { + // This gives you a Google Access Token. You can use it to access the Google API. + const credential = GoogleAuthProvider.credentialFromResult(res); + + const token = credential?.accessToken; + // The signed-in user info. + const user = res.user; + + // todo : don't need this? + // setCurrUser(new UserData(user)); + + console.log(user); + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.email; + // The AuthCredential type that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + console.log(error); + toast.error("something went wrong"); + }); + }; + useEffect(() => { // if auth, go to dashboard if (currUser) { diff --git a/packages/web/pages/teams/profile.tsx b/packages/web/pages/teams/profile.tsx index d34b9c2..054ae48 100644 --- a/packages/web/pages/teams/profile.tsx +++ b/packages/web/pages/teams/profile.tsx @@ -1,13 +1,13 @@ import { Divider } from "antd"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { useAuth } from "../../contexts/authContext"; import { User } from "../../models/user"; import UserService from "../../services/userService"; -import Image from "next/image"; +import { useAuth } from "../../contexts/authContext"; export default function Profile() { - const { currUser, logOut } = useAuth(); + const { currUser } = useAuth(); + const [user, setUser] = useState(null); const [loading, setLoading] = useState(false); diff --git a/packages/web/recoil/MainRecoilDataHandler.tsx b/packages/web/recoil/MainRecoilDataHandler.tsx index 391718e..c452971 100644 --- a/packages/web/recoil/MainRecoilDataHandler.tsx +++ b/packages/web/recoil/MainRecoilDataHandler.tsx @@ -1,6 +1,7 @@ import Conversation, { ConversationMember, } from "@nirvana/common/models/conversation"; +import { User } from "@nirvana/common/models/user"; import Collections from "@nirvana/common/services/collections"; import { collection, @@ -14,14 +15,19 @@ import { } from "firebase/firestore"; import { useEffect } from "react"; import toast from "react-hot-toast"; -import { useRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import { useAuth } from "../contexts/authContext"; -import { allRelevantConversations, allUsersConversations } from "./main"; +import { + allRelevantConversations, + allUsersConversations, + nirvanaUserDataAtom, +} from "./main"; const db = getFirestore(); export default function MainRecoilDataHandler() { const { currUser } = useAuth(); + const [nirvanaUser, setNirvanaUser] = useRecoilState(nirvanaUserDataAtom); const [userConvos, setUserConvos] = useRecoilState(allUsersConversations); const [relevantConvos, setRelevantConvos] = useRecoilState( @@ -31,23 +37,20 @@ export default function MainRecoilDataHandler() { useEffect(() => { const unsubs: Unsubscribe[] = [] as Unsubscribe[]; - const convoSubs: Unsubscribe[] = [] as Unsubscribe[]; - (async function () { try { // get all conversations that I am a part of const q = query( collectionGroup(db, Collections.conversationMembers), - where("id", "==", currUser.uid) + where("id", "==", currUser?.uid) ); const unsubscribeUserConvoMember = onSnapshot(q, (snapshot) => { snapshot.docChanges().forEach((change) => { - if (change.type === "added" || change.type === "modified") { - const newConvoMember: ConversationMember = - change.doc.data() as ConversationMember; - newConvoMember.id = change.doc.id; - console.log("modified convo member found: ", change.doc.data()); + const newConvoMember: ConversationMember = + change.doc.data() as ConversationMember; + newConvoMember.id = change.doc.id; + if (change.type === "added" || change.type === "modified") { // update all user convo associations setUserConvos((prevMap) => { return new Map(prevMap.set(newConvoMember.id, newConvoMember)); @@ -56,25 +59,26 @@ export default function MainRecoilDataHandler() { // if we are adding a new convo relevant to me, then start a convo listener for this convo const convoId = change.doc.ref.parent.parent?.id; + // find if this is a done, later, or inbox, and only add listeners accordingly console.log(convoId); - if (change.type === "added" && convoId) { - const unsubConvo = onSnapshot( - doc(db, Collections.conversations, convoId), - (convoDoc) => { - const updatedConvo = convoDoc.data() as Conversation; - console.log("got convo data and subscribed"); - console.log(updatedConvo); + // if (change.type === "added" && convoId) { + // const unsubConvo = onSnapshot( + // doc(db, Collections.conversations, convoId), + // (convoDoc) => { + // const updatedConvo = convoDoc.data() as Conversation; + // console.log("got convo data and subscribed"); + // console.log(updatedConvo); - setRelevantConvos((prevMap) => { - return new Map(prevMap.set(convoId, updatedConvo)); - }); - } - ); + // setRelevantConvos((prevMap) => { + // return new Map(prevMap.set(convoId, updatedConvo)); + // }); + // } + // ); - // todo : remove specific listeners if they are no longer priority conversations + // // todo : remove specific listeners if they are no longer priority conversations - unsubs.push(unsubConvo); - } + // unsubs.push(unsubConvo); + // } } if (change.type === "removed") { console.log("Removed convo member: ", change.doc.data()); @@ -83,6 +87,51 @@ export default function MainRecoilDataHandler() { }); unsubs.push(unsubscribeUserConvoMember); + + // get my current user profile and stay updated to my status + const unsubUser = onSnapshot( + doc(db, Collections.users, currUser!.uid), + (doc) => { + const nirvanaUser = doc.data() as User; + setNirvanaUser(nirvanaUser); + } + ); + unsubs.push(unsubUser); + + // get every conversation where I am in the activeMembers list + const convoQuery = query( + collection(db, Collections.conversations), + where("activeMembers", "array-contains", currUser!.uid) + ); + const unsubConvosListener = onSnapshot(convoQuery, (snapshot) => { + const arrayConvos: Conversation[] = []; + snapshot.docChanges().forEach((change) => { + const newOrUpdatedConvo: Conversation = + change.doc.data() as Conversation; + newOrUpdatedConvo.id = change.doc.id; + + if (change.type === "added" || change.type === "modified") { + arrayConvos.push(newOrUpdatedConvo); + } + if (change.type === "removed") { + // todo: if convo was removed for me, then take out of array + // arrayConvos.filter() + } + }); + + // add to the main convos atom, by modifying the current map + setRelevantConvos((prevConvosMap) => { + const newMap = new Map(prevConvosMap); + arrayConvos.forEach((currconvo) => { + newMap.set(currconvo.id, currconvo); + }); + + console.log("updated convos: ", newMap); + + return newMap; + }); + }); + unsubs.push(unsubConvosListener); } catch (error) { console.log(error); toast.error("Problem in retrieving data"); diff --git a/packages/web/recoil/main.tsx b/packages/web/recoil/main.tsx index 806ed28..0b13ed0 100644 --- a/packages/web/recoil/main.tsx +++ b/packages/web/recoil/main.tsx @@ -1,21 +1,38 @@ +import { User as FirebaseUser } from "@firebase/auth"; import Conversation, { ConversationMember, AudioClip, Link, } from "@nirvana/common/models/conversation"; +import { User as NirvanaUser } from "@nirvana/common/models/user"; import { atom } from "recoil"; export enum RecoilActions { + TEST = "TEST", + CURR_PAGE_PATH = "CURR_PAGE_PATH", + ALL_COMPLETE_CONVERSATIONS = "ALL_COMPLETE_CONVERSATIONS", ALL_USERS_CONVERSATION_RELATIONSHIPS = "ALL_USERS_CONVERSATION_RELATIONSHIPS", ALL_RELEVANT_CONVERSATIONS = "ALL_RELEVANT_CONVERSATIONS", + + USER_DATA = "USER_DATA", } -// export const currPagePath = atom({ -// key: RecoilActions.CURR_PAGE_PATH, // unique ID (with respect to other atoms/selectors) -// default: "/s", // default value (aka initial value) +export const nirvanaUserDataAtom = atom({ + key: RecoilActions.ALL_COMPLETE_CONVERSATIONS, + default: null, +}); + +// testing if we can import data in server side, but since it's next js we need firebase calls in client side +// import { conversationService } from "@nirvana/common/services"; + +// const getTest = (async () => await conversationService.test())(); + +// export const test = atom({ +// key: RecoilActions.TEST, // unique ID (with respect to other atoms/selectors) +// default: getTest, // default value (aka initial value) // }); export class CompleteConversation { diff --git a/packages/web/services/firebaseService.js b/packages/web/services/firebaseService.js index 540f6a4..ef6bedf 100644 --- a/packages/web/services/firebaseService.js +++ b/packages/web/services/firebaseService.js @@ -54,3 +54,5 @@ enableIndexedDbPersistence(db).catch((err) => { }); setPersistence(getAuth(), browserSessionPersistence); + +export default app; diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index 4c7bfaa..f20c2db 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "strictNullChecks": true + "strictNullChecks": true, + "noUnusedLocals": false }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/workspace.code-workspace b/workspace.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/workspace.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file