Skip to content
This repository has been archived by the owner on Aug 6, 2024. It is now read-only.

Add addUser admin interface for "users" admin role #751

Merged
merged 4 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/comps/ui/Alert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material";
import React from "react";

const dummy = () => 0; // dummy function as default value
const Alert = ({
title,
description,
affirmative = "",
negative = "",
affirmativeCallback = dummy,
open = false,
setOpen = dummy
}) => {
/**
* A basic alert modal.
* @constructor
* @param {string} title - The title of the modal.
* @param {string} description - The description of the modal.
* @param {string} affirmative - The string for the affirmative option (e.g. "OK"). When clicked, calls affirmativeCallback. If empty, not displayed.
* @param {string} negative - The string for the negative option (e.g. "Cancel"). If empty, not displayed.
* @param {function} affirmativeCallback - The function to call when affirmative action button is clicked. Not needed if affirmative is empty.
* @param {boolean} open - Whether the modal is active and displayed, stored in React state.
* @param {boolean} setOpen - The React state edit function, used to close the modal.
*/
return (
<Dialog open={open} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">{description}</DialogContentText>
</DialogContent>
<DialogActions>
{negative.length > 0 ? <Button onClick={() => setOpen(false)}>{negative}</Button> : <></>}
{affirmative.length > 0 ? (
<Button
onClick={() => {
setOpen(false);
affirmativeCallback();
}}
>
{affirmative}
</Button>
) : (
<></>
)}
</DialogActions>
</Dialog>
);
};

export default Alert;
103 changes: 103 additions & 0 deletions src/pages/admin/Users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Box, Button, Checkbox, FormControlLabel, FormGroup, Snackbar, TextField, Typography } from "@mui/material";
import React, { useState } from "react";
import Alert from "../../comps/ui/Alert";
import { gql, useMutation } from "@apollo/client";

const MUTATION = gql`
mutation ($email: String!, $isFaculty: Boolean!) {
createUser(email: $email, isFaculty: $isFaculty) {
email
}
}
`;

const Users = () => {
const [email, setEmail] = useState("");
const [isFaculty, setIsFaculty] = useState(false);
let [successMessage, setSuccessMessage] = useState("");

const [save] = useMutation(MUTATION, {
onCompleted() {
setEmail("");
setIsFaculty(false);
}
});

let [confirmOpen, setConfirmOpen] = useState(false);
let [errorMessage, setErrorMessage] = useState("");
return (
<Box
sx={{
width: "1200px",
maxWidth: "90%",
margin: "auto"
}}
>
<Typography variant="h3"> Add a user:</Typography>
<br />
<FormGroup>
<TextField
label="New User Email"
type="email"
id="emailInput"
required
value={email}
onChange={e => setEmail(e.target.value.toLowerCase())}
/>
<FormControlLabel
required
control={<Checkbox value={isFaculty} onClick={() => setIsFaculty(!isFaculty)} />}
label="Faculty member"
/>
</FormGroup>

<br />
<Button
variant="contained"
onClick={() => {
if (!email.match(/[a-zA-Z0-9.]+@stuy(su)?.(edu|org)/)) {
setErrorMessage("Invalid email. Please only enter stuy.edu or stuysu.org email addresses.");
} else {
console.log(email, isFaculty);
setConfirmOpen(true);
}
}}
>
{" "}
Register{" "}
</Button>
<Alert
title={`Are you sure you want to register ${email}?`}
description={`${email} will be registered as a ${isFaculty ? "faculty member" : "student"}.`}
affirmative="Yes"
negative="No"
affirmativeCallback={async () => {
try {
await save({ variables: { email, isFaculty } });
setSuccessMessage("User added!");
} catch (e) {
setErrorMessage("Failed to add user: " + e.message);
}
}}
open={confirmOpen}
setOpen={setConfirmOpen}
/>
<Alert
title="Error"
description={errorMessage}
negative="Ok"
open={errorMessage?.length > 0}
setOpen={setErrorMessage}
/>
<Snackbar
autoHideDuration={1000}
open={successMessage?.length > 0}
onClose={() => setSuccessMessage("")}
message={successMessage}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
/>
</Box>
);
};

