Skip to content

Commit 2d2260b

Browse files
aleksandernsilvagaolin1
authored andcommitted
feat: Added invitation badge to sidebar (RocketChat#37635)
1 parent 7591874 commit 2d2260b

File tree

16 files changed

+259
-9
lines changed

16 files changed

+259
-9
lines changed

.changeset/twelve-forks-destroy.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
"@rocket.chat/i18n": patch
4+
---
5+
6+
Adds invitation badge to sidebar
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { composeStories } from '@storybook/react';
2+
import { render } from '@testing-library/react';
3+
import { axe } from 'jest-axe';
4+
5+
import * as stories from './InvitationBadge.stories';
6+
7+
const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);
8+
test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
9+
const { baseElement } = render(<Story />);
10+
expect(baseElement).toMatchSnapshot();
11+
});
12+
13+
test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
14+
const { container } = render(<Story />);
15+
16+
const results = await axe(container);
17+
expect(results).toHaveNoViolations();
18+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { mockAppRoot } from '@rocket.chat/mock-providers';
2+
import type { Meta } from '@storybook/react';
3+
4+
import InvitationBadge from './InvitationBadge';
5+
6+
const meta = {
7+
component: InvitationBadge,
8+
parameters: {
9+
layout: 'centered',
10+
},
11+
decorators: [
12+
mockAppRoot()
13+
.withTranslations('en', 'core', {
14+
Invited__date__: 'Invited {{date}}',
15+
})
16+
.buildStoryDecorator(),
17+
],
18+
} satisfies Meta<typeof InvitationBadge>;
19+
20+
export default meta;
21+
22+
export const WithISOStringDate = {
23+
args: {
24+
invitationDate: '2025-01-01T12:00:00Z',
25+
},
26+
};
27+
28+
export const WithDateObject = {
29+
args: {
30+
invitationDate: new Date('2025-01-01T12:00:00Z'),
31+
},
32+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Icon } from '@rocket.chat/fuselage';
2+
import type { ComponentProps } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
5+
import { useTimeAgo } from '../../hooks/useTimeAgo';
6+
7+
type InvitationBadgeProps = Omit<ComponentProps<typeof Icon>, 'name' | 'color' | 'role'> & {
8+
invitationDate: string | Date;
9+
};
10+
11+
const InvitationBadge = ({ invitationDate, ...props }: InvitationBadgeProps) => {
12+
const { t } = useTranslation();
13+
const timeAgo = useTimeAgo();
14+
15+
return (
16+
<Icon
17+
size='x20'
18+
{...props}
19+
role='status'
20+
color='info'
21+
name='mail'
22+
aria-hidden='false'
23+
title={t('Invited__date__', { date: timeAgo(invitationDate) })}
24+
/>
25+
);
26+
};
27+
28+
export default InvitationBadge;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`renders WithDateObject without crashing 1`] = `
4+
<body>
5+
<div>
6+
<i
7+
aria-hidden="false"
8+
class="rcx-box rcx-box--full rcx-icon--name-mail rcx-icon rcx-css-dpa92h"
9+
role="status"
10+
title="Invited January 1, 2025"
11+
>
12+
13+
</i>
14+
</div>
15+
</body>
16+
`;
17+
18+
exports[`renders WithISOStringDate without crashing 1`] = `
19+
<body>
20+
<div>
21+
<i
22+
aria-hidden="false"
23+
class="rcx-box rcx-box--full rcx-icon--name-mail rcx-icon rcx-css-dpa92h"
24+
role="status"
25+
title="Invited January 1, 2025"
26+
>
27+
28+
</i>
29+
</div>
30+
</body>
31+
`;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './InvitationBadge';

