Skip to content

Commit 890812c

Browse files
authored
[IMPROVE] Redesign Administration > Invites (#17390)
1 parent 1cd82ca commit 890812c

File tree

7 files changed

+189
-197
lines changed

7 files changed

+189
-197
lines changed

app/invites/client/admin/adminInvites.html

Lines changed: 0 additions & 80 deletions
This file was deleted.

app/invites/client/admin/adminInvites.js

Lines changed: 0 additions & 93 deletions
This file was deleted.

app/invites/client/admin/route.js

Lines changed: 0 additions & 11 deletions
This file was deleted.

app/invites/client/admin/startup.js

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import {
2+
Button,
3+
Icon,
4+
Table,
5+
Box,
6+
} from '@rocket.chat/fuselage';
7+
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
8+
import React, { useState, useEffect } from 'react';
9+
import moment from 'moment';
10+
11+
import { Page } from '../../../../client/components/basic/Page';
12+
import { useTranslation } from '../../../../client/contexts/TranslationContext';
13+
import { useEndpoint } from '../../../../client/contexts/ServerContext';
14+
import { useModal } from '../../../../client/hooks/useModal';
15+
import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext';
16+
import { GenericTable } from '../../../ui/client/components/GenericTable';
17+
import { useFormatDateAndTime } from '../../../../client/hooks/useFormatDateAndTime';
18+
19+
20+
function InviteRow({ _id, createdAt, expires, days, uses, maxUses, onRemove }) {
21+
const t = useTranslation();
22+
const formatDateAndTime = useFormatDateAndTime();
23+
const modal = useModal();
24+
const dispatchToastMessage = useToastMessageDispatch();
25+
26+
const removeInvite = useEndpoint('DELETE', `removeInvite/${ _id }`);
27+
28+
const daysToExpire = ({ expires, days }) => {
29+
if (days > 0) {
30+
if (expires < Date.now()) {
31+
return t('Expired');
32+
}
33+
34+
return moment(expires).fromNow(true);
35+
}
36+
37+
return t('Never');
38+
};
39+
40+
const maxUsesLeft = ({ maxUses, uses }) => {
41+
if (maxUses > 0) {
42+
if (uses >= maxUses) {
43+
return 0;
44+
}
45+
46+
return maxUses - uses;
47+
}
48+
49+
return t('Unlimited');
50+
};
51+
52+
const handleRemoveButtonClick = async (event) => {
53+
event.stopPropagation();
54+
55+
modal.open({ // TODO REFACTOR
56+
text: t('Are_you_sure_you_want_to_delete_this_record'),
57+
type: 'warning',
58+
showCancelButton: true,
59+
confirmButtonColor: '#DD6B55',
60+
confirmButtonText: t('Yes'),
61+
cancelButtonText: t('No'),
62+
closeOnConfirm: true,
63+
html: false,
64+
}, async (confirmed) => {
65+
if (!confirmed) {
66+
return;
67+
}
68+
69+
try {
70+
await removeInvite();
71+
onRemove && onRemove(_id);
72+
} catch (error) {
73+
dispatchToastMessage({ type: 'error', message: error });
74+
}
75+
});
76+
};
77+
78+
const notSmall = useMediaQuery('(min-width: 768px)');
79+
80+
return <Table.Row>
81+
<Table.Cell>
82+
<Box textStyle='p1' textColor='hint'>{_id}</Box>
83+
</Table.Cell>
84+
{notSmall && <>
85+
<Table.Cell>
86+
{formatDateAndTime(createdAt)}
87+
</Table.Cell>
88+
<Table.Cell>
89+
{daysToExpire({ expires, days })}
90+
</Table.Cell>
91+
<Table.Cell>
92+
{uses}
93+
</Table.Cell>
94+
<Table.Cell>
95+
{maxUsesLeft({ maxUses, uses })}
96+
</Table.Cell>
97+
</>}
98+
<Table.Cell>
99+
<Button ghost danger small square onClick={handleRemoveButtonClick}>
100+
<Icon name='cross' size='x20' />
101+
</Button>
102+
</Table.Cell>
103+
</Table.Row>;
104+
}
105+
106+
function InvitesPage() {
107+
const t = useTranslation();
108+
109+
const [invites, setInvites] = useState([]);
110+
111+
const listInvites = useEndpoint('GET', 'listInvites');
112+
113+
useEffect(() => {
114+
const loadInvites = async () => {
115+
const result = await listInvites() || [];
116+
117+
const invites = result.map((data) => ({
118+
...data,
119+
createdAt: new Date(data.createdAt),
120+
expires: data.expires ? new Date(data.expires) : '',
121+
}));
122+
123+
setInvites(invites);
124+
};
125+
126+
loadInvites();
127+
}, []);
128+
129+
const handleInviteRemove = (_id) => {
130+
setInvites((invites = []) => invites.filter((invite) => invite._id !== _id));
131+
};
132+
133+
const notSmall = useMediaQuery('(min-width: 768px)');
134+
135+
return <Page>
136+
<Page.Header title={t('Invites')} />
137+
<Page.ContentShadowScroll>
138+
<GenericTable
139+
results={invites}
140+
header={
141+
<>
142+
<Table.Cell is='th' width={notSmall ? '20%' : '80%'}>{t('Token')}</Table.Cell>
143+
{ notSmall && <>
144+
<Table.Cell is='th' width='35%'>{t('Created_at')}</Table.Cell>
145+
<Table.Cell is='th' width='20%'>{t('Expiration')}</Table.Cell>
146+
<Table.Cell is='th' width='10%'>{t('Uses')}</Table.Cell>
147+
<Table.Cell is='th' width='10%'>{t('Uses_left')}</Table.Cell>
148+
</>}
149+
<Table.Cell is='th' />
150+
</>
151+
}
152+
renderRow={(invite) => <InviteRow key={invite._id} {...invite} onRemove={handleInviteRemove} />}
153+
/>
154+
</Page.ContentShadowScroll>
155+
</Page>;
156+
}
157+
158+
export default InvitesPage;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
3+
import { usePermission } from '../../../../client/contexts/AuthorizationContext';
4+
import NotAuthorizedPage from '../../../ui-admin/client/components/NotAuthorizedPage';
5+
import InvitesPage from './InvitesPage';
6+
7+
function InvitesRoute() {
8+
const canCreateInviteLinks = usePermission('create-invite-links');
9+
10+
if (!canCreateInviteLinks) {
11+
return <NotAuthorizedPage />;
12+
}
13+
14+
return <InvitesPage />;
15+
}
16+
17+
export default InvitesRoute;

app/invites/client/index.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1-
import './admin/route';
2-
import './admin/startup';
1+
import { registerAdminRoute, registerAdminSidebarItem } from '../../ui-admin/client';
2+
import { hasPermission } from '../../authorization/client';
3+
4+
registerAdminSidebarItem({
5+
href: 'invites',
6+
i18nLabel: 'Invites',
7+
icon: 'user-plus',
8+
permissionGranted: () => hasPermission('create-invite-links'),
9+
});
10+
11+
registerAdminRoute('/invites', {
12+
name: 'invites',
13+
lazyRouteComponent: () => import('./components/InvitesRoute'),
14+
});

0 commit comments

Comments
 (0)