Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Show parent & team information on UI #33199

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/twenty-spiders-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Show `parent` and `team` information on the header of rooms
3 changes: 3 additions & 0 deletions apps/meteor/client/lib/rooms/roomCoordinator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class RoomCoordinatorClient extends RoomCoordinator {
canSendMessage(rid: string): boolean {
return ChatSubscription.find({ rid }).count() > 0;
},
hasSubscription(rid: string): boolean {
return ChatSubscription.find({ rid }).count() > 0;
},
...directives,
config: roomConfig,
});
Expand Down
9 changes: 7 additions & 2 deletions apps/meteor/client/views/room/Header/ParentRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,21 @@ type ParentRoomProps = {
room: Pick<IRoom, '_id' | 't' | 'name' | 'fname' | 'prid' | 'u'>;
};

const hasSubscription = (room: ParentRoomProps['room']): boolean => roomCoordinator.getRoomDirectives(room.t).hasSubscription(room._id);
const ParentRoom = ({ room }: ParentRoomProps): ReactElement => {
const icon = useRoomIcon(room);

const handleRedirect = (): void => roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room });
const handleRedirect = hasSubscription(room)
? (): void => {
roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room });
}
: undefined;

return (
<HeaderTag
role='button'
tabIndex={0}
onKeyDown={(e) => (e.code === 'Space' || e.code === 'Enter') && handleRedirect()}
onKeyDown={(e) => (e.code === 'Space' || e.code === 'Enter') && handleRedirect?.()}
onClick={handleRedirect}
>
<HeaderTagIcon icon={icon} />
Expand Down
21 changes: 14 additions & 7 deletions apps/meteor/client/views/room/Header/ParentRoomWithData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@ type ParentRoomWithDataProps = {
room: IRoom;
};

const ParentRoomWithData = ({ room }: ParentRoomWithDataProps): ReactElement => {
const { prid } = room;
const getParentId = ({ prid, teamId }: IRoom): string => {
if (prid) {
return prid;
}

if (!prid) {
throw new Error('Parent room ID is missing');
if (teamId) {
return teamId;
}

const subscription = useUserSubscription(prid);
throw new Error('Parent room ID is missing');
};

const ParentRoomWithData = ({ room }: ParentRoomWithDataProps): ReactElement => {
const parentId = getParentId(room);

const subscription = useUserSubscription(parentId);
if (subscription) {
return <ParentRoom room={subscription} />;
return <ParentRoom room={{ ...subscription, _id: subscription.rid }} />;
}

return <ParentRoomWithEndpointData rid={prid} />;
return <ParentRoomWithEndpointData rid={room._id} />;
};

export default ParentRoomWithData;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import { HeaderTagSkeleton } from '../../../components/Header';
import { useRoomInfoEndpoint } from '../../../hooks/useRoomInfoEndpoint';
import ParentRoom from './ParentRoom';
import ParentTeam from './ParentTeam';

type ParentRoomWithEndpointDataProps = {
rid: IRoom['_id'];
Expand All @@ -21,6 +22,17 @@ const ParentRoomWithEndpointData = ({ rid }: ParentRoomWithEndpointDataProps): R
return null;
}

if (data.parent) {
return <ParentRoom room={data.parent} />;
}

if (data.team) {
if (data.team.roomId === rid) {
return null;
}

return <ParentTeam team={data.team} />;
}
return <ParentRoom room={data.room} />;
};

Expand Down
56 changes: 13 additions & 43 deletions apps/meteor/client/views/room/Header/ParentTeam.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,34 @@
import type { IRoom } from '@rocket.chat/core-typings';
import type { ITeam } from '@rocket.chat/core-typings';
import { TEAM_TYPE } from '@rocket.chat/core-typings';
import { useUserId, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useUserSubscription } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

import { HeaderTag, HeaderTagIcon, HeaderTagSkeleton } from '../../../components/Header';
import { HeaderTag, HeaderTagIcon } from '../../../components/Header';
import { goToRoomById } from '../../../lib/utils/goToRoomById';

type APIErrorResult = { success: boolean; error: string };
const ParentTeam = ({ team }: { team: Pick<ITeam, 'name' | 'roomId' | 'type'> }): ReactElement | null => {
const isTeamPublic = team.type === TEAM_TYPE.PUBLIC;

const ParentTeam = ({ room }: { room: IRoom }): ReactElement | null => {
const { teamId } = room;
const userId = useUserId();

if (!teamId) {
throw new Error('invalid rid');
}

if (!userId) {
throw new Error('invalid uid');
}

const teamsInfoEndpoint = useEndpoint('GET', '/v1/teams.info');
const userTeamsListEndpoint = useEndpoint('GET', '/v1/users.listTeams');

const {
data: teamInfoData,
isLoading: teamInfoLoading,
isError: teamInfoError,
} = useQuery(['teamId', teamId], async () => teamsInfoEndpoint({ teamId }), {
keepPreviousData: true,
retry: (_, error) => (error as APIErrorResult)?.error === 'unauthorized' && false,
});

const { data: userTeams, isLoading: userTeamsLoading } = useQuery(['userId', userId], async () => userTeamsListEndpoint({ userId }));

const userBelongsToTeam = userTeams?.teams?.find((team) => team._id === teamId) || false;
const isTeamPublic = teamInfoData?.teamInfo.type === TEAM_TYPE.PUBLIC;
const subscription = useUserSubscription(team.roomId);

const redirectToMainRoom = (): void => {
const rid = teamInfoData?.teamInfo.roomId;
const rid = team.roomId;
if (!rid) {
return;
}

const isTeamPublic = team.type === TEAM_TYPE.PUBLIC;

const userBelongsToTeam = !!subscription;

if (!(isTeamPublic || userBelongsToTeam)) {
return;
}

goToRoomById(rid);
};

if (teamInfoLoading || userTeamsLoading) {
return <HeaderTagSkeleton />;
}

if (teamInfoError) {
return null;
}

return (
<HeaderTag
role='button'
Expand All @@ -68,7 +37,8 @@ const ParentTeam = ({ room }: { room: IRoom }): ReactElement | null => {
onClick={redirectToMainRoom}
>
<HeaderTagIcon icon={{ name: isTeamPublic ? 'team' : 'team-lock' }} />
{teamInfoData?.teamInfo.name}

{team.name}
</HeaderTag>
);
};
Expand Down
4 changes: 1 addition & 3 deletions apps/meteor/client/views/room/Header/RoomHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Header, HeaderAvatar, HeaderContent, HeaderContentRow, HeaderSubtitle,
import MarkdownText from '../../../components/MarkdownText';
import FederatedRoomOriginServer from './FederatedRoomOriginServer';
import ParentRoomWithData from './ParentRoomWithData';
import ParentTeam from './ParentTeam';
import RoomTitle from './RoomTitle';
import RoomToolbox from './RoomToolbox';
import Encrypted from './icons/Encrypted';
Expand Down Expand Up @@ -47,8 +46,7 @@ const RoomHeader = ({ room, topic = '', slots = {}, roomToolbox }: RoomHeaderPro
<HeaderContentRow>
<RoomTitle room={room} />
<Favorite room={room} />
{room.prid && <ParentRoomWithData room={room} />}
{room.teamId && !room.teamMain && <ParentTeam room={room} />}
{(room.prid || room.teamId) && <ParentRoomWithData room={room} />}
{isRoomFederated(room) && <FederatedRoomOriginServer room={room} />}
<Encrypted room={room} />
<Translate room={room} />
Expand Down
9 changes: 7 additions & 2 deletions apps/meteor/client/views/room/HeaderV2/ParentRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ type ParentRoomProps = {
room: Pick<IRoom, '_id' | 't' | 'name' | 'fname' | 'prid' | 'u'>;
};

const hasSubscription = (room: ParentRoomProps['room']): boolean => roomCoordinator.getRoomDirectives(room.t).hasSubscription(room._id);
const ParentRoom = ({ room }: ParentRoomProps) => {
const icon = useRoomIcon(room);

const handleRedirect = (): void => roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room });
const handleRedirect = hasSubscription(room)
? (): void => {
roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room });
}
: undefined;

return (
<HeaderTag
role='button'
tabIndex={0}
onKeyDown={(e) => (e.code === 'Space' || e.code === 'Enter') && handleRedirect()}
onKeyDown={(e) => (e.code === 'Space' || e.code === 'Enter') && handleRedirect?.()}
onClick={handleRedirect}
>
<HeaderTagIcon icon={icon} />
Expand Down
21 changes: 14 additions & 7 deletions apps/meteor/client/views/room/HeaderV2/ParentRoomWithData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,27 @@ type ParentRoomWithDataProps = {
room: IRoom;
};

const ParentRoomWithData = ({ room }: ParentRoomWithDataProps) => {
const { prid } = room;
const getParentId = ({ prid, teamId }: IRoom): string => {
if (prid) {
return prid;
}

if (!prid) {
throw new Error('Parent room ID is missing');
if (teamId) {
return teamId;
}

const subscription = useUserSubscription(prid);
throw new Error('Parent room ID is missing');
};

const ParentRoomWithData = ({ room }: ParentRoomWithDataProps) => {
const parentId = getParentId(room);

const subscription = useUserSubscription(parentId);
if (subscription) {
return <ParentRoom room={subscription} />;
return <ParentRoom room={{ ...subscription, _id: subscription.rid }} />;
}

return <ParentRoomWithEndpointData rid={prid} />;
return <ParentRoomWithEndpointData rid={room._id} />;
};

export default ParentRoomWithData;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { HeaderTagSkeleton } from '../../../components/Header';
import { useRoomInfoEndpoint } from '../../../hooks/useRoomInfoEndpoint';
import ParentRoom from './ParentRoom';
import ParentTeam from './ParentTeam';

type ParentRoomWithEndpointDataProps = {
rid: IRoom['_id'];
Expand All @@ -20,6 +21,18 @@ const ParentRoomWithEndpointData = ({ rid }: ParentRoomWithEndpointDataProps) =>
return null;
}

if (data.parent) {
return <ParentRoom room={data.parent} />;
}

if (data.team) {
if (data.team.roomId === rid) {
return null;
}

return <ParentTeam team={data.team} />;
}

return <ParentRoom room={data.room} />;
};

Expand Down
61 changes: 14 additions & 47 deletions apps/meteor/client/views/room/HeaderV2/ParentTeam.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,34 @@
import type { IRoom } from '@rocket.chat/core-typings';
import type { ITeam } from '@rocket.chat/core-typings';
import { TEAM_TYPE } from '@rocket.chat/core-typings';
import { useUserId, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useUserSubscription } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

import { HeaderTag, HeaderTagIcon, HeaderTagSkeleton } from '../../../components/Header';
import { HeaderTag, HeaderTagIcon } from '../../../components/Header';
import { goToRoomById } from '../../../lib/utils/goToRoomById';

type APIErrorResult = { success: boolean; error: string };
const ParentTeam = ({ team }: { team: Pick<ITeam, 'name' | 'roomId' | 'type'> }): ReactElement | null => {
const isTeamPublic = team.type === TEAM_TYPE.PUBLIC;

type ParentTeamProps = {
room: IRoom;
};

const ParentTeam = ({ room }: ParentTeamProps) => {
const { teamId } = room;
const userId = useUserId();

if (!teamId) {
throw new Error('invalid rid');
}

if (!userId) {
throw new Error('invalid uid');
}

const teamsInfoEndpoint = useEndpoint('GET', '/v1/teams.info');
const userTeamsListEndpoint = useEndpoint('GET', '/v1/users.listTeams');

const {
data: teamInfoData,
isLoading: teamInfoLoading,
isError: teamInfoError,
} = useQuery(['teamId', teamId], async () => teamsInfoEndpoint({ teamId }), {
keepPreviousData: true,
retry: (_, error) => (error as APIErrorResult)?.error === 'unauthorized' && false,
});

const { data: userTeams, isLoading: userTeamsLoading } = useQuery(['userId', userId], async () => userTeamsListEndpoint({ userId }));

const userBelongsToTeam = userTeams?.teams?.find((team) => team._id === teamId) || false;
const isTeamPublic = teamInfoData?.teamInfo.type === TEAM_TYPE.PUBLIC;
const subscription = useUserSubscription(team.roomId);

const redirectToMainRoom = (): void => {
const rid = teamInfoData?.teamInfo.roomId;
const rid = team.roomId;
if (!rid) {
return;
}

const isTeamPublic = team.type === TEAM_TYPE.PUBLIC;

const userBelongsToTeam = !!subscription;

if (!(isTeamPublic || userBelongsToTeam)) {
return;
}

goToRoomById(rid);
};

if (teamInfoLoading || userTeamsLoading) {
return <HeaderTagSkeleton />;
}

if (teamInfoError) {
return null;
}

return (
<HeaderTag
role='button'
Expand All @@ -71,7 +37,8 @@ const ParentTeam = ({ room }: ParentTeamProps) => {
onClick={redirectToMainRoom}
>
<HeaderTagIcon icon={{ name: isTeamPublic ? 'team' : 'team-lock' }} />
{teamInfoData?.teamInfo.name}

{team.name}
</HeaderTag>
);
};
Expand Down
Loading
Loading