Skip to content

Commit 6790afa

Browse files
committed
feat(FR-863): Add a modal to manage invited folder
1 parent 55904d9 commit 6790afa

25 files changed

Lines changed: 403 additions & 31 deletions
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { filterNonNullItems } from '../helper';
2+
import { useSuspendedBackendaiClient } from '../hooks';
3+
import { useCurrentUserInfo } from '../hooks/backendai';
4+
import { useTanMutation } from '../hooks/reactQueryAlias';
5+
import { usePainKiller } from '../hooks/usePainKiller';
6+
import UserUnionIcon from './BAIIcons/UserUnionIcon';
7+
import BAIModal, { BAIModalProps } from './BAIModal';
8+
import BAITable from './BAITable';
9+
import Flex from './Flex';
10+
import VFolderPermissionCell from './VFolderPermissionCell';
11+
import {
12+
SharedFolderPermissionInfoModalFragment$data,
13+
SharedFolderPermissionInfoModalFragment$key,
14+
} from './__generated__/SharedFolderPermissionInfoModalFragment.graphql';
15+
import { UserOutlined } from '@ant-design/icons';
16+
import {
17+
Alert,
18+
App,
19+
Button,
20+
Descriptions,
21+
Popconfirm,
22+
Tooltip,
23+
Typography,
24+
theme,
25+
} from 'antd';
26+
import graphql from 'babel-plugin-relay/macro';
27+
import { LogOut } from 'lucide-react';
28+
import { useTranslation } from 'react-i18next';
29+
import { useFragment } from 'react-relay';
30+
31+
interface SharedFolderPermissionInfoModalProps extends BAIModalProps {
32+
vfolderFrgmt: SharedFolderPermissionInfoModalFragment$key | null;
33+
onRequestClose: (success?: boolean) => void;
34+
}
35+
36+
type VFolder = NonNullable<SharedFolderPermissionInfoModalFragment$data>;
37+
38+
const SharedFolderPermissionInfoModal: React.FC<
39+
SharedFolderPermissionInfoModalProps
40+
> = ({ vfolderFrgmt, onRequestClose, ...modalProps }) => {
41+
const { t } = useTranslation();
42+
const { token } = theme.useToken();
43+
const { message } = App.useApp();
44+
const [currentUser] = useCurrentUserInfo();
45+
const baiClient = useSuspendedBackendaiClient();
46+
const painKiller = usePainKiller();
47+
48+
const vfolder = useFragment(
49+
graphql`
50+
fragment SharedFolderPermissionInfoModalFragment on VirtualFolderNode {
51+
id
52+
name
53+
creator
54+
ownership_type
55+
user_email
56+
permission
57+
58+
...VFolderPermissionCellFragment
59+
}
60+
`,
61+
vfolderFrgmt,
62+
);
63+
64+
const leaveFolder = useTanMutation({
65+
mutationFn: ({ folderName }: { folderName: string }) => {
66+
return baiClient.vfolder.leave_invited(folderName);
67+
},
68+
});
69+
70+
return (
71+
<BAIModal
72+
title={t('data.SharedFolderPermission')}
73+
onCancel={() => onRequestClose()}
74+
footer={null}
75+
{...modalProps}
76+
>
77+
<Flex direction="column" align="stretch" gap="lg">
78+
<Alert
79+
showIcon
80+
type="info"
81+
message={
82+
vfolder?.ownership_type === 'user'
83+
? t('data.folders.SharedFolderAlertDesc')
84+
: t('data.folders.ProjectFolderAlertDesc')
85+
}
86+
/>
87+
<Descriptions column={2} bordered title={t('data.FolderInfo')}>
88+
<Descriptions.Item label="Name">
89+
<Typography.Text copyable>{vfolder?.name}</Typography.Text>
90+
</Descriptions.Item>
91+
<Descriptions.Item label="Type">
92+
{vfolder?.ownership_type === 'user' ? (
93+
<Flex gap={'xs'}>
94+
<Typography.Text>{t('data.User')}</Typography.Text>
95+
<UserOutlined style={{ color: token.colorTextTertiary }} />
96+
</Flex>
97+
) : (
98+
<Flex gap={'xs'}>
99+
<Typography.Text>{t('data.Project')}</Typography.Text>
100+
<UserUnionIcon style={{ color: token.colorTextTertiary }} />
101+
</Flex>
102+
)}
103+
</Descriptions.Item>
104+
<Descriptions.Item label="Owner">
105+
{vfolder?.creator || vfolder?.user_email}
106+
</Descriptions.Item>
107+
</Descriptions>
108+
109+
{vfolder?.ownership_type === 'user' ? (
110+
<Flex direction="column" align="stretch">
111+
<Typography.Title
112+
level={5}
113+
style={{ marginTop: 0, marginBottom: token.marginMD }}
114+
>
115+
{t('data.folders.Permission')}
116+
</Typography.Title>
117+
<BAITable<VFolder>
118+
bordered
119+
pagination={false}
120+
dataSource={filterNonNullItems([vfolder])}
121+
columns={[
122+
{
123+
key: 'userName',
124+
title: t('general.E-Mail'),
125+
render: () => currentUser.email,
126+
},
127+
{
128+
key: 'permissions',
129+
title: t('data.folders.MountPermission'),
130+
render: (perm: string, vfolder) => {
131+
return <VFolderPermissionCell vfolderFrgmt={vfolder} />;
132+
},
133+
},
134+
{
135+
key: 'control',
136+
title: t('data.folders.Control'),
137+
render: (data) => (
138+
<Flex align="stretch" justify="center">
139+
<Popconfirm
140+
title={t('data.invitation.LeaveSharedFolderDesc', {
141+
folderName: data?.name,
142+
})}
143+
onConfirm={() => {
144+
leaveFolder.mutate(
145+
{
146+
folderName: data?.name,
147+
},
148+
{
149+
onSuccess: () => {
150+
message.success(
151+
t(
152+
'data.invitation.SuccessfullyLeftSharedFolder',
153+
),
154+
);
155+
onRequestClose(true);
156+
},
157+
onError: (err) => {
158+
message.error(
159+
painKiller.relieve(err?.message) ||
160+
t('general.ErrorOccurred'),
161+
);
162+
onRequestClose();
163+
},
164+
},
165+
);
166+
}}
167+
>
168+
<Tooltip
169+
title={t('data.invitation.LeaveSharedFolder')}
170+
placement="right"
171+
>
172+
<Button
173+
size="small"
174+
type="text"
175+
icon={<LogOut />}
176+
style={{
177+
color: token.colorError,
178+
background: token.colorErrorBg,
179+
}}
180+
/>
181+
</Tooltip>
182+
</Popconfirm>
183+
</Flex>
184+
),
185+
},
186+
]}
187+
/>
188+
</Flex>
189+
) : null}
190+
</Flex>
191+
</BAIModal>
192+
);
193+
};
194+
195+
export default SharedFolderPermissionInfoModal;

