snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
import {
Modal,
Button,
Box,
Icon,
Label,
CheckBox,
ModalHeader,
ModalHeaderText,
ModalTitle,
ModalClose,
ModalContent,
ModalFooter,
ModalFooterAnnotation,
ModalFooterControllers,
} from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';
import { useId, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
export type AddMatrixUsersModalProps = {
matrixIdVerifiedStatus: Map<string, string>;
completeUserList: string[];
onClose: () => void;
onSave: (args_0: { users: string[]; unbanConfirmed?: boolean }) => Promise<void>;
};
type FormValues = {
usersToInvite: string[];
};
const verificationStatusAsIcon = (verificationStatus: string): ComponentProps<typeof Icon>['name'] => {
if (verificationStatus === 'VERIFIED') {
return 'circle-check';
}
if (verificationStatus === 'UNVERIFIED') {
return 'circle-cross';
}
return 'circle-exclamation';
};
const AddMatrixUsersModal = ({ onClose, matrixIdVerifiedStatus, onSave, completeUserList }: AddMatrixUsersModalProps) => {
const { t } = useTranslation();
const checkboxId = useId();
const [bannedError, setBannedError] = useState(false);
const [unbanConfirmed, setUnbanConfirmed] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
const usersToInvite = completeUserList.filter(
(user) => !(matrixIdVerifiedStatus.has(user) && matrixIdVerifiedStatus.get(user) === 'UNVERIFIED'),
);
const rocketChatUsers = usersToInvite.filter((user) => !matrixIdVerifiedStatus.has(user));
const { handleSubmit } = useForm<FormValues>({
defaultValues: {
usersToInvite,
},
});
const onSubmit = async (data: FormValues) => {
setLoading(true);
setError(undefined);
try {
await onSave({ users: data.usersToInvite, unbanConfirmed });
onClose();
} catch (err: any) {
if (err?.error === 'error-user-is-banned') {
setBannedError(true);
setUnbanConfirmed(false);
} else {
setError(err?.message || t('Something_went_wrong'));
}
} finally {
setLoading(false);
}
};
return (
<Modal>
<ModalHeader>
<ModalHeaderText>
<ModalTitle>{t('Continue_Adding')}</ModalTitle>
</ModalHeaderText>
<ModalClose title={t('Close')} onClick={onClose} />
</ModalHeader>
<ModalContent>
<Box is='ul'>
{[...matrixIdVerifiedStatus.entries()].map(([_matrixId, _verificationStatus]) => (
<Box is='li' display='flex' key={_matrixId}>
{_matrixId}{' '}
<Icon marginInlineStart={4} name={verificationStatusAsIcon(_verificationStatus)} title={t(_verificationStatus)} size='x20' />
</Box>
))}
{rocketChatUsers.map((_user) => (
<Box is='li' key={`rocket-chat-${_user}`}>
{_user}
</Box>
))}
</Box>
{bannedError && (
<Box color='danger' fontScale='c1' marginBlockStart={8}>
{t('User_is_banned_from_room_confirm_unban')}
</Box>
)}
{error && (
<Box color='danger' fontScale='c1' marginBlockStart={8}>
{error}
</Box>
)}
</ModalContent>
<ModalFooter justifyContent={bannedError ? 'space-between' : 'flex-end'}>
{bannedError && (
<ModalFooterAnnotation>
<Box display='flex' alignItems='center'>
<CheckBox checked={unbanConfirmed} onChange={() => setUnbanConfirmed((prev) => !prev)} id={checkboxId} />
<Label htmlFor={checkboxId} marginInlineStart={8}>
{t('Yes_unban_user')}
</Label>
</Box>
</ModalFooterAnnotation>
)}
<ModalFooterControllers>
<Button onClick={onClose}>{t('Cancel')}</Button>
<Button
primary
onClick={handleSubmit(onSubmit)}
disabled={!(usersToInvite.length > 0) || (bannedError && !unbanConfirmed)}
loading={loading}
>
{t('Yes_continue')}
</Button>
</ModalFooterControllers>
</ModalFooter>
</Modal>
);
};
export default AddMatrixUsersModal;

View File

@@ -0,0 +1,42 @@
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
import AddMatrixUsersModal from './AddMatrixUsersModal';
export type useAddMatrixUsersProps = {
handleSave: (args_0: { users: string[]; unbanConfirmed?: boolean }) => Promise<void>;
users: string[];
};
export const useAddMatrixUsers = () => {
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const handleClose = useStableCallback(() => setModal(null));
const dispatchVerifyEndpoint = useEndpoint('GET', '/v1/federation/matrixIds.verify');
return useMutation({
mutationFn: async ({ users, handleSave }: useAddMatrixUsersProps) => {
try {
let matrixIdVerificationMap = new Map();
const matrixIds = users.filter((user) => user.startsWith('@'));
if (matrixIds.length > 0) {
const matrixIdsVerificationResponse = await dispatchVerifyEndpoint({ matrixIds });
const { results: matrixIdsVerificationResults } = matrixIdsVerificationResponse;
matrixIdVerificationMap = new Map(Object.entries(matrixIdsVerificationResults));
}
setModal(
<AddMatrixUsersModal
completeUserList={users}
onClose={handleClose}
onSave={handleSave}
matrixIdVerifiedStatus={matrixIdVerificationMap as Map<string, string>}
/>,
);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error as Error });
}
},
});
};

