️ This Gitlab will be shut down at 2021-12-31 23:59:59.
Students and staff can migrate to gitlab.utwente.nl.
SNT members can migrate to gitlab.snt.utwente.nl.
Contact bestuur@snt.utwente.nl for more information.

Migrate your projects today!
Export your project using the webinterface or use a script.

Commit 1349df25 authored by TJHeeringa's avatar TJHeeringa

Changed association router

parent 20041af4
......@@ -5,9 +5,8 @@ import { useHistory } from "react-router-dom";
import {Button,Col,Container,Row} from "reactstrap";
export const BackButton = (props) => {
export const BackButton = ({ container }) => {
const history = useHistory();
const { container } = props;
if (container) {
return (
......
import makeStyles from "@material-ui/core/styles/makeStyles";
import PropTypes from "prop-types";
import IconButton from "@material-ui/core/IconButton";
import clsx from "clsx";
import MenuIcon from "@material-ui/icons/Menu";
import MenuOpenIcon from "@material-ui/icons/MenuOpen";
import React from "react";
const useStyles = makeStyles(theme=>({
}));
const Burger = ({ open, onClick }) => {
const classes = useStyles();
return (
<IconButton
color={"inherit"}
......
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import {Skeleton} from "@material-ui/lab";
import useSize from "@react-hook/size";
import Image from "img/default_photo.jpg"; // Import using relative path
import PropTypes from "prop-types";
......@@ -49,7 +50,7 @@ const PageContent = (props) => {
<div className={classes.photo_header} style={{width: size.width}}/>
<div className={classes.titlebar}>
<Typography className={classes.header} variant={"h3"}>{ title || "" }</Typography>
<Typography className={classes.header} variant={"h3"}>{ title || <Skeleton /> }</Typography>
</div>
<div className={classes.sub_photo_container}>
......
......@@ -29,10 +29,10 @@ const AssociationsNavTabs = ({ association_memberships, url, onImgLoad }) => {
if (slug_end_index < 0) {
return location.pathname;
} else {
return location.pathname.slice(0,slug_end_index);
return location.pathname.slice(0, slug_end_index);
}
} else {
return 0;
return "home";
}
}, [location]);
......@@ -89,6 +89,7 @@ const AssociationsNavTabs = ({ association_memberships, url, onImgLoad }) => {
return (
<Tabs value={getTabsValue}>
<Tab
value={"home"}
onClick={()=>authenticator.authenticated && history.push("/protected/home")}
icon={<img className={classes.logo} src={logo} alt={""}/>}
// label={"Home"}
......@@ -103,8 +104,8 @@ const AssociationsNavTabs = ({ association_memberships, url, onImgLoad }) => {
/>
)) }
<Tab
value={url+"/associations/add"}
onClick={()=>history.push(url+"/associations/add")}
value={url+"/associations/overview"}
onClick={()=>history.push(url+"/associations/overview")}
icon={
<AddIcon fontSize={"large"} color={"inherit"} className={classes.logo}/>
}
......
import "react-simple-tree-menu/dist/main.css";
import "./Groups.css";
import { makeStyles, useTheme } from "@material-ui/core/styles";
import PropTypes from "prop-types";
import React from "react";
import { Route , useLocation, withRouter } from "react-router-dom";
import {Route, useLocation, useRouteMatch} from "react-router-dom";
import { useGet } from "restful-react";
import CardGrid from "../../../Components/Card/CardGrid";
......@@ -13,15 +12,9 @@ import PageContent from "../../../Components/PageLayout/Content/Content";
import Detail from "./Detail";
const useStyles = makeStyles(theme => ({
}));
const Committees = ({ association, path }) => {
const theme = useTheme();
const classes = useStyles(theme);
const Committees = ({ association }) => {
const location = useLocation();
path = path + "/committees";
const { path } = useRouteMatch();
let { data: committees, loading, error } = useGet({
path: "/groups",
......
......@@ -10,7 +10,7 @@ import PageContent from "../../Components/PageLayout/Content/Content";
const AssociationDetail = ({ slug }) => {
let { data: association, loading: loadingAssociation } = useGet({
let { data: association } = useGet({
path: "/associations/" + slug,
resolve: data => data
});
......
......@@ -2,25 +2,31 @@ import Divider from "@material-ui/core/Divider";
import Typography from "@material-ui/core/Typography";
import SearchBar from "material-ui-search-bar";
import PropTypes from "prop-types";
import React, { useState } from "react";
import React, {useEffect, useState} from "react";
import Container from "react-bootstrap/esm/Container";
import { useGet } from "restful-react";
import AssociationCard from "../../Components/Card/AssociationCard";
import CardGrid from "../../Components/Card/CardGrid";
import Block from "../../Components/PageLayout/Content/Block";
import {useAPI} from "../../Contexts/API";
import MembershipRequest from "./MembershipRequest";
import {useHistory} from "react-router-dom";
export const Associations = ({ preferred_profile, association_memberships, refreshSunmemberMe }) => {
export const Associations = ({ associationsAPI }) => {
const API = useAPI();
const history = useHistory();
const [searchTerm, setSearchTerm] = useState("");
const [selectedAssociation, setSelectedAssociation] = useState(null);
let { data: associations, loading: loadingAssociations } = useGet({
path: "/associations",
queryParams: {limit: 10000},
resolve: data => data && data.results
});
useEffect(()=>{
if (!associationsAPI.loaded) {
associationsAPI.refresh();
}
}, [associationsAPI.loaded]);
const association_memberships = API.sunmember.association_memberships;
const associations = associationsAPI.collection;
const KEY_TO_FILTER = "name";
const createFilter = (searchTerm, key) => item=>item[key].toLowerCase().includes(searchTerm.toLowerCase());
......@@ -35,7 +41,7 @@ export const Associations = ({ preferred_profile, association_memberships, refre
description: association.description,
slug: association.slug,
photo: association.photo,
onBecomeMemberClick: ()=>setSelectedAssociation(associations.find(association_=>association_.slug === association.slug)),
onBecomeMemberClick: ()=>history.push(`${association.slug}/request`),
becomeMemberButton: memberOfAssociation(association)
});
......@@ -44,10 +50,8 @@ export const Associations = ({ preferred_profile, association_memberships, refre
<Container>
{ selectedAssociation !== null &&
<MembershipRequest
refreshSunmemberMe={refreshSunmemberMe}
onCancel={()=>setSelectedAssociation(null)}
association={selectedAssociation}
preferred_profile={preferred_profile}
/>
}
<Block>
......@@ -72,8 +76,7 @@ export const Associations = ({ preferred_profile, association_memberships, refre
};
Associations.propTypes = {
preferred_profile: PropTypes.object,
association_memberships: PropTypes.array
associationsAPI: PropTypes.object.isRequired,
};
export default Associations;
import PropTypes from "prop-types";
import React from "react";
import { Route, Switch } from "react-router-dom";
import useCollectionAPI from "../../Components/Hooks/useCollectionAPI";
/* Work in progress */
const AssociationsRouter = ({ path }) => {
const associationsAPI = useCollectionAPI("/associations", {limit: 10000});
return (
<Switch>
<Route />
<Route />
</Switch>
);
};
AssociationsRouter.propTypes = {
path: PropTypes.string.isRequired,
};
export default AssociationsRouter;
\ No newline at end of file
......@@ -8,6 +8,7 @@ import { ValidatorForm } from "react-material-ui-form-validator";
import { useHistory } from "react-router-dom";
import {useGet} from "restful-react";
import {BackButton} from "../../Components/BackButton";
import SelectField from "../../Components/Fields/SelectField";
import SpecificDataField from "../../Components/Fields/SpecificDataField";
import Wrapper from "../../Components/Fields/Wrapper";
......@@ -15,11 +16,12 @@ import Block from "../../Components/PageLayout/Content/Block";
import isTruthy from "../../Components/ValidatorRules/isTruthy";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import Container from "@material-ui/core/Container";
const breakedTextNode = (text) => <>{ text }<br/></>;
const MembershipRequest = ({ onCancel, association, refreshSunmemberMe, preferred_profile: profile }) => {
const MembershipRequest = ({ onCancel, association }) => {
const alerthandler = useAlertHandler();
const API = useAPI();
const history = useHistory();
......@@ -30,12 +32,12 @@ const MembershipRequest = ({ onCancel, association, refreshSunmemberMe, preferre
membertype: ""
});
let { data: dataFields, loading: loadingDataFields } = useGet({
let { data: dataFields } = useGet({
path: "/association_data_fields",
queryParams: {limit: 10000, association__slug: association.slug, board_only: false},
resolve: data => data && data.results
});
let { data: membertypes, loading: loadingMembertypes } = useGet({
let { data: membertypes } = useGet({
path: "/membertypes",
queryParams: {limit: 10000, association__slug: association.slug},
resolve: data => data && data.results
......@@ -59,6 +61,7 @@ const MembershipRequest = ({ onCancel, association, refreshSunmemberMe, preferre
}
}, [dataFields]);
const profile = API.sunmember.preferred_profile;
const handleChange = (field, value) => setFormState(prevState => ({...prevState, [field]: value}));
const handleSubmit = () => {
// eslint-disable-next-line no-unused-vars
......@@ -86,7 +89,7 @@ const MembershipRequest = ({ onCancel, association, refreshSunmemberMe, preferre
},
on_succes: (response) => {
alerthandler.handleAlertHandler("green", "Membership request sent");
refreshSunmemberMe(
API.refetch(
()=>history.push("/protected/associations/" + response.association.slug + "/member")
);
},
......@@ -113,100 +116,94 @@ const MembershipRequest = ({ onCancel, association, refreshSunmemberMe, preferre
const raiseIbanMessage = useMemo(()=>association.member_needs_iban && !profile.iban, [association, profile]);
return (
<Block>
<Typography variant={"h5"}>Additional information needed</Typography>
{ association.new_member_text !== undefined
?
<Container>
<BackButton/>
<Block>
<Typography variant={"h4"}>Membership request for { association.name }</Typography>
<Typography>
{ association.new_member_text }
{ association.new_member_text || association.name + " requires additional information. To become a member you need to provide them with the following:" }
</Typography>
:
<Typography>
{ association.name } requires additional information. To become a member you need to
provide them with the following:
</Typography>
}
<hr className={"box-title-separator"}/>
{ raiseIbanMessage
? (
<>
<Typography>This association requires that you have an iban set in your profile. Please go to your profile and add it.</Typography>
<Wrapper>
<div/>
<Button variant={"contained"} onClick={onCancel}>Back</Button>
</Wrapper>
</>
)
: (
<ValidatorForm
onSubmit={handleSubmit}
onError={errors => console.error(errors)}
>
<Wrapper>
<SelectField
value={formState.membertype}
name={"membertype"}
onChange={(event)=>handleChange("membertype", event.target.value)}
label={"Member type"}
validators={["isTruthy"]}
errorMessages={["This field is required"]}
helperText={membertypeHelperText}
>
{ chooseable_membertypes.map((membertype, m) => (
<MenuItem key={m} value={membertype.url}>
{ membertype.type }
</MenuItem>
)) }
</SelectField>
</Wrapper>
{ dataFields && dataFields.map((dataField, d)=>(
<Wrapper key={d}>
<SpecificDataField
field={dataField}
value={formState[dataField.url] === undefined ? "" : formState[dataField.url]}
onChange={handleChange}
<hr className={"box-title-separator"}/>
{ raiseIbanMessage
? (
<>
<Typography>This association requires that you have an iban set in your profile. Please go to your profile and add it.</Typography>
<Wrapper>
<div/>
<Button variant={"contained"} onClick={onCancel}>Back</Button>
</Wrapper>
</>
)
: (
<ValidatorForm
onSubmit={handleSubmit}
onError={errors => console.error(errors)}
>
<Wrapper>
<SelectField
value={formState.membertype}
name={"membertype"}
onChange={(event)=>handleChange("membertype", event.target.value)}
label={"Member type"}
validators={["isTruthy"]}
errorMessages={["This field is required"]}
helperText={membertypeHelperText}
>
{ chooseable_membertypes.map((membertype, m) => (
<MenuItem key={m} value={membertype.url}>
{ membertype.type }
</MenuItem>
)) }
</SelectField>
</Wrapper>
{ dataFields && dataFields.map((dataField, d)=>(
<Wrapper key={d}>
<SpecificDataField
field={dataField}
value={formState[dataField.url] === undefined ? "" : formState[dataField.url]}
onChange={handleChange}
/>
</Wrapper>
)) }
<Wrapper>
<CheckboxField
checked={formState.privacy}
value={formState.privacy}
name={"privacy"}
onChange={(event)=>handleChange("privacy", event.target.checked)}
label={<span>I have read the <a href={association.privacy_statement}>Privacy Statement</a></span>}
labelPlacement={"end"}
validators={["isTruthy"]}
errorMessages={["This field is required"]}
/>
</Wrapper>
)) }
<Wrapper>
<CheckboxField
checked={formState.privacy}
value={formState.privacy}
name={"privacy"}
onChange={(event)=>handleChange("privacy", event.target.checked)}
label={<span>I have read the <a href={association.privacy_statement}>Privacy Statement</a></span>}
checked={formState.terms_of_service}
value={formState.terms_of_service}
name={"terms_of_service"}
onChange={(event)=>handleChange("terms_of_service", event.target.checked)}
label={<span>I have read and accept the <a href={association.terms_of_service}>Terms of Service</a></span>}
labelPlacement={"end"}
validators={["isTruthy"]}
errorMessages={["This field is required"]}
/>
</Wrapper>
<CheckboxField
checked={formState.terms_of_service}
value={formState.terms_of_service}
name={"terms_of_service"}
onChange={(event)=>handleChange("terms_of_service", event.target.checked)}
label={<span>I have read and accept the <a href={association.terms_of_service}>Terms of Service</a></span>}
labelPlacement={"end"}
validators={["isTruthy"]}
errorMessages={["This field is required"]}
/>
<Wrapper>
<div/>
<div>
<Button variant={"contained"} color={"primary"} type={"submit"}>Submit</Button>
&nbsp;
<Button variant={"contained"} onClick={onCancel}>Cancel</Button>
</div>
</Wrapper>
</ValidatorForm>
) }
</Block>
<Wrapper>
<div/>
<div>
<Button variant={"contained"} color={"primary"} type={"submit"}>Submit</Button>
&nbsp;
<Button variant={"contained"} onClick={onCancel}>Cancel</Button>
</div>
</Wrapper>
</ValidatorForm>
) }
</Block>
</Container>
);
};
MembershipRequest.propTypes = {
association: PropTypes.object.isRequired,
preferred_profile: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired
};
......
import PropTypes from "prop-types";
import React from "react";
import {Route, Switch, useHistory, useParams, useRouteMatch} from "react-router-dom";
import useCollectionAPI from "../../Components/Hooks/useCollectionAPI";
import {useAPI} from "../../Contexts/API";
import BoardMemberRoutes from "./MyAssociations/BoardMember";
import MemberRoutes from "./MyAssociations/Member";
import Associations from "../../Pages/Search/Associations";
import AssociationDetail from "../../Pages/Search/AssociationDetail";
import MembershipRequest from "../../Pages/Search/MembershipRequest";
const MembershipRequestRoute = ({ associationsAPI }) => {
const { goBack } = useHistory();
const { slug } = useParams();
const association = associationsAPI.collection.find(association=> association.slug === slug);
return (
<MembershipRequest
onCancel={goBack}
association={association}
/>
);
};
const InfoRoute = () => {
const { slug } = useParams();
return <AssociationDetail slug={slug}/>;
};
const MemberRoute = () => {
const API = useAPI();
const { slug } = useParams();
const { url } = useRouteMatch();
let association_membership = API.sunmember.association_memberships.find((membership)=>membership.association.slug === slug);
// make deepcopy of association; this way we preserve that the user is board,
// whilst we can properly make the member routes
const association = JSON.parse(JSON.stringify(association_membership.association));
association.is_board = false;
return (
<MemberRoutes
association={association_membership.association}
association_membership={association_membership}
committees={{}}
path={url}
show_board_view_button={association_membership.association.is_board}
/>
);
};
const BoardMemberRoute = () => {
const API = useAPI();
const { slug } = useParams();
const { url } = useRouteMatch();
let association_membership = API.sunmember.association_memberships.find((membership)=>membership.association.slug === slug);
return (
<BoardMemberRoutes
association={association_membership.association}
association_membership={association_membership}
committees={{}}
path={url}
/>
);
};
/* Work in progress */
const AssociationsRouter = ({ path }) => {
const associationsAPI = useCollectionAPI("/associations", {limit: 10000}, true);
return (
<Switch>
<Route path={path + "/overview"}>
<Associations
associationsAPI={associationsAPI}
/>
</Route>
<Route path={path + "/:slug/info"}>
<InfoRoute/>
</Route>
<Route path={path + "/:slug/request"}>
<MembershipRequestRoute
associationsAPI={associationsAPI}
/>
</Route>
<Route path={path + "/:slug/member"}>
<MemberRoute/>
</Route>
<Route path={path + "/:slug/boardmember"}>
<BoardMemberRoute/>
</Route>
</Switch>
);
};
AssociationsRouter.propTypes = {
path: PropTypes.string.isRequired,
};
export default AssociationsRouter;
\ No newline at end of file
......@@ -10,9 +10,7 @@ import Studies from "../../Contexts/Studies";
import Home from "../../Pages/Home/Home";
import Settings from "../../Pages/Profile/Settings";
import SharedProfile from "../../Pages/Profile/SharedProfile";
import { Associations } from "../../Pages/Search/Associations";
import AssociationRoutes from "./MyAssociations/Association";
import AssociationsRouter from "../../Pages/Search/AssociationsRouter";
import AssociationsRouter from "./AssociationsRouter";
/**
......@@ -94,22 +92,11 @@ export const AuthorizedRoutes = () => {
association_memberships={sunmember.association_memberships}
/>}
/>
<Route exact path={url + "/associations/add"}
render={() =>
<Associations
association_memberships={sunmember.association_memberships}
preferred_profile={sunmember.preferred_profile}
refreshSunmemberMe={refetch}
/>}
/>
<Route path={url + "/associations/:slug"}
render={({ match }) =>
<AssociationRoutes
url={match.url}
committees={sunmember.groups}
association_memberships={sunmember.association_memberships}
/>}
/>
<Route path={url + "/associations"}>
<AssociationsRouter