react/src/components/VFolderNodes.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import EditableVFolderName from './EditableVFolderName';
1818
import Flex from './Flex';
1919
import { useFolderExplorerOpener } from './FolderExplorerOpener';
2020
import InviteFolderSettingModal from './InviteFolderSettingModal';
21+
import SharedFolderPermissionInfoModal from './SharedFolderPermissionInfoModal';
2122
import VFolderNodeIdenticon from './VFolderNodeIdenticon';
2223
import VFolderPermissionCell from './VFolderPermissionCell';
2324
import {
@@ -84,6 +85,8 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
8485

8586
const [currentVFolder, setCurrentVFolder] =
8687
useState<VFolderNodeInList | null>(null);
88+
const [currentSharedVFolder, setCurrentSharedVFolder] =
89+
useState<VFolderNodeInList | null>(null);
8790

8891
const vfolders = useFragment(
8992
graphql`
@@ -99,6 +102,7 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
99102
...VFolderPermissionCellFragment
100103
...EditableVFolderNameFragment
101104
...VFolderNodeIdenticonFragment
105+
...SharedFolderPermissionInfoModalFragment
102106
}
103107
`,
104108
vfoldersFrgmt,
@@ -220,7 +224,9 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
220224
background: token.colorInfoBg,
221225
}}
222226
onClick={() => {
223-
setInviteFolderId(toLocalId(vfolder?.id ?? null));
227+
vfolder?.user === currentUser?.uuid
228+
? setInviteFolderId(toLocalId(vfolder?.id ?? null))
229+
: setCurrentSharedVFolder(vfolder);
224230
}}
225231
/>
226232
</Tooltip>
@@ -470,6 +476,16 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
470476
vfolderId={inviteFolderId}
471477
open={!!inviteFolderId}
472478
/>
479+
<SharedFolderPermissionInfoModal
480+
vfolderFrgmt={currentSharedVFolder}
481+
open={!!currentSharedVFolder}
482+
onRequestClose={(success?: boolean) => {
483+
setCurrentSharedVFolder(null);
484+
if (success) {
485+
onRequestChange?.();
486+
}
487+
}}
488+
/>
473489
</>
474490
);
475491
};

