-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #448 from cornell-dti/andrew/admin-fixes
Admin feature fixes, new admin privilege manager
- Loading branch information
Showing
22 changed files
with
1,059 additions
and
671 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,157 +1,103 @@ | ||
import React, { Component } from 'react' | ||
import React, { useEffect, useState } from 'react' | ||
import axios from 'axios' | ||
import styles from '../Styles/AdminReview.module.css' | ||
|
||
type Props = { | ||
info: any | ||
approveHandler: (arg1: any) => any | ||
removeHandler: (arg1: any, arg2: any) => any | ||
|
||
//was origially optional | ||
unReportHandler: (arg1: any) => any | ||
review: any | ||
approveHandler: (arg1: any) => any | ||
removeHandler: (arg1: any, arg2: any) => any | ||
unReportHandler: (arg1: any) => any | ||
} | ||
|
||
type State = { | ||
shortName: string | ||
longName: string | ||
} | ||
/* | ||
Update Review Component. | ||
Simple styling component that renders a single review (an li element) | ||
to show on the Admin interface. Admin-visible reviews will be of 2 types: | ||
- Unapproved: new reviews needing approval | ||
- Reported: reviews that have been reprot and need review. | ||
Unapproved Reivews will contain: | ||
- Name of the course the review belongs to | ||
- how long ago the review was added | ||
- all review content | ||
- button to approve the review | ||
- button to delete the review | ||
Reported Reviews will contain: | ||
- Name of the course the review belongs to | ||
- how long ago the review was added | ||
- all review content | ||
- button to un-report (restore) the review | ||
- button to delete the review | ||
- Reported: reviews that have been reported and require admin undo | ||
*/ | ||
|
||
export default class UpdateReview extends Component<Props, State> { | ||
constructor(props: Props) { | ||
super(props) | ||
|
||
// state of app will contain details about the class this reivew is for | ||
this.state = { | ||
shortName: '', | ||
longName: '', | ||
} | ||
const UpdateReview = ({review, approveHandler, removeHandler, unReportHandler}: Props) => { | ||
const [shortName, setShortName] = useState<string>("") | ||
const [fullName, setFullName] = useState<string>("") | ||
|
||
axios | ||
.post(`/api/getCourseById`, { | ||
courseId: props.info.class, | ||
}) | ||
.then((response) => { | ||
const course = response.data.result | ||
if (course) { | ||
this.setState({ | ||
shortName: course.classSub.toUpperCase() + ' ' + course.classNum, | ||
longName: course.classTitle, | ||
}) | ||
} else { | ||
// eslint-disable-next-line no-console | ||
console.log(`Unable to find course by id = ${props.info.class}`) | ||
} | ||
}) | ||
} | ||
|
||
// Decide which buttons to show, and what action the buttons take, | ||
// based on the type of update (report or approval) | ||
renderButtons(review: any) { | ||
const reported = review.reported | ||
if (reported === 1) { | ||
return ( | ||
<div className=""> | ||
<button | ||
type="button" | ||
className="" | ||
onClick={() => this.props.unReportHandler(review)} | ||
> | ||
{' '} | ||
Restore Review | ||
</button> | ||
<button | ||
type="button" | ||
className="" | ||
onClick={() => this.props.removeHandler(review, false)} | ||
> | ||
{' '} | ||
Remove Review | ||
</button> | ||
</div> | ||
) | ||
} else { | ||
return ( | ||
<div className=""> | ||
<button | ||
type="button" | ||
className="" | ||
onClick={() => this.props.approveHandler(review)} | ||
> | ||
{' '} | ||
Confirm Review | ||
</button> | ||
<button | ||
type="button" | ||
className="" | ||
onClick={() => this.props.removeHandler(review, true)} | ||
> | ||
{' '} | ||
Remove Review | ||
</button> | ||
</div> | ||
) | ||
} | ||
} | ||
.post(`/api/getCourseById`, { | ||
courseId: review.class, | ||
}) | ||
.then((response) => { | ||
const course = response.data.result | ||
if (course) { | ||
setShortName(course.classSub.toUpperCase() + ' ' + course.classNum) | ||
setFullName(course.classTitle) | ||
} | ||
}) | ||
|
||
render() { | ||
const review = this.props.info | ||
return ( | ||
<li id={review._id}> | ||
<div className=""> | ||
<div className=""> | ||
<b>Course:</b> {this.state.shortName}: {this.state.longName} | ||
<br></br> | ||
{/* <b>Posted </b> {moment(review.date).fromNow()} */} | ||
</div> | ||
</div> | ||
<div className=""> | ||
<div className=""> | ||
function renderButtons(review: any) { | ||
const reported = review.reported | ||
if (reported === 1) { | ||
return ( | ||
<div className=""> | ||
<div className=""> | ||
<div className=""> | ||
<div className="">{review.rating}</div> | ||
</div> | ||
<div className=""> | ||
<div className="">{review.difficulty}</div> | ||
</div> | ||
<div className=""> | ||
<div className="">{review.professors}</div> | ||
</div> | ||
</div> | ||
<div className=""> | ||
<div className=""> Overall Rating</div> | ||
<div className=""> Difficulty</div> | ||
<div className=""> Professor(s)</div> | ||
</div> | ||
<div className="">{review.text}</div> | ||
<button | ||
type="button" | ||
className={styles.approvebutton} | ||
onClick={() => unReportHandler(review)} | ||
> | ||
{' '} | ||
Restore Review | ||
</button> | ||
<button | ||
type="button" | ||
className={styles.removebutton} | ||
onClick={() => removeHandler(review, false)} | ||
> | ||
{' '} | ||
Remove Review | ||
</button> | ||
</div> | ||
) | ||
} else { | ||
return ( | ||
<div className=""> | ||
<div className="">{this.renderButtons(review)}</div> | ||
<button | ||
type="button" | ||
className={styles.approvebutton} | ||
onClick={() => approveHandler(review)} | ||
> | ||
{' '} | ||
Confirm Review | ||
</button> | ||
<button | ||
type="button" | ||
className={styles.removebutton} | ||
onClick={() => removeHandler(review, true)} | ||
> | ||
{' '} | ||
Remove Review | ||
</button> | ||
</div> | ||
</div> | ||
) | ||
} | ||
} | ||
return ( | ||
<div id = {review._id} className = {styles.pendingreview}> | ||
<div className = {styles.titleinfo}> | ||
<h4 className = ""> | ||
Course: {shortName}, {fullName} | ||
</h4> | ||
<p>{review.date}</p> | ||
</div> | ||
<div className = {styles.reviewinfo}> | ||
<p>Professor(s): {review.professors}</p> | ||
<p>Overall Rating: {review.rating}</p> | ||
<p>Difficulty: {review.difficulty}</p> | ||
<p>Workload: {review.workload}</p> | ||
<br></br> | ||
<p>{review.text}</p> | ||
</div> | ||
</li> | ||
<div className="">{renderButtons(review)}</div> | ||
</div> | ||
) | ||
} | ||
} | ||
export default UpdateReview |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import React, { useEffect, useState } from 'react' | ||
Check warning on line 1 in client/src/modules/Admin/Components/AdminUser.tsx
|
||
import axios from 'axios' | ||
import styles from '../Styles/AdminUser.module.css' | ||
|
||
type Props = { | ||
user: any | ||
token: string | ||
removeHandler: (arg1: any) => any | ||
} | ||
|
||
const AdminUser = ({user, token, removeHandler}: Props) => { | ||
|
||
return ( | ||
<div className={styles.userEntry}> | ||
{user.firstName} {user.lastName}, {user.netId} | ||
<button | ||
className={styles.removeButton} | ||
onClick={() => removeHandler(user)} | ||
> | ||
Remove | ||
</button> | ||
</div> | ||
) | ||
} | ||
|
||
export default AdminUser |
126 changes: 126 additions & 0 deletions
126
client/src/modules/Admin/Components/ManageAdminModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import React, { useEffect, useState } from 'react' | ||
import axios from 'axios' | ||
import styles from '../Styles/ManageAdminModal.module.css' | ||
|
||
import { Student } from 'common' | ||
import AdminUser from './AdminUser' | ||
import closeIcon from '../../../assets/icons/X.svg' | ||
|
||
type Props = { | ||
open: boolean | ||
setOpen: (open: boolean) => void | ||
token: string | ||
} | ||
|
||
const ManageAdminModal = ({token, open, setOpen}: Props) => { | ||
const [admins, setAdmins] = useState<Student[]>([]) | ||
const [netId, setNetId] = useState<string>("") | ||
|
||
function closeModal() { | ||
setOpen(false) | ||
} | ||
|
||
/** | ||
* Endpoint to get all admins | ||
*/ | ||
useEffect(() => { | ||
axios | ||
.post('/api/admin/users/get', {token: token}) | ||
.then((response) => { | ||
const result = response.data.result | ||
if (response.status === 200) { | ||
setAdmins(result) | ||
} else { | ||
console.log('Error at getAdmins') | ||
} | ||
}) | ||
}, [token]) | ||
|
||
/** | ||
* Removes an admin from the list, giving that user 'regular' privilege | ||
* @param user assumes that this user already has admin privilege | ||
*/ | ||
|
||
function removeAdmin(user: Student) { | ||
axios | ||
.post('/api/admin/users/remove', { | ||
userId: user.netId, | ||
token: token | ||
}) | ||
.then((response) => { | ||
if (response.status === 200) { | ||
const updatedAdmins = admins.filter((admin: Student) => { | ||
return admin && admin._id !== user.netId | ||
}) | ||
setAdmins(updatedAdmins) | ||
} | ||
}).catch((e) => console.log(`Unable to remove admin ${e}`)) | ||
} | ||
|
||
/** | ||
* Calls endpoint to add or update a user with admin privilege | ||
* @param _netId the user's net id | ||
*/ | ||
|
||
function addAdminByNetId(_netId: string) { | ||
axios | ||
.post('/api/admin/users/add', { | ||
userId: _netId, | ||
token: token | ||
}) | ||
.then((response) => { | ||
if (response.status === 200) { | ||
console.log(`Successfully gave admin privilege to ${_netId}`) | ||
} | ||
}).catch((e) => console.log(`Unable to remove admin ${e}`)) | ||
} | ||
|
||
function onTextChange(newText: string) { | ||
setNetId(newText) | ||
} | ||
|
||
if (!open) { | ||
return <></> | ||
} | ||
|
||
return ( | ||
<div className={styles.modalbg}> | ||
<div className={styles.modal}> | ||
<h2>Administrators</h2> | ||
<img | ||
className={styles.closeButton} | ||
onClick={closeModal} | ||
src={closeIcon} | ||
alt="close-modal" | ||
/> | ||
<div className={styles.addAdmin}> | ||
<input | ||
className={styles.textInputBox} | ||
value={netId} | ||
onChange={(e) => onTextChange(e.target.value)} | ||
name="new-admin" | ||
id="new-admin" | ||
placeholder="User net-id" | ||
></input> | ||
<button | ||
className = {styles.addAdminButton} | ||
onClick={() => addAdminByNetId(netId)} | ||
>Add administrator</button> | ||
</div> | ||
<br> | ||
</br> | ||
{admins.map((admin) => { | ||
return ( | ||
<AdminUser | ||
user={admin} | ||
token={token} | ||
removeHandler={removeAdmin} | ||
/> | ||
) | ||
})} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default ManageAdminModal |
Oops, something went wrong.