Skip to content
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
17,588 changes: 1,063 additions & 16,525 deletions frontend/package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@mui/x-date-pickers": "^5.0.8",
"@rjsf/core": "^3.2.1",
"@rjsf/material-ui": "^3.2.1",
"@types/react-gravatar": "^2.6.10",
"babel-polyfill": "^6.26.0",
"classnames": "^2.2.6",
"date-fns": "^2.28.0",
Expand All @@ -53,6 +54,7 @@
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-gravatar": "^2.6.3",
"react-loading-overlay-ts": "^1.0.4",
"react-redux": "^7.2.6",
"react-router-dom": "^6.2.1",
Expand Down Expand Up @@ -111,6 +113,7 @@
"connect-history-api-fallback": "1.3.0",
"css-loader": "^6.7.1",
"cssnano": "^5.0.9",
"dayjs": "^1.11.6",
"eslint": "^8.26.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
Expand All @@ -137,6 +140,7 @@
"postcss": "^8.3.11",
"postcss-loader": "^3.0.0",
"prettier": "^2.3.0",
"react-loading-overlay-ts": "2.0.0",
"redux-immutable-state-invariant": "1.2.4",
"rimraf": "^2.5.4",
"sass-loader": "^10.0.3",
Expand All @@ -153,9 +157,7 @@
"webpack-dev-middleware": "^5.3.3",
"webpack-dev-server": "^4.11.1",
"webpack-hot-middleware": "^2.25.2",
"webpack-merge": "^5.7.3",
"dayjs": "^1.11.6",
"react-loading-overlay-ts": "2.0.0"
"webpack-merge": "^5.7.3"
},
"keywords": [
"clowder-frontend"
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/actions/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,37 @@ export function fetchGroupAbout(id){
});
};
}

export const DELETE_GROUP_MEMBER = "DELETE_GROUP_MEMBER";
export function deleteGroupMember(groupId, username){
return (dispatch) => {
return V2.GroupsService.removeMemberApiV2GroupsGroupIdRemoveUsernamePost(groupId, username)
.then(json => {
dispatch({
type: DELETE_GROUP_MEMBER,
about: json,
receivedAt: Date.now(),
});
})
.catch(reason => {
dispatch(handleErrors(reason, deleteGroupMember(groupId, username)));
});
};
}

export const ADD_GROUP_MEMBER = "ADD_GROUP_MEMBER";
export function addGroupMember(groupId, username){
return (dispatch) => {
return V2.GroupsService.addMemberApiV2GroupsGroupIdAddUsernamePost(groupId, username)
.then(json => {
dispatch({
type: ADD_GROUP_MEMBER,
about: json,
receivedAt: Date.now(),
});
})
.catch(reason => {
dispatch(handleErrors(reason, addGroupMember(groupId, username)));
});
};
}
19 changes: 19 additions & 0 deletions frontend/src/actions/user.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {V2} from "../openapi";
import Cookies from "universal-cookie";
import config from "../app.config";
import {handleErrors} from "./common";
import {ADD_GROUP_MEMBER} from "./group";

const cookies = new Cookies();

Expand Down Expand Up @@ -89,3 +91,20 @@ export function logout() {
});
};
}

export const LIST_USERS = "LIST_USERS";
export function fetchAllUsers(skip=0, limit=101){
return (dispatch) => {
return V2.UsersService.getUsersApiV2UsersGet(skip, limit)
.then(json => {
dispatch({
type: LIST_USERS,
users: json,
receivedAt: Date.now(),
});
})
.catch(reason => {
dispatch(fetchAllUsers(skip=0, limit=21));
});
};
}
82 changes: 82 additions & 0 deletions frontend/src/components/groups/AddMemberModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, {useEffect, useState} from "react";
import {
Autocomplete,
Box,
Button,
Container,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
InputLabel,
MenuItem,
Select,
TextField
} from "@mui/material";
import {addGroupMember} from "../../actions/group";
import {useDispatch, useSelector} from "react-redux";
import {fetchAllUsers} from "../../actions/user";
import {RootState} from "../../types/data";
import {UserOut} from "../../openapi/v2";
import GroupsIcon from "@mui/icons-material/Groups";


type AddMemberModalProps = {
open: boolean,
handleClose: any,
groupName: string,
groupId: string,
}

