Skip to content

Feat/add classroom #376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
add admin Feature
admin users can now change user roles
  • Loading branch information
mariopesch committed Nov 25, 2024
commit 31dcbbde571df8043198e756c42471cfa432e272
40 changes: 40 additions & 0 deletions src/actions/adminActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

import axios from 'axios';
import { GET_USERS_SUCCESS, GET_USERS_FAIL, UPDATE_USER_ROLE_SUCCESS, UPDATE_USER_ROLE_FAIL } from './types';

// Fetch all users
export const getUsers = () => async (dispatch) => {
try {
const res = await axios.get(`${process.env.REACT_APP_BLOCKLY_API}/user/users`)
dispatch({
type: GET_USERS_SUCCESS,
payload: res.data
});
} catch (err) {
dispatch({
type: GET_USERS_FAIL,
payload: err.response.data
});
}
};

// Update user role
export const updateUserRole = (userId, role) => async (dispatch) => {
const body = {
"_id": userId,
"newRole": role
};

try {
const res = await axios.put(`${process.env.REACT_APP_BLOCKLY_API}/user/role`, body); // API endpoint to update user role
dispatch({
type: UPDATE_USER_ROLE_SUCCESS,
payload: { userId, role: res.data.newRole } // Assuming response contains updated role
});
} catch (err) {
dispatch({
type: UPDATE_USER_ROLE_FAIL,
payload: err.response ? err.response.data : 'Error updating role'
});
}
};
2 changes: 2 additions & 0 deletions src/components/Blockly/msg/de/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ export const UI = {
navbar_logout: "Abmelden",
navbar_settings: "Einstellungen",
navbar_classroom: "Klassenraum",
navbar_admin: "Admin",


/**
* Codeviewer
Expand Down
1 change: 1 addition & 0 deletions src/components/Blockly/msg/en/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ export const UI = {
navbar_logout: "Logout",
navbar_settings: "Settings",
navbar_classroom: "Classroom",
navbar_admin: "Admin",

/**
* Codeviewer
Expand Down
48 changes: 48 additions & 0 deletions src/components/Route/PrivateRouteAdmin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { Route, Redirect, withRouter } from 'react-router-dom';


class PrivateRouteAdmin extends Component {

render() {
return (
!this.props.progress ?
<Route
{...this.props.exact}
render={({ location }) =>
this.props.isAuthenticated &&
this.props.user &&
this.props.user.blocklyRole === 'admin' ? (
this.props.children
) : (()=>{
return (
<Redirect
to={{
pathname: "/",
state: { from: location }
}}
/>
)
})()
}
/> : null
);
}
}

PrivateRouteAdmin.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
user: PropTypes.object,
progress: PropTypes.bool.isRequired
};

const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user,
progress: state.auth.progress
});

export default connect(mapStateToProps, null)(withRouter(PrivateRouteAdmin));
153 changes: 153 additions & 0 deletions src/components/User/AdminDashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { getUsers, updateUserRole } from '../../actions/adminActions';
import { Grid, Paper, Typography, Tooltip, IconButton, List, ListItem, ListItemText, Divider, Select, MenuItem, FormControl, InputLabel, Dialog, DialogActions, DialogContent, DialogContentText, Button } from '@mui/material';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser, faEdit } from '@fortawesome/free-solid-svg-icons';

const AdminDashboard = () => {
const dispatch = useDispatch();
const users = useSelector(state => state.admin.users); // Fetch users from Redux store
const loggedInUser = useSelector(state => state.auth.user); // Get the currently logged-in user from the auth state

const [selectedRole, setSelectedRole] = useState({});
const [dialogOpen, setDialogOpen] = useState(false); // Manage confirmation dialog
const [targetUser, setTargetUser] = useState(null); // Store the user whose role is being changed

useEffect(() => {
dispatch(getUsers()); // Fetch users when component mounts
}, [dispatch]);

const handleRoleChange = (userId, newRole) => {
// Prevent role change for the current logged-in user
if (userId === loggedInUser._id) {
return;
}

setSelectedRole(prev => ({
...prev,
[userId]: newRole // Update selected role for the specific user
}));

setTargetUser({ userId, newRole });
if (newRole === 'admin' || newRole === 'user') {
setDialogOpen(true); // Open confirmation dialog if changing to/from 'admin'
} else {
updateUserRoleHandler(userId); // Otherwise, directly update the role
}
};

const updateUserRoleHandler = (userId) => {
if (selectedRole[userId]) {
dispatch(updateUserRole(userId, selectedRole[userId])); // Dispatch action to update user role
}
setDialogOpen(false); // Close confirmation dialog
};

const handleDialogClose = (confirmed) => {
if (confirmed) {
updateUserRoleHandler(targetUser.userId); // If confirmed, proceed with role change
}
setDialogOpen(false); // Close dialog regardless of confirmation
};

return (
<div>
<Typography variant="h4" gutterBottom>Admin Dashboard</Typography>
<Grid container spacing={3}>
{users.length > 0 ? users.map((user, i) => (
<Grid item xs={12} md={6} lg={4} key={i}>
<Paper elevation={3} style={{ padding: '16px' }}>
<List>
<ListItem>
<Tooltip title="User Name">
<FontAwesomeIcon icon={faUser} style={{ marginRight: '10px' }} />
</Tooltip>
<ListItemText primary={`Name: ${user.name}`} />
</ListItem>

<ListItem>
<Tooltip title="Email">
<FontAwesomeIcon icon={faUser} style={{ marginRight: '10px' }} />
</Tooltip>
<ListItemText primary={`Email: ${user.email}`} />
</ListItem>

<Divider style={{ margin: '10px 0' }} />
{user.email === loggedInUser.email ? (
<ListItem>
<FormControl fullWidth disabled> {/* Disable form control for the logged-in user */}
<InputLabel id={`role-select-label-${i}`}>User Role</InputLabel>
<Select
labelId={`role-select-label-${i}`}
id={`role-select-${i}`}
value={selectedRole[user._id] || user.role}
onChange={(e) => handleRoleChange(user._id, e.target.value)}
>
<MenuItem value="user">User</MenuItem>
<MenuItem value="creator">Creator</MenuItem>
<MenuItem value="admin">Admin</MenuItem>
</Select>
</FormControl>
</ListItem>) : (

<ListItem>
<FormControl fullWidth> {/* Disable form control for the logged-in user */}
<InputLabel id={`role-select-label-${i}`}>User Role</InputLabel>
<Select
labelId={`role-select-label-${i}`}
id={`role-select-${i}`}
value={selectedRole[user._id] || user.role}
onChange={(e) => handleRoleChange(user._id, e.target.value)}
>
<MenuItem value="user">User</MenuItem>
<MenuItem value="creator">Creator</MenuItem>
<MenuItem value="admin">Admin</MenuItem>
</Select>
</FormControl>
<Tooltip title="Change Role">
<IconButton
onClick={() => handleRoleChange(user._id, selectedRole[user._id])}
disabled={user._id === loggedInUser._id} // Disable button for the logged-in user
>
<FontAwesomeIcon icon={faEdit} />
</IconButton>
</Tooltip>
</ListItem>)}
</List>
</Paper>
</Grid>
)) : (
<Typography>No users found.</Typography> // Display message if no users found
)}
</Grid>

