diff --git a/package-lock.json b/package-lock.json index b16dfdc..7fa37a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4815,6 +4815,7 @@ "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", + "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -8032,6 +8033,12 @@ "dev": true, "dependencies": { "imagemin": "^7.0.1", + "imagemin-gifsicle": "^7.0.0", + "imagemin-mozjpeg": "^9.0.0", + "imagemin-optipng": "^8.0.0", + "imagemin-pngquant": "^9.0.2", + "imagemin-svgo": "^9.0.0", + "imagemin-webp": "^7.0.0", "loader-utils": "^2.0.0", "object-assign": "^4.1.1", "schema-utils": "^2.7.1" diff --git a/src/App.tsx b/src/App.tsx index bb1333a..f8e9b69 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import CalendarPage from "./components/calendar/CalendarPage"; import { ThemeProvider } from "@emotion/react"; import ProtectedRoute from "./ProtectedRoute"; import MeetingDetails from "./components/meeting-details/MeetingDetails"; +import MeetingLink from "./components/meeting-link/MeetingLink"; import Theme from "./Theme"; @@ -29,6 +30,7 @@ const App: React.FC = () => { + ); }; diff --git a/src/components/contacts/contacts-components/body-components/UpperBody.tsx b/src/components/contacts/contacts-components/body-components/UpperBody.tsx index 82551e5..7708dea 100644 --- a/src/components/contacts/contacts-components/body-components/UpperBody.tsx +++ b/src/components/contacts/contacts-components/body-components/UpperBody.tsx @@ -1,14 +1,5 @@ -import { - Box, - Button, - Dialog, - DialogContent, - DialogContentText, - DialogTitle, - IconButton, - Typography, -} from "@mui/material"; -import React, { useState } from "react"; +import { Box, Button, IconButton, Typography } from "@mui/material"; +import React from "react"; import PhoneIcon from "@mui/icons-material/Phone"; import AddIcon from "@mui/icons-material/Add"; import UserLite from "../../../../api-bodies/UserLite"; @@ -28,6 +19,7 @@ import { MeetingStatus } from "../../../../utils"; import { selectManager, selectMe } from "../../../../redux/slices/usersSlice"; import axios from "../../../../api/axios"; import NewMeeting from "../../../../api-bodies/NewMeeting"; +import { open } from "../../../../redux/slices/meetingUrlSlice"; interface Props { contactInfo: UserLite; @@ -35,7 +27,6 @@ interface Props { const UpperBody: React.FC = ({ contactInfo }) => { const dispatch = useAppDispatch(); - const [dialog, setDialog] = useState({ open: false, url: "" }); const userStatus: UserStatus = useAppSelector((state) => selectUserStatus(state, contactInfo.uuid) ); @@ -53,18 +44,21 @@ const UpperBody: React.FC = ({ contactInfo }) => { : MeetingStatus.NOT_IN_MEETING; const startCall = async () => { + const start = new Date().toISOString(); + let newStart = start.split(".")[0]; + newStart += start.includes("Z") ? "Z" : ""; const newMeeting: NewMeeting = { - startTime: "2022-03-30T23:40:00Z", + startTime: newStart, duration: 30, topic: `Meeting with ${contactInfo.name}`, - registrantIds: [contactInfo.uuid], + registrantIds: [contactInfo.uuid, me], }; try { const response = await axios.post( `/users/${me}/meetings`, JSON.stringify(newMeeting) ); - setDialog({ open: true, url: response.data.joinUrl }); + dispatch(open(response.data.joinUrl)); } catch (e) { console.log(e); } @@ -92,7 +86,9 @@ const UpperBody: React.FC = ({ contactInfo }) => { mx: 4, }} > - {contactInfo.name} + + {contactInfo.name} + {!favoritesUuids.includes(contactInfo.uuid) ? ( - { - seturlDialog({ open: false, url: "" }); - }} - > - Join - - {urlDialog.url} - - ); }; diff --git a/src/components/home/MeetingsPanel.tsx b/src/components/home/MeetingsPanel.tsx index ef5332d..2abd93a 100644 --- a/src/components/home/MeetingsPanel.tsx +++ b/src/components/home/MeetingsPanel.tsx @@ -9,7 +9,6 @@ import { selectUsers } from "../../redux/slices/usersSlice"; import { formatTimeFromDate } from "../../utils"; import UserLite from "../../api-bodies/UserLite"; - const EmptyMeetingsList: React.FC = () => { return ( { {/* */} - - No Meetings in Progress - + No Meetings in Progress @@ -36,7 +33,7 @@ const EmptyMeetingsList: React.FC = () => { ); -} +}; const MeetingsList: React.FC = () => { const meetings = useAppSelector(selectMeetings); @@ -68,73 +65,55 @@ const MeetingsList: React.FC = () => { let i = 1; return ( <> - - {meetings.map((meeting) => { - const meetingMembers: UserLite[] = []; - participants.forEach((userLite) => { + + {meetings.map((meeting) => { + const meetingMembers: UserLite[] = []; + participants.forEach((userLite) => { + if ( + userLite != undefined && + meeting.liveParticipantIds.includes(userLite.uuid) + ) { + meetingMembers.push(userLite); + } + }); + const startDate = new Date(meeting.startTime); + const startDatemil = startDate.getTime(); + const endDatemil = startDatemil + meeting.duration * 60000; + const endDate = new Date(endDatemil); + + const currentDatemil = currentDate.getTime(); + + // console.log(meetingMembersString); if ( - userLite != undefined && - meeting.liveParticipantIds.includes(userLite.uuid) + (currentDatemil >= startDatemil && currentDatemil <= endDatemil) || + meeting.liveParticipantIds.length > 0 ) { - meetingMembers.push(userLite); - } - }); - const startDate = new Date(meeting.startTime); - const startDatemil = startDate.getTime(); - const endDatemil = startDatemil + meeting.duration * 60000; - const endDate = new Date(endDatemil); - - const currentDatemil = currentDate.getTime(); - - const meetingMembersString = () => { - if (meetingMembers.length > 3) { + const lastMeetingClass = meetings.length == i ? " lastMeeting" : ""; + i += 1; 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() + " " + userLite.name + " ") + .toString()} + /> ); } - }; - // console.log(meetingMembersString); - if ((currentDatemil >= startDatemil && currentDatemil <= endDatemil) || meeting.liveParticipantIds.length > 0) { - const lastMeetingClass = meetings.length == i ? " lastMeeting" : ""; - i += 1; - return ( - " " + userLite.name + " ") - .toString()} - /> - ); - } - })} - - {i === 1 ? : <>} + })} + + {i === 1 ? : <>} ); -} +}; const MeetingsPanel: React.FC = () => { - return (
diff --git a/src/components/home/ScheduleMeetingDialog.tsx b/src/components/home/ScheduleMeetingDialog.tsx index 14709b6..19d45fd 100644 --- a/src/components/home/ScheduleMeetingDialog.tsx +++ b/src/components/home/ScheduleMeetingDialog.tsx @@ -1,197 +1,194 @@ import { - Button, - Checkbox, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - FormControlLabel, - FormGroup, - TextField, - Typography, - } from "@mui/material"; - import LocalizationProvider from '@mui/lab/LocalizationProvider'; - import AdapterDateFns from '@mui/lab/AdapterDateFns'; - import DateTimePicker from '@mui/lab/DateTimePicker'; - import React, { useState } from "react"; - import { useAppSelector } from "../../redux/hooks"; - import { selectFavorites } from "../../redux/slices/favoritesSlice"; - import { selectTeam, selectUsers } from "../../redux/slices/usersSlice"; - import GroupSelect from "../sidebar/GroupSelect"; - import axios from "../../api/axios"; - import useAuth from "../../hooks/useAuth"; - import DetailedMeeting from "../../api-bodies/DetailedMeeting"; - import NewMeeting from "../../api-bodies/NewMeeting"; - - interface Props { - open: boolean; - handleClose: () => void; - } - - const CallScheduledMeetingDialog: React.FC = ({ - open, - handleClose, - }: Props) => { - - const [meetingStartDate, setMeetingStartDate] = React.useState(new Date()); - const [meetingTopic, setMeetingTopic] = useState(""); - const [meetingDuration, setMeetingDuration] = useState(60); - const [group, setGroup] = useState("Favorites"); - const [inputText, setInputText] = useState(""); - const [checkedUuids, setCheckedUuids] = useState([]); - const [urlDialog, seturlDialog] = useState({ open: false, url: "" }); - - const handleCheck = (uuid: string, checked: boolean) => { - if (checked) { - setCheckedUuids(checkedUuids.concat([uuid])); - } else { - setCheckedUuids(checkedUuids.filter((id) => id != uuid)); - } - }; - - const handleGroupChange = () => { - setCheckedUuids([]); - }; + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControlLabel, + FormGroup, + TextField, + Typography, +} from "@mui/material"; +import LocalizationProvider from "@mui/lab/LocalizationProvider"; +import AdapterDateFns from "@mui/lab/AdapterDateFns"; +import DateTimePicker from "@mui/lab/DateTimePicker"; +import React, { useState } from "react"; +import { useAppSelector, useAppDispatch } from "../../redux/hooks"; +import { selectFavorites } from "../../redux/slices/favoritesSlice"; +import { selectTeam, selectUsers } from "../../redux/slices/usersSlice"; +import GroupSelect from "../sidebar/GroupSelect"; +import axios from "../../api/axios"; +import useAuth from "../../hooks/useAuth"; +import DetailedMeeting from "../../api-bodies/DetailedMeeting"; +import NewMeeting from "../../api-bodies/NewMeeting"; +import { open as openMeetingUrl } from "../../redux/slices/meetingUrlSlice"; - const handleDateChange = (startDate: Date | null, keyboardInputValue?: string | undefined) => { - setMeetingStartDate(startDate); - }; - - - const favoritesUuids = useAppSelector(selectFavorites); - const teamUuids = useAppSelector(selectTeam); - - const groupMembersUuids: string[] = - group === "Favorites" ? favoritesUuids : teamUuids; - const groupMembers = useAppSelector((state) => - selectUsers(state, groupMembersUuids) - ); - - const auth = useAuth(); - - const handleCall = async(e: React.SyntheticEvent) => { - const start = meetingStartDate ? meetingStartDate.toISOString() : new Date().toISOString(); - let newStart = start.split('.')[0]; - newStart += start.includes("Z") ? "Z" : ""; - const newMeeting: NewMeeting ={ - startTime: newStart, - duration: meetingDuration, - topic: meetingTopic, - registrantIds: checkedUuids - }; - const response = await axios.post( - `/users/${auth?.uuid}/meetings`, - JSON.stringify(newMeeting) - ); - const meeting: DetailedMeeting = response?.data; - console.log("create meeting response: " + meeting.startTime + ":" + meeting.duration); - seturlDialog({ open: true, url: meeting.joinUrl }); - handleClose(); - }; - - return ( - <> - - Schedule A Meeting - - - } - /> - - { - setMeetingDuration(parseInt(event.target.value)); - }} - /> - - - { - setMeetingTopic(event.target.value); - }} - /> - - - - - - { - setInputText(e.target.value); - }} - /> - - {groupMembers - .filter((member) => - member.name.toLowerCase().includes(inputText.toLowerCase()) - ) - .map((member) => ( - { - handleCheck(member.uuid, e.target.checked); - }} - control={ - - } - /> - ))} - - - - - - - - { - seturlDialog({ open: false, url: "" }); - }} - > - Join - - {urlDialog.url} - - - - ); +interface Props { + open: boolean; + handleClose: () => void; +} + +const CallScheduledMeetingDialog: React.FC = ({ + open, + handleClose, +}: Props) => { + const [meetingStartDate, setMeetingStartDate] = React.useState( + new Date() + ); + const [meetingTopic, setMeetingTopic] = useState(""); + const [meetingDuration, setMeetingDuration] = useState(60); + const [group, setGroup] = useState("Favorites"); + const [inputText, setInputText] = useState(""); + const [checkedUuids, setCheckedUuids] = useState([]); + const dispatch = useAppDispatch(); + + const handleCheck = (uuid: string, checked: boolean) => { + if (checked) { + setCheckedUuids(checkedUuids.concat([uuid])); + } else { + setCheckedUuids(checkedUuids.filter((id) => id != uuid)); + } }; - - export default CallScheduledMeetingDialog; \ No newline at end of file + + const handleGroupChange = () => { + setCheckedUuids([]); + }; + + const handleDateChange = ( + startDate: Date | null, + /* eslint-disable @typescript-eslint/no-unused-vars */ + keyboardInputValue?: string | undefined + ) => { + setMeetingStartDate(startDate); + }; + + const favoritesUuids = useAppSelector(selectFavorites); + const teamUuids = useAppSelector(selectTeam); + + const groupMembersUuids: string[] = + group === "Favorites" ? favoritesUuids : teamUuids; + const groupMembers = useAppSelector((state) => + selectUsers(state, groupMembersUuids) + ); + + const auth = useAuth(); + + const handleCall = async () => { + const start = meetingStartDate + ? meetingStartDate.toISOString() + : new Date().toISOString(); + let newStart = start.split(".")[0]; + newStart += start.includes("Z") ? "Z" : ""; + const newMeeting: NewMeeting = { + startTime: newStart, + duration: meetingDuration, + topic: meetingTopic, + registrantIds: checkedUuids, + }; + const response = await axios.post( + `/users/${auth?.uuid}/meetings`, + JSON.stringify(newMeeting) + ); + const meeting: DetailedMeeting = response?.data; + console.log( + "create meeting response: " + meeting.startTime + ":" + meeting.duration + ); + dispatch(openMeetingUrl(response.data.joinUrl)); + handleClose(); + }; + + return ( + <> + + Schedule A Meeting + + + } + /> + + { + setMeetingDuration(parseInt(event.target.value)); + }} + /> + + + { + setMeetingTopic(event.target.value); + }} + /> + + + + + + { + setInputText(e.target.value); + }} + /> + + {groupMembers + .filter((member) => + member.name.toLowerCase().includes(inputText.toLowerCase()) + ) + .map((member) => ( + { + handleCheck(member.uuid, e.target.checked); + }} + control={ + + } + /> + ))} + + + + + + + + + ); +}; + +export default CallScheduledMeetingDialog; diff --git a/src/components/meeting-details/meeting-details-components/Header.tsx b/src/components/meeting-details/meeting-details-components/Header.tsx index 3c6b2f4..56474ee 100644 --- a/src/components/meeting-details/meeting-details-components/Header.tsx +++ b/src/components/meeting-details/meeting-details-components/Header.tsx @@ -1,19 +1,10 @@ -import { - AppBar, - Button, - Dialog, - DialogContent, - DialogContentText, - DialogTitle, - IconButton, - Toolbar, - Typography, -} from "@mui/material"; -import React, { useState } from "react"; +import { AppBar, Button, IconButton, Toolbar, Typography } from "@mui/material"; +import React from "react"; import CloseIcon from "@mui/icons-material/Close"; import { useAppDispatch } from "../../../redux/hooks"; import { close } from "../../../redux/slices/meetingDetailsOpenSlice"; import DetailedMeeting from "../../../api-bodies/DetailedMeeting"; +import { open as openMeetingUrl } from "../../../redux/slices/meetingUrlSlice"; interface Props { meeting: DetailedMeeting; @@ -21,7 +12,6 @@ interface Props { const Header: React.FC = ({ meeting }) => { const dispatch = useAppDispatch(); - const [dialog, setDialog] = useState({ open: false, url: "" }); return ( @@ -33,7 +23,7 @@ const Header: React.FC = ({ meeting }) => { variant="contained" color="secondary" onClick={() => { - setDialog({ open: true, url: meeting.joinUrl }); + dispatch(openMeetingUrl(meeting.joinUrl)); }} > Join @@ -48,17 +38,6 @@ const Header: React.FC = ({ meeting }) => { - { - setDialog({ open: false, url: "" }); - }} - > - Join - - {dialog.url} - - ); }; diff --git a/src/components/meeting-link/MeetingLink.tsx b/src/components/meeting-link/MeetingLink.tsx new file mode 100644 index 0000000..1caed73 --- /dev/null +++ b/src/components/meeting-link/MeetingLink.tsx @@ -0,0 +1,30 @@ +import { + Dialog, + DialogContent, + DialogContentText, + DialogTitle, +} from "@mui/material"; +import React from "react"; +import { useAppSelector, useAppDispatch } from "../../redux/hooks"; +import { close, selectMeetingUrl } from "../../redux/slices/meetingUrlSlice"; + +const warningText = + "For real-time user status updates to function correctly, please ensure you are logged into the Zoom desktop client with your Digital Walkaround Zoom account and that you are not logged into the Zoom.us website in this browser with a Zoom account different from your Digital Walkaround one"; + +const MeetingLink: React.FC = () => { + const meetingUrl = useAppSelector(selectMeetingUrl); + const dispatch = useAppDispatch(); + return ( + dispatch(close())}> + Join + + + {"Link: " + meetingUrl.url} + + {warningText} + + + ); +}; + +export default MeetingLink; diff --git a/src/components/sidebar/SettingsButton.tsx b/src/components/sidebar/SettingsButton.tsx index 1798b22..9297940 100644 --- a/src/components/sidebar/SettingsButton.tsx +++ b/src/components/sidebar/SettingsButton.tsx @@ -15,6 +15,7 @@ import { useNavigate } from "react-router-dom"; import { selectMe } from "../../redux/slices/usersSlice"; import NewMeeting from "../../api-bodies/NewMeeting"; import axios from "../../api/axios"; +import { open as openMeetingUrl } from "../../redux/slices/meetingUrlSlice"; interface Props { user: UserLite; @@ -38,7 +39,10 @@ const SettingsButton: React.FC = ({ user, status }: Props) => { const handleClose = () => { setAnchorEl(null); }; - const handleSnackbarClose = (event?: React.SyntheticEvent | Event, reason?: string) => { + const handleSnackbarClose = ( + event?: React.SyntheticEvent | Event, + reason?: string + ) => { if (reason === "clickaway") { return; } @@ -46,18 +50,21 @@ const SettingsButton: React.FC = ({ user, status }: Props) => { }; const startCall = async () => { + const start = new Date().toISOString(); + let newStart = start.split(".")[0]; + newStart += start.includes("Z") ? "Z" : ""; const newMeeting: NewMeeting = { - startTime: "2022-03-30T23:40:00Z", + startTime: newStart, duration: 30, topic: `Meeting with ${user.name}`, - registrantIds: [user.uuid], + registrantIds: [user.uuid, me], }; try { const response = await axios.post( `/users/${me}/meetings`, JSON.stringify(newMeeting) ); - window.open(response.data.joinUrl, "_blank")?.focus(); + dispatch(openMeetingUrl(response.data.joinUrl)); } catch (e) { console.log(e); } @@ -100,7 +107,6 @@ const SettingsButton: React.FC = ({ user, status }: Props) => { > Add to Favorites - )} {status.inMeeting ? ( = ({ user, status }: Props) => { Create meeting - - + open={snackbarOpen} + autoHideDuration={3000} + onClose={handleSnackbarClose} + > + User successfully added to favourites list! diff --git a/src/redux/slices/meetingDetailsOpenSlice.tsx b/src/redux/slices/meetingDetailsOpenSlice.tsx index 9ee00fc..7b6e6ca 100644 --- a/src/redux/slices/meetingDetailsOpenSlice.tsx +++ b/src/redux/slices/meetingDetailsOpenSlice.tsx @@ -19,7 +19,6 @@ export const meetingDetailsOpenSlice = createSlice({ open: (state, action) => { state.open = true; state.meeting = action.payload; - console.log(action.payload); }, close: (state) => { state.open = false; diff --git a/src/redux/slices/meetingUrlSlice.tsx b/src/redux/slices/meetingUrlSlice.tsx new file mode 100644 index 0000000..24fa102 --- /dev/null +++ b/src/redux/slices/meetingUrlSlice.tsx @@ -0,0 +1,31 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { RootState } from "../store"; + +interface MeetingUrlState { + open: boolean; + url: string; +} + +const initialState: MeetingUrlState = { + open: false, + url: "", +}; + +export const meetingUrlSlice = createSlice({ + name: "meetingUrl", + initialState, + reducers: { + open: (state, action) => { + state.open = true; + state.url = action.payload; + }, + close: (state) => { + state.open = false; + state.url = ""; + }, + }, +}); + +export const { open, close } = meetingUrlSlice.actions; +export const selectMeetingUrl = (state: RootState) => state.meetingUrl; +export default meetingUrlSlice.reducer; diff --git a/src/redux/slices/meetingsAndUserStatusSlice.tsx b/src/redux/slices/meetingsAndUserStatusSlice.tsx index 6298a9d..f3af0c4 100644 --- a/src/redux/slices/meetingsAndUserStatusSlice.tsx +++ b/src/redux/slices/meetingsAndUserStatusSlice.tsx @@ -31,9 +31,28 @@ export const meetingsAndUserStatusSlice = createSlice({ state.isConnected = true; }, meetingCreated: (state, action) => { + console.log(action.payload); state.meetings.push(action.payload); }, userStatusChanged: (state, action) => { + const meetingIndex = state.meetings.findIndex( + (meeting) => meeting.meetingId === action.payload.meetingId + ); + const liveParticipantIndex = state.meetings[ + meetingIndex + ].liveParticipantIds.indexOf(action.payload.userId); + if (action.payload.inMeeting) { + if (liveParticipantIndex === -1) + state.meetings[meetingIndex].liveParticipantIds.push( + action.payload.userId + ); + } else { + if (liveParticipantIndex !== -1) + state.meetings[meetingIndex].liveParticipantIds.splice( + liveParticipantIndex, + 1 + ); + } state.userStatuses[action.payload.userId] = action.payload; }, }, diff --git a/src/redux/store.tsx b/src/redux/store.tsx index 287a169..9a06073 100644 --- a/src/redux/store.tsx +++ b/src/redux/store.tsx @@ -3,6 +3,7 @@ import meetingDetailsOpenReducer from "./slices/meetingDetailsOpenSlice"; import favoritesReducer from "./slices/favoritesSlice"; import meetingsAndUserStatusReducer from "./slices/meetingsAndUserStatusSlice"; import usersReducer from "./slices/usersSlice"; +import meetingUrlReducer from "./slices/meetingUrlSlice"; import socketMiddleware from "./middleware/socketMiddleware"; export const store = configureStore({ @@ -11,6 +12,7 @@ export const store = configureStore({ favorites: favoritesReducer, meetingsAndUserStatuses: meetingsAndUserStatusReducer, users: usersReducer, + meetingUrl: meetingUrlReducer, }, middleware: (getDefaultMiddleware) => { return getDefaultMiddleware().concat([socketMiddleware]);