export default function AddMemberModal(props: AddMemberModalProps) {
const { open, handleClose, groupName, groupId } = props;

const dispatch = useDispatch();
const listAllUsers = (skip:number, limit:number) => dispatch(fetchAllUsers(skip, limit));
const groupMemberAdded = (groupId:string | undefined, username: string | undefined) => dispatch(addGroupMember(groupId, username));
const users = useSelector((state: RootState) => state.group.users);

const [email, setEmail] = useState("");
const [options, setOptions] = useState([]);

useEffect(() => {
listAllUsers(0, 21);
}, [])

useEffect(() =>{
setOptions(users.reduce((list:string[], user:UserOut) => { return [...list, user.email];}, []));
}, [users])

const handleAddButtonClick = () =>{
groupMemberAdded(groupId, email);
setEmail("");
handleClose();
}
return (
<Container>
<Dialog open={open} onClose={handleClose} fullWidth={true}>
<DialogTitle>Add People to <GroupsIcon sx={{verticalAlign: "middle", fontSize: "1.5em",
margin: "auto 5px"}}/>{groupName}</DialogTitle>
<DialogContent>
<Autocomplete
id="email-auto-complete"
freeSolo
autoHighlight
inputValue={email}
onInputChange={(_, value) => {
setEmail(value)
}}
options={options}
renderInput={(params) =>
<TextField {...params} sx={{mt: 1, width: "100%" }} required label="Enter email address" />}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleAddButtonClick}>Add</Button>
<Button onClick={handleClose}>Cancel</Button>
</DialogActions>
</Dialog>
</Container>
);
}
65 changes: 32 additions & 33 deletions frontend/src/components/groups/Group.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
import React, {useEffect, useState} from "react";
import {
Box,
Button,
ButtonGroup, CardActionArea,
Divider,
Grid,
List,
} from "@mui/material";
import {Box, Button} from "@mui/material";
import Layout from "../Layout";
import {RootState} from "../../types/data";
import {useDispatch, useSelector} from "react-redux";
import {fetchGroupAbout} from "../../actions/group";
import {fetchGroupRole} from "../../actions/authorization";
import {ArrowBack, ArrowForward} from "@material-ui/icons";
import Typography from '@mui/material/Typography';
import {theme} from "../../theme";
import {Link, useParams} from "react-router-dom";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import RoleChip from "../auth/RoleChip";
import FilesTable from "../files/FilesTable";
import Typography from "@mui/material/Typography";
import {useParams} from "react-router-dom";
import {AuthWrapper} from "../auth/AuthWrapper";
import PersonAddAlt1Icon from "@mui/icons-material/PersonAddAlt1";
import MembersTable from "./MembersTable";
import AddMemberModal from "./AddMemberModal";


export function Group() {
Expand All @@ -33,9 +23,11 @@ export function Group() {
const fetchGroupInfo = (groupId: string | undefined) => dispatch(fetchGroupAbout(groupId));
const fetchCurrentGroupRole = (groupId: string | undefined) => dispatch(fetchGroupRole(groupId));

const about = useSelector((state: RootState) => state.group.about);
const groupAbout = useSelector((state: RootState) => state.group.about);
const role = useSelector((state: RootState) => state.group.role);

const [addMemberModalOpen,setAddMemberModalOpen] = useState(false);

// component did mount
useEffect(() => {
fetchGroupInfo(groupId);
Expand All @@ -44,22 +36,29 @@ export function Group() {

return (
<Layout>
<Grid container>
{/*title*/}
<Grid item xs={8} sx={{display: "flex", alignItems: "center"}}>
<Box sx={{display: "inline-flex", justifyContent: "space-between", alignItems: "baseline"}}>
<Typography variant="h3" paragraph>{about !== undefined ? about.name : "Not found"}</Typography>
</Box>
</Grid>
<Grid container spacing={2}>
<Grid item xs={10}>
<Typography variant="body1" paragraph>{about.description}</Typography>
</Grid>
</Grid>
</Grid>
<Grid container>
<MembersTable groupId={groupId}/>
</Grid>
<AddMemberModal open={addMemberModalOpen} handleClose={()=>{setAddMemberModalOpen(false);}}
groupName={groupAbout.name} groupId={groupAbout.id}/>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center"}}>
<Box sx={{
display: "flex",
flexDirection: "column",
p: 1,
m: 1
}}>
<Typography variant="h3" paragraph>{groupAbout !== undefined ? groupAbout.name : "Not found"}
</Typography>
<Typography variant="body1" paragraph>{groupAbout.description}</Typography>
</Box>
<AuthWrapper currRole={role} allowedRoles={["owner", "editor"]}>
<Button variant="contained"
onClick={() => {
setAddMemberModalOpen(true);
}} endIcon={<PersonAddAlt1Icon/>}>
Add Member
</Button>
</AuthWrapper>
</Box>
<MembersTable groupId={groupId}/>
</Layout>
);
}
59 changes: 0 additions & 59 deletions frontend/src/components/groups/MemberMenu.tsx

This file was deleted.

Loading