Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
76ce638
feat: add backend and frontend development instructions for project s…
Sep 4, 2025
9203cd3
feat: replace InviteUser with ParticipantSelector for improved partic…
Sep 5, 2025
e3b5f34
feat: add tooltip to add participant button for better user guidance
Sep 5, 2025
04bf3f7
feat: update ParticipantSelector title for clarity in user management
Sep 5, 2025
031a38f
feat: implement CoProposers and PrincipalInvestigator components for …
Sep 8, 2025
a2ade17
fix: remove testing files
Sep 8, 2025
3748a11
Merge develop into SWAP-4829
Sep 9, 2025
780c0af
feat: add SQL patch to clarify email search description for improved …
Sep 9, 2025
dc2affe
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
Sep 9, 2025
8945e97
Do not add proposer as exludedUserIds
Sep 9, 2025
ab1ab24
fix: update lint-staged configuration to use echo commands and remove…
Sep 9, 2025
19bbff5
feat: update proposal tests to disable legacy email invite feature an…
Sep 10, 2025
11889c2
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
Sep 11, 2025
be3c6f8
debug: see if test runs now
Sep 11, 2025
d2011e5
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
jekabs-karklins Sep 11, 2025
4055a06
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
jekabs-karklins Sep 12, 2025
72765b1
refactor: simplify isSameParticipants function using keyOf for compar…
Sep 12, 2025
c485b1d
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
jekabs-karklins Sep 18, 2025
f398ec1
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
jekabs-karklins Sep 18, 2025
1521c38
fix: correct JSON formatting in lint-staged configuration
Oct 3, 2025
183508e
fix: hidding new ParticipantSelector behind EMAIL_LEGACY FE
Oct 3, 2025
303fc50
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
Oct 6, 2025
1fae9ae
fix: update invite handling to use allowInviteByEmail prop consistently
Oct 6, 2025
f1d0820
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
jekabs-karklins Oct 12, 2025
cb11eb6
test: revert test until new flow activated by all facilities
Oct 12, 2025
489de64
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
yoganandaness Oct 14, 2025
f93b4f8
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
yoganandaness Oct 15, 2025
d6de0d9
Merge branch 'develop' into SWAP-4829-use-the-new-userselect-componen…
yoganandaness Oct 15, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DO
$$
BEGIN
IF register_patch('Update_email_search_description.sql', 'jekabskarklins', 'Updates email search description to make it more clear.', '2025-09-09') THEN
BEGIN
UPDATE features
SET description = 'By setting this feature ON, users can be searched *only* by their exact email addresses.'
WHERE feature_id = 'EMAIL_SEARCH';
END;
END IF;
END;
$$
LANGUAGE plpgsql;
41 changes: 23 additions & 18 deletions apps/frontend/src/components/common/UserManagementTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ScheduleSend } from '@mui/icons-material';
import PersonAddIcon from '@mui/icons-material/PersonAdd';
import SendIcon from '@mui/icons-material/Send';
import { Chip } from '@mui/material';
import { Chip, Tooltip } from '@mui/material';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import FormControl from '@mui/material/FormControl';
Expand All @@ -15,20 +15,21 @@ import { FeatureContext } from 'context/FeatureContextProvider';
import { UserContext } from 'context/UserContextProvider';
import { BasicUserDetails, FeatureId, Invite, UserRole } from 'generated/sdk';

import InviteUser from '../proposal/InviteUser';
import ParticipantModal from '../proposal/ParticipantModal';
import ParticipantSelector from '../proposal/ParticipantSelector';

