Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,18 +1,33 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { useRoomRoute } from '@rocket.chat/ui-client';
import { useSetting } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';

import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator';
import ParentRoomButton from '../ParentRoomButton';

export type ParentDiscussionProps = {
loading?: boolean;
room: Pick<IRoom, '_id' | 't' | 'name' | 'fname' | 'prid' | 'u'>;
room: Pick<IRoom, '_id' | 't' | 'name' | 'fname' | 'prid' | 'u' | 'federated'>;
};

const getChannelRoomName = (room: ParentDiscussionProps['room'], allowSpecialChars: boolean): string => {
if (room.prid || isRoomFederated(room)) {
return room.fname || '';
}

return (allowSpecialChars ? room.fname || room.name : room.name) || '';
};

const ParentDiscussion = ({ loading = false, room }: ParentDiscussionProps) => {
const { t } = useTranslation();
const roomName = roomCoordinator.getRoomName(room.t, room);
const handleRedirect = (): void => roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room });
const goToRoom = useRoomRoute();
const allowSpecialChars = useSetting('UI_Allow_room_names_with_special_chars', false);
const roomName = room.t === 'c' || room.t === 'p' ? getChannelRoomName(room, allowSpecialChars) : room.fname || room.name || '';

const handleRedirect = (): void => {
goToRoom({ rid: room._id, t: room.t, name: room.name });
};

return <ParentRoomButton loading={loading} onClick={handleRedirect} title={t('Back_to__roomName__channel', { roomName })} />;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import type { IUser } from '@rocket.chat/core-typings';
import { Margins } from '@rocket.chat/fuselage';
import { useRouter } from '@rocket.chat/ui-contexts';

import RoomForewordUsernameListItem from './RoomForewordUsernameListItem';
import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator';

export type RoomForewordUsernameListProps = { usernames: Array<NonNullable<IUser['username']>> };

