Skip to content

Commit bd59af4

Browse files
feat(frontend): finish polls feature
1 parent 8c208ca commit bd59af4

6 files changed

Lines changed: 171 additions & 23 deletions

File tree

frontend/src/components/Meeting/MeetingTabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const useStyles = makeStyles(() => ({
6464

6565
type clubType = { authority: "admin" | "member"; _id: string; name: string };
6666
export type pollType = {
67-
voter: string[];
67+
voters: { userId: string; optionId: string }[];
6868
_id: string;
6969
question: string;
7070
club: string;

frontend/src/components/Meeting/pollStuff/PollCard.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import FormControlLabel from "@material-ui/core/FormControlLabel";
77
import FormControl from "@material-ui/core/FormControl";
88
import MoreOptionsButton from "../MoreOptionsButton";
99
import { pollType } from "../MeetingTabs";
10+
import { votePoll } from "src/utils/pollCalls";
11+
import useTokenStore from "src/store/tokenStore";
12+
import { server } from "src/store/global";
13+
import { mutate } from "swr";
1014

1115
const useStyles = makeStyles(() =>
1216
createStyles({
@@ -44,14 +48,33 @@ interface PollCardProps {
4448
userId: string;
4549
}
4650

47-
const PollCard: React.FC<PollCardProps> = ({ pollData, isAdmin }) => {
51+
const PollCard: React.FC<PollCardProps> = ({ pollData, isAdmin, userId }) => {
4852
const classes = useStyles();
53+
const accessToken = useTokenStore((state) => state.token);
4954
const [option, setOption] = React.useState<string | null>(null);
5055
const [polled, setPolled] = React.useState<boolean>(false);
51-
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
52-
setOption((event.target as HTMLInputElement).value);
53-
setPolled(true);
56+
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
57+
const res = await votePoll(
58+
(event.target as HTMLInputElement).value,
59+
accessToken,
60+
pollData._id
61+
);
62+
if (res.done) {
63+
setOption((event.target as HTMLInputElement).value);
64+
setPolled(true);
65+
mutate([`${server}/api/poll/get_all`, accessToken]);
66+
} else {
67+
console.log(res.err);
68+
}
5469
};
70+
React.useEffect(() => {
71+
for (let i = 0; i < pollData.voters.length; i++) {
72+
if (pollData.voters[i].userId === userId) {
73+
setOption(pollData.voters[i].optionId);
74+
setPolled(true);
75+
}
76+
}
77+
}, [pollData]);
5578
return (
5679
<Card className={classes.root}>
5780
<CardContent>
@@ -68,10 +91,10 @@ const PollCard: React.FC<PollCardProps> = ({ pollData, isAdmin }) => {
6891
<FormControlLabel
6992
key={key}
7093
disabled={polled}
71-
value={e.name}
94+
value={e._id}
7295
color="inherit"
7396
control={<Radio />}
74-
label={e.name}
97+
label={`${e.name} (${e.votes} votes)`}
7598
/>
7699
))}
77100
</RadioGroup>

frontend/src/components/Meeting/pollStuff/PollForm.tsx

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
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, FieldArray } 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 { pollType } from "../MeetingTabs";
14+
import { Alert } from "@material-ui/lab";
15+
import { createPoll, updatePoll } from "src/utils/pollCalls";
16+
import useTokenStore from "src/store/tokenStore";
17+
import { mutate } from "swr";
18+
import { server } from "src/store/global";
819

920
export const useStyles = makeStyles((theme: Theme) =>
1021
createStyles({
@@ -53,13 +64,17 @@ export const useStyles = makeStyles((theme: Theme) =>
5364
);
5465

5566
const validSchema = yup.object({
56-
question: yup.string().required().min(3).max(64),
57-
options: yup
58-
.array()
59-
.of(yup.string().required().min(2).max(64))
60-
.required()
61-
.min(2, "Atleast two options required")
62-
.max(10),
67+
club: yup.string().required("Club name is required").min(3).max(32),
68+
question: yup.string().required("Question cannot be empty").min(3).max(64),
69+
options: yup.array().of(
70+
yup.object().shape({
71+
name: yup
72+
.string()
73+
.required("Option cannot be empty")
74+
.min(3, "Option cannot be empty")
75+
.max(32),
76+
})
77+
),
6378
});
6479

6580
interface PollFormProps {
@@ -70,24 +85,64 @@ interface PollFormProps {
7085

7186
const PollForm: React.FC<PollFormProps> = ({ close, open, initialVal }) => {
7287
const classes = useStyles();
88+
const accessToken = useTokenStore((state) => state.token);
89+
const [openError, setOpenError] = React.useState(false);
90+
const [errorText, setErrorText] = React.useState("Permission Denied");
91+
92+
const handleClose = (event?: React.SyntheticEvent, reason?: string) => {
93+
if (reason === "clickaway") {
94+
return;
95+
}
96+
97+
setOpenError(false);
98+
};
7399

74100
return (
75101
<Dialog aria-labelledby="poll-dialog" open={open}>
76102
<DialogContent>
77103
<Formik
78104
initialValues={{
105+
club: initialVal ? initialVal.club : "",
79106
question: initialVal ? initialVal.question : "",
80-
options: initialVal ? initialVal.options : [""],
107+
options: initialVal ? initialVal.options : [{ name: "" }],
81108
}}
82109
validationSchema={validSchema}
83110
onSubmit={async (data, { setSubmitting }) => {
84111
setSubmitting(true);
85-
console.log(data);
112+
if (data.options.length < 2 || data.options.length > 5) {
113+
setErrorText("Poll must have 2-5 options");
114+
setOpenError(true);
115+
} else {
116+
if (initialVal) {
117+
const res = await updatePoll(data, initialVal._id, accessToken);
118+
if (!res.done) {
119+
setErrorText("Permission Denied");
120+
setOpenError(true);
121+
} else {
122+
mutate([`${server}/api/poll/get_all`, accessToken]);
123+
close();
124+
}
125+
} else {
126+
const res = await createPoll(data, accessToken);
127+
if (!res.done) {
128+
setErrorText("Permission Denied");
129+
setOpenError(true);
130+
} else {
131+
mutate([`${server}/api/poll/get_all`, accessToken]);
132+
close();
133+
}
134+
}
135+
}
86136
setSubmitting(false);
87137
}}
88138
>
89139
{({ isSubmitting, values }) => (
90140
<Form className={classes.root} noValidate autoComplete="off">
141+
<FormikTextField
142+
label="Club Name"
143+
className={classes.input}
144+
name="club"
145+
/>
91146
<FormikTextField
92147
label="Question"
93148
className={classes.input}
@@ -103,7 +158,7 @@ const PollForm: React.FC<PollFormProps> = ({ close, open, initialVal }) => {
103158
<FormikTextField
104159
label={`Option ${key + 1}`}
105160
className={classes.optInput}
106-
name={`options.${key}`}
161+
name={`options.${key}.name`}
107162
/>
108163
<Button
109164
className={classes.optBtn}
@@ -114,7 +169,10 @@ const PollForm: React.FC<PollFormProps> = ({ close, open, initialVal }) => {
114169
</Button>
115170
</div>
116171
))}
117-
<Button onClick={() => arr.push("")} variant="outlined">
172+
<Button
173+
onClick={() => arr.push({ name: "" })}
174+
variant="outlined"
175+
>
118176
Add
119177
</Button>
120178
</div>
@@ -145,6 +203,11 @@ const PollForm: React.FC<PollFormProps> = ({ close, open, initialVal }) => {
145203
)}
146204
</Formik>
147205
</DialogContent>
206+
<Snackbar open={openError} autoHideDuration={6000} onClose={handleClose}>
207+
<Alert onClose={handleClose} severity="error">
208+
{errorText}
209+
</Alert>
210+
</Snackbar>
148211
</Dialog>
149212
);
150213
};

frontend/src/utils/pollCalls.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { server } from "src/store/global";
2+
3+
type pollType = {
4+
question: string;
5+
options: { name: string }[];
6+
club: string;
7+
};
8+
9+
export const votePoll = async (
10+
optionId: string,
11+
token: string,
12+
pollId: string
13+
): Promise<{ done: boolean; err?: string }> => {
14+
const res = await fetch(server + "/api/poll/vote/" + pollId, {
15+
method: "PATCH",
16+
headers: {
17+
Authorization: "bearer " + token,
18+
"Content-Type": "application/json",
19+
},
20+
body: JSON.stringify({ optionId }),
21+
});
22+
const data = res.json();
23+
return data;
24+
};
25+
26+
export const createPoll = async (
27+
pollData: pollType,
28+
token: string
29+
): Promise<{ done: boolean; err?: string }> => {
30+
const res = await fetch(server + "/api/poll/create", {
31+
method: "POST",
32+
headers: {
33+
Authorization: "bearer " + token,
34+
"Content-Type": "application/json",
35+
},
36+
body: JSON.stringify(pollData),
37+
});
38+
const data = res.json();
39+
return data;
40+
};
41+
42+
export const updatePoll = async (
43+
pollData: pollType,
44+
pollId: string,
45+
token: string
46+
): Promise<{ done: boolean; err?: string }> => {
47+
const res = await fetch(server + "/api/poll/update/" + pollId, {
48+
method: "PATCH",
49+
headers: {
50+
Authorization: "bearer " + token,
51+
"Content-Type": "application/json",
52+
},
53+
body: JSON.stringify(pollData),
54+
});
55+
const data = res.json();
56+
return data;
57+
};

server/src/model/Poll.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type optionType = {
99
interface PollInterface extends mongoose.Document {
1010
question: string;
1111
club: string;
12-
voters: string[];
12+
voters: { userId: string; optionId: string }[];
1313
options: optionType[];
1414
}
1515

@@ -23,7 +23,12 @@ const pollSchema: mongoose.Schema = new mongoose.Schema(
2323
type: String,
2424
required: true,
2525
},
26-
voters: [String],
26+
voters: [
27+
{
28+
userId: String,
29+
optionId: String,
30+
},
31+
],
2732
options: [
2833
{
2934
name: {

server/src/routes/pollRoutes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Router.patch(
9393
if (!options) {
9494
return res.json({ done: false, err: "Invalid poll" });
9595
}
96-
const hasVotedBefore = poll.voters.find((e) => e === userId);
96+
const hasVotedBefore = poll.voters.find((e) => e.userId === userId);
9797
if (hasVotedBefore) {
9898
return res.json({ done: false, err: "You cannot vote again" });
9999
}
@@ -108,7 +108,7 @@ Router.patch(
108108
const ret = await Poll.updateOne(
109109
{ _id: pollId },
110110
{
111-
voters: [...voters, userId],
111+
voters: [...voters, { userId, optionId }],
112112
options: updatedOptions,
113113
}
114114
);

0 commit comments

Comments
 (0)