Skip to content

Commit 701524f

Browse files
committed
feat(FR-740): Create modal for accepting/rejecting invitations
1 parent 193e25f commit 701524f

25 files changed

Lines changed: 234 additions & 89 deletions
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { useVFolderInvitations } from '../hooks/backendai';
2+
import BAIModal, { BAIModalProps } from './BAIModal';
3+
import Flex from './Flex';
4+
import VFolderPermissionCell from './VFolderPermissionCell';
5+
import { FolderOutlined } from '@ant-design/icons';
6+
import { List, Button, Typography, theme, Descriptions } from 'antd';
7+
import React from 'react';
8+
import { useTranslation } from 'react-i18next';
9+
10+
interface InvitationItem {
11+
id: string;
12+
vfolder_id: string;
13+
vfolder_name: string;
14+
invitee_user_email: string;
15+
inviter_user_email: string;
16+
mount_permission: string;
17+
created_at: string;
18+
modified_at: string | null;
19+
status: string;
20+
perm: string;
21+
}
22+
23+
interface FolderInvitationResponseModalProps extends BAIModalProps {
24+
onRequestClose?: () => void;
25+
}
26+
27+
const FolderInvitationResponseModal: React.FC<
28+
FolderInvitationResponseModalProps
29+
> = ({ onRequestClose, ...baiModalProps }) => {
30+
const { token } = theme.useToken();
31+
const { t } = useTranslation();
32+
const [
33+
{ invitations },
34+
{ acceptInvitation, rejectInvitation: declineInvitation },
35+
] = useVFolderInvitations();
36+
37+
return (
38+
<BAIModal
39+
onCancel={onRequestClose}
40+
title={t('data.InvitedFolders')}
41+
footer={null}
42+
{...baiModalProps}
43+
>
44+
<List
45+
dataSource={invitations}
46+
renderItem={(item: InvitationItem) => (
47+
<List.Item
48+
actions={[
49+
<Button
50+
type="primary"
51+
onClick={() => acceptInvitation(item.id)}
52+
key="accept"
53+
>
54+
{t('summary.Accept')}
55+
</Button>,
56+
<Button
57+
danger
58+
onClick={() => declineInvitation(item.id)}
59+
key="decline"
60+
>
61+
{t('summary.Decline')}
62+
</Button>,
63+
]}
64+
style={{
65+
padding: token.paddingSM,
66+
}}
67+
>
68+
<List.Item.Meta
69+
title={
70+
<Flex gap={'xxs'}>
71+
<FolderOutlined />
72+
<Typography.Text strong>{item.vfolder_name}</Typography.Text>
73+
</Flex>
74+
}
75+
description={
76+
<Descriptions
77+
size="small"
78+
column={1}
79+
items={[
80+
{
81+
key: 'from',
82+
label: t('data.From'),
83+
children: item.inviter_user_email,
84+
},
85+
{
86+
key: 'permission',
87+
label: t('data.Permission'),
88+
children: (
89+
<VFolderPermissionCell permission={item.perm} />
90+
),
91+
},
92+
]}
93+
/>
94+
}
95+
/>
96+
</List.Item>
97+
)}
98+
/>
99+
</BAIModal>
100+
);
101+
};
102+
103+
export default FolderInvitationResponseModal;

react/src/components/StorageStatusPanelCard.tsx

