Skip to content

Commit 7faa0bf

Browse files
feat(frontend): fetch & manage auth, meetings, polls and user api all at once
1 parent 518a378 commit 7faa0bf

14 files changed

Lines changed: 233 additions & 62 deletions

File tree

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@testing-library/user-event": "^12.1.10",
1414
"date-fns": "^2.22.1",
1515
"formik": "^2.2.6",
16+
"jwt-decode": "^3.1.2",
1617
"query-string": "^7.0.0",
1718
"react": "^17.0.1",
1819
"react-dom": "^17.0.1",
@@ -52,6 +53,7 @@
5253
]
5354
},
5455
"devDependencies": {
56+
"@types/jwt-decode": "^3.1.0",
5557
"@types/node": "^15.0.2",
5658
"@types/react": "^17.0.5",
5759
"@types/react-dom": "^17.0.3",

frontend/src/components/Meeting/MeetingTabs.tsx

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React from "react";
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2+
import React, { useEffect, useState } from "react";
23
import SwipeableViews from "react-swipeable-views";
34
import { makeStyles, useTheme } from "@material-ui/core/styles";
45
import AppBar from "@material-ui/core/AppBar";
@@ -9,6 +10,9 @@ import MeetingCard from "./meetingStuff/MeetingCard";
910
import "./MeetingTabs.css";
1011
import PollCard from "./pollStuff/PollCard";
1112
import CreateButton from "./CreateButton";
13+
import useUser from "src/customHooks/useUser";
14+
import useMeetingData from "src/customHooks/useMeetingData";
15+
import usePollData from "src/customHooks/usePollData";
1216

1317
interface TabPanelProps {
1418
children?: React.ReactNode;
@@ -58,10 +62,64 @@ const useStyles = makeStyles(() => ({
5862
},
5963
}));
6064

65+
type clubType = { authority: "admin" | "member"; _id: string; name: string };
66+
export type pollType = {
67+
voter: string[];
68+
_id: string;
69+
question: string;
70+
club: string;
71+
createdAt: string;
72+
updatedAt: string;
73+
options: {
74+
votes: number;
75+
_id: string;
76+
name: string;
77+
}[];
78+
};
79+
export type meetingType = {
80+
_id: string;
81+
title: string;
82+
club: string;
83+
datetime: string;
84+
description: string;
85+
registered: {
86+
userId: number;
87+
_id: string;
88+
name: string;
89+
email: string;
90+
}[];
91+
};
92+
93+
// Returns an array of all clubs in which he is admin
94+
const getAdminClubs = (arr: clubType[]) => {
95+
const res = [];
96+
for (let i = 0; i < arr.length; i++) {
97+
const e = arr[i];
98+
if (e.authority === "admin") {
99+
res.push(e.name);
100+
}
101+
}
102+
return res;
103+
};
104+
61105
const MeetingTabs: React.FC = () => {
62106
const classes = useStyles();
63107
const theme = useTheme();
64108
const [value, setValue] = React.useState(0);
109+
const { userData } = useUser();
110+
const { meetingData } = useMeetingData();
111+
const { pollData } = usePollData();
112+
const [meetings, setMeetings] = useState<meetingType[] | undefined>();
113+
const [polls, setPolls] = useState<pollType[] | undefined>();
114+
115+
const [adminOfClubs, setAdminsOfClubs] = React.useState<string[]>([]);
116+
117+
useEffect(() => {
118+
if (userData && userData.done)
119+
setAdminsOfClubs(getAdminClubs(userData.data.clubs));
120+
if (meetingData) setMeetings(meetingData.data);
121+
if (pollData) setPolls(pollData.data);
122+
}, [userData, meetingData, pollData]);
65123

66124
// eslint-disable-next-line @typescript-eslint/ban-types
67125
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
@@ -72,6 +130,10 @@ const MeetingTabs: React.FC = () => {
72130
setValue(index);
73131
};
74132

133+
if (!adminOfClubs || !meetings || !polls) {
134+
return <div>Loading..</div>;
135+
}
136+
75137
return (
76138
<div className={classes.root}>
77139
<AppBar className={classes.tabbar} position="static" color="default">
@@ -92,18 +154,25 @@ const MeetingTabs: React.FC = () => {
92154
onChangeIndex={handleChangeIndex}
93155
>
94156
<TabPanel value={value} index={0} dir={theme.direction}>
95-
<MeetingCard />
96-
<MeetingCard />
97-
<MeetingCard />
98-
<MeetingCard />
99-
<MeetingCard />
100-
<MeetingCard />
157+
{meetings!.map((e, key) => (
158+
<MeetingCard
159+
key={key}
160+
meetingData={e}
161+
isAdmin={adminOfClubs.includes(e.club)}
162+
/>
163+
))}
101164
</TabPanel>
102165
<TabPanel value={value} index={1} dir={theme.direction}>
103-
<PollCard />
166+
{polls!.map((e, key) => (
167+
<PollCard
168+
key={key}
169+
pollData={e}
170+
isAdmin={adminOfClubs.includes(e.club)}
171+
/>
172+
))}
104173
</TabPanel>
105174
</SwipeableViews>
106-
<CreateButton formType={value} />
175+
{adminOfClubs.length > 0 ? <CreateButton formType={value} /> : null}
107176
</div>
108177
);
109178
};

frontend/src/components/Meeting/MenuOptions.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ const MenuOptions: React.FC<MenuOptionsProps> = ({
3737
>
3838
<MenuItem onClick={() => setFormOpen(true)}>Edit</MenuItem>
3939
<MenuItem onClick={handleClick}>Delete</MenuItem>
40+
{type === 0 ? (
41+
<MenuItem onClick={handleClick}>Registers</MenuItem>
42+
) : null}
4043
</Menu>
4144
{forms[type]}
4245
</div>

frontend/src/components/Buttons/MoreOptionsButton.tsx renamed to frontend/src/components/Meeting/MoreOptionsButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IconButton, IconButtonProps } from "@material-ui/core";
22
import { MoreHoriz } from "@material-ui/icons";
33
import React from "react";
4-
import MenuOptions from "../Meeting/MenuOptions";
4+
import MenuOptions from "./MenuOptions";
55

66
type MoreOptionsInterface = {
77
formType: number;

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import React from "react";
22
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
33
import { Card, CardContent, Grid, Typography } from "@material-ui/core";
44
import RegisterButton from "./RegisterButton";
5-
import MoreOptionsButton from "../../Buttons/MoreOptionsButton";
5+
import MoreOptionsButton from "../MoreOptionsButton";
6+
import { meetingType } from "../MeetingTabs";
67

78
const useStyles = makeStyles((theme: Theme) =>
89
createStyles({
@@ -47,9 +48,12 @@ const useStyles = makeStyles((theme: Theme) =>
4748
})
4849
);
4950

50-
// interface MeetingCardProps {}
51+
interface MeetingCardProps {
52+
meetingData: meetingType;
53+
isAdmin: boolean;
54+
}
5155

52-
const MeetingCard: React.FC = ({}) => {
56+
const MeetingCard: React.FC<MeetingCardProps> = ({ meetingData, isAdmin }) => {
5357
const classes = useStyles();
5458
// const [registerState, setRegisterState] = useState(0);
5559
return (
@@ -59,23 +63,29 @@ const MeetingCard: React.FC = ({}) => {
5963
<Grid item xs={12} sm={6}>
6064
<div>
6165
<Typography className={classes.title} component="span">
62-
EventName
66+
{meetingData.title}
6367
</Typography>
6468
<Typography className={classes.lang} component="span">
65-
Bitbyte
69+
{meetingData.club}
6670
</Typography>
6771
</div>
68-
<Typography className={classes.lang}>12:00AM 30.2.2022</Typography>
72+
<Typography className={classes.lang}>
73+
{meetingData.datetime}
74+
</Typography>
6975
<Typography className={classes.body}>
70-
coding coding coding coding coding coding coding{" "}
76+
{meetingData.description}
7177
</Typography>
7278
</Grid>
7379
<Grid item xs={12} sm={6} className={classes.alignRight}>
7480
<RegisterButton btnState={1} />
75-
<Typography className={classes.minorText}>25 registers</Typography>
81+
<Typography className={classes.minorText}>
82+
{meetingData.registered.length} registers
83+
</Typography>
7684
</Grid>
7785
</Grid>
78-
<MoreOptionsButton formType={0} className={classes.more} />
86+
{isAdmin ? (
87+
<MoreOptionsButton formType={0} className={classes.more} />
88+
) : null}
7989
</CardContent>
8090
</Card>
8191
);

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

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import Radio from "@material-ui/core/Radio";
55
import RadioGroup from "@material-ui/core/RadioGroup";
66
import FormControlLabel from "@material-ui/core/FormControlLabel";
77
import FormControl from "@material-ui/core/FormControl";
8-
import MoreOptionsButton from "../../Buttons/MoreOptionsButton";
8+
import MoreOptionsButton from "../MoreOptionsButton";
9+
import { pollType } from "../MeetingTabs";
910

1011
const useStyles = makeStyles(() =>
1112
createStyles({
@@ -37,11 +38,13 @@ const useStyles = makeStyles(() =>
3738
})
3839
);
3940

40-
// interface MeetingCardProps {}
41+
interface PollCardProps {
42+
pollData: pollType;
43+
isAdmin: boolean;
44+
}
4145

42-
const PollCard: React.FC = ({}) => {
46+
const PollCard: React.FC<PollCardProps> = ({ pollData, isAdmin }) => {
4347
const classes = useStyles();
44-
// const [registerState, setRegisterState] = useState(0);
4548
const [option, setOption] = React.useState<string | null>(null);
4649
const [polled, setPolled] = React.useState<boolean>(false);
4750
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -51,41 +54,31 @@ const PollCard: React.FC = ({}) => {
5154
return (
5255
<Card className={classes.root}>
5356
<CardContent>
54-
<Typography className={classes.title}>BitByte</Typography>
57+
<Typography className={classes.title}>{pollData.club}</Typography>
5558
<FormControl component="fieldset">
56-
<Typography className={classes.body}>
57-
What do you guys want next?
58-
</Typography>
59+
<Typography className={classes.body}>{pollData.question}</Typography>
5960
<RadioGroup
60-
aria-label="gender"
61-
name="gender1"
61+
aria-label="pollOptions"
62+
name="pollOptions"
6263
value={option}
6364
onChange={handleChange}
6465
>
65-
<FormControlLabel
66-
disabled={polled}
67-
value="female"
68-
color="inherit"
69-
control={<Radio />}
70-
label="Girlfriend"
71-
/>
72-
<FormControlLabel
73-
disabled={polled}
74-
color="default"
75-
value="webdev"
76-
control={<Radio />}
77-
label="Coding"
78-
/>
79-
<FormControlLabel
80-
disabled={polled}
81-
value="ml"
82-
control={<Radio />}
83-
label="10 CPI"
84-
/>
66+
{pollData.options.map((e, key) => (
67+
<FormControlLabel
68+
key={key}
69+
disabled={polled}
70+
value={e.name}
71+
color="inherit"
72+
control={<Radio />}
73+
label={e.name}
74+
/>
75+
))}
8576
</RadioGroup>
8677
</FormControl>
8778

88-
<MoreOptionsButton formType={1} className={classes.more} />
79+
{isAdmin ? (
80+
<MoreOptionsButton formType={1} className={classes.more} />
81+
) : null}
8982
</CardContent>
9083
</Card>
9184
);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { server } from "src/store/global";
2+
import useTokenStore from "src/store/tokenStore";
3+
import { fetcherToken } from "src/utils/fetcher";
4+
import useSWR from "swr";
5+
6+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7+
const useMeetingData = (): any => {
8+
const accessToken = useTokenStore((state) => state.token);
9+
const { data } = useSWR(
10+
[`${server}/api/meeting/get_all`, accessToken],
11+
fetcherToken,
12+
{ revalidateOnFocus: false, revalidateOnMount: true }
13+
);
14+
return {
15+
meetingData: data,
16+
};
17+
};
18+
export default useMeetingData;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { server } from "src/store/global";
2+
import useTokenStore from "src/store/tokenStore";
3+
import { fetcherToken } from "src/utils/fetcher";
4+
import useSWR from "swr";
5+
6+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7+
const usePollData = (): any => {
8+
const accessToken = useTokenStore((state) => state.token);
9+
const { data } = useSWR(
10+
[server + "/api/poll/get_all", accessToken],
11+
fetcherToken,
12+
{ revalidateOnFocus: false, revalidateOnMount: true }
13+
);
14+
return {
15+
pollData: data,
16+
};
17+
};
18+
export default usePollData;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { server } from "src/store/global";
2+
import useTokenStore from "src/store/tokenStore";
3+
import { fetcher } from "src/utils/fetcher";
4+
import useSWR from "swr";
5+
import jwt_decode from "jwt-decode";
6+
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
const useUser = (): any => {
9+
const accessToken = useTokenStore((state) => state.token);
10+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11+
let jwtData: any = { userId: null };
12+
if (accessToken) jwtData = jwt_decode(accessToken);
13+
const userId = jwtData.userId;
14+
const { data } = useSWR(server + "/api/user/get/" + userId, fetcher, {
15+
revalidateOnFocus: false,
16+
revalidateOnMount: true,
17+
});
18+
return {
19+
userData: data,
20+
};
21+
};
22+
export default useUser;

frontend/src/index.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,12 @@ import App from "./App";
44
import { BrowserRouter } from "react-router-dom";
55
import { MuiThemeProvider } from "@material-ui/core";
66
import Theme from "./theme/theme";
7-
import { SWRConfig } from "swr";
8-
9-
const fetcher = (resource: RequestInfo, init: RequestInit | undefined) =>
10-
fetch(resource, init).then((res) => res.json());
117

128
ReactDOM.render(
13-
<SWRConfig value={{ fetcher: fetcher }}>
14-
<MuiThemeProvider theme={Theme}>
15-
<BrowserRouter>
16-
<App />
17-
</BrowserRouter>
18-
</MuiThemeProvider>
19-
</SWRConfig>,
9+
<MuiThemeProvider theme={Theme}>
10+
<BrowserRouter>
11+
<App />
12+
</BrowserRouter>
13+
</MuiThemeProvider>,
2014
document.getElementById("root")
2115
);

0 commit comments

Comments
 (0)