Skip to content

Commit 8c208ca

Browse files
feat(frontend): finish meetings feature
1 parent 7faa0bf commit 8c208ca

12 files changed

Lines changed: 323 additions & 66 deletions

File tree

frontend/src/Routes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Meeting from "./pages/Meeting";
1010
import { updateAccessToken } from "./utils/updateAccessToken";
1111
import useTokenStore from "./store/tokenStore";
1212

13-
const REFRESH_TIME_MS = 60 * 60 * 1000;
13+
const REFRESH_TIME_MS = 50 * 60 * 1000;
1414
const Routes: React.FC = () => {
1515
const accessToken = useTokenStore((state) => state.token);
1616
const setToken = useTokenStore((state) => state.setToken);

frontend/src/components/Meeting/MeetingTabs.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export type meetingType = {
8383
datetime: string;
8484
description: string;
8585
registered: {
86-
userId: number;
86+
userId: string;
8787
_id: string;
8888
name: string;
8989
email: string;
@@ -113,10 +113,13 @@ const MeetingTabs: React.FC = () => {
113113
const [polls, setPolls] = useState<pollType[] | undefined>();
114114

115115
const [adminOfClubs, setAdminsOfClubs] = React.useState<string[]>([]);
116+
const [userId, setUserId] = React.useState<string>("");
116117

117118
useEffect(() => {
118-
if (userData && userData.done)
119+
if (userData && userData.done) {
120+
setUserId(userData.data._id);
119121
setAdminsOfClubs(getAdminClubs(userData.data.clubs));
122+
}
120123
if (meetingData) setMeetings(meetingData.data);
121124
if (pollData) setPolls(pollData.data);
122125
}, [userData, meetingData, pollData]);
@@ -156,6 +159,7 @@ const MeetingTabs: React.FC = () => {
156159
<TabPanel value={value} index={0} dir={theme.direction}>
157160
{meetings!.map((e, key) => (
158161
<MeetingCard
162+
userId={userId}
159163
key={key}
160164
meetingData={e}
161165
isAdmin={adminOfClubs.includes(e.club)}
@@ -165,6 +169,7 @@ const MeetingTabs: React.FC = () => {
165169
<TabPanel value={value} index={1} dir={theme.direction}>
166170
{polls!.map((e, key) => (
167171
<PollCard
172+
userId={userId}
168173
key={key}
169174
pollData={e}
170175
isAdmin={adminOfClubs.includes(e.club)}

frontend/src/components/Meeting/MenuOptions.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@ import Menu from "@material-ui/core/Menu";
33
import MenuItem from "@material-ui/core/MenuItem";
44
import MeetingForm from "./meetingStuff/MeetingForm";
55
import PollForm from "./pollStuff/PollForm";
6+
import { meetingType, pollType } from "./MeetingTabs";
67

78
interface MenuOptionsProps {
9+
meetingData?: meetingType;
10+
pollData?: pollType;
811
open: boolean;
912
type: number;
1013
close: () => void;
1114
parent: HTMLElement | null;
1215
}
1316
const MenuOptions: React.FC<MenuOptionsProps> = ({
1417
type,
18+
meetingData,
19+
pollData,
1520
open,
1621
close,
1722
parent,
@@ -22,8 +27,16 @@ const MenuOptions: React.FC<MenuOptionsProps> = ({
2227
const [formOpen, setFormOpen] = React.useState(false);
2328

2429
const forms = [
25-
<MeetingForm open={formOpen} close={() => setFormOpen(false)} />,
26-
<PollForm open={formOpen} close={() => setFormOpen(false)} />,
30+
<MeetingForm
31+
initialVal={meetingData}
32+
open={formOpen}
33+
close={() => setFormOpen(false)}
34+
/>,
35+
<PollForm
36+
initialVal={pollData}
37+
open={formOpen}
38+
close={() => setFormOpen(false)}
39+
/>,
2740
];
2841

2942
return (

frontend/src/components/Meeting/MoreOptionsButton.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { IconButton, IconButtonProps } from "@material-ui/core";
22
import { MoreHoriz } from "@material-ui/icons";
33
import React from "react";
4+
import { meetingType, pollType } from "./MeetingTabs";
45
import MenuOptions from "./MenuOptions";
56

67
type MoreOptionsInterface = {
78
formType: number;
9+
meetingData?: meetingType;
10+
pollData?: pollType;
811
} & IconButtonProps;
912
const MoreOptionsButton: React.FC<MoreOptionsInterface> = ({
1013
formType,
14+
meetingData,
15+
pollData,
1116
...props
1217
}) => {
1318
const [menuOpen, setMenuOpen] = React.useState(false);
@@ -21,6 +26,8 @@ const MoreOptionsButton: React.FC<MoreOptionsInterface> = ({
2126
<MoreHoriz ref={someRef} />
2227
</IconButton>
2328
<MenuOptions
29+
meetingData={meetingData}
30+
pollData={pollData}
2431
type={formType}
2532
parent={someRef.current}
2633
close={handleClose}

frontend/src/components/Meeting/meetingStuff/MeetingCard.tsx

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Card, CardContent, Grid, Typography } from "@material-ui/core";
44
import RegisterButton from "./RegisterButton";
55
import MoreOptionsButton from "../MoreOptionsButton";
66
import { meetingType } from "../MeetingTabs";
7+
import { format } from "date-fns";
78

89
const useStyles = makeStyles((theme: Theme) =>
910
createStyles({
@@ -14,6 +15,13 @@ const useStyles = makeStyles((theme: Theme) =>
1415
background: "#ffd166",
1516
position: "relative",
1617
},
18+
disabledRoot: {
19+
margin: "2vh",
20+
minHeight: "20vh",
21+
width: "90vw",
22+
background: "#D7B566",
23+
position: "relative",
24+
},
1725
title: {
1826
fontSize: "1.2rem",
1927
color: "#3650C7",
@@ -51,13 +59,32 @@ const useStyles = makeStyles((theme: Theme) =>
5159
interface MeetingCardProps {
5260
meetingData: meetingType;
5361
isAdmin: boolean;
62+
userId: string;
5463
}
5564

56-
const MeetingCard: React.FC<MeetingCardProps> = ({ meetingData, isAdmin }) => {
65+
const MeetingCard: React.FC<MeetingCardProps> = ({
66+
meetingData,
67+
isAdmin,
68+
userId,
69+
}) => {
5770
const classes = useStyles();
58-
// const [registerState, setRegisterState] = useState(0);
71+
const [cardState, setCardState] = React.useState(0);
72+
React.useEffect(() => {
73+
const givenDate = new Date(meetingData.datetime);
74+
const nowDate = new Date(Date.now());
75+
if (givenDate.toISOString() < nowDate.toISOString()) {
76+
setCardState(2);
77+
} else {
78+
const registers = meetingData.registered;
79+
for (let i = 0; i < registers.length; i++) {
80+
if (registers[0].userId === userId) {
81+
setCardState(1);
82+
}
83+
}
84+
}
85+
}, [meetingData]);
5986
return (
60-
<Card className={classes.root}>
87+
<Card className={cardState === 2 ? classes.disabledRoot : classes.root}>
6188
<CardContent>
6289
<Grid container>
6390
<Grid item xs={12} sm={6}>
@@ -70,21 +97,29 @@ const MeetingCard: React.FC<MeetingCardProps> = ({ meetingData, isAdmin }) => {
7097
</Typography>
7198
</div>
7299
<Typography className={classes.lang}>
73-
{meetingData.datetime}
100+
{format(new Date(meetingData.datetime), "PPpp")}
74101
</Typography>
75102
<Typography className={classes.body}>
76103
{meetingData.description}
77104
</Typography>
78105
</Grid>
79106
<Grid item xs={12} sm={6} className={classes.alignRight}>
80-
<RegisterButton btnState={1} />
107+
<RegisterButton
108+
meetingId={meetingData._id}
109+
userId={userId}
110+
btnState={cardState}
111+
/>
81112
<Typography className={classes.minorText}>
82113
{meetingData.registered.length} registers
83114
</Typography>
84115
</Grid>
85116
</Grid>
86117
{isAdmin ? (
87-
<MoreOptionsButton formType={0} className={classes.more} />
118+
<MoreOptionsButton
119+
meetingData={meetingData}
120+
formType={0}
121+
className={classes.more}
122+
/>
88123
) : null}
89124
</CardContent>
90125
</Card>

frontend/src/components/Meeting/meetingStuff/MeetingForm.tsx

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
import { Button, Dialog, DialogContent, Theme } from "@material-ui/core";
1+
import {
2+
Button,
3+
Dialog,
4+
DialogContent,
5+
Snackbar,
6+
Theme,
7+
} from "@material-ui/core";
28
import { Formik, Form } from "formik";
39
import React from "react";
410
import FormikTextField from "../../FormikTextField";
511
import * as yup from "yup";
612
import { createStyles, makeStyles } from "@material-ui/core/styles";
713
import FormikDateTimeField from "../../FormikDateTimeField";
14+
import { meetingType } from "../MeetingTabs";
15+
import useTokenStore from "src/store/tokenStore";
16+
import { createMeeting, updateMeeting } from "src/utils/meetingCalls";
17+
import { mutate } from "swr";
18+
import { server } from "src/store/global";
19+
import { Alert } from "@material-ui/lab";
820

921
export const useStyles = makeStyles((theme: Theme) =>
1022
createStyles({
@@ -17,7 +29,7 @@ export const useStyles = makeStyles((theme: Theme) =>
1729
input: {
1830
marginBottom: "2vh",
1931
width: "60vh",
20-
[theme.breakpoints.down("sm")]: {
32+
[theme.breakpoints.down("xs")]: {
2133
width: "70vw",
2234
},
2335
},
@@ -43,32 +55,69 @@ const validSchema = yup.object({
4355
title: yup.string().required().min(3).max(64),
4456
description: yup.string().required().min(3),
4557
club: yup.string().required().min(3),
46-
datetime: yup.date().required(),
58+
datetime: yup.date().required().min(new Date(Date.now()), "Invalid Date"),
4759
});
4860

4961
interface MeetingFormProps {
5062
open: boolean;
5163
close: () => void;
64+
initialVal?: meetingType;
5265
}
5366

54-
const MeetingForm: React.FC<MeetingFormProps> = (props) => {
55-
const { close, open } = props;
67+
const MeetingForm: React.FC<MeetingFormProps> = ({
68+
close,
69+
open,
70+
initialVal,
71+
}) => {
5672
const classes = useStyles();
73+
const accessToken = useTokenStore((state) => state.token);
74+
const [openError, setOpenError] = React.useState(false);
5775

76+
const handleClose = (event?: React.SyntheticEvent, reason?: string) => {
77+
if (reason === "clickaway") {
78+
return;
79+
}
80+
81+
setOpenError(false);
82+
};
5883
return (
5984
<Dialog aria-labelledby="meeting-dialog" open={open}>
6085
<DialogContent>
6186
<Formik
6287
initialValues={{
63-
title: "",
64-
description: "",
65-
club: "",
66-
datetime: new Date("2021-08-18T20:11:54"),
88+
title: initialVal ? initialVal.title : "",
89+
description: initialVal ? initialVal.description : "",
90+
club: initialVal ? initialVal.club : "",
91+
datetime: initialVal
92+
? initialVal.datetime
93+
: new Date("2021-08-18T20:11:54"),
6794
}}
6895
validationSchema={validSchema}
6996
onSubmit={async (data, { setSubmitting }) => {
7097
setSubmitting(true);
71-
console.log(data);
98+
if (initialVal) {
99+
const res = await updateMeeting(
100+
data,
101+
initialVal._id,
102+
accessToken
103+
);
104+
if (res.done) {
105+
mutate([`${server}/api/meeting/get_all`, accessToken]);
106+
close();
107+
} else {
108+
console.log(res.err);
109+
setOpenError(true);
110+
}
111+
} else {
112+
const res = await createMeeting(data, accessToken);
113+
if (res.done) {
114+
mutate([`${server}/api/meeting/get_all`, accessToken]);
115+
close();
116+
} else {
117+
console.log(res.err);
118+
setOpenError(true);
119+
}
120+
}
72121
setSubmitting(false);
73122
}}
74123
>
@@ -114,6 +163,11 @@ const MeetingForm: React.FC<MeetingFormProps> = (props) => {
114163
)}
115164
</Formik>
116165
</DialogContent>
166+
<Snackbar open={openError} autoHideDuration={6000} onClose={handleClose}>
167+
<Alert onClose={handleClose} severity="error">
168+
Permission Denied
169+
</Alert>
170+
</Snackbar>
117171
</Dialog>
118172
);
119173
};

frontend/src/components/Meeting/meetingStuff/RegisterButton.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import React from "react";
22
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
33
import Button, { ButtonProps } from "@material-ui/core/Button";
4+
import useTokenStore from "src/store/tokenStore";
5+
import { registerMeeting } from "src/utils/meetingCalls";
6+
import { mutate } from "swr";
7+
import { server } from "src/store/global";
48

59
const useStyles = makeStyles((theme: Theme) =>
610
createStyles({
@@ -26,14 +30,19 @@ const useStyles = makeStyles((theme: Theme) =>
2630
);
2731

2832
type RegisterButtonProps = {
33+
userId: string;
2934
btnState: number;
35+
meetingId: string;
3036
} & ButtonProps;
3137

3238
const RegisterButton: React.FC<RegisterButtonProps> = ({
39+
userId,
40+
meetingId,
3341
btnState,
3442
...props
3543
}) => {
3644
const classes = useStyles();
45+
const accessToken = useTokenStore((state) => state.token);
3746
const states = [
3847
{
3948
content: "Register",
@@ -51,9 +60,18 @@ const RegisterButton: React.FC<RegisterButtonProps> = ({
5160
rootClass: classes.root,
5261
},
5362
];
63+
const handleClick = async () => {
64+
const res = await registerMeeting(userId, accessToken, meetingId);
65+
if (!res.done) {
66+
console.log(res.err);
67+
} else {
68+
mutate([`${server}/api/meeting/get_all`, accessToken]);
69+
}
70+
};
5471
return (
5572
<Button
5673
variant="outlined"
74+
onClick={handleClick}
5775
disabled={states[btnState].disabled}
5876
classes={{
5977
root: states[btnState].rootClass,

0 commit comments

Comments
 (0)