cozy-sharing 31.0.2 → 31.2.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/components/FederatedFolder/DumbFederatedFolderModal.js +15 -16
  3. package/dist/components/FederatedFolder/FederatedFolderModal.js +78 -103
  4. package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +102 -292
  5. package/dist/components/Recipient/GroupRecipientPermissions.js +15 -11
  6. package/dist/components/Recipient/LinkRecipient.js +15 -11
  7. package/dist/components/Recipient/LinkRecipientLite.js +1 -1
  8. package/dist/components/Recipient/LinkRecipientPermissions.js +2 -1
  9. package/dist/components/Recipient/MemberRecipient.spec.js +8 -10
  10. package/dist/components/Recipient/MemberRecipientLite.js +1 -1
  11. package/dist/components/Recipient/MemberRecipientPermissions.js +16 -12
  12. package/dist/components/Recipient/MemberRecipientPermissions.spec.js +62 -50
  13. package/dist/components/Recipient/RecipientConfirm.js +0 -5
  14. package/dist/components/ShareAutosuggest.js +9 -4
  15. package/dist/components/ShareByEmail.js +29 -274
  16. package/dist/components/ShareByEmail.spec.js +52 -230
  17. package/dist/components/ShareByLink.js +1 -3
  18. package/dist/components/ShareDialogCozyToCozy.js +169 -35
  19. package/dist/components/ShareDialogCozyToCozy.spec.js +123 -36
  20. package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +13 -3
  21. package/dist/components/ShareModal/SharingDetailsModal.js +0 -5
  22. package/dist/components/ShareRecipientsInput.js +6 -3
  23. package/dist/components/SharedDrive/SharedDriveForm.js +17 -25
  24. package/dist/components/SharedDrive/SharedDriveModal.js +1 -1
  25. package/dist/components/SharedDrive/SharedDriveRecipientPermissions.js +15 -10
  26. package/dist/components/SharedDrive/useSharedDrive.js +33 -57
  27. package/dist/components/SharedDrive/useSharedDrive.spec.js +29 -93
  28. package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +15 -26
  29. package/dist/components/Sharetypeselect.js +85 -90
  30. package/dist/hooks/usePendingRecipients.js +20 -0
  31. package/dist/hooks/usePendingRecipients.spec.js +30 -0
  32. package/dist/styles/autosuggest.styl +8 -2
  33. package/dist/styles/share.styl +0 -39
  34. package/dist/stylesheet.css +8 -36
  35. package/locales/en.json +14 -44
  36. package/locales/fr.json +14 -44
  37. package/locales/ru.json +14 -44
  38. package/locales/vi.json +14 -42
  39. package/package.json +2 -2
  40. package/src/components/FederatedFolder/DumbFederatedFolderModal.jsx +14 -15
  41. package/src/components/FederatedFolder/FederatedFolderModal.jsx +50 -67
  42. package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +63 -186
  43. package/src/components/Recipient/GroupRecipientPermissions.jsx +15 -13
  44. package/src/components/Recipient/LinkRecipient.jsx +9 -9
  45. package/src/components/Recipient/LinkRecipientLite.jsx +1 -3
  46. package/src/components/Recipient/LinkRecipientPermissions.jsx +2 -1
  47. package/src/components/Recipient/MemberRecipient.spec.jsx +4 -6
  48. package/src/components/Recipient/MemberRecipientLite.jsx +2 -2
  49. package/src/components/Recipient/MemberRecipientPermissions.jsx +16 -21
  50. package/src/components/Recipient/MemberRecipientPermissions.spec.jsx +21 -23
  51. package/src/components/ShareAutosuggest.jsx +7 -2
  52. package/src/components/ShareByEmail.jsx +35 -186
  53. package/src/components/ShareByEmail.spec.jsx +22 -173
  54. package/src/components/ShareByLink.jsx +1 -7
  55. package/src/components/ShareDialogCozyToCozy.jsx +171 -38
  56. package/src/components/ShareDialogCozyToCozy.spec.jsx +67 -1
  57. package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +11 -1
  58. package/src/components/ShareRecipientsInput.jsx +5 -2
  59. package/src/components/SharedDrive/SharedDriveForm.jsx +16 -25
  60. package/src/components/SharedDrive/SharedDriveModal.jsx +1 -1
  61. package/src/components/SharedDrive/SharedDriveRecipientPermissions.jsx +15 -17
  62. package/src/components/SharedDrive/useSharedDrive.js +24 -50
  63. package/src/components/SharedDrive/useSharedDrive.spec.js +19 -67
  64. package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +15 -29
  65. package/src/components/Sharetypeselect.jsx +75 -58
  66. package/src/hooks/usePendingRecipients.js +13 -0
  67. package/src/hooks/usePendingRecipients.spec.js +21 -0
  68. package/src/styles/autosuggest.styl +8 -2
  69. package/src/styles/share.styl +0 -39
  70. package/dist/components/Recipient/actions/revokeGroup.js +0 -42
  71. package/dist/components/Recipient/actions/revokeMember.js +0 -42
  72. package/dist/components/Recipient/actions/revokeSharedDriveMember.js +0 -41
  73. package/dist/components/SharedDrive/helpers.js +0 -102
  74. package/dist/components/SharedDrive/helpers.spec.js +0 -227
  75. package/dist/components/Sharesubmit.js +0 -28
  76. package/src/components/Recipient/actions/revokeGroup.js +0 -42
  77. package/src/components/Recipient/actions/revokeMember.js +0 -42
  78. package/src/components/Recipient/actions/revokeSharedDriveMember.js +0 -41
  79. package/src/components/SharedDrive/helpers.js +0 -108
  80. package/src/components/SharedDrive/helpers.spec.js +0 -177
  81. package/src/components/Sharesubmit.jsx +0 -31