export type UserManagementTableProps = {
/** Basic user details array to be shown in the table. */
users: BasicUserDetails[];
/** Function for setting up the users. */
setUsers: (users: BasicUserDetails[]) => void;
invites: Invite[];
setInvites: (invites: Invite[]) => void;
invites?: Invite[];
setInvites?: (invites: Invite[]) => void;
sx?: SxProps<Theme>;
title: string;
preserveSelf?: boolean;
addButtonLabel?: string;
addButtonTooltip?: string;
/** Disable the add button */
disabled?: boolean;
/** Custom actions to be passed to PeopleTable */
Expand All @@ -41,12 +42,13 @@ export type UserManagementTableProps = {
const UserManagementTable = ({
users,
setUsers,
invites,
invites = [],
setInvites,
sx,
title,
preserveSelf,
addButtonLabel = 'Add',
addButtonTooltip = 'Add a participant',
disabled = false,
onUserAction,
excludeUserIds = [],
Expand All @@ -73,7 +75,7 @@ const UserManagementTable = ({
invites: Invite[];
}) => {
setUsers([...users, ...props.users]);
setInvites([...invites, ...props.invites]);
setInvites?.([...invites, ...props.invites]);
setOpen(false);
};

Expand All @@ -85,12 +87,13 @@ const UserManagementTable = ({
};

const handleDeleteInvite = (invite: Invite) => {
setInvites(invites.filter((i) => i.email !== invite.email));
setInvites?.(invites.filter((i) => i.email !== invite.email));
};

const InviteComponent = (
<InviteUser
<ParticipantSelector
modalOpen={modalOpen}
title="Add co-proposers"
onClose={() => setOpen(false)}
onAddParticipants={handleAddParticipants}
excludeUserIds={[...users.map((user) => user.id), ...excludeUserIds]}
Expand Down Expand Up @@ -197,16 +200,18 @@ const UserManagementTable = ({
marginTop: theme.spacing(1),
})}
>
<Button
variant="outlined"
onClick={openModal}
data-cy="add-participant-button"
size="small"
startIcon={<PersonAddIcon />}
disabled={disabled}
>
{addButtonLabel}
</Button>
<Tooltip title={addButtonTooltip}>
<Button
variant="outlined"
onClick={openModal}
data-cy="add-participant-button"
size="small"
startIcon={<PersonAddIcon />}
disabled={disabled}
>
{addButtonLabel}
</Button>
</Tooltip>
</ActionButtonContainer>
</div>
</FormControl>
Expand Down
53 changes: 53 additions & 0 deletions apps/frontend/src/components/proposal/CoProposers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useContext } from 'react';

import UserManagementTable, {
UserManagementTableProps,
} from 'components/common/UserManagementTable';
import {
createMissingContextErrorMessage,
QuestionaryContext,
} from 'components/questionary/QuestionaryContext';
import { BasicUserDetails } from 'generated/sdk';

import { ProposalContextType } from './ProposalContainer';

type CoProposersProps = Pick<
UserManagementTableProps,
'setInvites' | 'setUsers' | 'disabled'
> & {
setPrincipalInvestigator?: (user: BasicUserDetails) => void;
};

const CoProposers = ({
setPrincipalInvestigator,
...props
}: CoProposersProps) => {
const handleUserAction = (action: string, user: BasicUserDetails) => {
if (action === 'setPrincipalInvestigator' && setPrincipalInvestigator) {
setPrincipalInvestigator(user);
}
};

const { state } = useContext(QuestionaryContext) as ProposalContextType;
if (!state) {
throw new Error(createMissingContextErrorMessage());
}
const { proposer, users, coProposerInvites } = state.proposal;

return (
<UserManagementTable
{...props}
title="Co-Proposers"
addButtonTooltip="Add a co-proposer"
onUserAction={handleUserAction}
excludeUserIds={proposer ? [proposer.id] : []}
// QuickFix for material table changing immutable state
// https://github.com/mbrn/material-table/issues/666
users={JSON.parse(JSON.stringify(users))}
invites={coProposerInvites}
allowInviteByEmail={true}
/>
);
};

export default CoProposers;
17 changes: 14 additions & 3 deletions apps/frontend/src/components/proposal/NoOptionsText.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MenuItem, Typography } from '@mui/material';
import React from 'react';
import React, { useContext } from 'react';

import { BasicUserDetails } from 'generated/sdk';
import { FeatureContext } from 'context/FeatureContextProvider';
import { BasicUserDetails, FeatureId } from 'generated/sdk';
import { isValidEmail } from 'utils/net';
import { getFullUserNameWithInstitution } from 'utils/user';

Expand All @@ -12,6 +13,9 @@ interface NoOptionsTextProps {
exactEmailMatch?: BasicUserDetails;
excludeEmails?: string[];
minSearchLength?: number;
/* If true, if no user with email is found show invite option */
allowEmailInvites?: boolean;
/* If true, only allow searching users by entering their email */
isEmailSearchOnly: boolean;
allowInviteByEmail?: boolean;
}
Expand All @@ -26,7 +30,14 @@ function NoOptionsText({
isEmailSearchOnly,
allowInviteByEmail = false,
}: NoOptionsTextProps) {
const isEmailInviteEnabled = allowInviteByEmail;
const featureContext = useContext(FeatureContext);
const isEmailInviteFeatureEnabled = !!featureContext.featuresMap.get(
FeatureId.EMAIL_INVITE
)?.isEnabled;

// email invite will be enabled if both the prop and the feature flag are true
const isEmailInviteEnabled =
allowInviteByEmail && isEmailInviteFeatureEnabled;

if (exactEmailMatch) {
return (
Expand Down
Loading
Loading