cozy-sharing 12.0.0 → 12.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 (49) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/components/ConfirmTrustedRecipientsDialog.js +11 -3
  3. package/dist/components/ConfirmTrustedRecipientsDialog.stories.js +39 -0
  4. package/dist/components/ContactSuggestion.js +3 -14
  5. package/dist/components/ContactSuggestion.spec.js +22 -104
  6. package/dist/components/Recipient/GroupRecipient.js +36 -5
  7. package/dist/components/Recipient/GroupRecipient.stories.js +3 -2
  8. package/dist/components/Recipient/GroupRecipientDetail.js +43 -0
  9. package/dist/components/Recipient/GroupRecipientDetail.stories.js +55 -0
  10. package/dist/components/Recipient/GroupRecipientDetailWithAccess.js +44 -0
  11. package/dist/components/Recipient/GroupRecipientDetailWithoutAccess.js +45 -0
  12. package/dist/components/Recipient/LinkRecipient.js +5 -1
  13. package/dist/components/Recipient/MemberRecipient.js +5 -1
  14. package/dist/components/Recipient/RecipientList.stories.js +3 -0
  15. package/dist/components/ShareAutosuggest.js +0 -3
  16. package/dist/components/ShareDialogCozyToCozy.js +9 -3
  17. package/dist/components/ShareDialogOnlyByLink.js +12 -4
  18. package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +1 -0
  19. package/dist/components/ShareModal/SharingDetailsModal.js +7 -1
  20. package/dist/components/ShareRecipientsInput.js +23 -1
  21. package/dist/components/ShareRecipientsInput.spec.js +206 -115
  22. package/dist/components/WhoHasAccess.js +1 -3
  23. package/jest.config.js +2 -1
  24. package/locales/en.json +13 -0
  25. package/locales/fr.json +13 -0
  26. package/package.json +2 -2
  27. package/src/components/ConfirmTrustedRecipientsDialog.jsx +24 -13
  28. package/src/components/ConfirmTrustedRecipientsDialog.stories.jsx +45 -0
  29. package/src/components/ContactSuggestion.jsx +3 -17
  30. package/src/components/ContactSuggestion.spec.jsx +16 -99
  31. package/src/components/Recipient/GroupRecipient.jsx +51 -24
  32. package/src/components/Recipient/GroupRecipient.stories.jsx +3 -2
  33. package/src/components/Recipient/GroupRecipientDetail.jsx +50 -0
  34. package/src/components/Recipient/GroupRecipientDetail.stories.jsx +53 -0
  35. package/src/components/Recipient/GroupRecipientDetailWithAccess.jsx +57 -0
  36. package/src/components/Recipient/GroupRecipientDetailWithoutAccess.jsx +60 -0
  37. package/src/components/Recipient/LinkRecipient.jsx +3 -1
  38. package/src/components/Recipient/MemberRecipient.jsx +3 -1
  39. package/src/components/Recipient/RecipientList.stories.jsx +3 -0
  40. package/src/components/ShareAutosuggest.jsx +1 -6
  41. package/src/components/ShareDialogCozyToCozy.jsx +40 -36
  42. package/src/components/ShareDialogOnlyByLink.jsx +22 -8
  43. package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +1 -0
  44. package/src/components/ShareModal/SharingDetailsModal.jsx +10 -1
  45. package/src/components/ShareRecipientsInput.jsx +19 -1
  46. package/src/components/ShareRecipientsInput.spec.jsx +170 -108
  47. package/src/components/WhoHasAccess.jsx +1 -1
  48. package/src/components/__snapshots__/ContactSuggestion.spec.jsx.snap +0 -1192
  49. package/src/components/__snapshots__/ShareRecipientsInput.spec.jsx.snap +0 -99
@@ -2,6 +2,7 @@ import cx from 'classnames'
2
2
  import React from 'react'
3
3
 
4
4
  import Typography from 'cozy-ui/transpiled/react/Typography'
5
+ import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints'
5
6
  import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'