{/* Confirmation Dialog for Changing Roles */}
<Dialog
open={dialogOpen}
onClose={() => handleDialogClose(false)}
>
<DialogContent>
<DialogContentText>
Are you sure you want to change the role to {targetUser?.newRole === 'admin' ? 'Admin' : 'User'}? This will grant/revoke admin privileges.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => handleDialogClose(false)} color="primary">
Cancel
</Button>
<Button onClick={() => handleDialogClose(true)} color="secondary">
Confirm
</Button>
</DialogActions>
</Dialog>
</div>
);
};

AdminDashboard.propTypes = {
users: PropTypes.array.isRequired
};

export default AdminDashboard;
4 changes: 2 additions & 2 deletions src/components/Workspace/SaveProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class SaveProject extends Component {
<Tooltip title={this.state.projectType === 'project' ? Blockly.Msg.tooltip_update_project : Blockly.Msg.tooltip_save_project} arrow>
<IconButton
className={this.props.classes.button}
onClick={this.props.user.blocklyRole !== 'user' && (!this.props.project || this.props.user.email === this.props.project.creator) ? (e) => this.toggleMenu(e) : this.state.projectType === 'project' ? () => this.props.updateProject(this.state.projectType, this.props.project._id) : () => { this.setState({ projectType: 'project' }, () => this.saveProject()) }}
onClick={(!this.props.classroomUser && (!this.props.project || this.props.user.email === this.props.project.creator)) ? (e) => this.toggleMenu(e) : this.state.projectType === 'project' ? () => this.props.updateProject(this.state.projectType, this.props.project._id) : () => { this.setState({ projectType: 'project' }, () => this.saveProject()) }}
size="large">
<FontAwesomeIcon icon={faSave} size="xs" />
</IconButton>
Expand Down Expand Up @@ -204,7 +204,7 @@ class SaveProject extends Component {
</Dialog>
</div>
);
};
}
}

SaveProject.propTypes = {
Expand Down
6 changes: 4 additions & 2 deletions src/components/Workspace/WorkspaceFunc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ResetWorkspace from "./ResetWorkspace";
import DeleteProject from "./DeleteProject";
import CopyCode from "./CopyCode";
import AutoSave from "./AutoSave";

class WorkspaceFunc extends Component {
render() {
return (
Expand All @@ -24,7 +25,7 @@ class WorkspaceFunc extends Component {
alignItems: "center",
}}
>
{!this.props.assessment & !this.props.multiple ? <AutoSave /> : null}
{!this.props.assessment && !this.props.multiple ? <AutoSave /> : null}
{!this.props.assessment ? (
<WorkspaceName
style={{ marginRight: "5px" }}
Expand All @@ -42,7 +43,8 @@ class WorkspaceFunc extends Component {

{!this.props.multiple ? <CopyCode iconButton /> : null}

{this.props.user || this.props.classroomUser && !this.props.multiple ? (
{(this.props.user || this.props.classroomUser) &&
!this.props.multiple ? (
<SaveProject
style={{ marginRight: "5px" }}
projectType={this.props.projectType}
Expand Down
47 changes: 47 additions & 0 deletions src/reducers/adminReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { GET_USERS, GET_USERS_SUCCESS, GET_USERS_FAIL, UPDATE_USER_ROLE, UPDATE_USER_ROLE_SUCCESS, UPDATE_USER_ROLE_FAIL } from '../actions/types';

const initialState = {
users: [],
loading: false,
error: null,
};

export default function foo(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return {
...state,
loading: true,
};
case GET_USERS_SUCCESS:
return {
...state,
users: action.payload,
loading: false,
};
case GET_USERS_FAIL:
return {
...state,
error: action.payload,
loading: false,
};
case UPDATE_USER_ROLE:
return {
...state,
loading: true,
};
case UPDATE_USER_ROLE_SUCCESS:
return {
...state,
loading: false,
};
case UPDATE_USER_ROLE_FAIL:
return {
...state,
error: action.payload,
loading: false,
};
default:
return state;
}
}