apps/meteor/client/sidebar/badges/SidebarItemBadges.spec.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jest.mock('../../views/omnichannel/components/OmnichannelBadges', () => ({
1212
describe('SidebarItemBadges', () => {
1313
const appRoot = mockAppRoot()
1414
.withTranslations('en', 'core', {
15-
Message_request: 'Message request',
15+
Invited__date__: 'Invited {{date}}',
1616
mentions_counter_one: '{{count}} mention',
1717
mentions_counter_other: '{{count}} mentions',
1818
__unreadTitle__from__roomTitle__: '{{unreadTitle}} from {{roomTitle}}',
@@ -50,4 +50,27 @@ describe('SidebarItemBadges', () => {
5050

5151
expect(screen.queryByRole('status', { name: 'OmnichannelBadges' })).not.toBeInTheDocument();
5252
});
53+
54+
it('should render InvitationBadge when subscription has status INVITED', () => {
55+
render(
56+
<SidebarItemBadges
57+
room={createFakeSubscription({
58+
status: 'INVITED',
59+
inviter: { name: 'Rocket Cat', username: 'rocket.cat', _id: 'rocket.cat' },
60+
ts: new Date('2025-01-01T00:00:00.000Z'),
61+
})}
62+
/>,
63+
{
64+
wrapper: appRoot,
65+
},
66+
);
67+
68+
expect(screen.getByRole('status', { name: 'Invited January 1, 2025' })).toBeInTheDocument();
69+
});
70+
71+
it('should not render InvitationBadge when subscription does not have status INVITED', () => {
72+
render(<SidebarItemBadges room={createFakeSubscription()} />, { wrapper: appRoot });
73+
74+
expect(screen.queryByRole('status', { name: /Invited/ })).not.toBeInTheDocument();
75+
});
5376
});

apps/meteor/client/sidebar/badges/SidebarItemBadges.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
1+
import { isOmnichannelRoom, isInviteSubscription } from '@rocket.chat/core-typings';
22
import { Margins } from '@rocket.chat/fuselage';
33
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
44

55
import UnreadBadge from './UnreadBadge';
6+
import InvitationBadge from '../../components/InvitationBadge';
67
import OmnichannelBadges from '../../views/omnichannel/components/OmnichannelBadges';
78
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
89

@@ -18,6 +19,7 @@ const SidebarItemBadges = ({ room, roomTitle }: SidebarItemBadgesProps) => {
1819
<Margins inlineStart={8}>
1920
{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}
2021
{isOmnichannelRoom(room) && <OmnichannelBadges room={room} />}
22+
{isInviteSubscription(room) && <InvitationBadge mbs={2} invitationDate={room.ts} />}
2123
</Margins>
2224
);
2325
};

apps/meteor/client/sidebarv2/badges/SidebarItemBadges.spec.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jest.mock('../../views/omnichannel/components/OmnichannelBadges', () => ({
1212
describe('SidebarItemBadges', () => {
1313
const appRoot = mockAppRoot()
1414
.withTranslations('en', 'core', {
15-
Message_request: 'Message request',
15+
Invited__date__: 'Invited {{date}}',
1616
mentions_counter_one: '{{count}} mention',
1717
mentions_counter_other: '{{count}} mentions',
1818
__unreadTitle__from__roomTitle__: '{{unreadTitle}} from {{roomTitle}}',
@@ -50,4 +50,29 @@ describe('SidebarItemBadges', () => {
5050

5151
expect(screen.queryByRole('status', { name: 'OmnichannelBadges' })).not.toBeInTheDocument();
5252
});
53+
54+
it('should render InvitationBadge when subscription has status INVITED', () => {
55+
render(
56+
<SidebarItemBadges
57+
room={createFakeSubscription({
58+
status: 'INVITED',
59+
inviter: { name: 'Rocket Cat', username: 'rocket.cat', _id: 'rocket.cat' },
60+
ts: new Date('2025-01-01T00:00:00.000Z'),
61+
})}
62+
/>,
63+
{
64+
wrapper: appRoot,
65+
},
66+
);
67+
68+
expect(screen.getByRole('status', { name: 'Invited January 1, 2025' })).toBeInTheDocument();
69+
});
70+
71+
it('should not render InvitationBadge when subscription does not have status INVITED', () => {
72+
render(<SidebarItemBadges room={createFakeSubscription()} />, {
73+
wrapper: appRoot,
74+
});
75+
76+
expect(screen.queryByRole('status', { name: /Invited/ })).not.toBeInTheDocument();
77+
});
5378
});

apps/meteor/client/sidebarv2/badges/SidebarItemBadges.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
1+
import { isInviteSubscription, isOmnichannelRoom } from '@rocket.chat/core-typings';
22
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
33

44
import UnreadBadge from './UnreadBadge';
5+
import InvitationBadge from '../../components/InvitationBadge';
56
import OmnichannelBadges from '../../views/omnichannel/components/OmnichannelBadges';
67
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
78

@@ -17,6 +18,7 @@ const SidebarItemBadges = ({ room, roomTitle }: SidebarItemBadgesProps) => {
1718
<>
1819
{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}
1920
{isOmnichannelRoom(room) && <OmnichannelBadges room={room} />}
21+
{isInviteSubscription(room) && <InvitationBadge mbs={2} invitationDate={room.ts} />}
2022
</>
2123
);
2224
};

0 commit comments

Comments
 (0)