6
7
 
7
8
  import { default as DumbShareByEmail } from './ShareByEmail'
@@ -39,47 +40,49 @@ const SharingContent = ({
39
40
  onRevokeLink
40
41
  }) => {
41
42
  const { t } = useI18n()
43
+ const { isMobile } = useBreakpoints()
42
44
 
43
45
  return (
44
46
  <div className={cx(styles['share-modal-content'])}>
45
- {showShareOnlyByLink && (
46
- <div className={styles['share-byemail-onlybylink']}>
47
- {t(`${documentType}.share.shareByEmail.onlyByLink`, {
48
- type: t(
49
- `${documentType}.share.shareByEmail.type.${
50
- document.type === 'directory' ? 'folder' : 'file'
51
- }`
52
- )
53
- })}{' '}
54
- <strong>
55
- {t(
56
- `${documentType}.share.shareByEmail.${
57
- hasSharedParent ? 'hasSharedParent' : 'hasSharedChild'
58
- }`
59
- )}
60
- </strong>
61
- </div>
62
- )}
63
- <Typography variant="h6" className="u-mb-1-half">
64
- {t('Share.contacts.whoHasAccess')}
65
- </Typography>
66
- {showShareByEmail && (
67
- <DumbShareByEmail
68
- contacts={contacts}
69
- contactGroups={contactGroups}
70
- createContact={createContact}
71
- currentRecipients={recipients}
72
- document={document}
73
- documentType={documentType}
74
- needsContactsPermission={needsContactsPermission}
75
- onShare={onShare}
76
- sharing={sharing}
77
- sharingDesc={sharingDesc}
78
- />
79
- )}
47
+ <div className={cx('u-pt-1-half', isMobile ? 'u-ph-1' : 'u-ph-2')}>
48
+ {showShareOnlyByLink && (
49
+ <div className={styles['share-byemail-onlybylink']}>
50
+ {t(`${documentType}.share.shareByEmail.onlyByLink`, {
51
+ type: t(
52
+ `${documentType}.share.shareByEmail.type.${
53
+ document.type === 'directory' ? 'folder' : 'file'
54
+ }`
55
+ )
56
+ })}{' '}
57
+ <strong>
58
+ {t(
59
+ `${documentType}.share.shareByEmail.${
60
+ hasSharedParent ? 'hasSharedParent' : 'hasSharedChild'
61
+ }`
62
+ )}
63
+ </strong>
64
+ </div>
65
+ )}
66
+ <Typography variant="h6" className="u-mb-1-half">
67
+ {t('Share.contacts.whoHasAccess')}
68
+ </Typography>
69
+ {showShareByEmail && (
70
+ <DumbShareByEmail
71
+ contacts={contacts}
72
+ contactGroups={contactGroups}
73
+ createContact={createContact}
74
+ currentRecipients={recipients}
75
+ document={document}
76
+ documentType={documentType}
77
+ needsContactsPermission={needsContactsPermission}
78
+ onShare={onShare}
79
+ sharing={sharing}
80
+ sharingDesc={sharingDesc}
81
+ />
82
+ )}
83
+ </div>
80
84
  {showWhoHasAccess && (
81
85
  <WhoHasAccess
82
- className="u-mt-half"
83
86
  document={document}
84
87
  documentType={documentType}
85
88
  isOwner={isOwner}
@@ -129,6 +132,7 @@ const ShareDialogCozyToCozy = ({
129
132
  dialogContentOnShare={SharingContent}
130
133
  dialogActionsOnShare={showShareByLink ? DumbShareByLink : null}
131
134
  dialogTitleOnShare={SharingTitleFunction({ documentType, document })}
135
+ disableGutters
132
136
  />
133
137
  )
134
138
  }
@@ -1,7 +1,9 @@
1
+ import cx from 'classnames'
1
2
  import React from 'react'
2
3
 
3
4
  import { Dialog } from 'cozy-ui/transpiled/react/CozyDialogs'
4
5
  import Typography from 'cozy-ui/transpiled/react/Typography'
6
+ import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints'
5
7
  import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'
6
8
 
7
9
  import { default as DumbShareByLink } from './ShareByLink'
@@ -22,8 +24,11 @@ const ShareDialogOnlyByLink = ({
22
24
  permissions
23
25
  }) => {
24
26
  const { t } = useI18n()
27
+ const { isMobile } = useBreakpoints()
28
+
25
29
  return (
26
30
  <Dialog
31
+ disableGutters
27
32
  disableEnforceFocus
28
33
  open={true}
29
34
  onClose={onClose}
@@ -32,11 +37,13 @@ const ShareDialogOnlyByLink = ({
32
37
  })}
33
38
  content={
34
39
  <>
35
- <Typography variant="h6">
40
+ <Typography
41
+ variant="h6"
42
+ className={cx(isMobile ? 'u-ph-1 u-mt-1' : 'u-ph-2 u-mt-1-half')}
43
+ >
36
44
  {t('Share.contacts.whoHasAccess')}
37
45
  </Typography>
38
46
  <WhoHasAccess
39
- className="u-mt-half"
40
47
  document={document}
41
48
  documentType={documentType}
42
49
  isOwner={isOwner}
@@ -48,12 +55,19 @@ const ShareDialogOnlyByLink = ({
48
55
  onUpdateShareLinkPermissions={onUpdateShareLinkPermissions}
49
56
  onRevokeLink={onRevokeLink}
50
57
  />
51
- <DumbShareByLink
52
- link={link}
53
- document={document}
54
- documentType={documentType}
55
- onEnable={onShareByLink}
56
- />
58
+ <div
59
+ className={cx(
60
+ 'u-mt-half',
61
+ isMobile ? 'u-ph-1 u-mb-1' : 'u-ph-2 u-mb-1-half'
62
+ )}
63
+ >
64
+ <DumbShareByLink
65
+ link={link}
66
+ document={document}
67
+ documentType={documentType}
68
+ onEnable={onShareByLink}
69
+ />
70
+ </div>
57
71
  </>
58
72
  }
59
73
  />
@@ -282,6 +282,7 @@ const ShareDialogTwoStepsConfirmationContainer = ({
282
282
 
283
283
  return (
284
284
  <FixedDialog
285
+ disableGutters
285
286
  disableEnforceFocus
286
287
  open={true}
287
288
  onClose={onClose}
@@ -1,8 +1,10 @@
1
+ import cx from 'classnames'
1
2
  import PropTypes from 'prop-types'
2
3
  import React from 'react'
3
4
 
4
5
  import { Dialog } from 'cozy-ui/transpiled/react/CozyDialogs'
5
6
  import Divider from 'cozy-ui/transpiled/react/Divider'
7
+ import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints'
6
8
  import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'
7
9
 
8
10
  import { getDisplayName } from '../../models'
@@ -21,16 +23,23 @@ export const SharingDetailsModal = ({
21
23
  onRevokeSelf
22
24
  }) => {
23
25
  const { t, f } = useI18n()
26
+ const { isMobile } = useBreakpoints()
24
27
 
25
28
  return (
26
29
  <Dialog
30
+ disableGutters
27
31
  open={true}
28
32
  onClose={onClose}
29
33
  className={styles['share-modal']}
30
34
  title={t(`${documentType}.share.details.title`)}
31
35
  content={
32
36
  <div className={styles['share-modal-content']}>
33
- <div className={styles['share-details']}>
37
+ <div
38
+ className={cx(
39
+ styles['share-details'],
40
+ isMobile ? 'u-ph-1 u-pt-1-half' : 'u-ph-2 u-pt-1'
41
+ )}
42
+ >
34
43
  <OwnerIdentity
35
44
  name={t(`${documentType}.share.sharedBy`, {
36
45
  name: getDisplayName(owner)
@@ -1,3 +1,4 @@
1
+ import get from 'lodash/get'
1
2
  import PropTypes from 'prop-types'
2
3
  import React, { useState, useEffect } from 'react'
3
4
 
@@ -42,7 +43,24 @@ const ShareRecipientsInput = ({
42
43
  if (contacts.hasMore || contacts.fetchStatus === 'loading') {
43
44
  return contacts.data
44
45
  } else {
45
- return [...contacts.data, ...contactGroups.data]
46
+ const contactGroupsWithCount = contactGroups.data
47
+ .map(contactGroup => ({
48
+ ...contactGroup,
49
+ membersCount: contacts.data.reduce((total, contact) => {
50
+ if (
51
+ get(contact, 'relationships.groups.data', [])
52
+ .map(group => group._id)
53
+ .includes(contactGroup._id)
54
+ ) {
55
+ return total + 1
56
+ }
57
+
58
+ return total
59
+ }, 0)
60
+ }))
61
+ .filter(contactGroup => contactGroup.membersCount > 0)
62
+
63
+ return [...contacts.data, ...contactGroupsWithCount]
46
64
  }
47
65
  }
48
66
 
@@ -1,138 +1,200 @@
1
- import { shallow } from 'enzyme'
1
+ import { render, screen } from '@testing-library/react'
2
2
  import React from 'react'
3
3
 
4
4
  import ShareRecipientsInput from './ShareRecipientsInput'
5
5
 
6
- describe('ShareRecipientsInput component', () => {
7
- it('should match snapshot', () => {
8
- const props = {
9
- client: '',
10
- label: 'Recipients',
11
- contacts: {
12
- id: 'contacts',
13
- fetchStatus: 'loaded',
14
- hasMore: false,
15
- data: [
16
- {
17
- id: 'df563cc4-6440',
18
- _id: 'df563cc4-6440',
19
- _type: 'io.cozy.contacts',
20
- name: {
21
- givenName: 'Michale',
22
- familyName: 'Russel'
23
- }
24
- },
25
- {
26
- id: '5a3b4ccf-c257',
27
- _id: '5a3b4ccf-c257',
28
- _type: 'io.cozy.contacts',
29
- name: {
30
- givenName: 'Teagan',
31
- familyName: 'Wolf'
32
- }
6
+ jest.mock('./ShareAutosuggest', () => ({
7
+ __esModule: true,
8
+ default: ({ contactsAndGroups }) => {
9
+ return (
10
+ <ul>
11
+ {contactsAndGroups.map(contactOrGroup => {
12
+ if (contactOrGroup._type === 'io.cozy.contacts') {
13
+ return (
14
+ <li key={contactOrGroup.id}>
15
+ {contactOrGroup.name.givenName} {contactOrGroup.name.familyName}
16
+ </li>
17
+ )
18
+ } else {
19
+ return <li key={contactOrGroup.id}>{contactOrGroup.name}</li>
33
20
  }
34
- ]
35
- },
36
- contactGroups: {
37
- id: 'groups',
38
- fetchStatus: 'loaded',
39
- hasMore: false,
40
- data: [
41
- {
42
- id: 'fe86af20-c6c5',
43
- name: "The Night's Watch",
44
- _id: 'fe86af20-c6c5',
45
- _type: 'io.cozy.contacts.groups'
46
- },
47
- {
48
- id: '3d8193ab-2ce4',
49
- name: 'The North',
50
- _id: '3d8193ab-2ce4',
51
- _type: 'io.cozy.contacts.groups'
21
+ })}
22
+ </ul>
23
+ )
24
+ }
25
+ }))
26
+
27
+ describe('ShareRecipientsInput component', () => {
28
+ const setup = ({ contacts, contactGroups }) => {
29
+ render(
30
+ <ShareRecipientsInput
31
+ contacts={contacts}
32
+ contactGroups={contactGroups}
33
+ onPick={() => {}}
34
+ onRemove={() => {}}
35
+ placeholder="Enter recipients here"
36
+ />
37
+ )
38
+ }
39
+
40
+ it('should include groups and contacts when are loaded', async () => {
41
+ const contacts = {
42
+ id: 'contacts',
43
+ fetchStatus: 'loaded',
44
+ hasMore: false,
45
+ data: [
46
+ {
47
+ id: 'df563cc4-6440',
48
+ _id: 'df563cc4-6440',
49
+ _type: 'io.cozy.contacts',
50
+ name: {
51
+ givenName: 'Michale',
52
+ familyName: 'Russel'
52
53
  }
53
- ]
54
- },
55
- recipients: [
54
+ },
56
55
  {
57
56
  id: '5a3b4ccf-c257',
57
+ _id: '5a3b4ccf-c257',
58
+ _type: 'io.cozy.contacts',
58
59
  name: {
59
60
  givenName: 'Teagan',
60
61
  familyName: 'Wolf'
62
+ },
63
+ relationships: {
64
+ groups: {
65
+ data: [
66
+ {
67
+ _id: 'fe86af20-c6c5',
68
+ _type: 'io.cozy.contacts.groups'
69
+ }
70
+ ]
71
+ }
61
72
  }
62
73
  }
63
- ],
64
- onPick: jest.fn().mockName('onPick'),
65
- onRemove: jest.fn().mockName('onRemove'),
66
- placeholder: 'Enter recipients here'
74
+ ]
75
+ }
76
+ const contactGroups = {
77
+ id: 'groups',
78
+ fetchStatus: 'loaded',
79
+ hasMore: false,
80
+ data: [
81
+ {
82
+ id: 'fe86af20-c6c5',
83
+ name: "The Night's Watch",
84
+ _id: 'fe86af20-c6c5',
85
+ _type: 'io.cozy.contacts.groups'
86
+ }
87
+ ]
67
88
  }
68
- const jsx = <ShareRecipientsInput {...props} />
69
- const wrapper = shallow(jsx)
70
- expect(wrapper).toMatchSnapshot()
89
+
90
+ setup({ contacts, contactGroups })
91
+
92
+ expect(screen.getByText('Michale Russel')).toBeInTheDocument()
93
+ expect(screen.getByText('Teagan Wolf')).toBeInTheDocument()
94
+ expect(screen.getByText("The Night's Watch")).toBeInTheDocument()
71
95
  })
72
96
 
73
- it('should include groups only if all contacts are loaded', () => {
74
- const props = {
75
- client: '',
76
- label: 'Recipients',
77
- contacts: {
78
- id: 'contacts',
79
- fetchStatus: 'loading',
80
- hasMore: false,
81
- data: [
82
- {
83
- id: 'df563cc4-6440',
84
- _id: 'df563cc4-6440',
85
- _type: 'io.cozy.contacts',
86
- name: {
87
- givenName: 'Michale',
88
- familyName: 'Russel'
89
- }
97
+ it('should include groups only if all contacts are loaded', async () => {
98
+ const contacts = {
99
+ id: 'contacts',
100
+ fetchStatus: 'loading',
101
+ hasMore: false,
102
+ data: [
103
+ {
104
+ id: 'df563cc4-6440',
105
+ _id: 'df563cc4-6440',
106
+ _type: 'io.cozy.contacts',
107
+ name: {
108
+ givenName: 'Michale',
109
+ familyName: 'Russel'
110
+ }
111
+ },
112
+ {
113
+ id: '5a3b4ccf-c257',
114
+ _id: '5a3b4ccf-c257',
115
+ _type: 'io.cozy.contacts',
116
+ name: {
117
+ givenName: 'Teagan',
118
+ familyName: 'Wolf'
90
119
  },
91
- {
92
- id: '5a3b4ccf-c257',
93
- _id: '5a3b4ccf-c257',
94
- _type: 'io.cozy.contacts',
95
- name: {
96
- givenName: 'Teagan',
97
- familyName: 'Wolf'
120
+ relationships: {
121
+ groups: {
122
+ data: [
123
+ {
124
+ _id: 'fe86af20-c6c5',
125
+ _type: 'io.cozy.contacts.groups'
126
+ }
127
+ ]
98
128
  }
99
129
  }
100
- ]
101
- },
102
- contactGroups: {
103
- id: 'groups',
104
- fetchStatus: 'loaded',
105
- hasMore: false,
106
- data: [
107
- {
108
- id: 'fe86af20-c6c5',
109
- name: "The Night's Watch",
110
- _id: 'fe86af20-c6c5',
111
- _type: 'io.cozy.contacts.groups'
112
- },
113
- {
114
- id: '3d8193ab-2ce4',
115
- name: 'The North',
116
- _id: '3d8193ab-2ce4',
117
- _type: 'io.cozy.contacts.groups'
130
+ }
131
+ ]
132
+ }
133
+ const contactGroups = {
134
+ id: 'groups',
135
+ fetchStatus: 'loaded',
136
+ hasMore: false,
137
+ data: [
138
+ {
139
+ id: 'fe86af20-c6c5',
140
+ name: "The Night's Watch",
141
+ _id: 'fe86af20-c6c5',
142
+ _type: 'io.cozy.contacts.groups'
143
+ }
144
+ ]
145
+ }
146
+
147
+ setup({ contacts, contactGroups })
148
+
149
+ expect(screen.getByText('Michale Russel')).toBeInTheDocument()
150
+ expect(screen.getByText('Teagan Wolf')).toBeInTheDocument()
151
+ expect(screen.queryByText("The Night's Watch")).toBeNull()
152
+ })
153
+
154
+ it('should include groups only if they have more than one member', async () => {
155
+ const contacts = {
156
+ id: 'contacts',
157
+ fetchStatus: 'loaded',
158
+ hasMore: false,
159
+ data: [
160
+ {
161
+ id: 'df563cc4-6440',
162
+ _id: 'df563cc4-6440',
163
+ _type: 'io.cozy.contacts',
164
+ name: {
165
+ givenName: 'Michale',
166
+ familyName: 'Russel'
118
167
  }
119
- ]
120
- },
121
- recipients: [
168
+ },
122
169
  {
123
170
  id: '5a3b4ccf-c257',
171
+ _id: '5a3b4ccf-c257',
172
+ _type: 'io.cozy.contacts',
124
173
  name: {
125
174
  givenName: 'Teagan',
126
175
  familyName: 'Wolf'
127
176
  }
128
177
  }
129
- ],
130
- onPick: jest.fn().mockName('onPick'),
131
- onRemove: jest.fn().mockName('onRemove'),
132
- placeholder: 'Enter recipients here'
178
+ ]
133
179
  }
134
- const jsx = <ShareRecipientsInput {...props} />
135
- const wrapper = shallow(jsx)
136
- expect(wrapper).toMatchSnapshot()
180
+ const contactGroups = {
181
+ id: 'groups',
182
+ fetchStatus: 'loaded',
183
+ hasMore: false,
184
+ data: [
185
+ {
186
+ id: 'fe86af20-c6c5',
187
+ name: "The Night's Watch",
188
+ _id: 'fe86af20-c6c5',
189
+ _type: 'io.cozy.contacts.groups'
190
+ }
191
+ ]
192
+ }
193
+
194
+ setup({ contacts, contactGroups })
195
+
196
+ expect(screen.getByText('Michale Russel')).toBeInTheDocument()
197
+ expect(screen.getByText('Teagan Wolf')).toBeInTheDocument()
198
+ expect(screen.queryByText("The Night's Watch")).toBeNull()
137
199
  })
138
200
  })
@@ -49,7 +49,7 @@ const WhoHasAccess = ({
49
49
  <RecipientWaitingForConfirmationAlert
50
50
  recipientsToBeConfirmed={recipientsToBeConfirmed}
51
51
  />
52
- <List disablePadding>
52
+ <List>
53
53
  {link && (
54
54
  <LinkRecipient
55
55
  document={document}