diff --git a/components/Dashboard/Header.tsx b/components/Dashboard/Header.tsx index f2ff05c..fceebce 100644 --- a/components/Dashboard/Header.tsx +++ b/components/Dashboard/Header.tsx @@ -105,7 +105,7 @@ export default function Header() { - + diff --git a/components/Dashboard/TeamVoiceLine.tsx b/components/Dashboard/TeamVoiceLine.tsx index aa1f87c..d02b838 100644 --- a/components/Dashboard/TeamVoiceLine.tsx +++ b/components/Dashboard/TeamVoiceLine.tsx @@ -6,11 +6,19 @@ import { useTeamDashboardContext } from "../../contexts/teamDashboardContext"; import { User, UserStatus } from "../../models/user"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; -import { TeamMemberRole } from "../../models/teamMember"; +import { TeamMemberRole, TeamMemberStatus } from "../../models/teamMember"; import UserService from "../../services/userService"; -import { DocumentSnapshot, Unsubscribe } from "firebase/firestore"; +import { + doc, + DocumentSnapshot, + getFirestore, + onSnapshot, + Unsubscribe, +} from "firebase/firestore"; +import { Collections } from "../../services/collections"; const userService = new UserService(); +const db = getFirestore(); function statusBubble(status: UserStatus) { console.log(status); @@ -72,31 +80,18 @@ export default function TeamVoiceLine() { try { // listeners for all teammates' status if (teamMembers) { - teamMembers.forEach(async (tmember) => { - const unsub = await userService.getUserRealtime( - tmember.userId, - (doc: DocumentSnapshot) => { - // setTeamUsers((prevTeamUsers) => ({ - // ...prevTeamUsers, - // })); + teamMembers.map((tmember) => { + if (tmember.status == TeamMemberStatus.activated) { + const docRef = doc(this.db, Collections.users, tmember.userId); - // updated user - const updatedUser = doc.data() as User; + const unsub = onSnapshot(docRef, (doc) => { + console.log(doc.data()); + }); - // update the teamUsers array in state - const newUsers: User[] = teamUsers.map((user, i) => { - if (user.id == updatedUser.id) { - return updatedUser; - } + unsubs.push(unsub); + } - return user; - }); - - setTeamUsers(newUsers); - } - ); - - unsubs.push(unsub); + return; }); } } catch (error) { diff --git a/contexts/teamDashboardContext.tsx b/contexts/teamDashboardContext.tsx index 6b1433f..c4dc869 100644 --- a/contexts/teamDashboardContext.tsx +++ b/contexts/teamDashboardContext.tsx @@ -119,15 +119,17 @@ export function TeamDashboardContextProvider({ children }) { } // SECTION: get all teammembers for team, will have listeners in other subcomponents - const teamMembers = await teamService.getTeamMembersByTeamIdNotUser( - returnedTeam.id, - currUser.uid + var teamMembers: TeamMember[] = + await teamService.getTeamMembersByTeamId(returnedTeam.id); + + teamMembers = teamMembers.filter( + (element) => element.userId != currUser.uid ); setValue((prevValue) => ({ ...prevValue, teamMembers })); } catch (error) { console.log(error); - router.push("/teams/login"); + router.push("/teams/landing"); } setLoading(false); diff --git a/pages/teams/[teamid]/admin.tsx b/pages/teams/[teamid]/admin.tsx index 1cc91fc..46ed1ce 100644 --- a/pages/teams/[teamid]/admin.tsx +++ b/pages/teams/[teamid]/admin.tsx @@ -6,10 +6,19 @@ import { TeamDashboardContextProvider, useTeamDashboardContext, } from "../../../contexts/teamDashboardContext"; -import { TeamMemberRole } from "../../../models/teamMember"; +import { + TeamMember, + TeamMemberRole, + TeamMemberStatus, +} from "../../../models/teamMember"; import { toast } from "react-hot-toast"; import { Team, TeamStatus } from "../../../models/team"; +import { FaMoneyBillWave, FaTrash, FaPaperPlane } from "react-icons/fa"; +import TeamService from "../../../services/teamService"; +import Router from "next/router"; +import Moment from "react-moment"; + export default function TeamAdminWrapper() { return ( @@ -20,6 +29,31 @@ export default function TeamAdminWrapper() { 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; @@ -28,6 +62,7 @@ function TeamAdmin() { 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(() => { @@ -48,7 +83,7 @@ function TeamAdmin() { })(); }, []); - async function handleSubmit(e) { + async function handleSubmitTeamUpdate(e) { e.preventDefault(); setLoading(true); @@ -84,96 +119,265 @@ function TeamAdmin() { 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(teamMemberId: string) { + 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(teamMemberId: string) { + 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 */} -
🙌Manage Team
+
+ + {/* header */} +
🙌Manage Team
- {error && {error}} + {error && {error}} - + - - Team Name - - Everyone in your team will see this. + + Team Name + + Everyone in your team will see this. + + setTeamName(e.target.value)} + /> - setTeamName(e.target.value)} - /> - - - Company Site - optional - setcompanySite(e.target.value)} - /> - + + Company Site + optional + setcompanySite(e.target.value)} + /> + - + - Team + + + + + - {/* current user */} -
- asdf - - - {user.firstName + " " + user.lastName} +
+ - - admin + + + + + Billing + + Please email{" "} + {" "} + to open more spots for your team. - {user.emailAddress} + + + {teamSpotsRemaining} spots remaining + -
- {teamMembers.map((tmember, i) => { - return ( -
-
+ - - - {tmember.inviteEmailAddress} + Team + + {/* current user */} +
+ asdf + + + {user.firstName + " " + user.lastName} + + + admin - {user.emailAddress} + + {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/services/teamService.tsx b/services/teamService.tsx index a4ba56b..cdfbb28 100644 --- a/services/teamService.tsx +++ b/services/teamService.tsx @@ -158,14 +158,10 @@ export default class TeamService implements IService { return teamMembers; } - async getTeamMembersByTeamIdNotUser( - teamId: string, - userId: string - ): Promise { + async getTeamMembersByTeamId(teamId: string): Promise { const q = query( collection(this.db, Collections.teamMembers), - where("teamId", "==", teamId), - where("userId", "!=", userId) + where("teamId", "==", teamId) ); const querySnapshot = await getDocs(q); @@ -192,4 +188,39 @@ export default class TeamService implements IService { { merge: true } ); } + + async createTeamInvite(teamMember: TeamMember) { + // 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 } + ); + } } diff --git a/services/userService.tsx b/services/userService.tsx index 69ac2c3..47d8995 100644 --- a/services/userService.tsx +++ b/services/userService.tsx @@ -38,13 +38,12 @@ export default class UserService { } } - async getUserRealtime( - userId: string, - handleDataFetch: (doc: DocumentSnapshot) => void - ): Promise { + async getUserRealtime(userId: string): Promise { const docRef = doc(this.db, Collections.users, userId); - const unsub = onSnapshot(docRef, handleDataFetch); + const unsub = onSnapshot(docRef, (doc) => { + console.log(doc.data()); + }); return unsub; }