Skip to content

Commit 540b74c

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

25 files changed

Lines changed: 245 additions & 91 deletions
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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, { useCallback } 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+
const renderInvitationItem = useCallback(
38+
(item: InvitationItem) => (
39+
<List.Item
40+
actions={[
41+
<Button
42+
type="primary"
43+
onClick={() => acceptInvitation(item.id)}
44+
key="accept"
45+
>
46+
{t('summary.Accept')}
47+
</Button>,
48+
<Button
49+
danger
50+
onClick={() => declineInvitation(item.id)}
51+
key="decline"
52+
>
53+
{t('summary.Decline')}
54+
</Button>,
55+
]}
56+
style={{
57+
padding: token.paddingSM,
58+
}}
59+
>
60+
<List.Item.Meta
61+
title={
62+
<Flex gap={'xxs'}>
63+
<FolderOutlined />
64+
<Typography.Text strong>{item.vfolder_name}</Typography.Text>
65+
</Flex>
66+
}
67+
description={
68+
<Descriptions
69+
size="small"
70+
column={1}
71+
items={[
72+
{
73+
key: 'from',
74+
label: t('data.From'),
75+
children: item.inviter_user_email,
76+
},
77+
{
78+
key: 'permission',
79+
label: t('data.Permission'),
80+
children: <VFolderPermissionCell permission={item.perm} />,
81+
},
82+
]}
83+
/>
84+
}
85+
/>
86+
</List.Item>
87+
),
88+
// eslint-disable-next-line react-hooks/exhaustive-deps
89+
[acceptInvitation, declineInvitation],
90+
);
91+
92+
return (
93+
<BAIModal
94+
onCancel={onRequestClose}
95+
title={t('data.InvitedFolders')}
96+
footer={null}
97+
{...baiModalProps}
98+
>
99+
<List dataSource={invitations} renderItem={renderInvitationItem} />
100+
</BAIModal>
101+
);
102+
};
103+
104+
export default FolderInvitationResponseModal;

react/src/components/StorageStatusPanelCard.tsx

Lines changed: 98 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,100 @@ 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+
// Add a tag to the Tooltip to make it clickable
147+
// eslint-disable-next-line
148+
<a
149+
onClick={() => {
150+
toggleInvitationResponseModal();
151+
}}
152152
>
153-
<Badge
154-
count={count > 0 ? `+${count}` : null}
155-
offset={[0, -`${token.sizeXS}`]}
153+
<Tooltip
154+
title={
155+
count > 0
156+
? t('data.InvitedFoldersTooltip', {
157+
count: count,
158+
})
159+
: null
160+
}
161+
rootClassName={styles.invitationTooltip}
162+
placement="topRight"
156163
>
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>
164+
<Badge
165+
count={count > 0 ? `+${count}` : null}
166+
offset={[0, -`${token.sizeXS}`]}
167+
>
168+
<Typography.Title level={5} style={{ margin: 0 }}>
169+
{t('data.InvitedFolders')}
170+
</Typography.Title>
171+
</Badge>
172+
</Tooltip>
173+
</a>
174+
}
175+
value={
176+
<Typography.Text
177+
strong
178+
style={{
179+
fontSize: token.fontSizeHeading1,
180+
color: token.Layout?.headerBg,
181+
}}
182+
>
183+
{invitedCount}
184+
</Typography.Text>
185+
}
186+
/>
187+
</Col>
188+
</Row>
189+
</BAICard>
190+
<FolderInvitationResponseModal
191+
open={isInvitationResponseModalOpen}
192+
onRequestClose={toggleInvitationResponseModal}
193+
/>
194+
</>
179195
);
180196
};
181197

react/src/components/VFolderPermissionCell.tsx

Lines changed: 21 additions & 8 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,22 +35,33 @@ 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 { permissionInfo } = useMemo(() => {
48+
const perm = vfolderData?.permissions
49+
? _.includes(vfolderData.permissions, 'mount_rw')
50+
? 'rw'
51+
: 'ro'
52+
: permissionProp === 'wd'
53+
? 'rw'
54+
: permissionProp || 'ro';
55+
return {
56+
permissionInfo: permissionMap[perm],
57+
};
58+
}, [vfolderData?.permissions, permissionProp]);
4659

4760
return (
48-
<Flex gap={'xs'}>
49-
<Typography.Text>{permissionMap[permission]?.label}</Typography.Text>
61+
<Flex gap={'xs'} {...props}>
62+
<Typography.Text>{permissionInfo?.label}</Typography.Text>
5063
<Flex>
51-
{_.map(permissionMap[permission]?.icon, (tag) => (
64+
{_.map(permissionInfo?.icon, (tag) => (
5265
<Typography.Text key={tag} code>
5366
{_.toUpper(tag)}
5467
</Typography.Text>

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": "Εμφανίζει πληροφορίες ποσόστωσης για τον επιλεγμένο κεντρικό υπολογιστή αποθήκευσης.",

0 commit comments

Comments
 (0)