diff --git a/apps/meteor/client/components/UserInfo/UserInfo.stories.tsx b/apps/meteor/client/components/UserInfo/UserInfo.stories.tsx
index 81e32c7f6cd31..65b94ed0d4445 100644
--- a/apps/meteor/client/components/UserInfo/UserInfo.stories.tsx
+++ b/apps/meteor/client/components/UserInfo/UserInfo.stories.tsx
@@ -53,3 +53,8 @@ WithABACAttributes.args = {
},
],
};
+
+export const InvitedUser = Template.bind({});
+InvitedUser.args = {
+ invitationDate: '2025-01-01T12:00:00Z',
+};
diff --git a/apps/meteor/client/components/UserInfo/UserInfo.tsx b/apps/meteor/client/components/UserInfo/UserInfo.tsx
index 28a82c3835da1..59ddb7710a921 100644
--- a/apps/meteor/client/components/UserInfo/UserInfo.tsx
+++ b/apps/meteor/client/components/UserInfo/UserInfo.tsx
@@ -52,6 +52,7 @@ type UserInfoProps = UserInfoDataProps & {
actions: ReactElement;
roles: ReactElement[];
reason?: string;
+ invitationDate?: string;
};
const UserInfo = ({
@@ -75,6 +76,7 @@ const UserInfo = ({
reason,
freeSwitchExtension,
abacAttributes,
+ invitationDate,
...props
}: UserInfoProps): ReactElement => {
const { t } = useTranslation();
@@ -207,6 +209,13 @@ const UserInfo = ({
),
)}
+ {invitationDate && (
+
+ {t('Invitation_date')}
+ {timeAgo(invitationDate)}
+
+ )}
+
{createdAt && (
{t('Created_at')}
diff --git a/apps/meteor/client/components/UserInfo/__snapshots__/UserInfo.spec.tsx.snap b/apps/meteor/client/components/UserInfo/__snapshots__/UserInfo.spec.tsx.snap
index 3625392b362c2..ad214a8b86be0 100644
--- a/apps/meteor/client/components/UserInfo/__snapshots__/UserInfo.spec.tsx.snap
+++ b/apps/meteor/client/components/UserInfo/__snapshots__/UserInfo.spec.tsx.snap
@@ -261,6 +261,281 @@ exports[`renders Default without crashing 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ guilherme.gazzo
+
+
+
+
+
+ currently working on User Card
+
+
+
+
+
+
+ Nickname
+
+
+ gazzo
+
+
+
+
+ Roles
+
+
+
+
+
+
+ admin
+
+
+
+
+
+
+ user
+
+
+
+
+
+
+
+
+ Username
+
+
+ guilherme.gazzo
+
+
+
+
+ Bio
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tempus, eros convallis vulputate cursus, nisi neque eleifend libero, eget lacinia justo purus nec est. In at sodales ipsum. Sed lacinia quis purus eget pulvinar. Aenean eu pretium nunc, at aliquam magna. Praesent dignissim, tortor sed volutpat mattis, mauris diam pulvinar leo, porta commodo risus est non purus.
+
+
+
+
+
+
+ Invitation_date
+
+
+ January 1, 2025
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx
index ba5c7fc6644c6..c53892d42252d 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx
@@ -7,10 +7,19 @@ import { useUserInfoActions } from '../../hooks/useUserInfoActions';
type RoomMembersActionsProps = Pick
& {
rid: IRoom['_id'];
+ isInvited?: boolean;
reload: () => void;
};
-const RoomMembersActions = ({ username, _id, name, rid, freeSwitchExtension, reload }: RoomMembersActionsProps): ReactElement | null => {
+const RoomMembersActions = ({
+ username,
+ _id,
+ name,
+ rid,
+ freeSwitchExtension,
+ isInvited,
+ reload,
+}: RoomMembersActionsProps): ReactElement | null => {
const { t } = useTranslation();
const { menuActions: menuOptions } = useUserInfoActions({
@@ -18,7 +27,8 @@ const RoomMembersActions = ({ username, _id, name, rid, freeSwitchExtension, rel
user: { _id, username, name, freeSwitchExtension },
reload,
size: 0,
- isMember: true,
+ isMember: !isInvited,
+ isInvited,
});
if (!menuOptions) {
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersItem.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersItem.tsx
index 52e84a3f04350..26e09d4ef4621 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersItem.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersItem.tsx
@@ -37,12 +37,14 @@ const RoomMembersItem = ({
freeSwitchExtension,
onClickView,
rid,
+ subscription,
reload,
useRealName,
- subscription,
}: RoomMembersItemProps): ReactElement => {
const [showButton, setShowButton] = useState();
const isReduceMotionEnabled = usePrefersReducedMotion();
+ const isInvited = subscription?.status === 'INVITED';
+ const invitationDate = isInvited ? subscription?.ts : undefined;
const handleMenuEvent = {
[isReduceMotionEnabled ? 'onMouseEnter' : 'onTransitionEnd']: setShowButton,
};
@@ -52,7 +54,14 @@ const RoomMembersItem = ({
const [nameOrUsername, displayUsername] = getUserDisplayNames(name, username, useRealName);
return (
-
+
@@ -67,7 +76,15 @@ const RoomMembersItem = ({
)}
{showButton ? (
-
+
) : (
)}
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx
index 81f5503045406..c44c31f5e9cbd 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx
@@ -48,9 +48,9 @@ const RoomMembersWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => {
? Federation.canCreateInviteLinks(user, room, subscription)
: hasPermissionToCreateInviteLinks;
- const [state, setState] = useState<{ tab: ROOM_MEMBERS_TABS; userId?: IUser['_id'] }>({
+ const [state, setState] = useState<{ tab: ROOM_MEMBERS_TABS; user?: { id?: IUser['_id']; invitationDate?: string } }>({
tab: ROOM_MEMBERS_TABS.LIST,
- userId: undefined,
+ user: undefined,
});
const debouncedText = useDebouncedValue(text, 800);
@@ -74,10 +74,10 @@ const RoomMembersWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => {
}, []);
const openUserInfo = useEffectEvent((e: MouseEvent) => {
- const { userid } = e.currentTarget.dataset;
+ const { userid: userId, invitationdate: invitationDate } = e.currentTarget.dataset;
setState({
tab: ROOM_MEMBERS_TABS.INFO,
- userId: userid,
+ user: { id: userId, invitationDate },
});
});
@@ -93,8 +93,16 @@ const RoomMembersWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => {
setState({ tab: ROOM_MEMBERS_TABS.LIST });
});
- if (state.tab === ROOM_MEMBERS_TABS.INFO && state.userId) {
- return ;
+ if (state.tab === ROOM_MEMBERS_TABS.INFO && state.user?.id) {
+ return (
+
+ );
}
if (state.tab === ROOM_MEMBERS_TABS.INVITE) {
diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx
index 59bd612c4c72f..ca7c6b0c88531 100644
--- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx
+++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx
@@ -13,10 +13,11 @@ import { useUserInfoActions } from '../../hooks/useUserInfoActions';
type UserInfoActionsProps = {
user: Pick;
rid: IRoom['_id'];
+ isInvited?: boolean;
backToList?: () => void;
};
-const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): ReactElement => {
+const UserInfoActions = ({ user, rid, isInvited, backToList }: UserInfoActionsProps): ReactElement => {
const { t } = useTranslation();
const {
data: isMemberData,
@@ -31,8 +32,9 @@ const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): React
const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions({
rid,
user: { _id: userId, username, name, freeSwitchExtension },
- size: 3,
+ size: 2,
isMember,
+ isInvited,
reload: () => {
backToList?.();
refetch();
diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx
index 7e28c0b4eada7..cc0f5bb01bd09 100644
--- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx
+++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx
@@ -28,11 +28,12 @@ type UserInfoWithDataProps = {
uid?: IUser['_id'];
username?: IUser['username'];
rid: IRoom['_id'];
+ invitationDate?: string;
onClose: () => void;
onClickBack?: () => void;
};
-const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack }: UserInfoWithDataProps): ReactElement => {
+const UserInfoWithData = ({ uid, username, rid, invitationDate, onClose, onClickBack }: UserInfoWithDataProps): ReactElement => {
const { t } = useTranslation();
const getRoles = useRolesDescription();
@@ -113,7 +114,13 @@ const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack }: UserInfo
)}
- {!isPending && user && } />}
+ {!isPending && user && (
+ }
+ />
+ )}
);
};
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx
index 75a3ca69bb746..19b1078f8eece 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx
@@ -25,6 +25,7 @@ export const useRemoveUserAction = (
user: Pick,
rid: IRoom['_id'],
reload?: () => void,
+ invited?: boolean,
): UserInfoAction | undefined => {
const room = useUserRoom(rid);
@@ -100,7 +101,7 @@ export const useRemoveUserAction = (
setModal(
=> handleRemoveFromRoom(rid, uid)}
@@ -110,19 +111,31 @@ export const useRemoveUserAction = (
);
});
- const removeUserOption = useMemo(
- () =>
- roomCanRemove && userCanRemove
- ? {
- content: room?.teamMain ? t('Remove_from_team') : t('Remove_from_room'),
- icon: 'cross' as const,
- onClick: removeUserOptionAction,
- type: 'moderation' as const,
- variant: 'danger' as const,
- }
- : undefined,
- [room, roomCanRemove, userCanRemove, removeUserOptionAction, t],
- );
+ const content = useMemo(() => {
+ if (invited) {
+ return t('Revoke_invitation');
+ }
+
+ if (room?.teamMain) {
+ return t('Remove_from_team');
+ }
+
+ return t('Remove_from_room');
+ }, [invited, room?.teamMain, t]);
+
+ const removeUserOption = useMemo(() => {
+ if (!roomCanRemove || !userCanRemove) {
+ return undefined;
+ }
+
+ return {
+ content,
+ icon: 'cross' as const,
+ onClick: removeUserOptionAction,
+ type: 'moderation' as const,
+ variant: 'danger' as const,
+ };
+ }, [roomCanRemove, userCanRemove, removeUserOptionAction, content]);
return removeUserOption;
};
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts
index 6540fd26e9373..f9baa1f736680 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts
@@ -56,6 +56,7 @@ type UserInfoActionsParams = {
reload?: () => void;
size?: number;
isMember?: boolean;
+ isInvited?: boolean;
};
export const useUserInfoActions = ({
@@ -64,6 +65,7 @@ export const useUserInfoActions = ({
reload,
size = 2,
isMember,
+ isInvited,
}: UserInfoActionsParams): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => {
const addUser = useAddUserAction(user, rid, reload);
const blockUser = useBlockUserAction(user, rid);
@@ -74,7 +76,7 @@ export const useUserInfoActions = ({
const openDirectMessage = useDirectMessageAction(user, rid);
const ignoreUser = useIgnoreUserAction(user, rid);
const muteUser = useMuteUserAction(user, rid);
- const removeUser = useRemoveUserAction(user, rid, reload);
+ const removeUser = useRemoveUserAction(user, rid, reload, isInvited);
const videoCall = useVideoCallAction(user);
const reportUserOption = useReportUser(user);
const isLayoutEmbedded = useEmbeddedLayout();
@@ -94,8 +96,8 @@ export const useUserInfoActions = ({
...(isMember && ignoreUser && { ignoreUser }),
...(isMember && muteUser && { muteUser }),
...(blockUser && { toggleBlock: blockUser }),
+ ...((isMember || isInvited) && removeUser && { removeUser }),
...(reportUserOption && { reportUser: reportUserOption }),
- ...(isMember && removeUser && { removeUser }),
}),
[
openDirectMessage,
@@ -113,6 +115,7 @@ export const useUserInfoActions = ({
openModerationConsole,
addUser,
isMember,
+ isInvited,
],
);
diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json
index 616d58b289ea9..bb33465f6e6b3 100644
--- a/packages/i18n/src/locales/en.i18n.json
+++ b/packages/i18n/src/locales/en.i18n.json
@@ -2751,6 +2751,7 @@
"Invalid_username": "The username entered is invalid",
"Invisible": "Invisible",
"Invitation": "Invitation",
+ "Invitation_date": "Invitation date",
"Invitation_Email_Description": "You may use the following placeholders: \n - `[email]` for the recipient email. \n - `[Site_Name]` and `[Site_URL]` for the Application Name and URL respectively. ",
"Invitation_HTML": "Invitation HTML",
"Invitation_HTML_Default": "You have been invited to [Site_Name] Go to [Site_URL] and try the best open source chat solution available today!
",
@@ -4567,6 +4568,7 @@
"Review": "Review",
"Review_contact": "Review contact",
"Review_devices": "Review when and where devices are connecting from",
+ "Revoke_invitation": "Revoke invitation",
"Right": "Right",
"Ringing": "Ringing",
"Ringtones_and_visual_indicators_notify_people_of_incoming_calls": "Ringtones and visual indicators notify people of incoming calls.",