Skip to content
This repository has been archived by the owner on Nov 13, 2024. It is now read-only.

Commit

Permalink
dex 1341 - add collaboration from user page (#464)
Browse files Browse the repository at this point in the history
* Update user collaboration cards

* Update MyCollaborationsCard columns to fix no-unstable-nested-components error

* Add AddCollaboratorButton shell

* Add logic to AddCollaboratorButton

* Internationalize default error message for handleAxiosError

* Remove 'limit' and 'offset' from useGetUsers

* Fix useMutation error handling

* Rename prefixApiURL to prefixApiUrl

* Rename useHandleAxiosError to useHandleRequestError and update logic

* Inline handleSubmitCollaborator

* Rename axiosUtils.js to requestUtils and prefixApiUrl to withApiPrefix
  • Loading branch information
Emily-Ke authored Oct 6, 2022
1 parent ad726ae commit 93c9bf0
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 82 deletions.
4 changes: 4 additions & 0 deletions locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
"SEARCH_ITIS_SPECIES": "Search ITIS species",
"SEARCH_INDIVIDUALS_INSTRUCTION": "Search for an individual by name or guid",
"SEARCH_SIGHTINGS_INSTRUCTION": "Search for a sighting by location, owner or guid",
"SEARCH_USER_INSTRUCTION": "Search for a user by name or email",
"PRAIRIE": "Prairie",
"EDIT_USER_METADATA": "Edit user metadata",
"EDIT_SIGHTING_METADATA": "Edit sighting metadata",
Expand Down Expand Up @@ -987,6 +988,7 @@
"COMPONENT_COMMIT_HASH": "{component} commit hash: ",
"INDIVIDUAL_SEARCH_NO_RESULTS": "Your search \"{searchTerm}\" did not match any individuals.",
"SIGHTING_SEARCH_NO_RESULTS": "Your search \"{searchTerm}\" did not match any sightings.",
"POTENTIAL_COLLABORATOR_SEARCH_NO_RESULTS": "Your search \"{searchTerm}\" did not match any potential collaborators.",
"SEARCH_SERVER_ERROR": "A server error occurred while attempting to search.",
"CONFIGURATION_SITE_NAME_LABEL": "Site name",
"CONFIGURATION_SITE_NAME_DESCRIPTION": "The name of this site, excluding domain name suffix (ie. PandaMatcher, NOT pandamatcher.org).",
Expand Down Expand Up @@ -1197,6 +1199,8 @@
"EDIT_COLLABORATION_CURRENT_STATE_DESCRIPTION": "Changing the state will cancel any pending requests.",
"COLLABORATIONS": "Collaborations",
"URLS_MUST_INCLUDE_HTTPS": "All URLs must include https:// to be valid",
"ADD_COLLABORATION": "Add collaboration",
"EMAIL": "Email",
"EDIT_COLLABORATION_REVOKED_BY_USER_MANAGER": "Edit collaboration revoked by a user manager.",
"EDIT_COLLABORATION_WAS_REVOKED_BY_A_USER_MANAGER": "An edit-level collaboration with {otherUserNameForManagerNotifications} was revoked by a user manager {managerName}.",
"COLLABORATION_EDIT_DENIED": "Collaboration edit denied",
Expand Down
47 changes: 11 additions & 36 deletions src/components/UserProfile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import React, { useState, useMemo } from 'react';
import { useIntl, FormattedMessage } from 'react-intl';
import { get } from 'lodash-es';

import Card from '@material-ui/core/Card';
import Grid from '@material-ui/core/Grid';

import { getHighestRoleLabelId } from '../utils/roleUtils';
import useUserMetadataSchemas from '../models/users/useUserMetadataSchemas';
import useGetUserSightings from '../models/users/useGetUserSightings';
import useGetMe from '../models/users/useGetMe';
import useGetUserUnprocessedAssetGroupSightings from '../models/users/useGetUserUnproccessedAssetGroupSightings';
import { formatDate, formatUserMessage } from '../utils/formatters';
import EntityHeader from './EntityHeader';
Expand All @@ -19,9 +18,9 @@ import Text from './Text';
import RequestCollaborationButton from './RequestCollaborationButton';
import MetadataCard from './cards/MetadataCard';
import SightingsCard from './cards/SightingsCard';
import CollaborationsCard from './cards/CollaborationsCard';
import MyCollaborationsCard from './cards/MyCollaborationsCard';
import UserManagerCollaborationsCard from './cards/UserManagerCollaborationsCard';
import CardContainer from './cards/CardContainer';
import UserManagerCollaborationEditTable from './UserManagerCollaborationEditTable';

export default function UserProfile({
children,
Expand All @@ -30,6 +29,7 @@ export default function UserProfile({
userDataLoading,
refreshUserData,
someoneElse,
viewerIsUserManager,
noCollaborate = false,
}) {
const { data: sightingsData, loading: sightingsLoading } =
Expand All @@ -39,18 +39,6 @@ export default function UserProfile({
const metadataSchemas = useUserMetadataSchemas(userId);
const { data: agsData, loading: agsLoading } =
useGetUserUnprocessedAssetGroupSightings(userId);
const {
data: currentUserData,
loading: currentUserDataLoading,
error: currentUserDataError,
} = useGetMe();
const isUserManager = get(
currentUserData,
'is_user_manager',
false,
);

const nonSelfCollabData = get(userData, ['collaborations'], []);

const metadata = useMemo(() => {
if (!userData || !metadataSchemas) return [];
Expand Down Expand Up @@ -182,27 +170,14 @@ export default function UserProfile({
}
/>
{!someoneElse && (
<CollaborationsCard
htmlId="collab-card"
userId={userId}
/>
<Grid item xs={12} id='collab-card'>
<MyCollaborationsCard userData={userData} />
</Grid>
)}
{someoneElse && isUserManager && (
<Card
elevation={2}
style={{
marginLeft: 8,
marginTop: 20,
marginBottom: 12,
padding: 18,
}}
>
<UserManagerCollaborationEditTable
inputData={nonSelfCollabData}
collaborationLoading={currentUserDataLoading}
collaborationError={currentUserDataError}
/>
</Card>
{someoneElse && viewerIsUserManager && (
<Grid item xs={12} id='collab-card'>
<UserManagerCollaborationsCard userData={userData} />
</Grid>
)}
</CardContainer>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
import React, { useState, useEffect } from 'react';
import { useIntl } from 'react-intl';
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { get, partition } from 'lodash-es';
import { useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query';

import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';

import useGetUser from '../../models/users/useGetUser';
import Card from './Card';
import ActionIcon from '../ActionIcon';
import { withApiPrefix } from '../../utils/requestUtils';
import { cellRendererTypes } from '../dataDisplays/cellRenderers';
import Text from '../Text';
import Link from '../Link';
import DataDisplay from '../dataDisplays/DataDisplay';
import AddCollaboratorButton from './collaborations/AddCollaboratorButton';
import CollaborationsDialog from './collaborations/CollaborationsDialog';
import queryKeys from '../../constants/queryKeys';
import useHandleRequestError from '../../hooks/useHandleRequestError';

export default function CollaborationsCard({
userId,
htmlId = null,
}) {
export default function MyCollaborationsCard({ userData }) {
const intl = useIntl();
const queryClient = useQueryClient();
const handleRequestError = useHandleRequestError();

const [activeCollaboration, setActiveCollaboration] =
useState(null);
const [
collabDialogButtonClickLoading,
setCollabDialogButtonClickLoading,
] = useState(false);

const { data, loading } = useGetUser(userId);
const handleEdit = useCallback((_, collaboration) => {
setActiveCollaboration(collaboration);
}, []);

async function addCollaboratorMutationFn({ userGuid }) {
try {
const result = await axios.request({
url: withApiPrefix('/collaborations/'),
method: 'POST',
data: { user_guid: userGuid },
});
return result;
} catch (error) {
return handleRequestError(error);
}
}

const mutation = useMutation(addCollaboratorMutationFn, {
onSuccess: async () =>
queryClient.invalidateQueries(queryKeys.me),
});

useEffect(() => {
setCollabDialogButtonClickLoading(false);
}, [data]);
}, [userData]);

const collaborations = get(data, ['collaborations'], []);
const collaborations = get(userData, ['collaborations'], []);
const tableData = collaborations.map(collaboration => {
const collaborationMembers = Object.values(
get(collaboration, 'members', []),
Expand All @@ -40,7 +67,7 @@ export default function CollaborationsCard({
);
const [thisUserDataArray, otherUserDataArray] = partition(
filteredCollaborationMembers,
member => member.guid === userId,
member => member.guid === userData?.guid,
);

const thisUserData = get(thisUserDataArray, '0', {});
Expand Down Expand Up @@ -91,11 +118,11 @@ export default function CollaborationsCard({
name: 'otherUserName',
label: intl.formatMessage({ id: 'NAME' }),
options: {
customBodyRender: (otherUserName, datum) => (
<Link to={`/users/${get(datum, 'otherUserId')}`}>
<Text variant="body2">{otherUserName}</Text>
</Link>
),
cellRenderer: cellRendererTypes.user,
cellRendererProps: {
guidProperty: 'otherUserId',
nameProperty: 'otherUserName',
},
},
},
{
Expand All @@ -110,19 +137,14 @@ export default function CollaborationsCard({
name: 'actions',
label: intl.formatMessage({ id: 'ACTIONS' }),
options: {
customBodyRender: (_, collaboration) => (
<ActionIcon
labelId="EDIT"
variant="edit"
onClick={() => setActiveCollaboration(collaboration)}
/>
),
cellRenderer: cellRendererTypes.actionGroup,
cellRendererProps: { onEdit: handleEdit },
},
},
];

return (
<Card titleId="COLLABORATIONS" htmlId={htmlId}>
<>
<CollaborationsDialog
open={Boolean(activeCollaboration)}
onClose={() => setActiveCollaboration(null)}
Expand All @@ -131,16 +153,27 @@ export default function CollaborationsCard({
setCollabDialogButtonClickLoading
}
/>
<DataDisplay
loading={loading || collabDialogButtonClickLoading}
noResultsTextId="NO_COLLABORATIONS"
style={{ marginTop: 12 }}
noTitleBar
columns={columns}
data={tableData}
idKey="guid"
tableContainerStyles={{ maxHeight: 600 }}
/>
</Card>
<Card>
<CardContent>
<Text id="COLLABORATIONS" style={{ fontWeight: 'bold' }} />
<DataDisplay
loading={collabDialogButtonClickLoading}
noResultsTextId="NO_COLLABORATIONS"
style={{ marginTop: 12 }}
noTitleBar
columns={columns}
data={tableData}
idKey="guid"
tableContainerStyles={{ maxHeight: 600 }}
/>
</CardContent>
<CardActions>
<AddCollaboratorButton
userData={userData}
mutation={mutation}
/>
</CardActions>
</Card>
</>
);
}
59 changes: 59 additions & 0 deletions src/components/cards/UserManagerCollaborationsCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import axios from 'axios';
import { get } from 'lodash-es';
import { useMutation, useQueryClient } from 'react-query';

import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';

import { withApiPrefix } from '../../utils/requestUtils';
import { getUserQueryKey } from '../../constants/queryKeys';
import AddCollaboratorButton from './collaborations/AddCollaboratorButton';
import UserManagerCollaborationEditTable from '../UserManagerCollaborationEditTable';
import useHandleRequestError from '../../hooks/useHandleRequestError';

export default function UserManagerCollaborationsCard({ userData }) {
const queryClient = useQueryClient();
const collaborations = get(userData, ['collaborations'], []);
const handleRequestError = useHandleRequestError();

const userGuid = userData?.guid;

async function mutationFn({ userGuid: secondUserGuid }) {
try {
const result = await axios.request({
url: withApiPrefix('/collaborations/'),
method: 'POST',
data: {
user_guid: userGuid,
second_user_guid: secondUserGuid,
},
});
return result;
} catch (error) {
return handleRequestError(error);
}
}

const mutation = useMutation(mutationFn, {
onSuccess: async () =>
queryClient.invalidateQueries(getUserQueryKey(userGuid)),
});

return (
<Card>
<CardContent>
<UserManagerCollaborationEditTable
inputData={collaborations}
/>
</CardContent>
<CardActions>
<AddCollaboratorButton
userData={userData}
mutation={mutation}
/>
</CardActions>
</Card>
);
}
Loading

0 comments on commit 93c9bf0

Please sign in to comment.