View File

@@ -0,0 +1,17 @@
import { Contextualbar } from '@rocket.chat/ui-client';
import type { StoryObj, Meta } from '@storybook/react';
import AddUsers from './AddUsers';
export default {
component: AddUsers,
parameters: {
layout: 'fullscreen',
actions: { argTypesRegex: '^on.*' },
},
decorators: [(fn) => <Contextualbar height='100vh'>{fn()}</Contextualbar>],
} satisfies Meta<typeof AddUsers>;
export const Default: StoryObj<typeof AddUsers> = {
name: 'AddUsers',
};

View File

@@ -0,0 +1,175 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isRoomFederated, isRoomNativeFederated } from '@rocket.chat/core-typings';
import { Field, FieldError, FieldLabel, Button, ButtonGroup, FieldGroup } from '@rocket.chat/fuselage';
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
ContextualbarHeader,
ContextualbarBack,
ContextualbarTitle,
ContextualbarClose,
ContextualbarScrollableContent,
ContextualbarFooter,
ContextualbarDialog,
} from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useSetModal, useEndpoint, useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useId } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useAddMatrixUsers } from './AddMatrixUsers/useAddMatrixUsers';
import BannedUsersUnbanModal from './BannedUsersUnbanModal';
import UserAutoCompleteMultiple from '../../../../../components/UserAutoCompleteMultiple';
import { useRoom } from '../../../contexts/RoomContext';
const hasExternalUsers = (users: string[]): boolean => users.some((user) => user.startsWith('@'));
export type AddUsersProps = {
rid: IRoom['_id'];
onClickBack: () => void;
reload: () => void;
};
const AddUsers = ({ rid, onClickBack, reload }: AddUsersProps) => {
const { t } = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const room = useRoom();
const usersFieldId = useId();
const roomIsFederated = isRoomFederated(room);
// we are dropping the non native federation for now
const isFederationBlocked = room && !isRoomNativeFederated(room);
const isFederated = roomIsFederated && !isFederationBlocked;
const setModal = useSetModal();
const { closeTab } = useRoomToolbox();
const inviteToChannel = useEndpoint('POST', '/v1/channels.invite');
const inviteToGroup = useEndpoint('POST', '/v1/groups.invite');
const getBannedUsers = useEndpoint('GET', '/v1/rooms.bannedUsers');
const unbanUser = useEndpoint('POST', '/v1/rooms.unbanUser');
const {
handleSubmit,
control,
getValues,
formState: { isDirty, isSubmitting, errors },
} = useForm({ defaultValues: { users: [] } });
const handleSave = useStableCallback(async ({ users, unbanConfirmed }: { users: string[]; unbanConfirmed?: boolean }) => {
if (unbanConfirmed) {
const { bannedUsers } = await getBannedUsers({ roomId: rid });
const bannedSet = new Set(bannedUsers.map((u) => u.username));
const usersToUnban = users.filter((username) => bannedSet.has(username));
if (usersToUnban.length) {
await Promise.all(usersToUnban.map((username) => unbanUser({ roomId: rid, username })));
}
}
await Promise.all(
users.map((username) => (room.t === 'c' ? inviteToChannel({ roomId: rid, username }) : inviteToGroup({ roomId: rid, username }))),
);
dispatchToastMessage({ type: 'success', message: t(roomIsFederated && !isFederationBlocked ? 'Users_invited' : 'Users_added') });
onClickBack();
reload();
});
const handleSaveWithBannedCheck = useStableCallback(async ({ users }: { users: string[] }) => {
try {
await handleSave({ users });
} catch (error: any) {
if (error.error === 'error-user-is-banned') {
const { bannedUsers } = await getBannedUsers({ roomId: rid });
const bannedSet = new Set(bannedUsers.map((u) => u.username));
const usersToUnban = users.filter((username) => bannedSet.has(username));
if (usersToUnban.length) {
setModal(
<BannedUsersUnbanModal
onClose={() => setModal(null)}
onConfirm={async () => {
await Promise.all(usersToUnban.map((username) => unbanUser({ roomId: rid, username })));
await Promise.all(
users.map((username) =>
room.t === 'c' ? inviteToChannel({ roomId: rid, username }) : inviteToGroup({ roomId: rid, username }),
),
);
setModal(null);
dispatchToastMessage({
type: 'success',
message: t(roomIsFederated && !isFederationBlocked ? 'Users_invited' : 'Users_added'),
});
onClickBack();
reload();
}}
/>,
);
return;
}
}
dispatchToastMessage({ type: 'error', message: error as Error });
}
});
const addClickHandler = useAddMatrixUsers();
return (
<ContextualbarDialog>
<ContextualbarHeader>
{onClickBack && <ContextualbarBack onClick={onClickBack} />}
<ContextualbarTitle>{t('Add_users')}</ContextualbarTitle>
{closeTab && <ContextualbarClose onClick={closeTab} />}
</ContextualbarHeader>
<ContextualbarScrollableContent>
<FieldGroup>
<Field>
<FieldLabel flexGrow={0}>{t('Choose_users')}</FieldLabel>
<Controller
name='users'
control={control}
rules={{
validate: (users) => !isFederated && (!hasExternalUsers(users) || t('You_cannot_add_external_users_to_non_federated_room')),
}}
render={({ field }) => (
<UserAutoCompleteMultiple
federated={isFederated}
placeholder={t('Choose_users')}
aria-describedby={`${usersFieldId}-error`}
{...field}
/>
)}
/>
{errors.users && (
<FieldError role='alert' id={`${usersFieldId}-error`}>
{errors.users.message}
</FieldError>
)}
</Field>
</FieldGroup>
</ContextualbarScrollableContent>
<ContextualbarFooter>
<ButtonGroup stretch>
{roomIsFederated ? (
!isFederationBlocked && (
<Button
primary
disabled={addClickHandler.isPending || !isDirty}
onClick={() =>
addClickHandler.mutate({
users: getValues('users'),
handleSave,
})
}
>
{t('Add_users')}
</Button>
)
) : (
<Button primary loading={isSubmitting} disabled={!isDirty} onClick={handleSubmit(handleSaveWithBannedCheck)}>
{t('Add_users')}
</Button>
)}
</ButtonGroup>
</ContextualbarFooter>
</ContextualbarDialog>
);
};
export default AddUsers;

