Skip to content

Commit 2c66ded

Browse files
committed
feat(FR-740): Create modal for accepting/rejecting invitations
1 parent 513539a commit 2c66ded

26 files changed

Lines changed: 432 additions & 119 deletions
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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, App } 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?: (success: boolean) => void;
25+
fetchKey?: string;
26+
}
27+
28+
const FolderInvitationResponseModal: React.FC<
29+
FolderInvitationResponseModalProps
30+
> = ({ onRequestClose, fetchKey, ...baiModalProps }) => {
31+
const { token } = theme.useToken();
32+
const { message } = App.useApp();
33+
const { t } = useTranslation();
34+
const [
35+
{ invitations },
36+
{ acceptInvitation, rejectInvitation: declineInvitation },
37+
] = useVFolderInvitations(fetchKey);
38+
39+
const renderInvitationItem = useCallback(
40+
(item: InvitationItem) => (
41+
<List.Item
42+
actions={[
43+
<Button
44+
type="primary"
45+
onClick={() =>
46+
acceptInvitation(item.id, {
47+
onSuccess: () => {
48+
onRequestClose?.(true);
49+
message.success(
50+
t('data.invitation.SuccessfullyAcceptedInvitation'),
51+
);
52+
},
53+
onError: (e) => {
54+
onRequestClose?.(false);
55+
message.error(
56+
e.message || t('data.invitation.FailedToAcceptInvitation'),
57+
);
58+
},
59+
})
60+
}
61+
key="accept"
62+
>
63+
{t('summary.Accept')}
64+
</Button>,
65+
<Button
66+
danger
67+
onClick={() =>
68+
declineInvitation(item.id, {
69+
onSuccess: () => {
70+
onRequestClose?.(true);
71+
message.success(
72+
t('data.invitation.SuccessfullyDeclinedInvitation'),
73+
);
74+
},
75+
onError: (e) => {
76+
onRequestClose?.(false);
77+
message.error(
78+
e.message || t('data.invitation.FailedToDeclineInvitation'),
79+
);
80+
},
81+
})
82+
}
83+
key="decline"
84+
>
85+
{t('summary.Decline')}
86+
</Button>,
87+
]}
88+
style={{
89+
padding: token.paddingSM,
90+
}}
91+
>
92+
<List.Item.Meta
93+
title={
94+
<Flex gap={'xxs'}>
95+
<FolderOutlined />
96+
<Typography.Text strong>{item.vfolder_name}</Typography.Text>
97+
</Flex>
98+
}
99+
description={
100+
<Descriptions
101+
size="small"
102+
column={1}
103+
items={[
104+
{
105+
key: 'from',
106+
label: t('data.From'),
107+
children: item.inviter_user_email,
108+
},
109+
{
110+
key: 'permission',
111+
label: t('data.Permission'),
112+
children: <VFolderPermissionCell permission={item.perm} />,
113+
},
114+
]}
115+
/>
116+
}
117+
/>
118+
</List.Item>
119+
),
120+
// eslint-disable-next-line react-hooks/exhaustive-deps
121+
[acceptInvitation, declineInvitation, onRequestClose],
122+
);
123+
124+
return (
125+
<BAIModal
126+
onCancel={() => onRequestClose?.(false)}
127+
title={t('data.InvitedFolders')}
128+
footer={null}
129+
{...baiModalProps}
130+
>
131+
<List
132+
dataSource={invitations}
133+
renderItem={(item) =>
134+
renderInvitationItem({
135+
...item,
136+
})
137+
}
138+
/>
139+
</BAIModal>
140+
);
141+
};
142+
143+
export default FolderInvitationResponseModal;

react/src/components/StorageStatusPanelCard.tsx

Lines changed: 98 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ const useStyles = createStyles(({ css, token }) => ({
2828

2929
interface StorageStatusPanelProps extends BAICardProps {
3030
fetchKey?: string;
31+
onRequestBadgeClick?: () => void;
3132
}
3233

3334
const StorageStatusPanelCard: React.FC<StorageStatusPanelProps> = ({
3435
fetchKey,
36+
onRequestBadgeClick,
3537
...cardProps
3638
}) => {
3739
const { t } = useTranslation();
@@ -40,7 +42,7 @@ const StorageStatusPanelCard: React.FC<StorageStatusPanelProps> = ({
4042
const baiClient = useSuspendedBackendaiClient();
4143
const currentProject = useCurrentProjectValue();
4244
const deferredFetchKey = useDeferredValue(fetchKey);
43-
const [{ count }] = useVFolderInvitations();
45+
const [{ count }] = useVFolderInvitations(deferredFetchKey);
4446

4547
const isExcludedCount = (status: string) => {
4648
return _.includes(
@@ -50,7 +52,7 @@ const StorageStatusPanelCard: React.FC<StorageStatusPanelProps> = ({
5052
};
5153

5254
const { data: vfolders } = useSuspenseTanQuery({
53-
queryKey: ['vfolders', { deferredFetchKey }],
55+
queryKey: ['vfolders', { deferredFetchKey, id: currentProject?.id }],
5456
queryFn: () => {
5557
return baiClient.vfolder.list(currentProject?.id);
5658
},
@@ -92,96 +94,102 @@ const StorageStatusPanelCard: React.FC<StorageStatusPanelProps> = ({
9294
);
9395

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

0 commit comments

Comments
 (0)