Merge pull request #51 from CPSC319-Winter-term-2/mbalsdon-browser-notifications
Upcoming meetings notifications
This commit is contained in:
commit
4d4278b958
@ -2,21 +2,72 @@ import { useLocation, Outlet, Navigate } from "react-router-dom";
|
|||||||
import useAuth from "./hooks/useAuth";
|
import useAuth from "./hooks/useAuth";
|
||||||
import Navbar from "./components/navbar/Navbar";
|
import Navbar from "./components/navbar/Navbar";
|
||||||
import Sidebar from "./components/sidebar/Sidebar";
|
import Sidebar from "./components/sidebar/Sidebar";
|
||||||
|
import { Alert, AlertTitle, Box, Snackbar, Typography } from "@mui/material";
|
||||||
|
import { store } from "./redux/store";
|
||||||
|
import { fetchFavorites } from "./redux/slices/favoritesSlice";
|
||||||
|
import { fetchMeetings, selectMeetings } from "./redux/slices/meetingsAndUserStatusSlice";
|
||||||
|
import { fetchUsers, selectMe } from "./redux/slices/usersSlice";
|
||||||
|
import { useAppDispatch, useAppSelector, useInterval } from "./redux/hooks";
|
||||||
|
import DetailedMeeting from "./api-bodies/DetailedMeeting";
|
||||||
|
import React, { useState } from "react";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
const ProtectedRoute = () => {
|
const ProtectedRoute = () => {
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
/* Temporary data */
|
useAppDispatch();
|
||||||
// if (auth?.isLoggedIn != undefined && auth?.isLoggedIn == true) {
|
const currentUserUuid: string = useAppSelector(selectMe);
|
||||||
// store.dispatch(fetchMeetings(auth?.uuid));
|
const meetings: DetailedMeeting[] = useAppSelector(selectMeetings);
|
||||||
// store.dispatch(fetchUsers(auth?.uuid));
|
const currentUserMeetings = meetings.filter((m) => m.registrantIds.includes(currentUserUuid));
|
||||||
// store.dispatch(fetchFavorites(auth?.uuid));
|
|
||||||
// store.dispatch(socketActions.startConnecting());
|
const [open, setOpen] = useState(false);
|
||||||
// }
|
const [notifMeetings, setNotifMeetings] = useState([""]);
|
||||||
|
|
||||||
|
const sixtySec = 60000;
|
||||||
|
// polls the current time against meeting times
|
||||||
|
useInterval(
|
||||||
|
() => {
|
||||||
|
console.log("polling");
|
||||||
|
const currentTime = Math.floor(Date.now() / sixtySec); // in minutes
|
||||||
|
const upcomingMeetings = currentUserMeetings.filter((meeting) =>
|
||||||
|
(currentTime == Math.floor(Date.parse(meeting.start) / sixtySec) - 15) || // 15 mins before meeting time
|
||||||
|
(currentTime == Math.floor(Date.parse(meeting.start) / sixtySec) - 30) // or 30 mins before meeting time
|
||||||
|
);
|
||||||
|
if (upcomingMeetings.length != 0) {
|
||||||
|
setOpen(true);
|
||||||
|
const newNotifMeetings: string[] = upcomingMeetings.map((m) => {
|
||||||
|
if (currentTime == Math.floor(Date.parse(m.start) / sixtySec) - 15) {
|
||||||
|
return m.topic + " in 15 minutes";
|
||||||
|
} else {
|
||||||
|
return m.topic + " in 30 minutes";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setNotifMeetings(newNotifMeetings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sixtySec // poll time
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
|
||||||
|
if (reason == "clickaway") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return auth?.isLoggedIn ? (
|
return auth?.isLoggedIn ? (
|
||||||
<>
|
<>
|
||||||
|
{/* ----- */}
|
||||||
|
<Snackbar open={open} onClose={handleClose}>
|
||||||
|
<Alert onClose={handleClose} severity="info" sx={{ width: "100%" }}>
|
||||||
|
<AlertTitle>Upcoming Meetings</AlertTitle>
|
||||||
|
{notifMeetings.map((m, i) =>
|
||||||
|
<Typography key={i}>{m}</Typography>
|
||||||
|
)}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
{/* ----- */}
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Box id="drawer-container" sx={{ display: "flex", height: "100%" }}>
|
<Box id="drawer-container" sx={{ display: "flex", height: "100%" }}>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
|||||||
@ -1,6 +1,30 @@
|
|||||||
|
import { useLayoutEffect, useEffect, useRef } from "react";
|
||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||||
import type { RootState, AppDispatch } from "./store";
|
import type { RootState, AppDispatch } from "./store";
|
||||||
|
|
||||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
|
|
||||||
|
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
||||||
|
export function useInterval(callback: () => void, delay: number | null) {
|
||||||
|
const savedCallback = useRef(callback);
|
||||||
|
|
||||||
|
// Remember the latest callback if it changes.
|
||||||
|
useIsomorphicLayoutEffect(() => {
|
||||||
|
savedCallback.current = callback;
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
// Set up the interval.
|
||||||
|
useEffect(() => {
|
||||||
|
// Don't schedule if no delay is specified.
|
||||||
|
// Note: 0 is a valid value for delay.
|
||||||
|
if (!delay && delay !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = setInterval(() => savedCallback.current(), delay);
|
||||||
|
|
||||||
|
return () => clearInterval(id);
|
||||||
|
}, [delay]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user