View File

@@ -0,0 +1,79 @@
import {
Modal,
Box,
Button,
Label,
CheckBox,
ModalHeader,
ModalHeaderText,
ModalTitle,
ModalClose,
ModalContent,
ModalFooter,
ModalFooterAnnotation,
ModalFooterControllers,
} from '@rocket.chat/fuselage';
import { useId, useState } from 'react';
import { useTranslation } from 'react-i18next';
export type BannedUsersUnbanModalProps = {
onClose: () => void;
onConfirm: () => Promise<void>;
};
const BannedUsersUnbanModal = ({ onClose, onConfirm }: BannedUsersUnbanModalProps) => {
const { t } = useTranslation();
const checkboxId = useId();
const [unbanConfirmed, setUnbanConfirmed] = useState(false);
const [error, setError] = useState<string>();
const [loading, setLoading] = useState(false);
const handleConfirm = async () => {
setLoading(true);
setError(undefined);
try {
await onConfirm();
} catch (err: any) {
setError(err?.message || t('Something_went_wrong'));
} finally {
setLoading(false);
}
};
return (
<Modal>
<ModalHeader>
<ModalHeaderText>
<ModalTitle>{t('User_is_banned')}</ModalTitle>
</ModalHeaderText>
<ModalClose title={t('Close')} onClick={onClose} />
</ModalHeader>
<ModalContent>
<Box>{t('User_is_banned_from_room_confirm_unban')}</Box>
{error && (
<Box color='danger' fontScale='c1' marginBlockStart={8}>
{error}
</Box>
)}
</ModalContent>
<ModalFooter justifyContent='space-between'>
<ModalFooterAnnotation>
<Box display='flex' alignItems='center'>
<CheckBox checked={unbanConfirmed} onChange={() => setUnbanConfirmed((prev) => !prev)} id={checkboxId} />
<Label htmlFor={checkboxId} marginInlineStart={8}>
{t('Yes_unban_user')}
</Label>
</Box>
</ModalFooterAnnotation>
<ModalFooterControllers>
<Button onClick={onClose}>{t('Cancel')}</Button>
<Button primary onClick={handleConfirm} disabled={!unbanConfirmed} loading={loading}>
{t('Add_users')}
</Button>
</ModalFooterControllers>
</ModalFooter>
</Modal>
);
};
export default BannedUsersUnbanModal;

View File

@@ -0,0 +1 @@
export { default } from './AddUsers';