cozy-sharing 31.1.0 → 32.0.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 (65) hide show
  1. package/CHANGELOG.md +28 -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/RecipientConfirm.js +0 -5
  6. package/dist/components/ShareAutosuggest.js +9 -4
  7. package/dist/components/ShareByEmail.js +29 -274
  8. package/dist/components/ShareByEmail.spec.js +52 -230
  9. package/dist/components/ShareByLink.js +1 -3
  10. package/dist/components/ShareDialogCozyToCozy.js +169 -35
  11. package/dist/components/ShareDialogCozyToCozy.spec.js +122 -35
  12. package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +13 -3
  13. package/dist/components/ShareModal/SharingDetailsModal.js +0 -5
  14. package/dist/components/ShareRecipientsInput.js +6 -3
  15. package/dist/components/SharedDrive/SharedDriveForm.js +11 -23
  16. package/dist/components/SharedDrive/SharedDriveModal.js +1 -1
  17. package/dist/components/SharedDrive/useSharedDrive.js +33 -57
  18. package/dist/components/SharedDrive/useSharedDrive.spec.js +29 -93
  19. package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +15 -26
  20. package/dist/components/Sharetypeselect.js +85 -90
  21. package/dist/hooks/usePendingRecipients.js +20 -0
  22. package/dist/hooks/usePendingRecipients.spec.js +30 -0
  23. package/dist/styles/autosuggest.styl +8 -2
  24. package/dist/styles/share.styl +0 -39
  25. package/dist/stylesheet.css +8 -44
  26. package/locales/en.json +5 -9
  27. package/locales/fr.json +5 -9
  28. package/locales/ru.json +5 -9
  29. package/locales/vi.json +5 -9
  30. package/package.json +4 -5
  31. package/src/components/FederatedFolder/DumbFederatedFolderModal.jsx +14 -15
  32. package/src/components/FederatedFolder/FederatedFolderModal.jsx +50 -67
  33. package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +63 -186
  34. package/src/components/ShareAutosuggest.jsx +7 -2
  35. package/src/components/ShareByEmail.jsx +35 -186
  36. package/src/components/ShareByEmail.spec.jsx +22 -173
  37. package/src/components/ShareByLink.jsx +1 -7
  38. package/src/components/ShareDialogCozyToCozy.jsx +171 -38
  39. package/src/components/ShareDialogCozyToCozy.spec.jsx +66 -0
  40. package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +11 -1
  41. package/src/components/ShareRecipientsInput.jsx +5 -2
  42. package/src/components/SharedDrive/SharedDriveForm.jsx +10 -23
  43. package/src/components/SharedDrive/SharedDriveModal.jsx +1 -1
  44. package/src/components/SharedDrive/useSharedDrive.js +24 -50
  45. package/src/components/SharedDrive/useSharedDrive.spec.js +19 -67
  46. package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +15 -29
  47. package/src/components/Sharetypeselect.jsx +75 -58
  48. package/src/hooks/usePendingRecipients.js +13 -0
  49. package/src/hooks/usePendingRecipients.spec.js +21 -0
  50. package/src/styles/autosuggest.styl +8 -2
  51. package/src/styles/share.styl +0 -39
  52. package/dist/components/SharedDrive/helpers.js +0 -102
  53. package/dist/components/SharedDrive/helpers.spec.js +0 -227
  54. package/dist/components/SharedStatus.js +0 -61
  55. package/dist/components/SharedStatus.spec.js +0 -44
  56. package/dist/components/Sharesubmit.js +0 -28
  57. package/dist/components/__snapshots__/SharedStatus.spec.js.snap +0 -94
  58. package/dist/propTypes.js +0 -26
  59. package/src/components/SharedDrive/helpers.js +0 -108
  60. package/src/components/SharedDrive/helpers.spec.js +0 -177
  61. package/src/components/SharedStatus.jsx +0 -50
  62. package/src/components/SharedStatus.spec.js +0 -48
  63. package/src/components/Sharesubmit.jsx +0 -31
  64. package/src/components/__snapshots__/SharedStatus.spec.js.snap +0 -94
  65. package/src/propTypes.js +0 -29
@@ -1,167 +1,43 @@
1
1
  import PropTypes from 'prop-types'
2
- import React, { useState } from 'react'
2
+ import React from 'react'
3
3
 
4
- import { useClient } from 'cozy-client'
5
4
  import flag from 'cozy-flags'
6
- import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
7
5
  import { useI18n } from 'twake-i18n'
8
6
 
