Merge branch 'main' into mbalsdon-browser-notifications

This commit is contained in:
cth0604 2022-03-30 04:38:54 -07:00 committed by GitHub
commit ffc230a631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 533 additions and 273 deletions

View File

@ -10,20 +10,12 @@ import { fetchUsers, selectMe } from "./redux/slices/usersSlice";
import { useAppDispatch, useAppSelector, useInterval } from "./redux/hooks"; import { useAppDispatch, useAppSelector, useInterval } from "./redux/hooks";
import DetailedMeeting from "./api-bodies/DetailedMeeting"; import DetailedMeeting from "./api-bodies/DetailedMeeting";
import React, { useState } from "react"; import React, { useState } from "react";
import { Box } from "@mui/material";
const ProtectedRoute = () => { const ProtectedRoute = () => {
const auth = useAuth(); const auth = useAuth();
const location = useLocation(); const location = useLocation();
/* Temporary data */
if (auth?.isLoggedIn != undefined && auth?.isLoggedIn == true) {
store.dispatch(fetchMeetings(auth?.uuid));
store.dispatch(fetchUsers(auth?.uuid));
store.dispatch(fetchFavorites(auth?.uuid));
}
useAppDispatch(); useAppDispatch();
const currentUserUuid: string = useAppSelector(selectMe); const currentUserUuid: string = useAppSelector(selectMe);
const meetings: DetailedMeeting[] = useAppSelector(selectMeetings); const meetings: DetailedMeeting[] = useAppSelector(selectMeetings);
@ -64,8 +56,6 @@ const ProtectedRoute = () => {
setOpen(false); setOpen(false);
}; };
// -----
return auth?.isLoggedIn ? ( return auth?.isLoggedIn ? (
<> <>
{/* ----- */} {/* ----- */}

View File

@ -2,7 +2,7 @@ interface DetailedMeeting {
meetingId: string; // primary key meetingId: string; // primary key
liveParticipantIds: string[]; liveParticipantIds: string[];
registrantIds: string[]; registrantIds: string[];
start: string; startTime: string;
duration: number; duration: number;
timezone: string; timezone: string;
joinUrl: string; joinUrl: string;

View File

@ -48,7 +48,7 @@ const meetings = [
meetingId: "", meetingId: "",
liveParticipantIds: [], liveParticipantIds: [],
registrantIds: ["", "1", "2", "3", "4", "5", "6"], registrantIds: ["", "1", "2", "3", "4", "5", "6"],
start: "2022-03-13T17:00:00", startTime: "2022-03-13T17:00:00",
duration: 15, duration: 15,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -58,7 +58,7 @@ const meetings = [
meetingId: "1", meetingId: "1",
liveParticipantIds: [], liveParticipantIds: [],
registrantIds: ["", "2", "4"], registrantIds: ["", "2", "4"],
start: "2022-03-16T17:30:00", startTime: "2022-03-16T17:30:00",
duration: 30, duration: 30,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -68,7 +68,7 @@ const meetings = [
meetingId: "2", meetingId: "2",
liveParticipantIds: ["3", "5"], liveParticipantIds: ["3", "5"],
registrantIds: ["3", "5", "6"], registrantIds: ["3", "5", "6"],
start: "2022-03-13T17:30:00", startTime: "2022-03-13T17:30:00",
duration: 30, duration: 30,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -78,7 +78,7 @@ const meetings = [
meetingId: "3", meetingId: "3",
liveParticipantIds: [], liveParticipantIds: [],
registrantIds: ["", "1"], registrantIds: ["", "1"],
start: "2022-03-10T07:27:27", startTime: "2022-03-10T07:27:27",
duration: 727, duration: 727,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -88,7 +88,7 @@ const meetings = [
meetingId: "4", meetingId: "4",
liveParticipantIds: [], liveParticipantIds: [],
registrantIds: ["", "2", "3"], registrantIds: ["", "2", "3"],
start: "2022-03-10T12:30:00", startTime: "2022-03-10T12:30:00",
duration: 120, duration: 120,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -98,7 +98,7 @@ const meetings = [
meetingId: "5", meetingId: "5",
liveParticipantIds: [""], liveParticipantIds: [""],
registrantIds: [""], registrantIds: [""],
start: "2022-03-24T23:11:11", startTime: "2022-03-24T23:11:11",
duration: 11, duration: 11,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -108,7 +108,7 @@ const meetings = [
meetingId: "6", meetingId: "6",
liveParticipantIds: ["", "1"], liveParticipantIds: ["", "1"],
registrantIds: ["", "1", "2", "3", "4", "5"], registrantIds: ["", "1", "2", "3", "4", "5"],
start: "2022-03-25T09:00:00", startTime: "2022-03-25T09:00:00",
duration: 360, duration: 360,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -118,7 +118,7 @@ const meetings = [
meetingId: "7", meetingId: "7",
liveParticipantIds: [], liveParticipantIds: [],
registrantIds: ["", "5"], registrantIds: ["", "5"],
start: "2022-03-25T15:00:00", startTime: "2022-03-25T15:00:00",
duration: 150, duration: 150,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -128,7 +128,7 @@ const meetings = [
meetingId: "8", meetingId: "8",
liveParticipantIds: ["2"], liveParticipantIds: ["2"],
registrantIds: ["", "5", "2", "3"], registrantIds: ["", "5", "2", "3"],
start: "2022-03-25T17:45:00", startTime: "2022-03-25T17:45:00",
duration: 60, duration: 60,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -138,7 +138,7 @@ const meetings = [
meetingId: "9", meetingId: "9",
liveParticipantIds: ["2", "5"], liveParticipantIds: ["2", "5"],
registrantIds: ["", "4"], registrantIds: ["", "4"],
start: "2022-03-25T18:15:30", startTime: "2022-03-25T18:15:30",
duration: 75, duration: 75,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",
@ -148,7 +148,7 @@ const meetings = [
meetingId: "10", meetingId: "10",
liveParticipantIds: [], liveParticipantIds: [],
registrantIds: ["", "1", "2", "3", "4", "5", "6"], registrantIds: ["", "1", "2", "3", "4", "5", "6"],
start: "2022-04-04T18:30:00", startTime: "2022-04-04T18:30:00",
duration: 90, duration: 90,
timezone: "", timezone: "",
joinUrl: "", joinUrl: "",

View File

@ -1,10 +1,8 @@
interface NewMeeting { interface NewMeeting {
uuid: string;
startTime: string; startTime: string;
duration: number; duration: number;
timezone: string;
topic: string; topic: string;
registrantsIds: string[]; registrantIds: string[];
} }
export default NewMeeting; export default NewMeeting;

View File

@ -2,7 +2,7 @@ import UserLite from "./UserLite";
interface UserFull { interface UserFull {
userInfo: UserLite; userInfo: UserLite;
manager: UserLite; manager?: UserLite;
managerDirectReports: UserLite[]; managerDirectReports: UserLite[];
userDirectReports: UserLite[]; userDirectReports: UserLite[];
} }

View File

@ -64,15 +64,15 @@ const CalendarPage: React.FC = () => {
meetingId: m.meetingId, meetingId: m.meetingId,
liveParticipantIds: m.liveParticipantIds, liveParticipantIds: m.liveParticipantIds,
registrantIds: m.registrantIds, registrantIds: m.registrantIds,
startIso: m.start, startIso: m.startTime,
duration: m.duration, duration: m.duration,
timezone: m.timezone, timezone: m.timezone,
joinUrl: m.joinUrl, joinUrl: m.joinUrl,
topic: m.topic, topic: m.topic,
// fields needed by calendar // fields needed by calendar
title: m.topic, title: m.topic,
start: new Date(Date.parse(m.start)), // Turns the ISO String into a date object start: new Date(Date.parse(m.startTime)), // Turns the ISO String into a date object
end: new Date(Date.parse(m.start) + m.duration * 60000), // result of Date.parse() is milliseconds, and m.duration is given in minutes end: new Date(Date.parse(m.startTime) + m.duration * 60000), // result of Date.parse() is milliseconds, and m.duration is given in minutes
})); }));
const [selectedUserUuid, setSelectedUserUuid] = useState(currentUserUuid); const [selectedUserUuid, setSelectedUserUuid] = useState(currentUserUuid);
@ -83,7 +83,7 @@ const CalendarPage: React.FC = () => {
meetingId: event.meetingId, meetingId: event.meetingId,
liveParticipantIds: event.liveParticipantIds, liveParticipantIds: event.liveParticipantIds,
registrantIds: event.registrantIds, registrantIds: event.registrantIds,
start: event.startIso, startTime: event.startIso,
duration: event.duration, duration: event.duration,
timezone: event.timezone, timezone: event.timezone,
joinUrl: event.joinUrl, joinUrl: event.joinUrl,
@ -108,14 +108,16 @@ const CalendarPage: React.FC = () => {
}; };
return ( return (
<Box sx={{ <Box
display: "flex", sx={{
flexDirection: "column", display: "flex",
width: "98.5%", flexDirection: "column",
height: "99.5%", width: "98.5%",
ml: 1, height: "99.5%",
mt: 1}}> ml: 1,
mt: 1,
}}
>
<Toolbar /> <Toolbar />
<FormControl fullWidth> <FormControl fullWidth>
@ -156,13 +158,12 @@ const CalendarPage: React.FC = () => {
showMultiDayTimes showMultiDayTimes
localizer={momentLocalizer(moment)} localizer={momentLocalizer(moment)}
style={{ height: "83%" }} style={{ height: "83%" }}
eventPropGetter = {() => { eventPropGetter={() => {
const backgroundColor = "IndianRed"; const backgroundColor = "IndianRed";
const borderColor = "White"; const borderColor = "White";
return { style: { backgroundColor, borderColor } }; return { style: { backgroundColor, borderColor } };
}} }}
/> />
</Box> </Box>
); );
}; };

View File

@ -7,6 +7,7 @@ import UserLite from "../../api-bodies/UserLite";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { selectUser } from "../../redux/slices/usersSlice"; import { selectUser } from "../../redux/slices/usersSlice";
import { useAppSelector } from "../../redux/hooks"; import { useAppSelector } from "../../redux/hooks";
import EmptyBody from "./contacts-components/EmptyBody";
const Contacts: React.FC = () => { const Contacts: React.FC = () => {
const { uuid } = useParams(); const { uuid } = useParams();
@ -15,9 +16,14 @@ const Contacts: React.FC = () => {
); );
return ( return (
<Box sx={{ display: "flex", height: "100%" }}> <Box
sx={{
display: "flex",
height: "100%",
}}
>
<Sidebar /> <Sidebar />
{uriContact ? <Body contactSelected={uriContact} /> : null} {uriContact ? <Body contactSelected={uriContact} /> : <EmptyBody />}
</Box> </Box>
); );
}; };

View File

@ -1,7 +1,7 @@
import DetailedMeeting from "../../api-bodies/DetailedMeeting"; import DetailedMeeting from "../../api-bodies/DetailedMeeting";
const getUpcomingMeetingTime = (meeting: DetailedMeeting) => { const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
const startDate = new Date(meeting.start); const startDate = new Date(meeting.startTime);
const endDate = new Date(startDate.getTime() + meeting.duration * 60000); const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
const startTime = startDate const startTime = startDate
.toTimeString() .toTimeString()

View File

@ -16,7 +16,7 @@ const Body: React.FC<Props> = (props) => {
flexDirection: "column", flexDirection: "column",
width: "100%", width: "100%",
height: "100%", height: "100%",
mt: 2, mt: 5,
}} }}
> >
<Toolbar /> <Toolbar />

View File

@ -0,0 +1,29 @@
import { Box, Typography } from "@mui/material";
import React from "react";
import PermContactCalendarIcon from "@mui/icons-material/PermContactCalendar";
const EmptyBody: React.FC = () => {
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center",
}}
>
<Box>
<PermContactCalendarIcon color="secondary" sx={{ fontSize: 100 }} />
</Box>
<Box>
<Typography>
View contact info by clicking a contact in the left panel
</Typography>
</Box>
</Box>
);
};
export default EmptyBody;

View File

@ -24,40 +24,55 @@ const LowerBody: React.FC<Props> = (props) => {
flexDirection: "column", flexDirection: "column",
border: 1, border: 1,
borderRadius: 2, borderRadius: 2,
borderColor: "#af000d",
width: "70%", width: "70%",
height: "60%", height: "60%",
alignSelf: "center", alignSelf: "center",
}} }}
> >
<Typography variant="h4" textAlign="center"> <Typography
sx={{ color: "white", my: 1 }}
variant="h4"
textAlign="center"
>
Upcoming meetings Upcoming meetings
</Typography> </Typography>
<List sx={{ maxHeight: "100%", overflow: "auto" }}> {meetings.length === 0 ? (
{meetings.map((meeting, i) => ( <Typography
<Box variant="h5"
sx={{ textAlign="center"
display: "flex", sx={{ position: "relative", top: "30%" }}
justifyContent: "space-between", >
height: "50px", No upcoming meetings today
px: "10px", </Typography>
borderTop: 1, ) : (
borderBottom: i === meetings.length - 1 ? 1 : 0, <List sx={{ maxHeight: "100%", overflow: "auto" }}>
}} {meetings.map((meeting, i) => (
key={i} <Box
> sx={{
<Button display: "flex",
variant="text" justifyContent: "space-between",
color="info" height: "50px",
onClick={() => dispatch(open(meeting))} px: "10px",
borderTop: 1,
borderBottom: i === meetings.length - 1 ? 1 : 0,
}}
key={i}
> >
{meeting.topic} <Button
</Button> variant="text"
<Typography sx={{ pt: 1.5 }}> color="info"
{getUpcomingMeetingTime(meeting)} onClick={() => dispatch(open(meeting))}
</Typography> >
</Box> {meeting.topic}
))} </Button>
</List> <Typography sx={{ pt: 1.5 }}>
{getUpcomingMeetingTime(meeting)}
</Typography>
</Box>
))}
</List>
)}
</Box> </Box>
); );
}; };