export default Users;
17 changes: 13 additions & 4 deletions src/pages/admin/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { Typography } from "@mui/material";
import { Typography, Box } from "@mui/material";
import { generatePath, Redirect, Route, Switch } from "react-router-dom";
import RouteTabs from "../../comps/ui/RouteTabs";
import Boograms from "./boograms";
Expand All @@ -9,21 +9,22 @@ import SignInRequired from "../../comps/ui/SignInRequired";
import Strikes from "./Strikes";
import OrgApprovals from "./approvals/OrgApprovals";
import AdminLog from "./AdminLog";
import ManageClubs from "./manageclubs.js"
import ManageClubs from "./manageclubs.js";
import {
AssignmentTurnedIn,
EmailOutlined,
Assignment,
SmsFailed,
AttachMoney,
FeaturedPlayList,
PersonAdd,
Settings as SettingsIcon,
Source
} from "@mui/icons-material";
import EmailClubLeaders from "./email";
import ManagePromotedClubs from "./promotedclub";
import Settings from "./settings";
import Box from "@mui/material/Box";
import Users from "./Users";

const classes = {
root: {
Expand Down Expand Up @@ -79,12 +80,19 @@ export default function AdminRouter({ match }) {
path: actualPath + "/promotedclubs",
icon: <FeaturedPlayList />
},
{
label: "Add Users",
role: "users",
path: actualPath + "/users",
icon: <PersonAdd />
},
{
label: "Update Site Settings",
role: "charters",
path: actualPath + "/settings",
icon: <SettingsIcon />
}, {
},
{
label: "Manage Clubs",
role: "charters",
path: actualPath + "/manageclubs",
Expand All @@ -109,6 +117,7 @@ export default function AdminRouter({ match }) {
<Route path={match.path + "/log"} component={AdminLog} />
<Route path={match.path + "/email"} component={EmailClubLeaders} />
<Route path={match.path + "/promotedclubs"} component={ManagePromotedClubs} />
<Route path={match.path + "/users"} component={Users} />
<Route path={match.path + "/settings"} component={Settings} />
<Route path={match.path + "/manageclubs"} component={ManageClubs} />
<Route path={match.path}>
Expand Down
60 changes: 2 additions & 58 deletions src/pages/admin/settings.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import {
Typography,
TextField,
Button,
Box,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Snackbar
} from "@mui/material";
import { Typography, TextField, Button, Box, Snackbar } from "@mui/material";
import { gql, useQuery, useMutation } from "@apollo/client";
import React, { useState } from "react";
import Alert from "../../comps/ui/Alert";
import Loading from "../../comps/ui/Loading";

const QUERY = gql`
Expand All @@ -38,52 +28,6 @@ const classes = {
}
};

const dummy = () => 0; // dummy function as default value
const Alert = ({
title,
description,
affirmative = "",
negative = "",
affirmativeCallback = dummy,
open = false,
setOpen = dummy
}) => {
/**
* A basic alert modal.
* @constructor
* @param {string} title - The title of the modal.
* @param {string} description - The description of the modal.
* @param {string} affirmative - The string for the affirmative option (e.g. "OK"). When clicked, calls affirmativeCallback. If empty, not displayed.
* @param {string} negative - The string for the negative option (e.g. "Cancel"). If empty, not displayed.
* @param {function} affirmativeCallback - The function to call when affirmative action button is clicked. Not needed if affirmative is empty.
* @param {boolean} open - Whether the modal is active and displayed, stored in React state.
* @param {boolean} setOpen - The React state edit function, used to close the modal.
*/
return (
<Dialog open={open} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">{description}</DialogContentText>
</DialogContent>
<DialogActions>
{negative.length > 0 ? <Button onClick={() => setOpen(false)}>{negative}</Button> : <></>}
{affirmative.length > 0 ? (
<Button
onClick={() => {
setOpen(false);
affirmativeCallback();
}}
>
{affirmative}
</Button>
) : (
<></>
)}
</DialogActions>
</Dialog>
);
};

const Settings = () => {
const { data, loading } = useQuery(QUERY);

Expand Down