mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 06:47:25 +00:00
feat: Added invitation badge to sidebar (#37635)
This commit is contained in:
parent
1c06f6098e
commit
f056c451c2
6
.changeset/twelve-forks-destroy.md
Normal file
6
.changeset/twelve-forks-destroy.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@rocket.chat/meteor": patch
|
||||
"@rocket.chat/i18n": patch
|
||||
---
|
||||
|
||||
Adds invitation badge to sidebar
|
||||
@ -0,0 +1,18 @@
|
||||
import { composeStories } from '@storybook/react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { axe } from 'jest-axe';
|
||||
|
||||
import * as stories from './InvitationBadge.stories';
|
||||
|
||||
const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);
|
||||
test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
|
||||
const { baseElement } = render(<Story />);
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
|
||||
const { container } = render(<Story />);
|
||||
|
||||
const results = await axe(container);
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
@ -0,0 +1,32 @@
|
||||
import { mockAppRoot } from '@rocket.chat/mock-providers';
|
||||
import type { Meta } from '@storybook/react';
|
||||
|
||||
import InvitationBadge from './InvitationBadge';
|
||||
|
||||
const meta = {
|
||||
component: InvitationBadge,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
decorators: [
|
||||
mockAppRoot()
|
||||
.withTranslations('en', 'core', {
|
||||
Invited__date__: 'Invited {{date}}',
|
||||
})
|
||||
.buildStoryDecorator(),
|
||||
],
|
||||
} satisfies Meta<typeof InvitationBadge>;
|
||||
|
||||
export default meta;
|
||||
|
||||
export const WithISOStringDate = {
|
||||
args: {
|
||||
invitationDate: '2025-01-01T12:00:00Z',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDateObject = {
|
||||
args: {
|
||||
invitationDate: new Date('2025-01-01T12:00:00Z'),
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
import { Icon } from '@rocket.chat/fuselage';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useTimeAgo } from '../../hooks/useTimeAgo';
|
||||
|
||||
type InvitationBadgeProps = Omit<ComponentProps<typeof Icon>, 'name' | 'color' | 'role'> & {
|
||||
invitationDate: string | Date;
|
||||
};
|
||||
|
||||
const InvitationBadge = ({ invitationDate, ...props }: InvitationBadgeProps) => {
|
||||
const { t } = useTranslation();
|
||||
const timeAgo = useTimeAgo();
|
||||
|
||||
return (
|
||||
<Icon
|
||||
size='x20'
|
||||
{...props}
|
||||
role='status'
|
||||
color='info'
|
||||
name='mail'
|
||||
aria-hidden='false'
|
||||
title={t('Invited__date__', { date: timeAgo(invitationDate) })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvitationBadge;
|
||||
@ -0,0 +1,31 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`renders WithDateObject without crashing 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<i
|
||||
aria-hidden="false"
|
||||
class="rcx-box rcx-box--full rcx-icon--name-mail rcx-icon rcx-css-dpa92h"
|
||||
role="status"
|
||||
title="Invited January 1, 2025"
|
||||
>
|
||||
|
||||
</i>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`renders WithISOStringDate without crashing 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<i
|
||||
aria-hidden="false"
|
||||
class="rcx-box rcx-box--full rcx-icon--name-mail rcx-icon rcx-css-dpa92h"
|
||||
role="status"
|
||||
title="Invited January 1, 2025"
|
||||
>
|
||||
|
||||
</i>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
1
apps/meteor/client/components/InvitationBadge/index.ts
Normal file
1
apps/meteor/client/components/InvitationBadge/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './InvitationBadge';
|
||||
@ -12,7 +12,7 @@ jest.mock('../../views/omnichannel/components/OmnichannelBadges', () => ({
|
||||
describe('SidebarItemBadges', () => {
|
||||
const appRoot = mockAppRoot()
|
||||
.withTranslations('en', 'core', {
|
||||
Message_request: 'Message request',
|
||||
Invited__date__: 'Invited {{date}}',
|
||||
mentions_counter_one: '{{count}} mention',
|
||||
mentions_counter_other: '{{count}} mentions',
|
||||
__unreadTitle__from__roomTitle__: '{{unreadTitle}} from {{roomTitle}}',
|
||||
@ -50,4 +50,27 @@ describe('SidebarItemBadges', () => {
|
||||
|
||||
expect(screen.queryByRole('status', { name: 'OmnichannelBadges' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render InvitationBadge when subscription has status INVITED', () => {
|
||||
render(
|
||||
<SidebarItemBadges
|
||||
room={createFakeSubscription({
|
||||
status: 'INVITED',
|
||||
inviter: { name: 'Rocket Cat', username: 'rocket.cat', _id: 'rocket.cat' },
|
||||
ts: new Date('2025-01-01T00:00:00.000Z'),
|
||||
})}
|
||||
/>,
|
||||
{
|
||||
wrapper: appRoot,
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Invited January 1, 2025' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render InvitationBadge when subscription does not have status INVITED', () => {
|
||||
render(<SidebarItemBadges room={createFakeSubscription()} />, { wrapper: appRoot });
|
||||
|
||||
expect(screen.queryByRole('status', { name: /Invited/ })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import { isOmnichannelRoom, isInviteSubscription } from '@rocket.chat/core-typings';
|
||||
import { Margins } from '@rocket.chat/fuselage';
|
||||
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
|
||||
|
||||
import UnreadBadge from './UnreadBadge';
|
||||
import InvitationBadge from '../../components/InvitationBadge';
|
||||
import OmnichannelBadges from '../../views/omnichannel/components/OmnichannelBadges';
|
||||
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
|
||||
|
||||
@ -18,6 +19,7 @@ const SidebarItemBadges = ({ room, roomTitle }: SidebarItemBadgesProps) => {
|
||||
<Margins inlineStart={8}>
|
||||
{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}
|
||||
{isOmnichannelRoom(room) && <OmnichannelBadges room={room} />}
|
||||
{isInviteSubscription(room) && <InvitationBadge mbs={2} invitationDate={room.ts} />}
|
||||
</Margins>
|
||||
);
|
||||
};
|
||||
|
||||
@ -12,7 +12,7 @@ jest.mock('../../views/omnichannel/components/OmnichannelBadges', () => ({
|
||||
describe('SidebarItemBadges', () => {
|
||||
const appRoot = mockAppRoot()
|
||||
.withTranslations('en', 'core', {
|
||||
Message_request: 'Message request',
|
||||
Invited__date__: 'Invited {{date}}',
|
||||
mentions_counter_one: '{{count}} mention',
|
||||
mentions_counter_other: '{{count}} mentions',
|
||||
__unreadTitle__from__roomTitle__: '{{unreadTitle}} from {{roomTitle}}',
|
||||
@ -50,4 +50,29 @@ describe('SidebarItemBadges', () => {
|
||||
|
||||
expect(screen.queryByRole('status', { name: 'OmnichannelBadges' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render InvitationBadge when subscription has status INVITED', () => {
|
||||
render(
|
||||
<SidebarItemBadges
|
||||
room={createFakeSubscription({
|
||||
status: 'INVITED',
|
||||
inviter: { name: 'Rocket Cat', username: 'rocket.cat', _id: 'rocket.cat' },
|
||||
ts: new Date('2025-01-01T00:00:00.000Z'),
|
||||
})}
|
||||
/>,
|
||||
{
|
||||
wrapper: appRoot,
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Invited January 1, 2025' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render InvitationBadge when subscription does not have status INVITED', () => {
|
||||
render(<SidebarItemBadges room={createFakeSubscription()} />, {
|
||||
wrapper: appRoot,
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('status', { name: /Invited/ })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import { isInviteSubscription, isOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
|
||||
|
||||
import UnreadBadge from './UnreadBadge';
|
||||
import InvitationBadge from '../../components/InvitationBadge';
|
||||
import OmnichannelBadges from '../../views/omnichannel/components/OmnichannelBadges';
|
||||
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
|
||||
|
||||
@ -17,6 +18,7 @@ const SidebarItemBadges = ({ room, roomTitle }: SidebarItemBadgesProps) => {
|
||||
<>
|
||||
{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}
|
||||
{isOmnichannelRoom(room) && <OmnichannelBadges room={room} />}
|
||||
{isInviteSubscription(room) && <InvitationBadge mbs={2} invitationDate={room.ts} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,7 +7,7 @@ import { createFakeSubscription } from '../../../../../tests/mocks/data';
|
||||
describe('SidebarItemBadges', () => {
|
||||
const appRoot = mockAppRoot()
|
||||
.withTranslations('en', 'core', {
|
||||
Message_request: 'Message request',
|
||||
Invited__date__: 'Invited {{date}}',
|
||||
mentions_counter_one: '{{count}} mention',
|
||||
mentions_counter_other: '{{count}} mentions',
|
||||
__unreadTitle__from__roomTitle__: '{{unreadTitle}} from {{roomTitle}}',
|
||||
@ -33,4 +33,27 @@ describe('SidebarItemBadges', () => {
|
||||
|
||||
expect(screen.queryByRole('status', { name: 'Test Room' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render InvitationBadge when subscription has status INVITED and has inviter', () => {
|
||||
render(
|
||||
<SidebarItemBadges
|
||||
room={createFakeSubscription({
|
||||
status: 'INVITED',
|
||||
inviter: { name: 'Rocket Cat', username: 'rocket.cat', _id: 'rocket.cat' },
|
||||
ts: new Date('2025-01-01T00:00:00.000Z'),
|
||||
})}
|
||||
/>,
|
||||
{
|
||||
wrapper: appRoot,
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Invited January 1, 2025' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render InvitationBadge when subscription does not have status INVITED', () => {
|
||||
render(<SidebarItemBadges room={createFakeSubscription()} />, { wrapper: appRoot });
|
||||
|
||||
expect(screen.queryByRole('status', { name: /Invited/ })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { isInviteSubscription } from '@rocket.chat/core-typings';
|
||||
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
|
||||
|
||||
import InvitationBadge from '../../../../components/InvitationBadge';
|
||||
import UnreadBadge from '../../../../sidebarv2/badges/UnreadBadge';
|
||||
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
|
||||
|
||||
@ -11,7 +13,12 @@ type SidebarItemBadgesProps = {
|
||||
const SidebarItemBadges = ({ room, roomTitle }: SidebarItemBadgesProps) => {
|
||||
const { unreadCount, unreadTitle, unreadVariant, showUnread } = useUnreadDisplay(room);
|
||||
|
||||
return <>{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}</>;
|
||||
return (
|
||||
<>
|
||||
{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}
|
||||
{isInviteSubscription(room) && <InvitationBadge mbs={2} invitationDate={room.ts} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarItemBadges;
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import { SidebarV2ItemBadge } from '@rocket.chat/fuselage';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type UnreadBadgeProps = {
|
||||
title: string;
|
||||
roomTitle?: string;
|
||||
variant: 'primary' | 'warning' | 'danger' | 'secondary';
|
||||
total: number;
|
||||
};
|
||||
|
||||
const UnreadBadge = ({ title, variant, total, roomTitle }: UnreadBadgeProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SidebarV2ItemBadge
|
||||
variant={variant}
|
||||
title={title}
|
||||
role='status'
|
||||
aria-label={t('__unreadTitle__from__roomTitle__', { unreadTitle: title, roomTitle })}
|
||||
>
|
||||
<span aria-hidden>{total}</span>
|
||||
</SidebarV2ItemBadge>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnreadBadge;
|
||||
@ -12,7 +12,7 @@ jest.mock('../omnichannel/SidePanelOmnichannelBadges', () => ({
|
||||
describe('RoomSidePanelItemBadges', () => {
|
||||
const appRoot = mockAppRoot()
|
||||
.withTranslations('en', 'core', {
|
||||
Message_request: 'Message request',
|
||||
Invited__date__: 'Invited {{date}}',
|
||||
mentions_counter_one: '{{count}} mention',
|
||||
mentions_counter_other: '{{count}} mentions',
|
||||
__unreadTitle__from__roomTitle__: '{{unreadTitle}} from {{roomTitle}}',
|
||||
@ -52,4 +52,27 @@ describe('RoomSidePanelItemBadges', () => {
|
||||
|
||||
expect(screen.queryByRole('status', { name: 'OmnichannelBadges' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render InvitationBadge when subscription has status INVITED', () => {
|
||||
render(
|
||||
<RoomSidePanelItemBadges
|
||||
room={createFakeSubscription({
|
||||
status: 'INVITED',
|
||||
inviter: { name: 'Rocket Cat', username: 'rocket.cat', _id: 'rocket.cat' },
|
||||
ts: new Date('2025-01-01T00:00:00.000Z'),
|
||||
})}
|
||||
/>,
|
||||
{
|
||||
wrapper: appRoot,
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Invited January 1, 2025' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render InvitationBadge when subscription does not have status INVITED', () => {
|
||||
render(<RoomSidePanelItemBadges room={createFakeSubscription()} />, { wrapper: appRoot });
|
||||
|
||||
expect(screen.queryByRole('status', { name: 'Invited' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import { isInviteSubscription, isOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
|
||||
|
||||
import InvitationBadge from '../../../../components/InvitationBadge';
|
||||
import UnreadBadge from '../../../../sidebarv2/badges/UnreadBadge';
|
||||
import { useUnreadDisplay } from '../../../../sidebarv2/hooks/useUnreadDisplay';
|
||||
import SidePanelOmnichannelBadges from '../omnichannel/SidePanelOmnichannelBadges';
|
||||
@ -17,6 +18,7 @@ const RoomSidePanelItemBadges = ({ room, roomTitle }: RoomSidePanelItemBadgesPro
|
||||
<>
|
||||
{isOmnichannelRoom(room) && <SidePanelOmnichannelBadges room={room} />}
|
||||
{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}
|
||||
{isInviteSubscription(room) && <InvitationBadge invitationDate={room.ts} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2756,6 +2756,7 @@
|
||||
"Invitation_Subject": "Invitation Subject",
|
||||
"Invitation_Subject_Default": "You have been invited to [Site_Name]",
|
||||
"Invite": "Invite",
|
||||
"Invited__date__": "Invited {{date}}",
|
||||
"Invite_Link": "Invite Link",
|
||||
"Invite_Users": "Invite Members",
|
||||
"Invite_and_add_members_to_this_workspace_to_start_communicating": "Invite and add members to this workspace to start communicating.",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user