@@ -1,20 +1,15 @@
1
1
  import PropTypes from 'prop-types'
2
- import React, { useState, useEffect } from 'react'
2
+ import React, { useEffect, useState } from 'react'
3
3
 
4
4
  import { useClient } from 'cozy-client'
5
5
  import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
6
6
  import { useI18n } from 'twake-i18n'
7
7
 
8
8
  import { DumbFederatedFolderModal } from './DumbFederatedFolderModal'
9
+ import { getOrCreateFromArray } from '../../helpers/contacts'
9
10
  import withLocales from '../../hoc/withLocales'
11
+ import { usePendingRecipients } from '../../hooks/usePendingRecipients'
10
12
  import { useSharingContext } from '../../hooks/useSharingContext'
11
- import {
12
- formatRecipients,
13
- mergeAndDeduplicateRecipients,
14
- moveRecipientToReadWrite,
15
- moveRecipientToReadOnly,
16
- RECIPIENT_INDEX_PREFIX
17
- } from '../SharedDrive/helpers'
18
13
 
19
14
  export const FederatedFolderModal = withLocales(
20
15
  ({
@@ -38,6 +33,22 @@ export const FederatedFolderModal = withLocales(
38
33
  const { showAlert } = useAlert()
39
34
 
40
35
  const [sharingLink, setSharingLink] = useState(null)
36
+ const {
37
+ pendingRecipients,
38
+ setPendingRecipients,
39
+ selectedOption,
40
+ setSelectedOption
41
+ } = usePendingRecipients()
42
+
43
+ // share from CozyProvider is wired to callbacks and realtime that
44
+ // makes existingDocument and existingRecipients reactive to changes.
45
+ // But we want it to be reactive only when we revoke a member. When we add member
46
+ // we close the popup and we do not want to see briefly the new contacts added
47
+ // in members before the modal closes. That's why when clicking on "Share"
48
+ // we do not use the reactive existingDocument and existingRecipients.
49
+ const [isSending, setIsSending] = useState(false)
50
+ const [frozenDoc, setFrozenDoc] = useState(null)
51
+ const [frozenRecipients, setFrozenRecipients] = useState(null)
41
52
 
42
53
  const documentPermissions = existingDocument
43
54
  ? getDocumentPermissions(existingDocument._id)
@@ -65,32 +76,33 @@ export const FederatedFolderModal = withLocales(
65
76
  getSharingById
66
77
  ])
67
78
 
68
- const [federatedRecipients, setFederatedRecipients] = useState({
69
- recipients: [],
70
- readOnlyRecipients: []
71
- })
72
- const [folderName] = useState(existingDocument?.name || '')
73
-
74
- const onShare = params => {
75
- setFederatedRecipients(prev => ({
76
- recipients: mergeAndDeduplicateRecipients([
77
- prev.recipients,
78
- params.recipients || []
79
- ]),
80
- readOnlyRecipients: mergeAndDeduplicateRecipients([
81
- prev.readOnlyRecipients,
82
- params.readOnlyRecipients || []
83
- ])
84
- }))
85
- }
79
+ const folderName = existingDocument?.name || ''
86
80
 
87
81
  const onSend = async () => {
82
+ if (pendingRecipients.length === 0) {
83
+ onClose()
84
+ return
85
+ }
86
+
87
+ setIsSending(true)
88
+ setFrozenDoc(existingDocument)
89
+ setFrozenRecipients(existingRecipients)
90
+
88
91
  try {
92
+ const contacts = await getOrCreateFromArray(
93
+ client,
94
+ pendingRecipients,
95
+ contact => client.create('io.cozy.contacts', contact)
96
+ )
97
+ const readWriteRecipients =
98
+ selectedOption === 'readOnly' ? [] : contacts
99
+ const readOnlyRecipients = selectedOption === 'readOnly' ? contacts : []
100
+
89
101
  await share({
90
102
  description: folderName,
91
103
  document: existingDocument,
92
- recipients: federatedRecipients.recipients,
93
- readOnlyRecipients: federatedRecipients.readOnlyRecipients,
104
+ recipients: readWriteRecipients,
105
+ readOnlyRecipients,
94
106
  sharedDrive: true,
95
107
  openSharing: false
96
108
  })
@@ -108,31 +120,8 @@ export const FederatedFolderModal = withLocales(
108
120
  severity: 'error',
109
121
  variant: 'filled'
110
122
  })
111
- }
112
- }
113
-
114
- const onSetType = (index, newType) => {
115
- const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
116
-
117
- if (newType === 'two-way') {
118
- setFederatedRecipients(prev => moveRecipientToReadWrite(prev, _id))
119
- } else {
120
- setFederatedRecipients(prev => moveRecipientToReadOnly(prev, _id))
121
- }
122
- }
123
-
124
- const onRevoke = async (documentOrIndex, sharingId, memberIndex) => {
125
- if (
126
- typeof documentOrIndex === 'string' &&
127
- documentOrIndex.startsWith(RECIPIENT_INDEX_PREFIX)
128
- ) {
129
- const _id = documentOrIndex.split(RECIPIENT_INDEX_PREFIX)[1]
130
- setFederatedRecipients(prev => ({
131
- recipients: prev.recipients.filter(r => r._id !== _id),
132
- readOnlyRecipients: prev.readOnlyRecipients.filter(r => r._id !== _id)
133
- }))
134
- } else {
135
- await revoke(documentOrIndex, sharingId, memberIndex)
123
+ } finally {
124
+ setIsSending(false)
136
125
  }
137
126
  }
138
127
 
@@ -140,32 +129,26 @@ export const FederatedFolderModal = withLocales(
140
129
  ? getRecipients(existingDocument._id)
141
130
  : []
142
131
 
143
- const recipients = [
144
- ...existingRecipients,
145
- ...formatRecipients(federatedRecipients)
146
- ]
147
-
148
132
  const modalTitle = t('FederatedFolder.shareTitle', { name: folderName })
149
-
150
133
  const isSharedDrive = Boolean(existingDocument?.driveId)
151
134
 
152
135
  return (
153
136
  <DumbFederatedFolderModal
154
137
  title={modalTitle}
155
- document={existingDocument}
156
- createContact={contact => client.create('io.cozy.contacts', contact)}
157
- recipients={recipients}
158
- readOnlyRecipients={federatedRecipients.readOnlyRecipients}
159
- currentRecipients={existingRecipients}
160
- onRevoke={onRevoke}
161
- onSetType={onSetType}
138
+ document={isSending ? frozenDoc : existingDocument}
139
+ recipients={isSending ? frozenRecipients : existingRecipients}
140
+ currentRecipients={isSending ? frozenRecipients : existingRecipients}
141
+ onRevoke={revoke}
162
142
  onSend={onSend}
163
143
  onClose={onClose}
164
- onShare={onShare}
165
144
  sharingLink={sharingLink}
166
145
  showShareByEmail={!isSharedDrive}
167
146
  autoOpenShareRestriction={autoOpenShareRestriction}
168
147
  showGenerateLinkButton={showGenerateLinkButton}
148
+ pendingRecipients={pendingRecipients}
149
+ onPendingRecipientsChange={setPendingRecipients}
150
+ selectedOption={selectedOption}
151
+ onSelectedOptionChange={setSelectedOption}
169
152
  />
170
153
  )
171
154
  }
@@ -4,6 +4,8 @@ import React from 'react'
4
4
  import { createMockClient } from 'cozy-client'
5
5
 
6
6
  import { FederatedFolderModal } from './FederatedFolderModal'
7
+ import { getOrCreateFromArray } from '../../helpers/contacts'
8
+ import { usePendingRecipients } from '../../hooks/usePendingRecipients'
7
9
  import AppLike from '../SharingBanner/test/AppLike'
8
10
 
9
11
  const mockShare = jest.fn()
@@ -31,87 +33,45 @@ jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
31
33
 
32
34
  jest.mock('../../hoc/withLocales', () => Component => Component)
33
35
 
36
+ jest.mock('../../helpers/contacts', () => ({
37
+ getOrCreateFromArray: jest.fn()
38
+ }))
39
+
40
+ jest.mock('../../hooks/usePendingRecipients', () => ({
41
+ usePendingRecipients: jest.fn()
42
+ }))
43
+
34
44
  jest.mock('./DumbFederatedFolderModal', () => ({
35
45
  DumbFederatedFolderModal: ({
36
46
  title,
37
- createContact,
38
47
  recipients,
39
- readOnlyRecipients,
40
48
  onRevoke,
41
- onSetType,
42
49
  onSend,
43
50
  onClose,
44
- onShare,
45
- sharingLink
51
+ sharingLink,
52
+ pendingRecipients,
53
+ selectedOption
46
54
  }) => (
47
55
  <div data-testid="dumb-modal">
48
56
  <span data-testid="title">{title}</span>
49
57
  <span data-testid="sharing-link">{sharingLink}</span>
50
58
  <span data-testid="recipients-count">{recipients.length}</span>
51
- <span data-testid="readOnly-recipients-count">
52
- {readOnlyRecipients ? readOnlyRecipients.length : 0}
59
+ <span data-testid="pending-recipients-count">
60
+ {pendingRecipients.length}
53
61
  </span>
54
- <button
55
- data-testid="btn-share"
56
- onClick={() =>
57
- onShare({
58
- recipients: [{ _id: 'r1', id: 'r1', displayName: 'Alice' }],
59
- readOnlyRecipients: [{ _id: 'r2', id: 'r2', displayName: 'Bob' }]
60
- })
61
- }
62
- >
63
- Share
64
- </button>
65
- <button
66
- data-testid="btn-share-other"
67
- onClick={() =>
68
- onShare({
69
- recipients: [
70
- { _id: 'r1', id: 'r1', displayName: 'Alice' },
71
- { _id: 'r3', id: 'r3', displayName: 'Charlie' }
72
- ],
73
- readOnlyRecipients: []
74
- })
75
- }
76
- >
77
- Share Other
78
- </button>
62
+ <span data-testid="selected-option">{selectedOption}</span>
79
63
  <button data-testid="btn-send" onClick={onSend}>
80
64
  Send
81
65
  </button>
82
- <button
83
- data-testid="btn-set-type-two-way"
84
- onClick={() => onSetType('virtual-shared-drive-sharing-r2', 'two-way')}
85
- >
86
- Set Two-Way
87
- </button>
88
- <button
89
- data-testid="btn-set-type-one-way"
90
- onClick={() => onSetType('virtual-shared-drive-sharing-r1', 'one-way')}
91
- >
92
- Set One-Way
93
- </button>
94
66
  <button
95
67
  data-testid="btn-revoke"
96
- onClick={() => onRevoke('virtual-shared-drive-sharing-r1')}
97
- >
98
- Revoke
99
- </button>
100
- <button
101
- data-testid="btn-revoke-existing"
102
68
  onClick={() => onRevoke({ _id: 'folder-123' }, 'abc', 1)}
103
69
  >
104
- Revoke Existing
70
+ Revoke
105
71
  </button>
106
72
  <button data-testid="btn-close" onClick={onClose}>
107
73
  Close
108
74
  </button>
109
- <button
110
- data-testid="btn-create-contact"
111
- onClick={() => createContact({ email: 'test@example.com' })}
112
- >
113
- Create Contact
114
- </button>
115
75
  </div>
116
76
  )
117
77
  }))
@@ -162,6 +122,13 @@ describe('FederatedFolderModal', () => {
162
122
  mockGetRecipients.mockReturnValue([])
163
123
  client = createTestClient()
164
124
  sharingContextValue = createSharingContextValue()
125
+ usePendingRecipients.mockReturnValue({
126
+ pendingRecipients: [],
127
+ setPendingRecipients: jest.fn(),
128
+ selectedOption: 'readWrite',
129
+ setSelectedOption: jest.fn()
130
+ })
131
+ getOrCreateFromArray.mockResolvedValue([])
165
132
  })
166
133
 
167
134
  const setup = (props = {}) => {
@@ -211,46 +178,43 @@ describe('FederatedFolderModal', () => {
211
178
  expect(getByTestId('sharing-link').textContent).toBe('')
212
179
  })
213
180
  })
214
- })
215
181
 
