Skip to content

Commit

Permalink
members: [inveniosoftware#855] 5) implement 'wait for decision' flow …
Browse files Browse the repository at this point in the history
…in Members tab[+]

- Most significant contribution here is the proper link(s) serialization for Members.
  This affects invitations and membership requests.
  • Loading branch information
fenekku committed Jul 4, 2024
1 parent dbf1ce4 commit 863af9c
Show file tree
Hide file tree
Showing 25 changed files with 971 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// This file is part of Invenio-communities
// Copyright (C) 2022 CERN.
// Copyright (C) 2024 Northwestern University.
//
// Invenio-communities is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import { CommunityMembershipRequestsApi } from "./api";
import React, { Component } from "react";
import PropTypes from "prop-types";

export const MembershipRequestsContext = React.createContext({ api: undefined });

export class MembershipRequestsContextProvider extends Component {
constructor(props) {
super(props);
const { community } = props;
this.apiClient = new CommunityMembershipRequestsApi(community);
}
render() {
const { children } = this.props;
return (
<MembershipRequestsContext.Provider value={{ api: this.apiClient }}>
{children}
</MembershipRequestsContext.Provider>
);
}
}

MembershipRequestsContextProvider.propTypes = {
community: PropTypes.object.isRequired,
children: PropTypes.node.isRequired,
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export class Filters {
return { ...rolesFilters, ...statusFilters };
}

getMembershipRequestFilters() {
const statusFilters = this.getStatus();
const rolesFilters = this.getRoles();
return { ...rolesFilters, ...statusFilters };
}

getMembersFilters() {
const visibilityFilters = this.getVisibility();
const rolesFilters = this.getRoles();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* This file is part of Invenio.
* Copyright (C) 2022 CERN.
* Copyright (C) 2024 Northwestern University.
*
* Invenio is free software; you can redistribute it and/or modify it
* under the terms of the MIT License; see LICENSE file for more details.
*/

import React from "react";
import { Grid } from "semantic-ui-react";
import { ResultsPerPage, Pagination, ResultsList } from "react-searchkit";
import PropTypes from "prop-types";
import { Trans } from "react-i18next";

export const MemberRequestsResults = ({ paginationOptions, currentResultsState }) => {
const { total } = currentResultsState.data;
return (
total && (
<Grid>
<Grid.Row>
<Grid.Column width={16}>
<ResultsList />
</Grid.Column>
</Grid.Row>
<Grid.Row verticalAlign="middle">
<Grid.Column width={8} textAlign="right">
<Pagination
options={{
size: "mini",
showFirst: false,
showLast: false,
}}
/>
</Grid.Column>
<Grid.Column textAlign="right" width={8}>
<ResultsPerPage
values={paginationOptions.resultsPerPage}
label={(cmp) => (
// kept key for translation purposes - it should be
// the same across members, invitations, membership requests
// and beyond
<Trans key="communitiesInvitationsResult" count={cmp}>
{cmp} results per page
</Trans>
)}
/>
</Grid.Column>
</Grid.Row>
</Grid>
)
);
};

MemberRequestsResults.propTypes = {
paginationOptions: PropTypes.object.isRequired,
currentResultsState: PropTypes.object.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of Invenio.
* Copyright (C) 2022 CERN.
* Copyright (C) 2024 Northwestern University.
*
* Invenio is free software; you can redistribute it and/or modify it
* under the terms of the MIT License; see LICENSE file for more details.
*/

import React from "react";
import { Input } from "semantic-ui-react";
import { i18next } from "@translations/invenio_communities/i18next";
import PropTypes from "prop-types";

export const MemberRequestsSearchBarElement = ({
onBtnSearchClick,
onInputChange,
onKeyPress,
queryString,
uiProps,
className,
placeholder,
}) => {
return (
<Input
className={className}
action={{
icon: "search",
onClick: onBtnSearchClick,
className: "search",
title: i18next.t("Search"),
}}
fluid
placeholder={placeholder}
onChange={(_, { value }) => {
onInputChange(value);
}}
value={queryString}
onKeyPress={onKeyPress}
{...uiProps}
/>
);
};

MemberRequestsSearchBarElement.propTypes = {
onBtnSearchClick: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired,
onKeyPress: PropTypes.func.isRequired,
queryString: PropTypes.string.isRequired,
uiProps: PropTypes.object,
className: PropTypes.string,
placeholder: PropTypes.string,
};

MemberRequestsSearchBarElement.defaultProps = {
uiProps: null,
className: "",
placeholder: "",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Button, Header, Icon, Segment } from "semantic-ui-react";
import { withState } from "react-searchkit";
import { i18next } from "@translations/invenio_communities/i18next";

class MembershipRequestsEmptyResultsCmp extends Component {
render() {
const { resetQuery, extraContent, queryString } = this.props;

return (
<Segment.Group>
<Segment placeholder textAlign="center">
<Header icon>
<Icon name="search" />
{i18next.t("No matching members found.")}
</Header>
{queryString && (
<p>
<em>
{i18next.t("Current search")} "{queryString}"
</em>
</p>
)}
<Button primary onClick={() => resetQuery()}>
{i18next.t("Clear query")}
</Button>
{extraContent}
</Segment>
</Segment.Group>
);
}
}

MembershipRequestsEmptyResultsCmp.propTypes = {
resetQuery: PropTypes.func.isRequired,
queryString: PropTypes.string.isRequired,
extraContent: PropTypes.node,
};

MembershipRequestsEmptyResultsCmp.defaultProps = {
extraContent: null,
};

export const MembershipRequestsEmptyResults = withState(
MembershipRequestsEmptyResultsCmp
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* This file is part of Invenio.
* Copyright (C) 2022 CERN.
* Copyright (C) 2024 Northwestern University.
*
* Invenio is free software; you can redistribute it and/or modify it
* under the terms of the MIT License; see LICENSE file for more details.
*/

import { RequestActionController } from "@js/invenio_requests/request/actions/RequestActionController";
import RequestStatus from "@js/invenio_requests/request/RequestStatus";
import { i18next } from "@translations/invenio_communities/i18next";
import { DateTime } from "luxon";
import PropTypes from "prop-types";
import React, { Component } from "react";
import { Image } from "react-invenio-forms";
import { Grid, Item, Table } from "semantic-ui-react";

import { RoleDropdown } from "../components/dropdowns";

const formattedTime = (expiresAt) =>
DateTime.fromISO(expiresAt).setLocale(i18next.language).toRelative();

export class MembershipRequestsResultItem extends Component {
constructor(props) {
super(props);
const { result } = this.props;
this.state = { membershipRequest: result };
}

update = (data, value) => {
const { membershipRequest } = this.state;
this.setState({ membershipRequest: { ...membershipRequest, ...{ role: value } } });
};

actionSuccessCallback = () => undefined;

render() {
const {
config: { rolesCanAssign },
community,
} = this.props;

const {
membershipRequest: { member, request },
membershipRequest,
} = this.state;
// TODO: Decision flow
// const { api: membershipRequestsApi } = this.context;
const rolesCanAssignByType = rolesCanAssign[member.type];
const membershipRequestExpiration = formattedTime(request.expires_at);
return (
<Table.Row className="community-member-item">
<Table.Cell>
<Grid textAlign="left" verticalAlign="middle">
<Grid.Column>
<Item className="flex align-items-center" key={membershipRequest.id}>
<Image src={member.avatar} avatar circular className="mr-10" />
<Item.Content>
<Item.Header size="small" as="b">
<a href={`/communities/${community.slug}/requests/${request.id}`}>
{member.name}
</a>
</Item.Header>
{member.description && (
<Item.Meta>
<div className="truncate-lines-1">{member.description}</div>
</Item.Meta>
)}
</Item.Content>
</Item>
</Grid.Column>
</Grid>
</Table.Cell>
<Table.Cell data-label={i18next.t("Status")}>
<RequestStatus status={request.status} />
</Table.Cell>
<Table.Cell
aria-label={i18next.t("Expires") + " " + membershipRequestExpiration}
data-label={i18next.t("Expires")}
>
{membershipRequestExpiration}
</Table.Cell>
<Table.Cell data-label={i18next.t("Role")}>
<RoleDropdown
roles={rolesCanAssignByType}
successCallback={this.update}
// TODO: Decision flow
// action={membershipRequestsApi.updateRole}
disabled={!membershipRequest.permissions.can_update_role}
currentValue={membershipRequest.role}
resource={membershipRequest}
label={i18next.t("Role") + " " + membershipRequest.role}
/>
</Table.Cell>
<Table.Cell data-label={i18next.t("Actions")}>
<RequestActionController
request={membershipRequest}
// TODO: Decision flow
actionSuccessCallback={() => console.log("actionSuccessCallback called")}
/>
</Table.Cell>
</Table.Row>
);
}
}

MembershipRequestsResultItem.propTypes = {
result: PropTypes.object.isRequired,
config: PropTypes.object.isRequired,
community: PropTypes.object.isRequired,
};

MembershipRequestsResultItem.defaultProps = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* This file is part of Invenio.
* Copyright (C) 2022 CERN.
* Copyright (C) 2024 Northwestern University.
*
* Invenio is free software; you can redistribute it and/or modify it
* under the terms of the MIT License; see LICENSE file for more details.
*/

import { i18next } from "@translations/invenio_communities/i18next";
import PropTypes from "prop-types";
import React from "react";
import { Table } from "semantic-ui-react";

export const MembershipRequestsResultsContainer = ({ results }) => {
return (
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={5}>{i18next.t("Name")}</Table.HeaderCell>
<Table.HeaderCell width={2}>{i18next.t("Status")}</Table.HeaderCell>
<Table.HeaderCell width={3}>{i18next.t("Expires")}</Table.HeaderCell>
<Table.HeaderCell width={3}>{i18next.t("Role")}</Table.HeaderCell>
<Table.HeaderCell width={3}>{i18next.t("Actions")}</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>{results}</Table.Body>
</Table>
);
};

MembershipRequestsResultsContainer.propTypes = {
results: PropTypes.array.isRequired,
};

MembershipRequestsResultsContainer.defaultProps = {};
Loading

0 comments on commit 863af9c

Please sign in to comment.