react/src/hooks/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export type BackendAIClient = {
138138
user: string;
139139
vfolder: string;
140140
}): Promise<any>;
141+
leave_invited(name: string | null): Promise<any>;
141142
};
142143
supports: (feature: string) => boolean;
143144
[key: string]: any;

react/src/pages/VFolderNodeListPage.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import BAITabs from '../components/BAITabs';
1212
import DeleteVFolderModal from '../components/DeleteVFolderModal';
1313
import Flex from '../components/Flex';
1414
import FolderCreateModal from '../components/FolderCreateModal';
15-
import InviteFolderSettingModal from '../components/InviteFolderSettingModal';
1615
import QuotaPerStorageVolumePanelCard from '../components/QuotaPerStorageVolumePanelCard';
1716
import RestoreVFolderModal from '../components/RestoreVFolderModal';
1817
import StorageStatusPanelCard from '../components/StorageStatusPanelCard';
@@ -99,7 +98,6 @@ const VFolderNodeListPage: React.FC<VFolderNodeListPageProps> = ({
9998
const [selectedFolderList, setSelectedFolderList] = useState<
10099
Array<VFolderNodesType>
101100
>([]);
102-
const [inviteFolderId, setInviteFolderId] = useState<string | null>(null);
103101
const [isOpenCreateModal, { toggle: toggleCreateModal }] = useToggle(false);
104102
const [isOpenDeleteModal, { toggle: toggleDeleteModal }] = useToggle(false);
105103
const [isOpenRestoreModal, { toggle: toggleRestoreModal }] = useToggle(false);
@@ -207,6 +205,7 @@ const VFolderNodeListPage: React.FC<VFolderNodeListPageProps> = ({
207205
...EditableVFolderNameFragment
208206
...RestoreVFolderModalFragment
209207
...VFolderNodeIdenticonFragment
208+
...SharedFolderPermissionInfoModalFragment
210209
}
211210
}
212211
count
@@ -592,13 +591,6 @@ const VFolderNodeListPage: React.FC<VFolderNodeListPageProps> = ({
592591
/>
593592
</Flex>
594593
</BAICard>
595-
<InviteFolderSettingModal
596-
onRequestClose={() => {
597-
setInviteFolderId(null);
598-
}}
599-
vfolderId={inviteFolderId}
600-
open={inviteFolderId !== null}
601-
/>
602594
<FolderCreateModal
603595
open={isOpenCreateModal}
604596
onRequestClose={(success) => {

resources/i18n/de.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
"ExistingFolderName": "Vorhandener Ordnername",
227227
"FileAndFolderNameRequired": "Datei-/Ordnername ist erforderlich",
228228
"FolderAlreadyExists": "Ein Ordner mit diesem Namen gibt es bereits",
229+
"FolderInfo": "Forder Info",
229230
"FolderNameRequired": "Ordnername ist erforderlich",
230231
"FolderNameTooLong": "Geben Sie einen Ordnernamen mit weniger als 64 Zeichen ein",
231232
"FolderToCopy": "Ordner zum Kopieren",
@@ -256,6 +257,7 @@
256257
"ReadWrite": "Lesen und schreiben",
257258
"SearchByName": "Suche mit Name",
258259
"SelectStorageHost": "Speicherhost auswählen",
260+
"SharedFolderPermission": "Erlaubnis für gemeinsame Ordner",
259261
"StorageStatus": "Speicherstatus",
260262
"Type": "Art",
261263
"Update": "Aktualisieren",
@@ -374,6 +376,7 @@
374376
"Owner": "Inhaber",
375377
"Ownership": "Eigentum",
376378
"Permission": "Genehmigung",
379+
"ProjectFolderAlertDesc": "Dieser Ordner wird an alle Mitglieder des Projekts geteilt",
377380
"Rename": "Umbenennen",
378381
"Restore": "Wiederherstellen",
379382
"RestoreDescription": "Möchten Sie \"{{ folderName }}\" -Fordner wiederherstellen?",
@@ -382,6 +385,7 @@
382385
"SelectPermission": "Berechtigung auswählen",
383386
"Serve": "Das Modell dient",
384387
"ShareFolder": "Ordner teilen",
388+
"SharedFolderAlertDesc": "Dies ist ein extern geteilter Ordner.",
385389
"SharedUser": "Freigegebener Benutzer",
386390
"SharedUserDesc": "Nur Benutzer, die die Einladung angenommen haben, werden in der Liste angezeigt.\n\nBenutzer, die noch einladend sind, werden nicht angezeigt.",
387391
"Status": "Status",
@@ -396,11 +400,14 @@
396400
"FolderSharingNotAvailableToUser": "Die gemeinsame Nutzung von Ordnern ist für den/die gewünschten Benutzer nicht verfügbar:",
397401
"InvitationError": "Einladung fehlgeschlagen. Der Benutzer ist möglicherweise bereits eingeladen.",
398402
"Invited": "Erfolgreich eingeladen",
403+
"LeaveSharedFolder": "Lassen Sie den gemeinsam genutzten Ordner",
404+
"LeaveSharedFolderDesc": "Möchten Sie den freigegebenen Ordner \"{{Ordnername}}\" verlassen?",
399405
"NoOneWasInvited": "Niemand eingeladen, da Einladung bereits existiert",
400406
"NoOneWasShared": "Fehler beim Freigeben des Gruppenordners",
401407
"NoValidEmails": "Es wurden keine gültigen E-Mails eingegeben",
402408
"Shared": "Gruppenordner erfolgreich freigegeben",
403-
"SharingError": "Die Freigabe ist fehlgeschlagen. Der Benutzer ist möglicherweise bereits freigegeben oder gehört nicht zu dem entsprechenden Projekt."
409+
"SharingError": "Die Freigabe ist fehlgeschlagen. Der Benutzer ist möglicherweise bereits freigegeben oder gehört nicht zu dem entsprechenden Projekt.",
410+
"SuccessfullyLeftSharedFolder": "Erfolgreich den gemeinsam genutzten Ordner verlassen"
404411
},
405412
"modelStore": {
406413
"AddedItems": "Artikel hinzugefügt",
@@ -603,6 +610,7 @@
603610
"Disabled": "Behinderte",
604611
"E-Mail": "Email",
605612
"Enabled": "aktiviert",
613+
"ErrorOccurred": "Etwas lief schief. \nBitte versuchen Sie es später erneut.",
606614
"ExtendLoginSession": "Eine Anmeldesitzung verlängern",
607615
"Folders": "Ordner",
608616
"General": "Allgemein",

0 commit comments

Comments
 (0)