cozy-sharing 28.3.4 → 28.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/components/FederatedFolder/DumbFederatedFolderModal.js +56 -0
- package/dist/components/FederatedFolder/FederatedFolderModal.js +150 -0
- package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +543 -0
- package/dist/components/Recipient/LinkRecipient.js +1 -1
- package/dist/components/ShareAutosuggest.js +4 -1
- package/dist/components/ShareByEmail.js +105 -33
- package/dist/components/ShareByEmail.spec.js +111 -9
- package/dist/components/ShareByLink.js +3 -3
- package/dist/components/ShareModal/ShareModal.js +18 -2
- package/dist/components/ShareModal.js +1 -1
- package/dist/components/ShareRecipientsInput.js +5 -2
- package/dist/components/SharedDrive/DumbSharedDriveModal.js +39 -72
- package/dist/components/SharedDrive/SharedDriveModal.js +11 -5
- package/dist/components/SharedDrive/helpers.js +28 -2
- package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +159 -0
- package/dist/components/WhoHasAccess.js +1 -1
- package/dist/index.js +1 -0
- package/locales/en.json +13 -1
- package/locales/fr.json +15 -3
- package/locales/ru.json +13 -1
- package/locales/vi.json +13 -1
- package/package.json +3 -3
- package/src/components/FederatedFolder/DumbFederatedFolderModal.jsx +62 -0
- package/src/components/FederatedFolder/FederatedFolderModal.jsx +119 -0
- package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +376 -0
- package/src/components/Recipient/LinkRecipient.jsx +1 -1
- package/src/components/ShareAutosuggest.jsx +4 -1
- package/src/components/ShareByEmail.jsx +47 -8
- package/src/components/ShareByEmail.spec.jsx +96 -6
- package/src/components/ShareByLink.jsx +7 -3
- package/src/components/ShareModal/ShareModal.jsx +16 -1
- package/src/components/ShareModal.jsx +1 -1
- package/src/components/ShareRecipientsInput.jsx +5 -2
- package/src/components/SharedDrive/DumbSharedDriveModal.jsx +38 -88
- package/src/components/SharedDrive/SharedDriveModal.jsx +14 -4
- package/src/components/SharedDrive/helpers.js +33 -2
- package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +174 -0
- package/src/components/WhoHasAccess.jsx +1 -1
- package/src/index.jsx +1 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
import { useI18n } from 'twake-i18n'
|
|
4
|
+
|
|
5
|
+
import { useClient } from 'cozy-client'
|
|
6
|
+
import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
|
|
7
|
+
|
|
8
|
+
import { DumbFederatedFolderModal } from './DumbFederatedFolderModal'
|
|
9
|
+
import withLocales from '../../hoc/withLocales'
|
|
10
|
+
import { useSharingContext } from '../../hooks/useSharingContext'
|
|
11
|
+
import {
|
|
12
|
+
formatRecipients,
|
|
13
|
+
moveRecipientToReadWrite,
|
|
14
|
+
moveRecipientToReadOnly,
|
|
15
|
+
RECIPIENT_INDEX_PREFIX
|
|
16
|
+
} from '../SharedDrive/helpers'
|
|
17
|
+
|
|
18
|
+
export const FederatedFolderModal = withLocales(
|
|
19
|
+
({ onClose, document: existingDocument }) => {
|
|
20
|
+
const client = useClient()
|
|
21
|
+
const { t } = useI18n()
|
|
22
|
+
const { share, getSharingLink } = useSharingContext()
|
|
23
|
+
const { showAlert } = useAlert()
|
|
24
|
+
|
|
25
|
+
// Get sharing link
|
|
26
|
+
const sharingLink = existingDocument
|
|
27
|
+
? getSharingLink(existingDocument._id)
|
|
28
|
+
: null
|
|
29
|
+
|
|
30
|
+
const [federatedRecipients, setFederatedRecipients] = useState({
|
|
31
|
+
recipients: [],
|
|
32
|
+
readOnlyRecipients: []
|
|
33
|
+
})
|
|
34
|
+
const [folderName] = useState(existingDocument?.name || '')
|
|
35
|
+
|
|
36
|
+
const onShare = params => {
|
|
37
|
+
setFederatedRecipients({
|
|
38
|
+
recipients: params.recipients || [],
|
|
39
|
+
readOnlyRecipients: params.readOnlyRecipients || []
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const onSend = async () => {
|
|
44
|
+
try {
|
|
45
|
+
await share({
|
|
46
|
+
description: folderName,
|
|
47
|
+
document: existingDocument,
|
|
48
|
+
recipients: federatedRecipients.recipients,
|
|
49
|
+
readOnlyRecipients: federatedRecipients.readOnlyRecipients,
|
|
50
|
+
sharedDrive: true,
|
|
51
|
+
openSharing: false
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
showAlert({
|
|
55
|
+
message: t('FederatedFolder.successNotification'),
|
|
56
|
+
severity: 'success',
|
|
57
|
+
variant: 'filled'
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
onClose()
|
|
61
|
+
} catch (err) {
|
|
62
|
+
showAlert({
|
|
63
|
+
message: t('FederatedFolder.errorNotification'),
|
|
64
|
+
severity: 'error',
|
|
65
|
+
variant: 'filled'
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const onSetType = (index, newType) => {
|
|
71
|
+
const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
|
|
72
|
+
|
|
73
|
+
if (newType === 'two-way') {
|
|
74
|
+
setFederatedRecipients(prev => moveRecipientToReadWrite(prev, _id))
|
|
75
|
+
} else {
|
|
76
|
+
setFederatedRecipients(prev => moveRecipientToReadOnly(prev, _id))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const onRevoke = index => {
|
|
81
|
+
const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
|
|
82
|
+
|
|
83
|
+
setFederatedRecipients(prev => {
|
|
84
|
+
return {
|
|
85
|
+
recipients: prev.recipients.filter(r => r._id !== _id),
|
|
86
|
+
readOnlyRecipients: prev.readOnlyRecipients.filter(r => r._id !== _id)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const recipients = formatRecipients(federatedRecipients)
|
|
92
|
+
|
|
93
|
+
const modalTitle = t('FederatedFolder.shareTitle', { name: folderName })
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<DumbFederatedFolderModal
|
|
97
|
+
title={modalTitle}
|
|
98
|
+
document={existingDocument}
|
|
99
|
+
createContact={contact => client.create('io.cozy.contacts', contact)}
|
|
100
|
+
recipients={recipients}
|
|
101
|
+
readOnlyRecipients={federatedRecipients.readOnlyRecipients}
|
|
102
|
+
currentRecipients={[]}
|
|
103
|
+
onRevoke={onRevoke}
|
|
104
|
+
onSetType={onSetType}
|
|
105
|
+
onSend={onSend}
|
|
106
|
+
onClose={onClose}
|
|
107
|
+
onShare={onShare}
|
|
108
|
+
sharingLink={sharingLink}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
FederatedFolderModal.propTypes = {
|
|
115
|
+
onClose: PropTypes.func.isRequired,
|
|
116
|
+
document: PropTypes.object.isRequired
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default FederatedFolderModal
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { fireEvent, render, waitFor } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { createMockClient } from 'cozy-client'
|
|
5
|
+
|
|
6
|
+
import { FederatedFolderModal } from './FederatedFolderModal'
|
|
7
|
+
import AppLike from '../SharingBanner/test/AppLike'
|
|
8
|
+
|
|
9
|
+
const mockShare = jest.fn()
|
|
10
|
+
const mockGetSharingLink = jest.fn()
|
|
11
|
+
const mockShowAlert = jest.fn()
|
|
12
|
+
const mockOnClose = jest.fn()
|
|
13
|
+
|
|
14
|
+
jest.mock('../../hooks/useSharingContext', () => ({
|
|
15
|
+
useSharingContext: () => ({
|
|
16
|
+
share: mockShare,
|
|
17
|
+
getSharingLink: mockGetSharingLink,
|
|
18
|
+
getDocumentPermissions: jest.fn().mockReturnValue([])
|
|
19
|
+
})
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
|
|
23
|
+
useAlert: () => ({
|
|
24
|
+
showAlert: mockShowAlert
|
|
25
|
+
})
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
jest.mock('../../hoc/withLocales', () => Component => Component)
|
|
29
|
+
|
|
30
|
+
jest.mock('./DumbFederatedFolderModal', () => ({
|
|
31
|
+
DumbFederatedFolderModal: ({
|
|
32
|
+
title,
|
|
33
|
+
createContact,
|
|
34
|
+
recipients,
|
|
35
|
+
readOnlyRecipients,
|
|
36
|
+
onRevoke,
|
|
37
|
+
onSetType,
|
|
38
|
+
onSend,
|
|
39
|
+
onClose,
|
|
40
|
+
onShare,
|
|
41
|
+
sharingLink
|
|
42
|
+
}) => (
|
|
43
|
+
<div data-testid="dumb-modal">
|
|
44
|
+
<span data-testid="title">{title}</span>
|
|
45
|
+
<span data-testid="sharing-link">{sharingLink}</span>
|
|
46
|
+
<span data-testid="recipients-count">{recipients.length}</span>
|
|
47
|
+
<span data-testid="readOnly-recipients-count">
|
|
48
|
+
{readOnlyRecipients ? readOnlyRecipients.length : 0}
|
|
49
|
+
</span>
|
|
50
|
+
<button
|
|
51
|
+
data-testid="btn-share"
|
|
52
|
+
onClick={() =>
|
|
53
|
+
onShare({
|
|
54
|
+
recipients: [{ _id: 'r1', displayName: 'Alice' }],
|
|
55
|
+
readOnlyRecipients: [{ _id: 'r2', displayName: 'Bob' }]
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
Share
|
|
60
|
+
</button>
|
|
61
|
+
<button data-testid="btn-send" onClick={onSend}>
|
|
62
|
+
Send
|
|
63
|
+
</button>
|
|
64
|
+
<button
|
|
65
|
+
data-testid="btn-set-type-two-way"
|
|
66
|
+
onClick={() => onSetType('virtual-shared-drive-sharing-r2', 'two-way')}
|
|
67
|
+
>
|
|
68
|
+
Set Two-Way
|
|
69
|
+
</button>
|
|
70
|
+
<button
|
|
71
|
+
data-testid="btn-set-type-one-way"
|
|
72
|
+
onClick={() => onSetType('virtual-shared-drive-sharing-r1', 'one-way')}
|
|
73
|
+
>
|
|
74
|
+
Set One-Way
|
|
75
|
+
</button>
|
|
76
|
+
<button
|
|
77
|
+
data-testid="btn-revoke"
|
|
78
|
+
onClick={() => onRevoke('virtual-shared-drive-sharing-r1')}
|
|
79
|
+
>
|
|
80
|
+
Revoke
|
|
81
|
+
</button>
|
|
82
|
+
<button data-testid="btn-close" onClick={onClose}>
|
|
83
|
+
Close
|
|
84
|
+
</button>
|
|
85
|
+
<button
|
|
86
|
+
data-testid="btn-create-contact"
|
|
87
|
+
onClick={() => createContact({ email: 'test@example.com' })}
|
|
88
|
+
>
|
|
89
|
+
Create Contact
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}))
|
|
94
|
+
|
|
95
|
+
const mockDocument = {
|
|
96
|
+
_id: 'folder-123',
|
|
97
|
+
name: 'My Test Folder',
|
|
98
|
+
path: '/test/folder'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const createSharingContextValue = () => ({
|
|
102
|
+
refresh: jest.fn(),
|
|
103
|
+
hasWriteAccess: jest.fn(),
|
|
104
|
+
getRecipients: jest.fn(),
|
|
105
|
+
getSharingLink: mockGetSharingLink,
|
|
106
|
+
getDocumentPermissions: jest.fn().mockReturnValue([]),
|
|
107
|
+
share: mockShare
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const createTestClient = () => {
|
|
111
|
+
const client = createMockClient({
|
|
112
|
+
queries: {
|
|
113
|
+
'io.cozy.contacts/reachable': {
|
|
114
|
+
doctype: 'io.cozy.contacts',
|
|
115
|
+
data: []
|
|
116
|
+
},
|
|
117
|
+
'io.cozy.contacts/groups': {
|
|
118
|
+
doctype: 'io.cozy.contacts.groups',
|
|
119
|
+
data: []
|
|
120
|
+
},
|
|
121
|
+
'io.cozy.contacts/unreachable-with-groups': {
|
|
122
|
+
doctype: 'io.cozy.contacts',
|
|
123
|
+
data: []
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
client.create = jest.fn().mockResolvedValue({ data: {} })
|
|
128
|
+
return client
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
describe('FederatedFolderModal', () => {
|
|
132
|
+
let client
|
|
133
|
+
let sharingContextValue
|
|
134
|
+
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
jest.clearAllMocks()
|
|
137
|
+
mockGetSharingLink.mockReturnValue('https://example.com/share/abc123')
|
|
138
|
+
client = createTestClient()
|
|
139
|
+
sharingContextValue = createSharingContextValue()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const setup = (props = {}) => {
|
|
143
|
+
return render(
|
|
144
|
+
<AppLike client={client} sharingContextValue={sharingContextValue}>
|
|
145
|
+
<FederatedFolderModal
|
|
146
|
+
document={mockDocument}
|
|
147
|
+
onClose={mockOnClose}
|
|
148
|
+
{...props}
|
|
149
|
+
/>
|
|
150
|
+
</AppLike>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
describe('initialization', () => {
|
|
155
|
+
it('should pass document name as title', async () => {
|
|
156
|
+
const { getByTestId } = setup()
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(getByTestId('title').textContent).toBe('Share "My Test Folder"')
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should pass default title when document has no name', async () => {
|
|
164
|
+
const documentWithoutName = { _id: 'folder-456', path: '/test' }
|
|
165
|
+
const { getByTestId } = setup({ document: documentWithoutName })
|
|
166
|
+
|
|
167
|
+
await waitFor(() => {
|
|
168
|
+
expect(getByTestId('title').textContent).toBe('Share ""')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should pass sharing link to DumbFederatedFolderModal', async () => {
|
|
173
|
+
const { getByTestId } = setup()
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
expect(getByTestId('sharing-link').textContent).toBe(
|
|
177
|
+
'https://example.com/share/abc123'
|
|
178
|
+
)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should pass null sharing link when document is undefined', async () => {
|
|
183
|
+
const { getByTestId } = setup({ document: undefined })
|
|
184
|
+
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect(getByTestId('sharing-link').textContent).toBe('')
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe('onShare callback', () => {
|
|
192
|
+
it('should update recipients when onShare is called', async () => {
|
|
193
|
+
const { getByTestId } = setup()
|
|
194
|
+
|
|
195
|
+
await waitFor(() => {
|
|
196
|
+
expect(getByTestId('recipients-count').textContent).toBe('0')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
fireEvent.click(getByTestId('btn-share'))
|
|
200
|
+
|
|
201
|
+
await waitFor(() => {
|
|
202
|
+
expect(getByTestId('recipients-count').textContent).toBe('2')
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('onSend callback', () => {
|
|
208
|
+
it('should call share with correct parameters', async () => {
|
|
209
|
+
mockShare.mockResolvedValueOnce({})
|
|
210
|
+
const { getByTestId } = setup()
|
|
211
|
+
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(getByTestId('btn-send')).toBeTruthy()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
fireEvent.click(getByTestId('btn-share'))
|
|
217
|
+
fireEvent.click(getByTestId('btn-send'))
|
|
218
|
+
|
|
219
|
+
await waitFor(() => {
|
|
220
|
+
expect(mockShare).toHaveBeenCalledWith({
|
|
221
|
+
description: 'My Test Folder',
|
|
222
|
+
document: mockDocument,
|
|
223
|
+
recipients: [{ _id: 'r1', displayName: 'Alice' }],
|
|
224
|
+
readOnlyRecipients: [{ _id: 'r2', displayName: 'Bob' }],
|
|
225
|
+
sharedDrive: true,
|
|
226
|
+
openSharing: false
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should show success alert and close modal on successful share', async () => {
|
|
232
|
+
mockShare.mockResolvedValueOnce({})
|
|
233
|
+
const { getByTestId } = setup()
|
|
234
|
+
|
|
235
|
+
await waitFor(() => {
|
|
236
|
+
expect(getByTestId('btn-send')).toBeTruthy()
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
fireEvent.click(getByTestId('btn-send'))
|
|
240
|
+
|
|
241
|
+
await waitFor(() => {
|
|
242
|
+
expect(mockShowAlert).toHaveBeenCalledWith({
|
|
243
|
+
message: 'Folder has been shared',
|
|
244
|
+
severity: 'success',
|
|
245
|
+
variant: 'filled'
|
|
246
|
+
})
|
|
247
|
+
expect(mockOnClose).toHaveBeenCalled()
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should show error alert when share fails', async () => {
|
|
252
|
+
mockShare.mockRejectedValueOnce(new Error('Share failed'))
|
|
253
|
+
const { getByTestId } = setup()
|
|
254
|
+
|
|
255
|
+
await waitFor(() => {
|
|
256
|
+
expect(getByTestId('btn-send')).toBeTruthy()
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
fireEvent.click(getByTestId('btn-send'))
|
|
260
|
+
|
|
261
|
+
await waitFor(() => {
|
|
262
|
+
expect(mockShowAlert).toHaveBeenCalledWith({
|
|
263
|
+
message: 'Error while sharing folder',
|
|
264
|
+
severity: 'error',
|
|
265
|
+
variant: 'filled'
|
|
266
|
+
})
|
|
267
|
+
expect(mockOnClose).not.toHaveBeenCalled()
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
describe('onSetType callback', () => {
|
|
273
|
+
it('should move recipient from readOnlyRecipients to recipients when setting two-way', async () => {
|
|
274
|
+
const { getByTestId } = setup()
|
|
275
|
+
|
|
276
|
+
await waitFor(() => {
|
|
277
|
+
expect(getByTestId('btn-share')).toBeTruthy()
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
fireEvent.click(getByTestId('btn-share'))
|
|
281
|
+
|
|
282
|
+
// After btn-share: combined recipients = 2 (1 readWrite + 1 readOnly)
|
|
283
|
+
await waitFor(() => {
|
|
284
|
+
expect(getByTestId('recipients-count').textContent).toBe('2')
|
|
285
|
+
expect(getByTestId('readOnly-recipients-count').textContent).toBe('1')
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
fireEvent.click(getByTestId('btn-set-type-two-way'))
|
|
289
|
+
|
|
290
|
+
// After setting two-way: Bob moves from readOnlyRecipients to recipients
|
|
291
|
+
await waitFor(() => {
|
|
292
|
+
expect(getByTestId('recipients-count').textContent).toBe('2')
|
|
293
|
+
expect(getByTestId('readOnly-recipients-count').textContent).toBe('0')
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('should move recipient from recipients to readOnlyRecipients when setting one-way', async () => {
|
|
298
|
+
const { getByTestId } = setup()
|
|
299
|
+
|
|
300
|
+
await waitFor(() => {
|
|
301
|
+
expect(getByTestId('btn-share')).toBeTruthy()
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
fireEvent.click(getByTestId('btn-share'))
|
|
305
|
+
|
|
306
|
+
// After btn-share: combined recipients = 2 (1 readWrite + 1 readOnly)
|
|
307
|
+
await waitFor(() => {
|
|
308
|
+
expect(getByTestId('recipients-count').textContent).toBe('2')
|
|
309
|
+
expect(getByTestId('readOnly-recipients-count').textContent).toBe('1')
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
fireEvent.click(getByTestId('btn-set-type-one-way'))
|
|
313
|
+
|
|
314
|
+
// After setting one-way: Alice moves from recipients to readOnlyRecipients
|
|
315
|
+
// Combined count stays at 2, but readOnlyRecipients increases from 1 to 2
|
|
316
|
+
await waitFor(() => {
|
|
317
|
+
expect(getByTestId('recipients-count').textContent).toBe('2')
|
|
318
|
+
expect(getByTestId('readOnly-recipients-count').textContent).toBe('2')
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
describe('onRevoke callback', () => {
|
|
324
|
+
it('should remove recipient from both lists', async () => {
|
|
325
|
+
const { getByTestId } = setup()
|
|
326
|
+
|
|
327
|
+
await waitFor(() => {
|
|
328
|
+
expect(getByTestId('btn-share')).toBeTruthy()
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
fireEvent.click(getByTestId('btn-share'))
|
|
332
|
+
|
|
333
|
+
await waitFor(() => {
|
|
334
|
+
expect(getByTestId('recipients-count').textContent).toBe('2')
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
fireEvent.click(getByTestId('btn-revoke'))
|
|
338
|
+
|
|
339
|
+
await waitFor(() => {
|
|
340
|
+
expect(getByTestId('recipients-count').textContent).toBe('1')
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
describe('onClose callback', () => {
|
|
346
|
+
it('should call onClose when close button is clicked', async () => {
|
|
347
|
+
const { getByTestId } = setup()
|
|
348
|
+
|
|
349
|
+
await waitFor(() => {
|
|
350
|
+
expect(getByTestId('btn-close')).toBeTruthy()
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
fireEvent.click(getByTestId('btn-close'))
|
|
354
|
+
|
|
355
|
+
expect(mockOnClose).toHaveBeenCalled()
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
describe('createContact callback', () => {
|
|
360
|
+
it('should call client.create when creating a contact', async () => {
|
|
361
|
+
const { getByTestId } = setup()
|
|
362
|
+
|
|
363
|
+
await waitFor(() => {
|
|
364
|
+
expect(getByTestId('btn-create-contact')).toBeTruthy()
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
fireEvent.click(getByTestId('btn-create-contact'))
|
|
368
|
+
|
|
369
|
+
await waitFor(() => {
|
|
370
|
+
expect(client.create).toHaveBeenCalledWith('io.cozy.contacts', {
|
|
371
|
+
email: 'test@example.com'
|
|
372
|
+
})
|
|
373
|
+
})
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
})
|
|
@@ -29,7 +29,7 @@ const LinkRecipient = props => {
|
|
|
29
29
|
const { recipientConfirmationData, verifyRecipient, link, fadeIn, document } =
|
|
30
30
|
props
|
|
31
31
|
|
|
32
|
-
const permissions = getDocumentPermissions(document
|
|
32
|
+
const permissions = getDocumentPermissions(document?._id)
|
|
33
33
|
const hasPassword = checkIsPermissionHasPassword(permissions)
|
|
34
34
|
const expiresDate = getPermissionExpiresDate(permissions)
|
|
35
35
|
const dateFormatted = expiresDate
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
|
|
21
21
|
const ShareAutocomplete = ({
|
|
22
22
|
loading,
|
|
23
|
+
disabled,
|
|
23
24
|
contactsAndGroups,
|
|
24
25
|
recipients,
|
|
25
26
|
onPick,
|
|
@@ -192,7 +193,8 @@ const ShareAutocomplete = ({
|
|
|
192
193
|
value: inputValue,
|
|
193
194
|
type: 'email',
|
|
194
195
|
placeholder,
|
|
195
|
-
className: styles['suggestionInput']
|
|
196
|
+
className: styles['suggestionInput'],
|
|
197
|
+
disabled
|
|
196
198
|
}}
|
|
197
199
|
/>
|
|
198
200
|
)
|
|
@@ -200,6 +202,7 @@ const ShareAutocomplete = ({
|
|
|
200
202
|
|
|
201
203
|
ShareAutocomplete.propTypes = {
|
|
202
204
|
loading: PropTypes.bool,
|
|
205
|
+
disabled: PropTypes.bool,
|
|
203
206
|
contactsAndGroups: PropTypes.array,
|
|
204
207
|
recipients: PropTypes.array.isRequired,
|
|
205
208
|
onPick: PropTypes.func.isRequired,
|
|
@@ -29,12 +29,15 @@ export const ShareByEmail = ({
|
|
|
29
29
|
currentRecipients,
|
|
30
30
|
sharing,
|
|
31
31
|
submitLabel,
|
|
32
|
-
showNotifications = true
|
|
32
|
+
showNotifications = true,
|
|
33
|
+
sharedDrive = false
|
|
33
34
|
}) => {
|
|
34
35
|
const client = useClient()
|
|
35
36
|
const { t } = useI18n()
|
|
36
37
|
const { showAlert } = useAlert()
|
|
37
38
|
|
|
39
|
+
const isFederatedMode = flag('drive.federated-shared-folder.enabled')
|
|
40
|
+
|
|
38
41
|
const [recipients, setRecipients] = useState([])
|
|
39
42
|
const [loading, setLoading] = useState(false)
|
|
40
43
|
const [selectedOption, setSelectedOption] = useState('readWrite')
|
|
@@ -50,7 +53,39 @@ export const ShareByEmail = ({
|
|
|
50
53
|
setSelectedOption(value)
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
const onRecipientPick = recipient => {
|
|
56
|
+
const onRecipientPick = async recipient => {
|
|
57
|
+
// In federated mode, directly share with the recipient with readWrite access
|
|
58
|
+
if (isFederatedMode) {
|
|
59
|
+
setLoading(true)
|
|
60
|
+
try {
|
|
61
|
+
const contacts = await getOrCreateFromArray(
|
|
62
|
+
client,
|
|
63
|
+
[recipient],
|
|
64
|
+
createContact
|
|
65
|
+
)
|
|
66
|
+
await onShare({
|
|
67
|
+
document,
|
|
68
|
+
recipients: contacts,
|
|
69
|
+
readOnlyRecipients: [],
|
|
70
|
+
description: sharingDesc,
|
|
71
|
+
openSharing: false,
|
|
72
|
+
sharedDrive: true
|
|
73
|
+
})
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (showNotifications) {
|
|
76
|
+
showAlert({
|
|
77
|
+
message: t('Share.shareByEmail.error.addingRecipient'),
|
|
78
|
+
severity: 'error',
|
|
79
|
+
variant: 'filled'
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
reset()
|
|
84
|
+
}
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Normal mode: just add to the list
|
|
54
89
|
const mergedRecipients = flag('sharing.show-recipient-groups')
|
|
55
90
|
? mergeRecipients(recipients, recipient)
|
|
56
91
|
: spreadGroupAndMergeRecipients(recipients, recipient)
|
|
@@ -95,7 +130,8 @@ export const ShareByEmail = ({
|
|
|
95
130
|
recipients: readWriteRecipients,
|
|
96
131
|
readOnlyRecipients,
|
|
97
132
|
description: sharingDesc,
|
|
98
|
-
openSharing: readWriteRecipients.length > 0
|
|
133
|
+
openSharing: readWriteRecipients.length > 0,
|
|
134
|
+
sharedDrive
|
|
99
135
|
})
|
|
100
136
|
|
|
101
137
|
if (showNotifications) {
|
|
@@ -130,7 +166,7 @@ export const ShareByEmail = ({
|
|
|
130
166
|
|
|
131
167
|
const getSharingOptions = () => {
|
|
132
168
|
const isSharingReadOnly = sharing
|
|
133
|
-
? isReadOnlySharing(sharing, document
|
|
169
|
+
? isReadOnlySharing(sharing, document?._id)
|
|
134
170
|
: false
|
|
135
171
|
const readWrite = {
|
|
136
172
|
value: 'readWrite',
|
|
@@ -162,9 +198,10 @@ export const ShareByEmail = ({
|
|
|
162
198
|
onRemove={recipient => onRecipientRemove(recipient)}
|
|
163
199
|
currentRecipients={currentRecipients}
|
|
164
200
|
recipients={recipients}
|
|
201
|
+
disabled={loading}
|
|
165
202
|
/>
|
|
166
203
|
</div>
|
|
167
|
-
{showShareControl && (
|
|
204
|
+
{!isFederatedMode && showShareControl && (
|
|
168
205
|
<div className={styles['share-type-control']}>
|
|
169
206
|
<ShareTypeSelect
|
|
170
207
|
value={selectedOption}
|
|
@@ -181,7 +218,7 @@ export const ShareByEmail = ({
|
|
|
181
218
|
)}
|
|
182
219
|
{showRecipientsLimit ? (
|
|
183
220
|
<ShareRecipientsLimitModal
|
|
184
|
-
documentName={document
|
|
221
|
+
documentName={document?.name}
|
|
185
222
|
onConfirm={() => setRecipientsLimit(false)}
|
|
186
223
|
/>
|
|
187
224
|
) : null}
|
|
@@ -191,7 +228,7 @@ export const ShareByEmail = ({
|
|
|
191
228
|
|
|
192
229
|
ShareByEmail.propTypes = {
|
|
193
230
|
currentRecipients: PropTypes.arrayOf(PropTypes.object),
|
|
194
|
-
document: PropTypes.object
|
|
231
|
+
document: PropTypes.object,
|
|
195
232
|
documentType: PropTypes.string.isRequired,
|
|
196
233
|
sharingDesc: PropTypes.string.isRequired,
|
|
197
234
|
onShare: PropTypes.func.isRequired,
|
|
@@ -201,7 +238,9 @@ ShareByEmail.propTypes = {
|
|
|
201
238
|
// Customize the label of the button that submit contacts
|
|
202
239
|
submitLabel: PropTypes.string,
|
|
203
240
|
// Display success or error notifications
|
|
204
|
-
showNotifications: PropTypes.bool
|
|
241
|
+
showNotifications: PropTypes.bool,
|
|
242
|
+
// Set to true for shared drive context
|
|
243
|
+
sharedDrive: PropTypes.bool
|
|
205
244
|
}
|
|
206
245
|
|
|
207
246
|
export default ShareByEmail
|