const RoomForewordUsernameList = ({ usernames }: RoomForewordUsernameListProps) => {
const router = useRouter();

return (
<Margins inline={4}>
{usernames.map((username) => (
<RoomForewordUsernameListItem
username={username}
key={username}
href={roomCoordinator.getRouteLink('d', { name: username }) || undefined}
href={router.getRoomRoute('d', { name: username }).path || undefined}
/>
))}
</Margins>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getUserDisplayName, VideoConferenceStatus } from '@rocket.chat/core-typings';
import { useGoToRoom, useSetting, useTranslation, useUserId, useUserPreference } from '@rocket.chat/ui-contexts';
import { useSetting, useTranslation, useUserId, useUserPreference } from '@rocket.chat/ui-contexts';
import type * as UiKit from '@rocket.chat/ui-kit';
import {
VideoConfMessageSkeleton,
Expand All @@ -19,6 +19,7 @@ import type { MouseEventHandler } from 'react';
import { useContext, memo, useMemo } from 'react';

import { UiKitContext } from '../..';
import { useGoToRoom } from './hooks/useGoToRoom';
import { useVideoConfDataStream } from './hooks/useVideoConfDataStream';
import { useSurfaceType } from '../../hooks/useSurfaceType';
import type { BlockProps } from '../../utils/BlockProps';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { MockedRouterContext, MockedServerContext } from '@rocket.chat/mock-providers';
import type { MockedRouterContextProps } from '@rocket.chat/mock-providers';
import { renderHook, waitFor } from '@testing-library/react';

import { useGoToRoom } from './useGoToRoom';

const getWrapper =
(room: Partial<IRoom> | undefined, router?: MockedRouterContextProps['router']) =>
({ children }: { children: any }) => {
return (
<MockedServerContext handleRequest={async () => ({ room }) as any}>
<MockedRouterContext router={router}>{children}</MockedRouterContext>
</MockedServerContext>
);
};

describe('useGoToRoom', () => {
it('should not navigate if the room is not found', async () => {
const navigate = jest.fn();
const { result } = renderHook(() => useGoToRoom(), { wrapper: getWrapper(undefined, { navigate }) });

await result.current('room-id');

expect(navigate).not.toHaveBeenCalled();
});

it('should build the route by name for channels (c) and navigate to it', async () => {
const navigate = jest.fn();
const getRoomRoute = jest.fn().mockReturnValue({ path: '/channel/some-channel' });
const { result } = renderHook(() => useGoToRoom(), {
wrapper: getWrapper({ _id: 'room-id', t: 'c', name: 'some-channel' }, { navigate, getRoomRoute }),
});

await result.current('room-id');

await waitFor(() => expect(getRoomRoute).toHaveBeenCalledWith('c', { name: 'some-channel' }));
expect(navigate).toHaveBeenCalledWith({ pathname: '/channel/some-channel' });
});

it('should build the route by name for private groups (p) and navigate to it', async () => {
const navigate = jest.fn();
const getRoomRoute = jest.fn().mockReturnValue({ path: '/group/some-group' });
const { result } = renderHook(() => useGoToRoom(), {
wrapper: getWrapper({ _id: 'room-id', t: 'p', name: 'some-group' }, { navigate, getRoomRoute }),
});

await result.current('room-id');

await waitFor(() => expect(getRoomRoute).toHaveBeenCalledWith('p', { name: 'some-group' }));
expect(navigate).toHaveBeenCalledWith({ pathname: '/group/some-group' });
});

it('should build the route by rid for direct messages (d) and navigate to it', async () => {
const navigate = jest.fn();
const getRoomRoute = jest.fn().mockReturnValue({ path: '/direct/room-id' });
const { result } = renderHook(() => useGoToRoom(), {
wrapper: getWrapper({ _id: 'room-id', t: 'd', name: 'some-user' }, { navigate, getRoomRoute }),
});

await result.current('room-id');

await waitFor(() => expect(getRoomRoute).toHaveBeenCalledWith('d', { rid: 'room-id' }));
expect(navigate).toHaveBeenCalledWith({ pathname: '/direct/room-id' });
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useStableCallback } from '@rocket.chat/fuselage-hooks';

import { useEndpoint } from './useEndpoint';
import { useRouter } from './useRouter';
import { useEndpoint, useRouter } from '@rocket.chat/ui-contexts';

export const useGoToRoom = (): ((roomId: IRoom['_id']) => Promise<void>) => {
const router = useRouter();
const getRoomById = useEndpoint('GET', '/v1/rooms.info');
const getRoomInfo = useEndpoint('GET', '/v1/rooms.info');

return useStableCallback(async (roomId: IRoom['_id']) => {
const { room } = await getRoomById({ roomId });
const { room } = await getRoomInfo({ roomId });

if (!room) return;

Expand Down
1 change: 1 addition & 0 deletions packages/ui-client/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './useFeaturePreviewList';
export * from './useGoToDirectMessage';
export * from './useLicense';
export * from './usePreferenceFeaturePreviewList';
export * from './useRoomRoute';
export * from './useThemeMode';
export * from './useUserDisplayName';
export * from './useValidatePassword';
32 changes: 32 additions & 0 deletions packages/ui-client/src/hooks/useRoomRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { IRoom, RoomType } from '@rocket.chat/core-typings';
import { useRouter } from '@rocket.chat/ui-contexts';
import { useCallback } from 'react';

type RoomRouteData = {
Comment thread
tassoevan marked this conversation as resolved.
rid: IRoom['_id'];
t: RoomType;
name?: IRoom['name'];
};

/**
* Returns a function to navigate to a room using existing room data.
* Unlike `useGoToRoom`, this doesn't make an API call - use it when you already have the room data.
*/
export const useRoomRoute = ({ replace = false }: { replace?: boolean } = {}): ((room: RoomRouteData) => void) => {
const router = useRouter();

return useCallback(
(room: RoomRouteData) => {
const { t, name, rid } = room;
const { path } = router.getRoomRoute(t, ['c', 'p'].includes(t) ? { name } : { rid });

router.navigate(
{
pathname: path,
},
{ replace },
);
},
[router, replace],
);
};
1 change: 0 additions & 1 deletion packages/ui-contexts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export { useCurrentModal } from './hooks/useCurrentModal';
export { useCurrentRoutePath } from './hooks/useCurrentRoutePath';
export { useCustomSound } from './hooks/useCustomSound';
export { useEndpoint } from './hooks/useEndpoint';
export { useGoToRoom } from './hooks/useGoToRoom';
export type { EndpointFunction } from './hooks/useEndpoint';
export { useIsLoggingIn } from './hooks/useIsLoggingIn';
export { useIsPrivilegedSettingsContext } from './hooks/useIsPrivilegedSettingsContext';
Expand Down
Loading