Lines changed: 96 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { useSuspenseTanQuery } from '../hooks/reactQueryAlias';
44
import { useCurrentProjectValue } from '../hooks/useCurrentProject';
55
import BAICard, { BAICardProps } from './BAICard';
66
import BAIPanelItem from './BAIPanelItem';
7+
import FolderInvitationResponseModal from './FolderInvitationResponseModal';
78
import { StorageStatusPanelCardQuery } from './__generated__/StorageStatusPanelCardQuery.graphql';
9+
import { useToggle } from 'ahooks';
810
import { Badge, Col, Row, theme, Tooltip, Typography } from 'antd';
911
import { createStyles } from 'antd-style';
1012
import graphql from 'babel-plugin-relay/macro';
@@ -41,6 +43,10 @@ const StorageStatusPanelCard: React.FC<StorageStatusPanelProps> = ({
4143
const currentProject = useCurrentProjectValue();
4244
const deferredFetchKey = useDeferredValue(fetchKey);
4345
const [{ count }] = useVFolderInvitations();
46+
const [
47+
isInvitationResponseModalOpen,
48+
{ toggle: toggleInvitationResponseModal },
49+
] = useToggle(false);
4450

4551
const isExcludedCount = (status: string) => {
4652
return _.includes(
@@ -92,90 +98,98 @@ const StorageStatusPanelCard: React.FC<StorageStatusPanelProps> = ({
9298
);
9399

94100
return (
95-
<BAICard {...cardProps} title={t('data.StorageStatus')}>
96-
<Row gutter={[24, 16]}>
97-
<Col
98-
span={8}
99-
style={{
100-
borderRight: `1px solid ${token.colorBorderSecondary}`,
101-
justifyItems: 'center',
102-
}}
103-
>
104-
<BAIPanelItem
105-
title={t('data.MyFolders')}
106-
value={createdCount}
107-
unit={
108-
user_resource_policy?.max_vfolder_count
109-
? `/ ${user_resource_policy?.max_vfolder_count}`
110-
: undefined
111-
}
112-
/>
113-
</Col>
114-
<Col
115-
span={8}
116-
style={{
117-
borderRight: `1px solid ${token.colorBorderSecondary}`,
118-
justifyItems: 'center',
119-
}}
120-
>
121-
<BAIPanelItem
122-
title={t('data.ProjectFolders')}
123-
value={projectCount}
124-
unit={
125-
project_resource_policy?.max_vfolder_count
126-
? `/ ${project_resource_policy?.max_vfolder_count}`
127-
: undefined
128-
}
129-
/>
130-
</Col>
131-
<Col
132-
span={8}
133-
style={{
134-
justifyItems: 'center',
135-
}}
136-
>
137-
<BAIPanelItem
138-
title={
139-
// Add a tag to the Tooltip to make it clickable
140-
// eslint-disable-next-line
141-
<a>
142-
<Tooltip
143-
title={
144-
count > 0
145-
? t('data.InvitedFoldersTooltip', {
146-
count: count,
147-
})
148-
: null
149-
}
150-
rootClassName={styles.invitationTooltip}
151-
placement="topRight"
101+
<>
102+
<BAICard {...cardProps} title={t('data.StorageStatus')}>
103+
<Row gutter={[24, 16]}>
104+
<Col
105+
span={8}
106+
style={{
107+
borderRight: `1px solid ${token.colorBorderSecondary}`,
108+
justifyItems: 'center',
109+
}}
110+
>
111+
<BAIPanelItem
112+
title={t('data.MyFolders')}
113+
value={createdCount}
114+
unit={
115+
user_resource_policy?.max_vfolder_count
116+
? `/ ${user_resource_policy?.max_vfolder_count}`
117+
: undefined
118+
}
119+
/>
120+
</Col>
121+
<Col
122+
span={8}
123+
style={{
124+
borderRight: `1px solid ${token.colorBorderSecondary}`,
125+
justifyItems: 'center',
126+
}}
127+
>
128+
<BAIPanelItem
129+
title={t('data.ProjectFolders')}
130+
value={projectCount}
131+
unit={
132+
project_resource_policy?.max_vfolder_count
133+
? `/ ${project_resource_policy?.max_vfolder_count}`
134+
: undefined
135+
}
136+
/>
137+
</Col>
138+
<Col
139+
span={8}
140+
style={{
141+
justifyItems: 'center',
142+
}}
143+
>
144+
<BAIPanelItem
145+
title={
146+
<a
147+
onClick={() => {
148+
toggleInvitationResponseModal();
149+
}}
152150
>
153-
<Badge
154-
count={count > 0 ? `+${count}` : null}
155-
offset={[0, -`${token.sizeXS}`]}
151+
<Tooltip
152+
title={
153+
count > 0
154+
? t('data.InvitedFoldersTooltip', {
155+
count: count,
156+
})
157+
: null
158+
}
159+
rootClassName={styles.invitationTooltip}
160+
placement="topRight"
156161
>
157-
<Typography.Title level={5} style={{ margin: 0 }}>
158-
{t('data.InvitedFolders')}
159-
</Typography.Title>
160-
</Badge>
161-
</Tooltip>
162-
</a>
163-
}
164-
value={
165-
<Typography.Text
166-
strong
167-
style={{
168-
fontSize: token.fontSizeHeading1,
169-
color: token.Layout?.headerBg,
170-
}}
171-
>
172-
{invitedCount}
173-
</Typography.Text>
174-
}
175-
/>
176-
</Col>
177-
</Row>
178-
</BAICard>
162+
<Badge
163+
count={count > 0 ? `+${count}` : null}
164+
offset={[0, -`${token.sizeXS}`]}
165+
>
166+
<Typography.Title level={5} style={{ margin: 0 }}>
167+
{t('data.InvitedFolders')}
168+
</Typography.Title>
169+
</Badge>
170+
</Tooltip>
171+
</a>
172+
}
173+
value={
174+
<Typography.Text
175+
strong
176+
style={{
177+
fontSize: token.fontSizeHeading1,
178+
color: token.Layout?.headerBg,
179+
}}
180+
>
181+
{invitedCount}
182+
</Typography.Text>
183+
}
184+
/>
185+
</Col>
186+
</Row>
187+
</BAICard>
188+
<FolderInvitationResponseModal
189+
open={isInvitationResponseModalOpen}
190+
onRequestClose={toggleInvitationResponseModal}
191+
/>
192+
</>
179193
);
180194
};
181195

react/src/components/VFolderPermissionCell.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ import { VFolderPermissionCellFragment$key } from './__generated__/VFolderPermis
33
import { Typography } from 'antd';
44
import graphql from 'babel-plugin-relay/macro';
55
import _ from 'lodash';
6-
import React from 'react';
6+
import React, { useMemo } from 'react';
77
import { useTranslation } from 'react-i18next';
88
import { useFragment } from 'react-relay';
99

1010
interface VFolderPermissionCellProps {
11-
vfolderFrgmt: VFolderPermissionCellFragment$key;
11+
vfolderFrgmt?: VFolderPermissionCellFragment$key;
12+
permission?: string;
1213
}
1314

1415
const VFolderPermissionCell: React.FC<VFolderPermissionCellProps> = ({
1516
vfolderFrgmt,
17+
permission: permissionProp,
1618
...props
1719
}) => {
1820
const { t } = useTranslation();
@@ -33,19 +35,24 @@ const VFolderPermissionCell: React.FC<VFolderPermissionCellProps> = ({
3335
},
3436
};
3537

36-
const vfolder = useFragment(
38+
const vfolderData = useFragment(
3739
graphql`
3840
fragment VFolderPermissionCellFragment on VirtualFolderNode {
3941
permissions
4042
}
4143
`,
42-
vfolderFrgmt,
44+
vfolderFrgmt ?? null,
4345
);
4446

45-
const permission = _.includes(vfolder?.permissions, 'mount_rw') ? 'rw' : 'ro';
47+
const permission = useMemo(() => {
48+
if (vfolderData?.permissions) {
49+
return _.includes(vfolderData.permissions, 'mount_rw') ? 'rw' : 'ro';
50+
}
51+
return permissionProp === 'wd' ? 'rw' : permissionProp || 'ro';
52+
}, [vfolderData?.permissions, permissionProp]);
4653

4754
return (
48-
<Flex gap={'xs'}>
55+
<Flex gap={'xs'} {...props}>
4956
<Typography.Text>{permissionMap[permission]?.label}</Typography.Text>
5057
<Flex>
5158
{_.map(permissionMap[permission]?.icon, (tag) => (

react/src/hooks/backendai.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ export const useVFolderInvitations = () => {
329329

330330
return [
331331
{
332-
...vfolderInvitations?.invitations,
332+
invitations: vfolderInvitations?.invitations,
333333
count: vfolderInvitations?.invitations?.length,
334334
isPendingMutation:
335335
mutationToAcceptInvitation.isPending ||

resources/i18n/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@
231231
"FolderToCopy": "Ordner zum Kopieren",
232232
"Foldername": "Ordnernamen",
233233
"Folders": "Ordner",
234+
"From": "Aus",
234235
"General": "Allgemein",
235236
"Host": "Gastgeber",
236237
"HostDetails": "Zeigt Kontingentinformationen für den ausgewählten Speicherhost an.",

resources/i18n/el.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@
230230
"FolderToCopy": "Φάκελος για αντιγραφή",
231231
"Foldername": "Ονομα φακέλου",
232232
"Folders": "Φάκελοι",
233+
"From": "Από",
233234
"General": "Γενικός",
234235
"Host": "Πλήθος",
235236
"HostDetails": "Εμφανίζει πληροφορίες ποσόστωσης για τον επιλεγμένο κεντρικό υπολογιστή αποθήκευσης.",

resources/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@
233233
"FolderToCopy": "Folder to copy",
234234
"Foldername": "Folder name",
235235
"Folders": "Folders",
236+
"From": "From",
236237
"General": "General",
237238
"Host": "Host",
238239
"HostDetails": "Displays quota information for the selected storage host.",

resources/i18n/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@
231231
"FolderToCopy": "Carpeta a copiar",
232232
"Foldername": "Nombre de la carpeta",
233233
"Folders": "Carpetas",
234+
"From": "De",
234235
"General": "General",
235236
"Host": "Anfitrión",
236237
"HostDetails": "Muestra información sobre las cuotas del host de almacenamiento seleccionado.",

0 commit comments

Comments
 (0)