View File

@ -1,8 +1,6 @@
import { Box, Button, IconButton, Typography } from "@mui/material"; import { Box, Button, IconButton, Typography } from "@mui/material";
import React from "react"; import React from "react";
import PhoneIcon from "@mui/icons-material/Phone"; import PhoneIcon from "@mui/icons-material/Phone";
import VideocamIcon from "@mui/icons-material/Videocam";
import GroupsIcon from "@mui/icons-material/Groups";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import UserLite from "../../../../api-bodies/UserLite"; import UserLite from "../../../../api-bodies/UserLite";
import UserStatus from "../../../../api-bodies/UserStatus"; import UserStatus from "../../../../api-bodies/UserStatus";
@ -18,26 +16,50 @@ import {
} from "../../../../redux/slices/favoritesSlice"; } from "../../../../redux/slices/favoritesSlice";
import RemoveIcon from "@mui/icons-material/Remove"; import RemoveIcon from "@mui/icons-material/Remove";
import { MeetingStatus } from "../../../../utils"; import { MeetingStatus } from "../../../../utils";
import { selectMe } from "../../../../redux/slices/usersSlice"; import { selectManager, selectMe } from "../../../../redux/slices/usersSlice";
import axios from "../../../../api/axios";
import NewMeeting from "../../../../api-bodies/NewMeeting";
interface Props { interface Props {
contactInfo: UserLite; contactInfo: UserLite;
} }
const UpperBody: React.FC<Props> = (props) => { const UpperBody: React.FC<Props> = ({ contactInfo }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const userStatus: UserStatus = useAppSelector((state) => const userStatus: UserStatus = useAppSelector((state) =>
selectUserStatus(state, props.contactInfo.uuid) selectUserStatus(state, contactInfo.uuid)
); );
const me = useAppSelector(selectMe); const me = useAppSelector(selectMe);
const favoritesUuids = useAppSelector(selectFavorites); const favoritesUuids = useAppSelector(selectFavorites);
const detailedMeeting = useAppSelector((state) => const detailedMeeting = useAppSelector((state) =>
selectMeeting(state, userStatus.meetingId) selectMeeting(state, userStatus.inMeeting ? userStatus.meetingId : null)
); );
const managerId = useAppSelector(selectManager);
const meetingStatus: MeetingStatus = const meetingStatus: MeetingStatus =
userStatus && userStatus.inMeeting managerId === contactInfo.uuid
? MeetingStatus.IN_MEETING ? MeetingStatus.NOT_AVAILABLE
: MeetingStatus.NOT_IN_MEETING; : userStatus && userStatus.inMeeting
? MeetingStatus.IN_MEETING
: MeetingStatus.NOT_IN_MEETING;
const startCall = async () => {
const newMeeting: NewMeeting = {
startTime: "2022-03-30T23:40:00Z",
duration: 30,
topic: `Meeting with ${contactInfo.name}`,
registrantIds: [contactInfo.uuid],
};
try {
const response = await axios.post(
`/users/${me}/meetings`,
JSON.stringify(newMeeting)
);
window.open(response.data.joinUrl, "_blank")?.focus();
} catch (e) {
console.log(e);
}
};
return ( return (
<Box <Box
sx={{ sx={{
@ -45,6 +67,7 @@ const UpperBody: React.FC<Props> = (props) => {
flexDirection: "column", flexDirection: "column",
border: 1, border: 1,
borderRadius: 2, borderRadius: 2,
borderColor: "#af000d",
width: "70%", width: "70%",
alignSelf: "center", alignSelf: "center",
mb: 2, mb: 2,
@ -59,16 +82,14 @@ const UpperBody: React.FC<Props> = (props) => {
mx: 4, mx: 4,
}} }}
> >
<Typography variant="h3">{props.contactInfo.name}</Typography> <Typography variant="h3">{contactInfo.name}</Typography>
{!favoritesUuids.includes(props.contactInfo.uuid) ? ( {!favoritesUuids.includes(contactInfo.uuid) ? (
<Button <Button
variant="outlined" variant="outlined"
color="success" color="success"
startIcon={<AddIcon />} startIcon={<AddIcon />}
onClick={() => onClick={() =>
dispatch( dispatch(addFavorite({ userId: me, toBeAdded: contactInfo.uuid }))
addFavorite({ userId: me, toBeAdded: props.contactInfo.uuid })
)
} }
> >
favorites favorites
@ -82,7 +103,7 @@ const UpperBody: React.FC<Props> = (props) => {
dispatch( dispatch(
removeFavorite({ removeFavorite({
userId: me, userId: me,
toBeRemoved: props.contactInfo.uuid, toBeRemoved: contactInfo.uuid,
}) })
) )
} }
@ -103,23 +124,20 @@ const UpperBody: React.FC<Props> = (props) => {
> >
<Box> <Box>
<IconButton <IconButton
sx={{ border: 1, mr: 1, backgroundColor: "secondary" }} sx={{ border: 1, backgroundColor: "white", mr: 1 }}
size="large" size="large"
color="secondary" color="secondary"
onClick={startCall}
> >
<PhoneIcon fontSize="large" /> <PhoneIcon fontSize="large" />
</IconButton> </IconButton>
<IconButton sx={{ border: 1, mr: 1 }} size="large" color="secondary">
<VideocamIcon fontSize="large" />
</IconButton>
<IconButton sx={{ border: 1, mr: 1 }} size="large" color="secondary">
<GroupsIcon fontSize="large" />
</IconButton>
</Box> </Box>
<Box sx={{ display: "flex", flexDirection: "column" }}> <Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography sx={{ textAlign: "right" }}>{meetingStatus}</Typography> <Typography variant="button" sx={{ textAlign: "right" }}>
{meetingStatus}
</Typography>
{detailedMeeting ? ( {detailedMeeting ? (
<Typography>{detailedMeeting.topic}</Typography> <Typography variant="h6">{detailedMeeting.topic}</Typography>
) : null} ) : null}
</Box> </Box>
</Box> </Box>

View File

@ -8,31 +8,35 @@ import { useAppSelector } from "../../../../redux/hooks";
import { selectUserStatus } from "../../../../redux/slices/meetingsAndUserStatusSlice"; import { selectUserStatus } from "../../../../redux/slices/meetingsAndUserStatusSlice";
import UserStatus from "../../../../api-bodies/UserStatus"; import UserStatus from "../../../../api-bodies/UserStatus";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { selectManager } from "../../../../redux/slices/usersSlice";
interface Props { interface Props {
contactInfo: UserLite; contactInfo: UserLite;
} }
const ContactItem: React.FC<Props> = (props) => { const ContactItem: React.FC<Props> = ({ contactInfo }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const userStatus: UserStatus | null = useAppSelector((state) => const userStatus: UserStatus | null = useAppSelector((state) =>
selectUserStatus(state, props.contactInfo.uuid) selectUserStatus(state, contactInfo.uuid)
); );
const managerId = useAppSelector(selectManager);
const status: MeetingStatus = const status: MeetingStatus =
userStatus && userStatus.inMeeting managerId === contactInfo.uuid
? MeetingStatus.IN_MEETING ? MeetingStatus.NOT_AVAILABLE
: MeetingStatus.NOT_IN_MEETING; : userStatus && userStatus.inMeeting
? MeetingStatus.IN_MEETING
: MeetingStatus.NOT_IN_MEETING;
return ( return (
<ListItemButton <ListItemButton
onClick={() => { onClick={() => {
navigate(`/contacts/${props.contactInfo.uuid}`); navigate(`/contacts/${contactInfo.uuid}`);
}} }}
> >
<ListItemIcon> <ListItemIcon>
<PersonOutlineIcon /> <PersonOutlineIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
primary={props.contactInfo.name} primary={contactInfo.name}
secondary={status} secondary={status}
sx={{ flexGrow: 1 }} sx={{ flexGrow: 1 }}
/> />

View File

@ -15,25 +15,16 @@ import { useAppSelector } from "../../redux/hooks";
import { selectFavorites } from "../../redux/slices/favoritesSlice"; import { selectFavorites } from "../../redux/slices/favoritesSlice";
import { selectTeam, selectUsers } from "../../redux/slices/usersSlice"; import { selectTeam, selectUsers } from "../../redux/slices/usersSlice";
import GroupSelect from "../sidebar/GroupSelect"; import GroupSelect from "../sidebar/GroupSelect";
import { SidebarUserObj } from "./Home";
interface Props { interface Props {
open: boolean; open: boolean;
selectedValue: SidebarUserObj; handleClose: () => void;
onClose: (value: SidebarUserObj) => void;
users: SidebarUserObj[];
} }
const CallFavouritesDialog: React.FC<Props> = ({ const CallFavouritesDialog: React.FC<Props> = ({
open, open,
selectedValue, handleClose,
onClose,
users,
}: Props) => { }: Props) => {
const handleClose = () => {
onClose(selectedValue);
};
console.log(users);
const [group, setGroup] = useState<string>("Favorites"); const [group, setGroup] = useState<string>("Favorites");
const [inputText, setInputText] = useState<string>(""); const [inputText, setInputText] = useState<string>("");
@ -60,6 +51,10 @@ const CallFavouritesDialog: React.FC<Props> = ({
selectUsers(state, groupMembersUuids) selectUsers(state, groupMembersUuids)
); );
const handleCall = () => {
handleClose();
};
return ( return (
<Dialog onClose={handleClose} open={open} fullWidth maxWidth="sm"> <Dialog onClose={handleClose} open={open} fullWidth maxWidth="sm">
<DialogTitle>Select who to call:</DialogTitle> <DialogTitle>Select who to call:</DialogTitle>
@ -107,7 +102,7 @@ const CallFavouritesDialog: React.FC<Props> = ({
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button color="success" onClick={handleClose}> <Button color="success" onClick={handleCall}>
<Typography variant="button" color="black"> <Typography variant="button" color="black">
Call Call
</Typography> </Typography>

View File

@ -3,7 +3,6 @@ import ShortCuts from "./ShortCuts";
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { MeetingStatus } from "../../utils"; import { MeetingStatus } from "../../utils";
import { useState } from "react";
export interface SidebarUserObj { export interface SidebarUserObj {
id: number; id: number;
@ -15,12 +14,12 @@ const Home: React.FC = () => {
/* Temporary data - this is the same as what's in ProtectedRoute.tsx so it should not /* Temporary data - this is the same as what's in ProtectedRoute.tsx so it should not
be duplicated like this in the future (Route components were being weird when I tried be duplicated like this in the future (Route components were being weird when I tried
to pass it down from App.tsx) */ to pass it down from App.tsx) */
const [mockUsers] = useState<SidebarUserObj[]>([ // const [mockUsers] = useState<SidebarUserObj[]>([
{ id: 0, name: "Jincheng L.", meetingStatus: MeetingStatus.NOT_IN_MEETING }, // { id: 0, name: "Jincheng L.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
{ id: 1, name: "Matt B.", meetingStatus: MeetingStatus.IN_MEETING }, // { id: 1, name: "Matt B.", meetingStatus: MeetingStatus.IN_MEETING },
{ id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.IN_MEETING }, // { id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.IN_MEETING },
{ id: 3, name: "Bob A.", meetingStatus: MeetingStatus.NOT_IN_MEETING }, // { id: 3, name: "Bob A.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
]); // ]);
return ( return (
<Container className="main-home"> <Container className="main-home">
@ -29,7 +28,7 @@ const Home: React.FC = () => {
<MeetingsPanel /> <MeetingsPanel />
</Grid> </Grid>
<Grid item sm={4}> <Grid item sm={4}>
<ShortCuts users={mockUsers} /> <ShortCuts />
</Grid> </Grid>
</Grid> </Grid>
</Container> </Container>

View File

@ -1,17 +1,30 @@
import ListItemButton from "@mui/material/ListItemButton"; import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText"; import DetailedMeeting from "../../api-bodies/DetailedMeeting";
import { useAppDispatch } from "../../redux/hooks";
import { open } from "../../redux/slices/meetingDetailsOpenSlice";
import { Box, Typography } from "@mui/material";
interface Props { interface Props {
meeting: DetailedMeeting;
meetingClass: string;
meetingName: string; meetingName: string;
meetingTime: string; meetingTime: string;
meetingMembers: string meetingMembers: string
} }
function Meeting(props: Props) { function Meeting(props: Props) {
const dispatch = useAppDispatch();
return ( return (
<ListItemButton component="a" href="#" className="row meeting"> <ListItemButton
<ListItemText primary={props.meetingName} secondary={props.meetingMembers} /> onClick={() => dispatch(open(props.meeting))}
<ListItemText primary={props.meetingTime} /> component="a"
className={"row meeting " + props.meetingClass}>
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography color="white" variant="h5">{props.meetingName}</Typography>
<Typography color="white" variant="h6">{props.meetingTime.toUpperCase()}</Typography>
<Typography color="mistyrose" variant="body1">{props.meetingMembers}</Typography>
</Box>
</ListItemButton> </ListItemButton>
); );
} }

View File

@ -13,58 +13,97 @@ const MeetingsPanel: React.FC = () => {
const uuids: string[] = []; const uuids: string[] = [];
meetings.forEach((meeting) => { meetings.forEach((meeting) => {
meeting.liveParticipantIds.forEach((uuid) => { meeting.liveParticipantIds.forEach((uuid) => {
if(!uuids.includes(uuid)) { if (!uuids.includes(uuid)) {
uuids.push(uuid); uuids.push(uuid);
} }
}); });
}); });
const participants = useAppSelector((state) => const participants = useAppSelector((state) => selectUsers(state, uuids));
selectUsers(state,uuids)
);
// const participants: (UserLite | undefined)[] = []; // const participants: (UserLite | undefined)[] = [];
// uuids.forEach((uuid) => { // uuids.forEach((uuid) => {
// const userLite = useAppSelector((state) => // const userLite = useAppSelector((state) =>
// selectUser(state,uuid) // selectUser(state,uuid)
// ); // );
// participants.push(userLite); // participants.push(userLite);
// }); // });
const [currentDate, setCurrentDate] = useState<Date>(new Date()); const [currentDate, setCurrentDate] = useState<Date>(new Date());
useEffect(() => { useEffect(() => {
setInterval(() => setCurrentDate(new Date()), 1000); setInterval(() => setCurrentDate(new Date()), 1000);
}, []); }, []);
let i = 1;
return ( return (
<div className="meetings-panel"> <div className="meetings-panel">
<div className="row panel-label"> <div className="row panel-label">
<Typography className="mylabel" sx={{ ml: 1 }}> <Typography
Meetings in Progress className="mylabel"
variant="h5"
sx={{ color: "white", ml: 1 }}
>
Meetings in progress
</Typography> </Typography>
</div> </div>
<List style={{maxHeight: '100%', overflow: 'auto'}} > <List style={{ maxHeight: "100%", overflow: "auto" }}>
{meetings.map((meeting) => { {meetings.map((meeting) => {
const meetingMembers: UserLite[] = []; const meetingMembers: UserLite[] = [];
participants.forEach((userLite) => { participants.forEach((userLite) => {
if (userLite != undefined && meeting.liveParticipantIds.includes(userLite.uuid)) { if (
userLite != undefined &&
meeting.liveParticipantIds.includes(userLite.uuid)
) {
meetingMembers.push(userLite); meetingMembers.push(userLite);
} }
}); });
const startDate = new Date(meeting.start); const startDate = new Date(meeting.startTime);
const startDatemil = startDate.getTime(); const startDatemil = startDate.getTime();
const endDatemil = startDatemil + meeting.duration*60000; const endDatemil = startDatemil + meeting.duration * 60000;
const endDate = new Date(endDatemil); const endDate = new Date(endDatemil);
const currentDatemil = currentDate.getTime(); const currentDatemil = currentDate.getTime();
const meetingMembersString = () => {
if (meetingMembers.length > 3) {
return (
"Participants: " +
meetingMembers[0].name +
", " +
meetingMembers[1].name +
", " +
meetingMembers[2].name +
", and " +
(meetingMembers.length - 3).toString() +
" more..."
);
} else {
return (
"Participants: " +
meetingMembers.map((userLite) => " " + userLite.name).toString()
);
}
};
console.log(meetingMembersString);
if (currentDatemil >= startDatemil && currentDatemil <= endDatemil) { if (currentDatemil >= startDatemil && currentDatemil <= endDatemil) {
const lastMeetingClass = meetings.length == i ? " lastMeeting" : "";
i += 1;
return ( return (
<Meeting <Meeting
meetingName={meeting.topic} meeting={meeting}
meetingTime={formatTimeFromDate(startDate) + " - " + formatTimeFromDate(endDate)} meetingClass={"meeting-" + (i - 1) + lastMeetingClass}
meetingMembers={meetingMembers.map((userLite) => (" " + userLite.name + " ")).toString()} meetingName={meeting.topic}
meetingTime={
formatTimeFromDate(startDate) +
" - " +
formatTimeFromDate(endDate)
}
meetingMembers={meetingMembers
.map((userLite) => " " + userLite.name + " ")
.toString()}
/> />
); );
} }

View File

@ -1,29 +1,20 @@
import PeopleIcon from "@mui/icons-material/People"; import PeopleIcon from "@mui/icons-material/People";
import AddIcon from "@mui/icons-material/Add";
import PhoneEnabledIcon from "@mui/icons-material/PhoneEnabled"; import PhoneEnabledIcon from "@mui/icons-material/PhoneEnabled";
import CircleIcon from "@mui/icons-material/Circle";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { SidebarUserObj } from "./Home";
import CallFavouritesDialog from "./CallFavouritesDialog"; import CallFavouritesDialog from "./CallFavouritesDialog";
import { useState } from "react"; import { useState } from "react";
interface Props { const ShortCuts: React.FC = () => {
users: SidebarUserObj[];
}
const ShortCuts: React.FC<Props> = ({ users }: Props) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(users[0]);
const handleClickOpen = () => { const handleClickOpen = () => {
setOpen(true); setOpen(true);
}; };
const handleClose = (value: SidebarUserObj) => { const handleClose = () => {
setOpen(false); setOpen(false);
setSelectedValue(value);
}; };
return ( return (
@ -33,10 +24,24 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
<Button className="tile"> <Button className="tile">
<PeopleIcon className="icon" /> <PeopleIcon className="icon" />
</Button> </Button>
<Typography className="mylabel" sx={{ ml: 1 }}> <Typography variant="h6" className="mylabel" sx={{ ml: 1 }}>
New Meeting Schedule Meeting
</Typography> </Typography>
</Grid> </Grid>
<Grid item sm={6}>
<Button className="tile" onClick={handleClickOpen}>
<PhoneEnabledIcon className="icon" />
</Button>
<CallFavouritesDialog
open={open}
handleClose={handleClose}
/>
<Typography variant="h6" className="mylabel" sx={{ ml: 1 }}>
Call Group
</Typography>
</Grid>
</Grid>
{/* <Grid className="row-2" container spacing={1}>
<Grid item sm={6}> <Grid item sm={6}>
<Button className="tile"> <Button className="tile">
<AddIcon className="icon" /> <AddIcon className="icon" />
@ -45,22 +50,6 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
Join Join
</Typography> </Typography>
</Grid> </Grid>
</Grid>
<Grid className="row-2" container spacing={1}>
<Grid item sm={6}>
<Button className="tile" onClick={handleClickOpen}>
<PhoneEnabledIcon className="icon" />
</Button>
<CallFavouritesDialog
selectedValue={selectedValue}
open={open}
onClose={handleClose}
users={users}
/>
<Typography className="mylabel" sx={{ ml: 1 }}>
Call Group
</Typography>
</Grid>
<Grid item sm={6}> <Grid item sm={6}>
<Button className="tile"> <Button className="tile">
<CircleIcon className="icon" /> <CircleIcon className="icon" />
@ -69,7 +58,7 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
Recordings Recordings
</Typography> </Typography>
</Grid> </Grid>
</Grid> </Grid> */}
</div> </div>
); );
}; };

View File

@ -9,6 +9,13 @@ import zoomLogo from "../../assets/zoom.png";
import LoginIcon from "@mui/icons-material/Login"; import LoginIcon from "@mui/icons-material/Login";
import useAuth from "../../hooks/useAuth"; import useAuth from "../../hooks/useAuth";
import axios from "../../api/axios"; import axios from "../../api/axios";
import { store } from "../../redux/store";
import { fetchFavorites } from "../../redux/slices/favoritesSlice";
import {
fetchMeetings,
socketActions,
} from "../../redux/slices/meetingsAndUserStatusSlice";
import { fetchUsers } from "../../redux/slices/usersSlice";
interface LocationState { interface LocationState {
from: { pathname: string }; from: { pathname: string };
@ -36,13 +43,17 @@ const Login: React.FC = () => {
// setErrMsg(''); // setErrMsg('');
// }, [user, pwd]) // }, [user, pwd])
const handleLogin = async(e: React.SyntheticEvent) => { const handleLogin = async (e: React.SyntheticEvent) => {
e.preventDefault(); e.preventDefault();
try { try {
if (email === "" && password === "") { if (email === "" && password === "") {
setAuth["uuid"] = ""; setAuth["uuid"] = "";
setAuth["isLoggedIn"] = true; setAuth["isLoggedIn"] = true;
store.dispatch(fetchMeetings(""));
store.dispatch(fetchUsers(""));
store.dispatch(fetchFavorites(""));
store.dispatch(socketActions.startConnecting());
navigate(from, { replace: true }); navigate(from, { replace: true });
} }
@ -62,6 +73,10 @@ const Login: React.FC = () => {
if (logedInUserId != undefined) { if (logedInUserId != undefined) {
setAuth["uuid"] = logedInUserId; setAuth["uuid"] = logedInUserId;
setAuth["isLoggedIn"] = true; setAuth["isLoggedIn"] = true;
store.dispatch(fetchMeetings(logedInUserId));
store.dispatch(fetchUsers(logedInUserId));
store.dispatch(fetchFavorites(logedInUserId));
store.dispatch(socketActions.startConnecting());
navigate(from, { replace: true }); navigate(from, { replace: true });
} }
} catch (error) { } catch (error) {

View File

@ -1,16 +1,7 @@
import DetailedMeeting from "../../api-bodies/DetailedMeeting"; import DetailedMeeting from "../../api-bodies/DetailedMeeting";
const getMeetingStatus = (meeting: DetailedMeeting) => {
const startDate = new Date(meeting.start);
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
const currentDate = new Date();
if (currentDate > startDate && currentDate < endDate) return "Live";
else if (currentDate < startDate) return "Scheduled";
else return "Finished";
};
const getUpcomingMeetingTime = (meeting: DetailedMeeting) => { const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
const startDate = new Date(meeting.start); const startDate = new Date(meeting.startTime);
const endDate = new Date(startDate.getTime() + meeting.duration * 60000); const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
const startTime = startDate const startTime = startDate
.toTimeString() .toTimeString()
@ -27,4 +18,22 @@ const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
return `${startTime} - ${endTime}`; return `${startTime} - ${endTime}`;
}; };
export { getMeetingStatus, getUpcomingMeetingTime }; const getMeetingDuration = (meeting: DetailedMeeting) => {
const startDate = new Date(meeting.startTime);
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
const startDateString = startDate.toDateString();
const startTimeString = startDate
.toTimeString()
.split(" ")[0]
.split(":")
.splice(0, 2)
.join(":");
const endTimeString = endDate
.toTimeString()
.split(" ")[0]
.split(":")
.slice(0, 2)
.join(":");
return `${startDateString} ${startTimeString} - ${endTimeString}`;
};
export { getUpcomingMeetingTime, getMeetingDuration };

View File

@ -1,5 +1,6 @@
import { import {
Box, Box,
Divider,
List, List,
ListItem, ListItem,
ListItemIcon, ListItemIcon,
@ -11,18 +12,80 @@ import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
import DetailedMeeting from "../../../api-bodies/DetailedMeeting"; import DetailedMeeting from "../../../api-bodies/DetailedMeeting";
import { useAppSelector } from "../../../redux/hooks"; import { useAppSelector } from "../../../redux/hooks";
import { selectUsers } from "../../../redux/slices/usersSlice"; import { selectUsers } from "../../../redux/slices/usersSlice";
import { getMeetingStatus } from "../Utils";
import UserLite from "../../../api-bodies/UserLite"; import UserLite from "../../../api-bodies/UserLite";
enum MeetingStatus {
Live = "Live",
Scheduled = "Scheduled",
Finished = "Finished",
}
interface Props { interface Props {
meeting: DetailedMeeting; meeting: DetailedMeeting;
} }
const Body: React.FC<Props> = (props) => { const getMeetingStatus = (meeting: DetailedMeeting) => {
const startDate = new Date(meeting.startTime);
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
const currentDate = new Date();
if (meeting.liveParticipantIds.length === 0) {
if (startDate.getTime() - currentDate.getTime() > 0)
return MeetingStatus.Scheduled;
else if (endDate.getTime() - currentDate.getTime() > 0)
return MeetingStatus.Live;
else return MeetingStatus.Finished;
} else {
return MeetingStatus.Live;
}
};
const Body: React.FC<Props> = ({ meeting }) => {
const registrants: UserLite[] = useAppSelector((state) => const registrants: UserLite[] = useAppSelector((state) =>
selectUsers(state, props.meeting.registrantIds) selectUsers(state, meeting.registrantIds)
);
const liveParticipants: UserLite[] = useAppSelector((state) =>
selectUsers(state, meeting.liveParticipantIds)
); );
const parseIsoString = (s: string) => {
const month = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
][parseInt(s.slice(5, 7)) - 1];
const day = s.slice(8, 10);
const isoTime: string = s.slice(11, 16);
const hour: number = parseInt(isoTime.slice(0, 2));
const hourMod: string = hour % 12 == 0 ? "12" : (hour % 12).toString();
return (
month +
" " +
day +
", " +
hourMod +
":" +
isoTime.slice(3, 5) +
(hour > 11 ? "PM" : "AM")
);
};
const start = meeting.startTime;
const startDateString = parseIsoString(start);
const listLiveParticipants = () => {
const s: string[] = liveParticipants.map((user) => user.name + ", ");
return s.join("").slice(0, -2);
};
const meetingStatus = getMeetingStatus(meeting);
return ( return (
<Box <Box
sx={{ sx={{
@ -39,16 +102,28 @@ const Body: React.FC<Props> = (props) => {
flexDirection: "column", flexDirection: "column",
width: "70%", width: "70%",
height: "100%", height: "100%",
mt: 10,
}} }}
> >
<Typography>Feb 10, 2022 10:45 am - 11:00 am</Typography> <Typography sx={{ pl: 5 }} variant="overline">
<Typography>Status: {getMeetingStatus(props.meeting)}</Typography> Meeting ID: {meeting.meetingId}
<Typography> </Typography>
Topic: Lorem Ipsum is simply dummy text of the printing and <Typography sx={{ pl: 5, color: "#af000d" }} variant="h2">
typesetting industry. Lorem Ipsum has been the industrys standard {meeting.topic}
dummy text ever since the 1500s. Topic: Lorem Ipsum is simply dummy </Typography>
text of the printing and typesetting industry. Lorem Ipsum has been <Divider sx={{ mb: 3 }} />
the industrys standard dummy text ever since the 1500s. <Typography sx={{ pl: 5 }} variant="h4">
Start: {startDateString}
</Typography>
<Typography sx={{ pl: 5 }} variant="h4">
Duration: {meeting.duration.toString() + " minutes"}
</Typography>
<Typography sx={{ my: 1 }} />
<Typography sx={{ pl: 5 }} variant="button">
Currently inside: {listLiveParticipants()}
</Typography>
<Typography sx={{ pl: 5 }} variant="button">
Status: {meetingStatus}
</Typography> </Typography>
</Box> </Box>
<Box <Box

View File

@ -6,24 +6,27 @@ import { close } from "../../../redux/slices/meetingDetailsOpenSlice";
import DetailedMeeting from "../../../api-bodies/DetailedMeeting"; import DetailedMeeting from "../../../api-bodies/DetailedMeeting";
interface Props { interface Props {
meeting: DetailedMeeting | null; meeting: DetailedMeeting;
} }
const Header: React.FC<Props> = (props) => { const Header: React.FC<Props> = ({ meeting }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
return ( return (
<AppBar sx={{ position: "relative" }}> <AppBar sx={{ position: "relative" }}>
<Toolbar> <Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1 }}> <Typography variant="h6" sx={{ flexGrow: 1 }}>
{props.meeting !== null ? props.meeting.topic : null} {meeting !== null ? meeting.topic : null}
</Typography> </Typography>
<Button variant="contained" color="secondary"> <Button
variant="contained"
color="secondary"
onClick={() => {
window.open(meeting.joinUrl, "_blank")?.focus();
}}
>
Join Join
</Button> </Button>
<Button variant="contained" color="secondary" sx={{ ml: 1 }}>
Recording
</Button>
<IconButton <IconButton
edge="start" edge="start"
color="inherit" color="inherit"

View File

@ -1,5 +1,5 @@
import { Settings } from "@mui/icons-material"; import { Settings } from "@mui/icons-material";
import { IconButton, Menu, MenuItem } from "@mui/material"; import { Alert, IconButton, Menu, MenuItem, Snackbar } from "@mui/material";
import { useState } from "react"; import { useState } from "react";
import UserLite from "../../api-bodies/UserLite"; import UserLite from "../../api-bodies/UserLite";
import UserStatus from "../../api-bodies/UserStatus"; import UserStatus from "../../api-bodies/UserStatus";
@ -13,6 +13,8 @@ import { open as openMeetingDetails } from "../../redux/slices/meetingDetailsOpe
import { selectMeeting } from "../../redux/slices/meetingsAndUserStatusSlice"; import { selectMeeting } from "../../redux/slices/meetingsAndUserStatusSlice";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { selectMe } from "../../redux/slices/usersSlice"; import { selectMe } from "../../redux/slices/usersSlice";
import NewMeeting from "../../api-bodies/NewMeeting";
import axios from "../../api/axios";
interface Props { interface Props {
user: UserLite; user: UserLite;
@ -22,6 +24,7 @@ interface Props {
const SettingsButton: React.FC<Props> = ({ user, status }: Props) => { const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const me = useAppSelector(selectMe); const me = useAppSelector(selectMe);
const favoritesUuids = useAppSelector(selectFavorites); const favoritesUuids = useAppSelector(selectFavorites);
const meeting = useAppSelector((state) => const meeting = useAppSelector((state) =>
@ -35,6 +38,30 @@ const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
const handleSnackbarClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
if (reason === "clickaway") {
return;
}
setSnackbarOpen(false);
};
const startCall = async () => {
const newMeeting: NewMeeting = {
startTime: "2022-03-30T23:40:00Z",
duration: 30,
topic: `Meeting with ${user.name}`,
registrantIds: [user.uuid],
};
try {
const response = await axios.post(
`/users/${me}/meetings`,
JSON.stringify(newMeeting)
);
window.open(response.data.joinUrl, "_blank")?.focus();
} catch (e) {
console.log(e);
}
};
return ( return (
<div> <div>
@ -66,12 +93,14 @@ const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
) : ( ) : (
<MenuItem <MenuItem
onClick={() => { onClick={() => {
setSnackbarOpen(true);
handleClose(); handleClose();
dispatch(addFavorite({ userId: me, toBeAdded: user.uuid })); dispatch(addFavorite({ userId: me, toBeAdded: user.uuid }));
}} }}
> >
Add to Favorites Add to Favorites
</MenuItem> </MenuItem>
)} )}
{status.inMeeting ? ( {status.inMeeting ? (
<MenuItem <MenuItem
@ -91,8 +120,22 @@ const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
> >
View upcoming meetings View upcoming meetings
</MenuItem> </MenuItem>
<MenuItem onClick={handleClose}>Create meeting</MenuItem> <MenuItem
onClick={() => {
handleClose();
startCall();
}}
>
Create meeting
</MenuItem>
</Menu> </Menu>
<Snackbar
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
open={snackbarOpen} autoHideDuration={3000} onClose={handleSnackbarClose}>
<Alert onClose={handleSnackbarClose} severity="success" sx={{ width: "100%" }}>
User successfully added to favourites list!
</Alert>
</Snackbar>
</div> </div>
); );
}; };

View File

@ -1,10 +1,8 @@
import { Middleware } from "redux"; import { Middleware } from "redux";
import { io, Socket } from "socket.io-client";
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
import { socketActions } from "../slices/meetingsAndUserStatusSlice"; import { socketActions } from "../slices/meetingsAndUserStatusSlice";
const socketMiddleware: Middleware = (store) => { const socketMiddleware: Middleware = (store) => {
let socket: Socket; let socket: WebSocket;
return (next) => (action) => { return (next) => (action) => {
const isConnectionEstablished = const isConnectionEstablished =
@ -14,28 +12,23 @@ const socketMiddleware: Middleware = (store) => {
socketActions.startConnecting.match(action) && socketActions.startConnecting.match(action) &&
!isConnectionEstablished !isConnectionEstablished
) { ) {
console.log("startConnecting called"); console.log("start connecting");
socket = io("wss://uo5wdcbn6l.execute-api.us-west-2.amazonaws.com/Prod/"); socket = new WebSocket(
"wss://uo5wdcbn6l.execute-api.us-west-2.amazonaws.com/Prod/"
socket.on("Connect", () => {
console.log("connected!!!");
store.dispatch(socketActions.connectionEstablished());
});
socket.on("MeetingCreated", (meeting: DetailedMeeting) => {
store.dispatch(socketActions.meetingCreated(meeting));
});
socket.on(
"UserStatusChange",
(statusChange: {
userId: string;
inMeeting: boolean;
meetingId: string;
}) => {
store.dispatch(socketActions.userStatusChanged(statusChange));
}
); );
socket.onopen = () => {
console.log("connected");
store.dispatch(socketActions.connectionEstablished());
};
socket.addEventListener("message", (event: MessageEvent) => {
const json = JSON.parse(event.data);
console.log(json);
if ("inMeeting" in json) {
store.dispatch(socketActions.userStatusChanged(json));
} else {
store.dispatch(socketActions.meetingCreated(json));
}
});
} }
next(action); next(action);
}; };

View File

@ -31,10 +31,10 @@ export const meetingsAndUserStatusSlice = createSlice({
state.isConnected = true; state.isConnected = true;
}, },
meetingCreated: (state, action) => { meetingCreated: (state, action) => {
state.meetings.push(action.payload.meeting); state.meetings.push(action.payload);
}, },
userStatusChanged: (state, action) => { userStatusChanged: (state, action) => {
state.userStatuses[action.payload.uuid] = action.payload; state.userStatuses[action.payload.userId] = action.payload;
}, },
}, },
extraReducers(builder) { extraReducers(builder) {
@ -90,9 +90,25 @@ export const selectMeeting = (state: RootState, meetingID: string | null) => {
: null; : null;
}; };
export const selectUserUpcomingMeetings = (state: RootState, uuid: string) => { export const selectUserUpcomingMeetings = (state: RootState, uuid: string) => {
return state.meetingsAndUserStatuses.meetings.filter((meeting) => const isToday = (date: Date) => {
meeting.registrantIds.includes(uuid) const today = new Date();
); return (
date.getDate() == today.getDate() &&
date.getMonth() == today.getMonth() &&
date.getFullYear() == today.getFullYear()
);
};
return state.meetingsAndUserStatuses.meetings
.filter(
(meeting) =>
meeting.registrantIds.includes(uuid) &&
meeting.startTime &&
isToday(new Date(meeting.startTime))
)
.sort(
(a, b) =>
new Date(a.startTime).getTime() - new Date(b.startTime).getTime()
);
}; };
export const selectUserStatus = ( export const selectUserStatus = (
state: RootState, state: RootState,

View File

@ -47,14 +47,14 @@ export const fetchUsers = createAsyncThunk(
}; };
// fetch userfull // fetch userfull
const userResp = await axios.get(`/users/${uuid}`); const userResp = await axios.get(`/users/${uuid}`);
const user: UserFull = userResp.data.user; const user: UserFull = userResp.data["user"];
console.log("1");
users[user.userInfo.uuid] = user.userInfo; users[user.userInfo.uuid] = user.userInfo;
users[user.manager.uuid] = user.manager; console.log("2");
if (user.manager) users[user.manager.uuid] = user.manager;
team.user = user.userInfo.uuid; team.user = user.userInfo.uuid;
team.manager = user.manager.uuid; if (user.manager) team.manager = user.manager.uuid;
user.userDirectReports.forEach((userLite) => { user.userDirectReports.forEach((userLite) => {
users[userLite.uuid] = userLite; users[userLite.uuid] = userLite;
team.directReports.push(userLite.uuid); team.directReports.push(userLite.uuid);
@ -76,6 +76,7 @@ export const fetchUsers = createAsyncThunk(
); );
export const selectMe = (state: RootState) => state.users.team.user; export const selectMe = (state: RootState) => state.users.team.user;
export const selectManager = (state: RootState) => state.users.team.manager;
export const selectUser = ( export const selectUser = (
state: RootState, state: RootState,
uuid: string | undefined uuid: string | undefined

View File

@ -11,13 +11,13 @@ a {
} }
.main-home .meetings-panel { .main-home .meetings-panel {
margin: auto;
margin-top: 15vh; margin-top: 15vh;
padding-bottom: 10vh;
border-style: solid; border-style: solid;
border-color: #D3D3D3; border-color: mistyrose;
border-width: 2px; border-width: 2px;
border-radius: 40px; border-radius: 40px;
width: 65%; width: 85%;
height: 65vh; height: 65vh;
overflow: hidden; overflow: hidden;
} }
@ -28,24 +28,24 @@ a {
.main-home .meetings-panel .panel-label { .main-home .meetings-panel .panel-label {
text-align: center; text-align: center;
background-color: grey; background-color: #AF000D;
padding-top: 2%; padding-top: 2%;
padding-bottom: 2%; padding-bottom: 2%;
} }
.main-home .meetings-panel .meetingEntries::-webkit-scrollbar {
display: none;
}
.main-home .meetings-panel .meeting { .main-home .meetings-panel .meeting {
background-color: #D3D3D3; background-color: lightcoral;
text-align: center; text-align: left;
margin-top: 15px; margin-top: 15px;
line-height: 2; line-height: 2;
} }
.main-home .meetings-panel .lastMeeting {
margin-bottom: 10%;
}
.main-home .short-cuts .row-1 { .main-home .short-cuts .row-1 {
margin-top: 30vh; margin-top: 40vh;
} }
.main-home .short-cuts .row-2 { .main-home .short-cuts .row-2 {
@ -54,23 +54,24 @@ a {
.main-home .short-cuts .mylabel { .main-home .short-cuts .mylabel {
display: inline-block; display: inline-block;
margin: 0; margin-left: 25%;
width: 100px; width: 125px;
text-align: center; text-align: center;
} }
.main-home .short-cuts .tile { .main-home .short-cuts .tile {
margin-right: 100%; margin-left: 25%;
background-color: #D3D3D3; margin-right: 50%;
width: 100px; background-color: mistyrose;
height: 100px; width: 125px;
height: 125px;
border-radius: 16px; border-radius: 16px;
} }
.main-home .short-cuts .tile .icon { .main-home .short-cuts .tile .icon {
color: black; color: black;
width: 70%; width: 80%;
height: 70%; height: 80%;
} }
.login .grid-container { .login .grid-container {

View File

@ -1,6 +1,6 @@
{ {
"version": 3, "version": 3,
"mappings": "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", "mappings": "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",
"sources": [ "sources": [
"App.sass", "App.sass",
"_variables.sass", "_variables.sass",

View File

@ -3,20 +3,20 @@
// width: 100% !important // width: 100% !important
max-width: 85% !important max-width: 85% !important
.meetings-panel .meetings-panel
margin: auto
margin-top: 15vh margin-top: 15vh
padding-bottom: 10vh
border-style: solid border-style: solid
border-color: $main-home-background-color border-color: mistyrose
border-width: 2px border-width: 2px
border-radius: 40px border-radius: 40px
width: 65% width: 85%
height: 65vh height: 65vh
overflow: hidden overflow: hidden
.row .row
width: 100% width: 100%
.panel-label .panel-label
text-align: center text-align: center
background-color: grey background-color: #AF000D
padding-top: 2% padding-top: 2%
padding-bottom: 2% padding-bottom: 2%
// .current-time // .current-time
@ -29,27 +29,31 @@
// background-color: grey // background-color: grey
// padding-bottom: 1% // padding-bottom: 1%
.meeting .meeting
background-color: $main-home-background-color background-color: lightcoral
text-align: center text-align: left
margin-top: 15px margin-top: 15px
line-height: 2 line-height: 2
.lastMeeting
margin-bottom: 10%
.short-cuts .short-cuts
.row-1 .row-1
margin-top: 30vh // margin-top: 30vh
margin-top: 40vh
.row-2 .row-2
margin-top: 5vh margin-top: 5vh
.mylabel .mylabel
display: inline-block display: inline-block
margin: 0 margin-left: 25%
width: 100px width: 125px
text-align: center text-align: center
.tile .tile
margin-right: 100% margin-left: 25%
background-color: $main-home-background-color margin-right: 50%
width: 100px background-color: mistyrose
height: 100px width: 125px
height: 125px
border-radius: 16px border-radius: 16px
.icon .icon
color: black color: black
width: 70% width: 80%
height: 70% height: 80%

View File

@ -1,6 +1,7 @@
const enum MeetingStatus { const enum MeetingStatus {
NOT_IN_MEETING = "Not in meeting", NOT_IN_MEETING = "Not in meeting",
IN_MEETING = "In meeting", IN_MEETING = "In meeting",
NOT_AVAILABLE = "Not available",
} }
const getStatusColor = (ms: MeetingStatus): string => { const getStatusColor = (ms: MeetingStatus): string => {
@ -11,6 +12,9 @@ const getStatusColor = (ms: MeetingStatus): string => {
case MeetingStatus.IN_MEETING: { case MeetingStatus.IN_MEETING: {
return "#ff7070"; return "#ff7070";
} }
case MeetingStatus.NOT_AVAILABLE: {
return "#808080";
}
} }
}; };