9
7
  import ShareRecipientsInput from './ShareRecipientsInput'
10
- import { ShareRecipientsLimitModal } from './ShareRecipientsLimitModal'
11
- import ShareSubmit from './Sharesubmit'
12
8
  import ShareTypeSelect from './Sharetypeselect'
13
- import { getOrCreateFromArray } from '../helpers/contacts'
14
9
  import {
15
10
  mergeRecipients,
16
- spreadGroupAndMergeRecipients,
17
- hasReachRecipientsLimit
11
+ spreadGroupAndMergeRecipients
18
12
  } from '../helpers/recipients'
19
- import { getSuccessMessage, getErrorMessage } from '../helpers/share'
20
13
  import { isReadOnlySharing } from '../state'
21
14
  import styles from '../styles/share.styl'
22
15
 
23
16
  export const ShareByEmail = ({
24
17
  document,
25
- sharingDesc,
26
- onShare,
27
- createContact,
28
18
  documentType,
29
19
  currentRecipients,
30
- sharing,
31
- submitLabel,
32
- showNotifications = true,
33
- sharedDrive = false
20
+ pendingRecipients,
21
+ onPendingRecipientsChange,
22
+ selectedOption,
23
+ onSelectedOptionChange,
24
+ sharing
34
25
  }) => {
35
- const client = useClient()
36
26
  const { t } = useI18n()
37
- const { showAlert } = useAlert()
38
27
 
39
- const isFederatedMode = flag('drive.federated-shared-folder.enabled')
40
-
41
- const [recipients, setRecipients] = useState([])
42
- const [loading, setLoading] = useState(false)
43
- const [selectedOption, setSelectedOption] = useState('readWrite')
44
- const [showRecipientsLimit, setRecipientsLimit] = useState(false)
45
-
46
- const reset = () => {
47
- setSelectedOption('readWrite')
48
- setRecipients([])
49
- setLoading(false)
50
- }
51
-
52
- const onChange = value => {
53
- setSelectedOption(value)
54
- }
55
-
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
89
- const mergedRecipients = flag('sharing.show-recipient-groups')
90
- ? mergeRecipients(recipients, recipient)
91
- : spreadGroupAndMergeRecipients(recipients, recipient)
92
- setRecipients(mergedRecipients)
28
+ const onRecipientPick = recipient => {
29
+ const merged = flag('sharing.show-recipient-groups')
30
+ ? mergeRecipients(pendingRecipients, recipient)
31
+ : spreadGroupAndMergeRecipients(pendingRecipients, recipient)
32
+ onPendingRecipientsChange(merged)
93
33
  }
94
34
 
95
35
  const onRecipientRemove = recipient => {
96
- const idx = recipients.findIndex(r => r === recipient)
97
- setRecipients([...recipients.slice(0, idx), ...recipients.slice(idx + 1)])
98
- }
99
-
100
- const share = async () => {
101
- if (recipients.length === 0) {
102
- return
103
- }
104
-
105
- // we can't use currentRecipients prop in getSuccessMessage because it may use
106
- // the updated prop to count the new recipients
107
- const recipientsBefore = currentRecipients
108
-
109
- if (hasReachRecipientsLimit(recipientsBefore, recipients)) {
110
- setRecipientsLimit(true)
111
- return
112
- }
113
-
114
- setLoading(true)
115
- try {
116
- const contacts = await getOrCreateFromArray(
117
- client,
118
- recipients,
119
- createContact
120
- )
121
- let readWriteRecipients = []
122
- let readOnlyRecipients = []
123
- if (selectedOption === 'readOnly') {
124
- readOnlyRecipients = contacts
125
- } else {
126
- readWriteRecipients = contacts
127
- }
128
- await onShare({
129
- document,
130
- recipients: readWriteRecipients,
131
- readOnlyRecipients,
132
- description: sharingDesc,
133
- openSharing: readWriteRecipients.length > 0,
134
- sharedDrive
135
- })
136
-
137
- if (showNotifications) {
138
- showAlert({
139
- message: t(
140
- ...getSuccessMessage(recipientsBefore, contacts, documentType)
141
- ),
142
- severity: 'success',
143
- variant: 'filled'
144
- })
145
- }
146
- } catch (err) {
147
- if (showNotifications) {
148
- showAlert({
149
- message: t(
150
- ...getErrorMessage({
151
- t,
152
- err,
153
- documentType,
154
- recipients,
155
- selectedOption
156
- })
157
- ),
158
- severity: 'error',
159
- variant: 'filled'
160
- })
161
- }
162
- } finally {
163
- reset()
164
- }
36
+ const idx = pendingRecipients.findIndex(r => r === recipient)
37
+ onPendingRecipientsChange([
38
+ ...pendingRecipients.slice(0, idx),
39
+ ...pendingRecipients.slice(idx + 1)
40
+ ])
165
41
  }
166
42
 
167
43
  const getSharingOptions = () => {
@@ -170,58 +46,37 @@ export const ShareByEmail = ({
170
46
  : false
171
47
  const readWrite = {
172
48
  value: 'readWrite',
173
- label: t('Share.type.two-way'),
174
- desc: t('Share.type.desc.two-way'),
175
- disabled: false
49
+ label: t('Share.type.two-way')
176
50
  }
177
51
  const readOnly = {
178
52
  value: 'readOnly',
179
- label: t('Share.type.one-way'),
180
- desc: t('Share.type.desc.one-way'),
181
- disabled: false
53
+ label: t('Share.type.one-way')
182
54
  }
183
55
  return isSharingReadOnly ? [readOnly] : [readWrite, readOnly]
184
56
  }
185
57
 
186
- const showShareControl = recipients.length > 0
187
-
188
58
  return (
189
59
  <div className={styles['coz-form-group']}>
190
60
  <div className={styles['coz-form']}>
191
61
  <ShareRecipientsInput
192
62
  placeholder={
193
- recipients.length === 0
63
+ pendingRecipients.length === 0
194
64
  ? t(`${documentType}.share.shareByEmail.emailPlaceholder`)
195
65
  : ''
196
66
  }
197
- onPick={recipient => onRecipientPick(recipient)}
198
- onRemove={recipient => onRecipientRemove(recipient)}
67
+ onPick={onRecipientPick}
68
+ onRemove={onRecipientRemove}
199
69
  currentRecipients={currentRecipients}
200
- recipients={recipients}
201
- disabled={loading}
70
+ recipients={pendingRecipients}
71
+ endAdornment={
72
+ <ShareTypeSelect
73
+ value={selectedOption}
74
+ options={getSharingOptions()}
75
+ onChange={onSelectedOptionChange}
76
+ />
77
+ }
202
78
  />
203
79
  </div>
204
- {!isFederatedMode && showShareControl && (
205
- <div className={styles['share-type-control']}>
206
- <ShareTypeSelect
207
- value={selectedOption}
208
- options={getSharingOptions()}
209
- onChange={onChange}
210
- />
211
- <ShareSubmit
212
- label={submitLabel || t(`${documentType}.share.shareByEmail.send`)}
213
- onSubmit={share}
214
- loading={loading}
215
- disabled={recipients.length === 0}
216
- />
217
- </div>
218
- )}
219
- {showRecipientsLimit ? (
220
- <ShareRecipientsLimitModal
221
- documentName={document?.name}
222
- onConfirm={() => setRecipientsLimit(false)}
223
- />
224
- ) : null}
225
80
  </div>
226
81
  )
227
82
  }
@@ -230,17 +85,11 @@ ShareByEmail.propTypes = {
230
85
  currentRecipients: PropTypes.arrayOf(PropTypes.object),
231
86
  document: PropTypes.object,
232
87
  documentType: PropTypes.string.isRequired,
233
- sharingDesc: PropTypes.string.isRequired,
234
- onShare: PropTypes.func.isRequired,
235
- createContact: PropTypes.func.isRequired,
236
- // We can display this component without having created the sharing yet
237
- sharing: PropTypes.object,
238
- // Customize the label of the button that submit contacts
239
- submitLabel: PropTypes.string,
240
- // Display success or error notifications
241
- showNotifications: PropTypes.bool,
242
- // Set to true for shared drive context
243
- sharedDrive: PropTypes.bool
88
+ pendingRecipients: PropTypes.array.isRequired,
89
+ onPendingRecipientsChange: PropTypes.func.isRequired,
90
+ selectedOption: PropTypes.oneOf(['readWrite', 'readOnly']).isRequired,
91
+ onSelectedOptionChange: PropTypes.func.isRequired,
92
+ sharing: PropTypes.object
244
93
  }
245
94
 
246
95
  export default ShareByEmail
@@ -1,138 +1,44 @@
1
- import { render, fireEvent, waitFor, screen } from '@testing-library/react'
1
+ import { fireEvent, render, screen } from '@testing-library/react'
2
2
  import React from 'react'
3
3
  import { act } from 'react-dom/test-utils'
4
4
 
5
- import flag from 'cozy-flags'
6
-
7
5
  import { ShareByEmail } from './ShareByEmail'
8
6
  import AppLike from '../../test/AppLike'
9
- import { getOrCreateFromArray } from '../helpers/contacts'
10
-
11
- jest.mock('../helpers/contacts', () => ({
12
- // eslint-disable-next-line no-unused-vars
13
- getOrCreateFromArray: jest.fn((client, recipients, _createContact) => {
14
- // Return the recipients as-is by default (simulating contact creation/lookup)
15
- return Promise.resolve(recipients)
16
- })
17
- }))
18
-
19
- jest.mock('cozy-flags', () =>
20
- jest.fn(flagName => {
21
- if (flagName === 'drive.federated-shared-folder.enabled') {
22
- return false
23
- }
24
- return null
25
- })
26
- )
27
-
28
- describe('ShareByEmailComponent', () => {
29
- const defaultDocument = {
30
- id: 'doc_id',
31
- name: 'documentName'
32
- }
33
-
34
- const onShare = jest.fn()
35
7
 
36
- const setup = ({
37
- sharingDesc = 'test',
38
- document = defaultDocument,
39
- currentRecipients = [],
40
- sharedDrive = false
41
- } = {}) => {
8
+ describe('ShareByEmail (controlled)', () => {
9
+ const setup = (overrides = {}) => {
10
+ const onPendingRecipientsChange = jest.fn()
11
+ const onSelectedOptionChange = jest.fn()
42
12
  const props = {
13
+ document: { id: 'doc_id', name: 'documentName' },
43
14
  documentType: 'Files',
44
- onShare,
45
- document,
46
- sharingDesc,
47
- currentRecipients,
48
- createContact: jest.fn(),
49
- sharedDrive
15
+ currentRecipients: [],
16
+ pendingRecipients: [],
17
+ onPendingRecipientsChange,
18
+ selectedOption: 'readWrite',
19
+ onSelectedOptionChange,
20
+ ...overrides
50
21
  }
51
- return render(
22
+ const utils = render(
52
23
  <AppLike>
53
24
  <ShareByEmail {...props} />
54
25
  </AppLike>
55
26
  )
27
+ return { ...utils, onPendingRecipientsChange, onSelectedOptionChange }
56
28
  }
57
29
 
58
30
  beforeEach(() => {
59
31
  jest.clearAllMocks()
60
- // Reset flag mock to default (non-federated mode)
61
- flag.mockImplementation(flagName => {
62
- if (flagName === 'drive.federated-shared-folder.enabled') {
63
- return false
64
- }
65
- return null
66
- })
67
32
  })
68
33
 
69
- it('shoud call share if submited', async () => {
70
- const sharingDesc = 'test'
71
-
72
- setup({ sharingDesc })
34
+ it('calls onPendingRecipientsChange when an email is picked', () => {
35
+ const { onPendingRecipientsChange } = setup()
73
36
 
74
37
  act(() => {
75
38
  fireEvent.change(screen.getByPlaceholderText('Add contacts'), {
76
39
  target: { value: 'quentin@cozycloud.cc' }
77
40
  })
78
41
  })
79
-
80
- act(() => {
81
- fireEvent.keyPress(screen.getByPlaceholderText('Add contacts'), {
82
- key: 'Enter',
83
- code: 'Enter',
84
- charCode: 13
85
- })
86
- })
87
-
88
- act(() => {
89
- fireEvent.click(screen.getByRole('button', { name: 'Send' }))
90
- })
91
-
92
- await waitFor(() => {
93
- expect(onShare).toHaveBeenCalledWith({
94
- description: sharingDesc,
95
- document: defaultDocument,
96
- openSharing: true,
97
- readOnlyRecipients: [],
98
- recipients: [{ email: 'quentin@cozycloud.cc' }],
99
- sharedDrive: false
100
- })
101
- })
102
- })
103
-
104
- it('should alert user when it has reached the recipients limit for a document', async () => {
105
- flag.mockImplementation(flagName => {
106
- if (flagName === 'sharing.recipients-limit') {
107
- return 2
108
- }
109
- if (flagName === 'drive.federated-shared-folder.enabled') {
110
- return false
111
- }
112
- return null
113
- })
114
-
115
- setup({
116
- currentRecipients: [
117
- {
118
- email: 'alice@gmail.com',
119
- status: 'pending',
120
- type: 'two-way'
121
- },
122
- {
123
- email: 'bob@gmail.com',
124
- status: 'pending',
125
- type: 'two-way'
126
- }
127
- ]
128
- })
129
-
130
- act(() => {
131
- fireEvent.change(screen.getByPlaceholderText('Add contacts'), {
132
- target: { value: 'john@gmail.com' }
133
- })
134
- })
135
-
136
42
  act(() => {
137
43
  fireEvent.keyPress(screen.getByPlaceholderText('Add contacts'), {
138
44
  key: 'Enter',
@@ -141,70 +47,13 @@ describe('ShareByEmailComponent', () => {
141
47
  })
142
48
  })
143
49
 
144
- act(() => {
145
- fireEvent.click(screen.getByRole('button', { name: 'Send' }))
146
- })
147
-
148
- act(() => {
149
- fireEvent.click(screen.getByRole('button', { name: 'I understand' }))
150
- })
151
-
152
- await waitFor(() => {
153
- expect(onShare).not.toHaveBeenCalled()
154
- })
50
+ expect(onPendingRecipientsChange).toHaveBeenCalledWith([
51
+ { email: 'quentin@cozycloud.cc' }
52
+ ])
155
53
  })
156
54
 
157
- describe('federated mode', () => {
158
- beforeEach(() => {
159
- flag.mockImplementation(flagName => {
160
- if (flagName === 'drive.federated-shared-folder.enabled') {
161
- return true
162
- }
163
- return null
164
- })
165
- getOrCreateFromArray.mockResolvedValue([
166
- { _id: 'contact1', email: 'quentin@cozycloud.cc' }
167
- ])
168
- })
169
-
170
- it('should directly share when selecting a recipient in federated mode', async () => {
171
- const sharingDesc = 'test'
172
- const createContact = jest.fn()
173
-
174
- setup({ sharingDesc, createContact })
175
-
176
- act(() => {
177
- fireEvent.change(screen.getByPlaceholderText('Add contacts'), {
178
- target: { value: 'quentin@cozycloud.cc' }
179
- })
180
- })
181
-
182
- act(() => {
183
- fireEvent.keyPress(screen.getByPlaceholderText('Add contacts'), {
184
- key: 'Enter',
185
- code: 'Enter',
186
- charCode: 13
187
- })
188
- })
189
-
190
- await waitFor(() => {
191
- expect(getOrCreateFromArray).toHaveBeenCalledWith(
192
- expect.any(Object),
193
- [{ email: 'quentin@cozycloud.cc' }],
194
- expect.any(Function)
195
- )
196
- })
197
-
198
- await waitFor(() => {
199
- expect(onShare).toHaveBeenCalledWith({
200
- document: defaultDocument,
201
- recipients: [{ _id: 'contact1', email: 'quentin@cozycloud.cc' }],
202
- readOnlyRecipients: [],
203
- description: sharingDesc,
204
- openSharing: false,
205
- sharedDrive: true
206
- })
207
- })
208
- })
55
+ it('always shows the read/write dropdown trigger inside the input', () => {
56
+ setup()
57
+ expect(screen.getByRole('button', { name: 'Editor' })).not.toBeNull()
209
58
  })
210
59
  })
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
2
2
  import React, { useReducer, useState } from 'react'
3
3
 
4
4
  import { useClient } from 'cozy-client'
5
- import flag from 'cozy-flags'
6
5
  import Button from 'cozy-ui/transpiled/react/Buttons'
7
6
  import Icon from 'cozy-ui/transpiled/react/Icon'
8
7
  import CopyIcon from 'cozy-ui/transpiled/react/Icons/Copy'
@@ -36,7 +35,6 @@ const ShareByLink = ({
36
35
  const showGenerateButton = showGenerateLinkButton && !link
37
36
  const showCopyAndSendButtons = !showGenerateButton && isMobile && canShare
38
37
  const showOnlyCopyButton = !showGenerateButton && (!isMobile || !canShare)
39
- const isFederated = flag('drive.federated-shared-folder.enabled')
40
38
 
41
39
  const [isEditDialogOpen, toggleEditDialogOpen] = useReducer(
42
40
  state => !state,
@@ -139,11 +137,7 @@ const ShareByLink = ({
139
137
  }
140
138
 
141
139
  return (
142
- <div
143
- className={
144
- isFederated ? 'u-w-100' : 'u-w-100 u-flex u-flex-justify-center'
145
- }
146
- >
140
+ <div className="u-w-100">
147
141
  {showCopyAndSendButtons && (
148
142
  <>
149
143
  <Button