216
- describe('onShare callback', () => {
217
- it('should update recipients when onShare is called', async () => {
182
+ it('should pass selectedOption from usePendingRecipients', async () => {
218
183
  const { getByTestId } = setup()
219
184
 
220
185
  await waitFor(() => {
221
- expect(getByTestId('recipients-count').textContent).toBe('0')
222
- })
223
-
224
- fireEvent.click(getByTestId('btn-share'))
225
-
226
- await waitFor(() => {
227
- expect(getByTestId('recipients-count').textContent).toBe('2')
186
+ expect(getByTestId('selected-option').textContent).toBe('readWrite')
228
187
  })
229
188
  })
189
+ })
230
190
 
231
- it('should accumulate recipients when onShare is called multiple times', async () => {
232
- const { getByTestId } = setup()
233
-
234
- await waitFor(() => {
235
- expect(getByTestId('recipients-count').textContent).toBe('0')
236
- })
191
+ describe('onSend callback', () => {
192
+ const pendingRecipient = { name: 'Alice', email: 'alice@example.com' }
237
193
 
238
- fireEvent.click(getByTestId('btn-share'))
194
+ it('should call onClose and skip share when there are no pending recipients', async () => {
195
+ // pendingRecipients defaults to [] via beforeEach
196
+ const { getByTestId } = setup()
239
197
 
240
198
  await waitFor(() => {
241
- expect(getByTestId('recipients-count').textContent).toBe('2')
199
+ expect(getByTestId('btn-send')).toBeTruthy()
242
200
  })
243
201
 
244
- fireEvent.click(getByTestId('btn-share-other'))
202
+ fireEvent.click(getByTestId('btn-send'))
245
203
 
246
204
  await waitFor(() => {
247
- expect(getByTestId('recipients-count').textContent).toBe('3')
205
+ expect(mockOnClose).toHaveBeenCalled()
206
+ expect(mockShare).not.toHaveBeenCalled()
248
207
  })
249
208
  })
250
- })
251
209
 
252
- describe('onSend callback', () => {
253
210
  it('should call share with correct parameters', async () => {
211
+ usePendingRecipients.mockReturnValue({
212
+ pendingRecipients: [pendingRecipient],
213
+ setPendingRecipients: jest.fn(),
214
+ selectedOption: 'readWrite',
215
+ setSelectedOption: jest.fn()
216
+ })
217
+ getOrCreateFromArray.mockResolvedValue([pendingRecipient])
254
218
  mockShare.mockResolvedValueOnce({})
255
219
  const { getByTestId } = setup()
256
220
 
@@ -258,15 +222,14 @@ describe('FederatedFolderModal', () => {
258
222
  expect(getByTestId('btn-send')).toBeTruthy()
259
223
  })
260
224
 
261
- fireEvent.click(getByTestId('btn-share'))
262
225
  fireEvent.click(getByTestId('btn-send'))
263
226
 
264
227
  await waitFor(() => {
265
228
  expect(mockShare).toHaveBeenCalledWith({
266
229
  description: 'My Test Folder',
267
230
  document: mockDocument,
268
- recipients: [{ _id: 'r1', id: 'r1', displayName: 'Alice' }],
269
- readOnlyRecipients: [{ _id: 'r2', id: 'r2', displayName: 'Bob' }],
231
+ recipients: [pendingRecipient],
232
+ readOnlyRecipients: [],
270
233
  sharedDrive: true,
271
234
  openSharing: false
272
235
  })
@@ -274,6 +237,13 @@ describe('FederatedFolderModal', () => {
274
237
  })
275
238
 
276
239
  it('should show success alert and close modal on successful share', async () => {
240
+ usePendingRecipients.mockReturnValue({
241
+ pendingRecipients: [pendingRecipient],
242
+ setPendingRecipients: jest.fn(),
243
+ selectedOption: 'readWrite',
244
+ setSelectedOption: jest.fn()
245
+ })
246
+ getOrCreateFromArray.mockResolvedValue([pendingRecipient])
277
247
  mockShare.mockResolvedValueOnce({})
278
248
  const { getByTestId } = setup()
279
249
 
@@ -294,6 +264,13 @@ describe('FederatedFolderModal', () => {
294
264
  })
295
265
 
296
266
  it('should show error alert when share fails', async () => {
267
+ usePendingRecipients.mockReturnValue({
268
+ pendingRecipients: [pendingRecipient],
269
+ setPendingRecipients: jest.fn(),
270
+ selectedOption: 'readWrite',
271
+ setSelectedOption: jest.fn()
272
+ })
273
+ getOrCreateFromArray.mockResolvedValue([pendingRecipient])
297
274
  mockShare.mockRejectedValueOnce(new Error('Share failed'))
298
275
  const { getByTestId } = setup()
299
276
 
@@ -314,75 +291,19 @@ describe('FederatedFolderModal', () => {
314
291
  })
315
292
  })
316
293
 
317
- describe('onSetType callback', () => {
318
- it('should move recipient from readOnlyRecipients to recipients when setting two-way', async () => {
319
- const { getByTestId } = setup()
320
-
321
- await waitFor(() => {
322
- expect(getByTestId('btn-share')).toBeTruthy()
323
- })
324
-
325
- fireEvent.click(getByTestId('btn-share'))
326
-
327
- // After btn-share: combined recipients = 2 (1 readWrite + 1 readOnly)
328
- await waitFor(() => {
329
- expect(getByTestId('recipients-count').textContent).toBe('2')
330
- expect(getByTestId('readOnly-recipients-count').textContent).toBe('1')
331
- })
332
-
333
- fireEvent.click(getByTestId('btn-set-type-two-way'))
334
-
335
- // After setting two-way: Bob moves from readOnlyRecipients to recipients
336
- await waitFor(() => {
337
- expect(getByTestId('recipients-count').textContent).toBe('2')
338
- expect(getByTestId('readOnly-recipients-count').textContent).toBe('0')
339
- })
340
- })
341
-
342
- it('should move recipient from recipients to readOnlyRecipients when setting one-way', async () => {
343
- const { getByTestId } = setup()
344
-
345
- await waitFor(() => {
346
- expect(getByTestId('btn-share')).toBeTruthy()
347
- })
348
-
349
- fireEvent.click(getByTestId('btn-share'))
350
-
351
- // After btn-share: combined recipients = 2 (1 readWrite + 1 readOnly)
352
- await waitFor(() => {
353
- expect(getByTestId('recipients-count').textContent).toBe('2')
354
- expect(getByTestId('readOnly-recipients-count').textContent).toBe('1')
355
- })
356
-
357
- fireEvent.click(getByTestId('btn-set-type-one-way'))
358
-
359
- // After setting one-way: Alice moves from recipients to readOnlyRecipients
360
- // Combined count stays at 2, but readOnlyRecipients increases from 1 to 2
361
- await waitFor(() => {
362
- expect(getByTestId('recipients-count').textContent).toBe('2')
363
- expect(getByTestId('readOnly-recipients-count').textContent).toBe('2')
364
- })
365
- })
366
- })
367
-
368
294
  describe('onRevoke callback', () => {
369
- it('should remove recipient from both lists', async () => {
295
+ it('should call revoke from sharing context for existing members', async () => {
296
+ mockRevoke.mockResolvedValueOnce({})
370
297
  const { getByTestId } = setup()
371
298
 
372
299
  await waitFor(() => {
373
- expect(getByTestId('btn-share')).toBeTruthy()
374
- })
375
-
376
- fireEvent.click(getByTestId('btn-share'))
377
-
378
- await waitFor(() => {
379
- expect(getByTestId('recipients-count').textContent).toBe('2')
300
+ expect(getByTestId('dumb-modal')).toBeTruthy()
380
301
  })
381
302
 
382
303
  fireEvent.click(getByTestId('btn-revoke'))
383
304
 
384
305
  await waitFor(() => {
385
- expect(getByTestId('recipients-count').textContent).toBe('1')
306
+ expect(mockRevoke).toHaveBeenCalledWith({ _id: 'folder-123' }, 'abc', 1)
386
307
  })
387
308
  })
388
309
  })
@@ -431,49 +352,5 @@ describe('FederatedFolderModal', () => {
431
352
  expect(getByTestId('recipients-count').textContent).toBe('2')
432
353
  })
433
354
  })
434
-
435
- it('should pass existing recipients as currentRecipients', async () => {
436
- mockGetRecipients.mockReturnValue(existingRecipients)
437
- const { getByTestId } = setup()
438
-
439
- await waitFor(() => {
440
- expect(getByTestId('recipients-count').textContent).toBe('2')
441
- })
442
- })
443
-
444
- it('should call revoke for existing member', async () => {
445
- mockGetRecipients.mockReturnValue(existingRecipients)
446
- mockRevoke.mockResolvedValueOnce({})
447
- const { getByTestId } = setup()
448
-
449
- await waitFor(() => {
450
- expect(getByTestId('dumb-modal')).toBeTruthy()
451
- })
452
-
453
- // Simulate revoke of an existing member (non-virtual-shared-drive index)
454
- fireEvent.click(getByTestId('btn-revoke-existing'))
455
-
456
- await waitFor(() => {
457
- expect(mockRevoke).toHaveBeenCalledWith({ _id: 'folder-123' }, 'abc', 1)
458
- })
459
- })
460
- })
461
-
462
- describe('createContact callback', () => {
463
- it('should call client.create when creating a contact', async () => {
464
- const { getByTestId } = setup()
465
-
466
- await waitFor(() => {
467
- expect(getByTestId('btn-create-contact')).toBeTruthy()
468
- })
469
-
470
- fireEvent.click(getByTestId('btn-create-contact'))
471
-
472
- await waitFor(() => {
473
- expect(client.create).toHaveBeenCalledWith('io.cozy.contacts', {
474
- email: 'test@example.com'
475
- })
476
- })
477
- })
478
355
  })
479
356
  })
@@ -1,20 +1,18 @@
1
1
  import React, { useState, useRef } from 'react'
2
2
 
3
- import { useClient } from 'cozy-client'
4
3
  import ActionsMenu from 'cozy-ui/transpiled/react/ActionsMenu'
5
- import {
6
- makeActions,
7
- divider
8
- } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'
4
+ import { makeActions } from 'cozy-ui/transpiled/react/ActionsMenu/Actions'
9
5
  import ActionsMenuMobileHeader from 'cozy-ui/transpiled/react/ActionsMenu/ActionsMenuMobileHeader'
10
6
  import DropdownButton from 'cozy-ui/transpiled/react/DropdownButton'
7
+ import Icon from 'cozy-ui/transpiled/react/Icon'
8
+ import IconButton from 'cozy-ui/transpiled/react/IconButton'
9
+ import CrossCircleOutlineIcon from 'cozy-ui/transpiled/react/Icons/CrossCircleOutline'
11
10
  import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon'
12
11
  import ListItemText from 'cozy-ui/transpiled/react/ListItemText'
13
12
  import Spinner from 'cozy-ui/transpiled/react/Spinner'
14
13
  import { useI18n } from 'twake-i18n'
15
14
 
16
15
  import { permission } from './actions/permission'
17
- import { revokeGroup as revokeGroupAction } from './actions/revokeGroup'
18
16
  import { useSharingContext } from '../../hooks/useSharingContext'
19
17
  import { GroupAvatar } from '../Avatar/GroupAvatar'
20
18
 
@@ -30,7 +28,6 @@ const GroupRecipientPermissions = ({
30
28
  }) => {
31
29
  const { t } = useI18n()
32
30
  const buttonRef = useRef()
33
- const client = useClient()
34
31
  const { revokeGroup, revokeSelf } = useSharingContext()
35
32
 
36
33
  const [isMenuDisplayed, setMenuDisplayed] = useState(false)
@@ -53,12 +50,9 @@ const GroupRecipientPermissions = ({
53
50
 
54
51
  const type = isReadOnly ? 'one-way' : 'two-way'
55
52
 
56
- const actions = makeActions([permission, divider, revokeGroupAction], {
57
- client,
53
+ const actions = makeActions([permission], {
58
54
  t,
59
- type,
60
- isOwner,
61
- handleRevocation
55
+ type
62
56
  })
63
57
 
64
58
  return (
@@ -73,7 +67,7 @@ const GroupRecipientPermissions = ({
73
67
  onClick={toggleMenu}
74
68
  textVariant="body2"
75
69
  >
76
- {t(`Share.type.${type}`).toLowerCase()}
70
+ {t(`Share.type.${type}`)}
77
71
  </DropdownButton>
78
72
  <ActionsMenu
79
73
  ref={buttonRef}
@@ -92,6 +86,14 @@ const GroupRecipientPermissions = ({
92
86
  />
93
87
  </ActionsMenuMobileHeader>
94
88
  </ActionsMenu>
89
+ <IconButton
90
+ onClick={handleRevocation}
91
+ size="small"
92
+ className="u-ml-half"
93
+ aria-label={t('Share.members.revoke')}
94
+ >
95
+ <Icon icon={CrossCircleOutlineIcon} />
96
+ </IconButton>
95
97
  </>
96
98
  )}
97
99
  </div>
@@ -2,14 +2,15 @@ import PropTypes from 'prop-types'
2
2
  import React, { useState } from 'react'
3
3
 
4
4
  import Avatar from 'cozy-ui/transpiled/react/Avatar'
5
- import Button from 'cozy-ui/transpiled/react/Button'
6
5
  import Fade from 'cozy-ui/transpiled/react/Fade'
7
6
  import Icon from 'cozy-ui/transpiled/react/Icon'
7
+ import IconButton from 'cozy-ui/transpiled/react/IconButton'
8
8
  import Gear from 'cozy-ui/transpiled/react/Icons/Gear'
9
9
  import LinkIcon from 'cozy-ui/transpiled/react/Icons/Link'
10
10
  import ListItem from 'cozy-ui/transpiled/react/ListItem'
11
11
  import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon'
12
12
  import ListItemText from 'cozy-ui/transpiled/react/ListItemText'
13
+ import Typography from 'cozy-ui/transpiled/react/Typography'
13
14
  import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints'
14
15
  import { useI18n } from 'twake-i18n'
15
16
 
@@ -68,15 +69,14 @@ const LinkRecipient = props => {
68
69
  </Avatar>
69
70
  </ListItemIcon>
70
71
  <ListItemText
72
+ onClick={() => setOpenShareRestrictionModal(true)}
71
73
  primary={
72
- <Button
73
- variant="ghost"
74
- onClick={() => setOpenShareRestrictionModal(true)}
75
- endIcon={<Icon icon={Gear} />}
76
- className="u-p-0"
77
- >
78
- {textPrimary}
79
- </Button>
74
+ <div className="u-flex u-flex-items-center">
75
+ <Typography className="u-c-pointer ">{textPrimary}</Typography>
76
+ <IconButton size="small" className="u-ml-half">
77
+ <Icon icon={Gear} />
78
+ </IconButton>
79
+ </div>
80
80
  }
81
81
  secondary={textSecondary}
82
82
  />
@@ -32,9 +32,7 @@ const LinkRecipientLite = ({ permissions, link }) => {
32
32
  secondary={link}
33
33
  />
34
34
  <Typography className="u-flex-shrink-0" variant="body2">
35
- {t(
36
- `Share.type.${isReadOnlyPermissions ? 'one-way' : 'two-way'}`
37
- ).toLowerCase()}
35
+ {t(`Share.type.${isReadOnlyPermissions ? 'one-way' : 'two-way'}`)}
38
36
  </Typography>
39
37
  </ListItem>
40
38
  )
@@ -38,6 +38,7 @@ const LinkRecipientPermissions = ({ className, document }) => {
38
38
  const handleSetType = newType => {
39
39
  const verbs = newType === 'one-way' ? READ_ONLY_PERMS : WRITE_PERMS
40
40
  updateDocumentPermissions(document, { verbs })
41
+ hideMenu()
41
42
  }
42
43
 
43
44
  const handleRevocation = () => revokeSharingLink(document)
@@ -63,7 +64,7 @@ const LinkRecipientPermissions = ({ className, document }) => {
63
64
  textVariant="body2"
64
65
  onClick={toggleMenu}
65
66
  >
66
- {t(`Share.type.${type}`).toLowerCase()}
67
+ {t(`Share.type.${type}`)}
67
68
  </DropdownButton>
68
69
  <ActionsMenu
69
70
  ref={buttonRef}