cozy-sharing 30.3.0 → 31.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.
@@ -0,0 +1,98 @@
1
+ import { useState } from 'react'
2
+
3
+ import { useClient } from 'cozy-client'
4
+ import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
5
+ import { useI18n } from 'twake-i18n'
6
+
7
+ import {
8
+ RECIPIENT_INDEX_PREFIX,
9
+ formatRecipients,
10
+ mergeAndDeduplicateRecipients,
11
+ moveRecipientToReadOnly,
12
+ moveRecipientToReadWrite
13
+ } from './helpers'
14
+ import { Contact } from '../../models'
15
+
16
+ export const useSharedDrive = ({ onSuccess }) => {
17
+ const client = useClient()
18
+ const { t } = useI18n()
19
+ const { showAlert } = useAlert()
20
+
21
+ const [sharedDriveRecipients, setSharedDriveRecipients] = useState({
22
+ recipients: [],
23
+ readOnlyRecipients: []
24
+ })
25
+ const [sharedDriveName, setSharedDriveName] = useState('')
26
+
27
+ const handleSharedDriveNameChange = event => {
28
+ setSharedDriveName(event.target.value)
29
+ }
30
+
31
+ const onShare = params => {
32
+ setSharedDriveRecipients(prev => ({
33
+ recipients: mergeAndDeduplicateRecipients([
34
+ prev.recipients,
35
+ params.recipients
36
+ ]),
37
+ readOnlyRecipients: mergeAndDeduplicateRecipients([
38
+ prev.readOnlyRecipients,
39
+ params.readOnlyRecipients
40
+ ])
41
+ }))
42
+ }
43
+
44
+ const onCreate = async () => {
45
+ try {
46
+ await client.collection('io.cozy.sharings').createSharedDrive({
47
+ name: sharedDriveName,
48
+ description: sharedDriveName,
49
+ recipients: sharedDriveRecipients.recipients,
50
+ readOnlyRecipients: sharedDriveRecipients.readOnlyRecipients
51
+ })
52
+
53
+ showAlert({
54
+ message: t('SharedDrive.sharedDriveModal.successNotification'),
55
+ severity: 'success',
56
+ variant: 'filled'
57
+ })
58
+
59
+ onSuccess()
60
+ } catch (_err) {
61
+ showAlert({
62
+ message: t('SharedDrive.sharedDriveModal.errorNotification'),
63
+ severity: 'error',
64
+ variant: 'filled'
65
+ })
66
+ }
67
+ }
68
+
69
+ const onSetType = (index, newType) => {
70
+ const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
71
+ setSharedDriveRecipients(prev =>
72
+ newType === 'two-way'
73
+ ? moveRecipientToReadWrite(prev, _id)
74
+ : moveRecipientToReadOnly(prev, _id)
75
+ )
76
+ }
77
+
78
+ const onRevoke = index => {
79
+ const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
80
+ setSharedDriveRecipients(prev => ({
81
+ recipients: prev.recipients.filter(r => r._id !== _id),
82
+ readOnlyRecipients: prev.readOnlyRecipients.filter(r => r._id !== _id)
83
+ }))
84
+ }
85
+
86
+ const createContact = contact => client.create(Contact.doctype, contact)
87
+
88
+ return {
89
+ sharedDriveName,
90
+ recipients: formatRecipients(sharedDriveRecipients),
91
+ handleSharedDriveNameChange,
92
+ onShare,
93
+ onCreate,
94
+ onSetType,
95
+ onRevoke,
96
+ createContact
97
+ }
98
+ }
@@ -0,0 +1,155 @@
1
+ import { renderHook, act } from '@testing-library/react-hooks'
2
+
3
+ import { useSharedDrive } from './useSharedDrive'
4
+
5
+ const mockShowAlert = jest.fn()
6
+ const mockCreateSharedDrive = jest.fn()
7
+ const mockCollection = jest.fn(() => ({
8
+ createSharedDrive: mockCreateSharedDrive
9
+ }))
10
+ const mockT = jest.fn(key => key)
11
+
12
+ jest.mock('cozy-client', () => ({
13
+ useClient: jest.fn(() => ({
14
+ collection: mockCollection
15
+ })),
16
+ models: {
17
+ contact: {
18
+ getPrimaryEmail: jest.fn(r =>
19
+ Array.isArray(r.email) && r.email[0] ? r.email[0].address : ''
20
+ ),
21
+ getDisplayName: jest.fn(r => r.displayName || r.name || '')
22
+ }
23
+ }
24
+ }))
25
+
26
+ jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
27
+ useAlert: jest.fn(() => ({ showAlert: mockShowAlert }))
28
+ }))
29
+
30
+ jest.mock('twake-i18n', () => ({
31
+ useI18n: jest.fn(() => ({ t: mockT }))
32
+ }))
33
+
34
+ jest.mock('../../models', () => ({
35
+ Contact: { doctype: 'io.cozy.contacts' }
36
+ }))
37
+
38
+ describe('useSharedDrive', () => {
39
+ beforeEach(() => {
40
+ jest.clearAllMocks()
41
+ })
42
+
43
+ it('returns empty initial state', () => {
44
+ const { result } = renderHook(() =>
45
+ useSharedDrive({ onSuccess: jest.fn() })
46
+ )
47
+ expect(result.current.sharedDriveName).toBe('')
48
+ expect(result.current.recipients).toEqual([])
49
+ })
50
+
51
+ it('handleSharedDriveNameChange updates the name', () => {
52
+ const { result } = renderHook(() =>
53
+ useSharedDrive({ onSuccess: jest.fn() })
54
+ )
55
+ act(() => {
56
+ result.current.handleSharedDriveNameChange({
57
+ target: { value: 'My Drive' }
58
+ })
59
+ })
60
+ expect(result.current.sharedDriveName).toBe('My Drive')
61
+ })
62
+
63
+ it('onRevoke removes a recipient from both lists', () => {
64
+ const { result } = renderHook(() =>
65
+ useSharedDrive({ onSuccess: jest.fn() })
66
+ )
67
+ act(() => {
68
+ result.current.onShare({
69
+ recipients: [{ _id: 'abc', displayName: 'Alice', email: [] }],
70
+ readOnlyRecipients: []
71
+ })
72
+ })
73
+ act(() => {
74
+ result.current.onRevoke(`virtual-shared-drive-sharing-abc`)
75
+ })
76
+ expect(result.current.recipients).toHaveLength(0)
77
+ })
78
+
79
+ it('onSetType moves a recipient to read-only', () => {
80
+ const { result } = renderHook(() =>
81
+ useSharedDrive({ onSuccess: jest.fn() })
82
+ )
83
+ act(() => {
84
+ result.current.onShare({
85
+ recipients: [{ _id: 'abc', displayName: 'Alice', email: [] }],
86
+ readOnlyRecipients: []
87
+ })
88
+ })
89
+ act(() => {
90
+ result.current.onSetType(`virtual-shared-drive-sharing-abc`, 'one-way')
91
+ })
92
+ expect(result.current.recipients[0].type).toBe('one-way')
93
+ })
94
+
95
+ it('onSetType moves a recipient to read-write', () => {
96
+ const { result } = renderHook(() =>
97
+ useSharedDrive({ onSuccess: jest.fn() })
98
+ )
99
+ act(() => {
100
+ result.current.onShare({
101
+ recipients: [],
102
+ readOnlyRecipients: [{ _id: 'xyz', displayName: 'Bob', email: [] }]
103
+ })
104
+ })
105
+ act(() => {
106
+ result.current.onSetType(`virtual-shared-drive-sharing-xyz`, 'two-way')
107
+ })
108
+ expect(result.current.recipients).toHaveLength(1)
109
+ expect(result.current.recipients[0].type).toBe('two-way')
110
+ })
111
+
112
+ it('onCreate calls createSharedDrive, showAlert with success, and onSuccess', async () => {
113
+ const onSuccess = jest.fn()
114
+ mockCreateSharedDrive.mockResolvedValue({})
115
+
116
+ const { result } = renderHook(() => useSharedDrive({ onSuccess }))
117
+
118
+ act(() => {
119
+ result.current.handleSharedDriveNameChange({
120
+ target: { value: 'My Drive' }
121
+ })
122
+ })
123
+
124
+ await act(async () => {
125
+ await result.current.onCreate()
126
+ })
127
+
128
+ expect(mockCreateSharedDrive).toHaveBeenCalledWith(
129
+ expect.objectContaining({
130
+ name: 'My Drive',
131
+ description: 'My Drive'
132
+ })
133
+ )
134
+ expect(mockShowAlert).toHaveBeenCalledWith(
135
+ expect.objectContaining({ severity: 'success' })
136
+ )
137
+ expect(onSuccess).toHaveBeenCalledTimes(1)
138
+ })
139
+
140
+ it('onCreate shows error alert and does not call onSuccess on failure', async () => {
141
+ const onSuccess = jest.fn()
142
+ mockCreateSharedDrive.mockRejectedValue(new Error('network error'))
143
+
144
+ const { result } = renderHook(() => useSharedDrive({ onSuccess }))
145
+
146
+ await act(async () => {
147
+ await result.current.onCreate()
148
+ })
149
+
150
+ expect(mockShowAlert).toHaveBeenCalledWith(
151
+ expect.objectContaining({ severity: 'error' })
152
+ )
153
+ expect(onSuccess).not.toHaveBeenCalled()
154
+ })
155
+ })
package/src/index.jsx CHANGED
@@ -20,6 +20,7 @@ export { default as OwnerRecipientDefaultLite } from './components/Recipient/Own
20
20
  export { SharedRecipientsList } from './SharedRecipientsList'
21
21
  export { ShareButton } from './ShareButton'
22
22
  export { SharedDriveModal } from './components/SharedDrive/SharedDriveModal'
23
+ export { SharedDriveForm } from './components/SharedDrive/SharedDriveForm'
23
24
  export { FederatedFolderModal } from './components/FederatedFolder/FederatedFolderModal'
24
25
  export { ShareModal } from './components/ShareModal/ShareModal'
25
26
  export { RefreshableSharings } from './RefreshableSharings'
@@ -1,78 +0,0 @@
1
- import PropTypes from 'prop-types';
2
- import React from 'react';
3
- import { useI18n } from 'twake-i18n';
4
- import withLocales from '../../hoc/withLocales';
5
- import { DumbBatchSharedFolderModal } from '../SharedFolder/DumbBatchSharedFolderModal';
6
- export var DumbSharedDriveModal = withLocales(function (_ref) {
7
- var title = _ref.title,
8
- document = _ref.document,
9
- sharedDriveName = _ref.sharedDriveName,
10
- handleSharedDriveNameChange = _ref.handleSharedDriveNameChange,
11
- createContact = _ref.createContact,
12
- recipients = _ref.recipients,
13
- currentRecipients = _ref.currentRecipients,
14
- onRevoke = _ref.onRevoke,
15
- onSetType = _ref.onSetType,
16
- onCreate = _ref.onCreate,
17
- onSend = _ref.onSend,
18
- onClose = _ref.onClose,
19
- onShare = _ref.onShare,
20
- onRename = _ref.onRename,
21
- _ref$showNameField = _ref.showNameField,
22
- showNameField = _ref$showNameField === void 0 ? true : _ref$showNameField,
23
- sharingLink = _ref.sharingLink,
24
- autoOpenShareRestriction = _ref.autoOpenShareRestriction,
25
- showGenerateLinkButton = _ref.showGenerateLinkButton;
26
-
27
- var _useI18n = useI18n(),
28
- t = _useI18n.t;
29
-
30
- return /*#__PURE__*/React.createElement(DumbBatchSharedFolderModal, {
31
- title: title,
32
- document: document,
33
- folderName: sharedDriveName,
34
- handleFolderNameChange: handleSharedDriveNameChange,
35
- createContact: createContact,
36
- recipients: recipients,
37
- currentRecipients: currentRecipients,
38
- onRevoke: onRevoke,
39
- onSetType: onSetType,
40
- onCreate: onCreate,
41
- onSend: onSend,
42
- onClose: onClose,
43
- onShare: onShare,
44
- onRename: onRename,
45
- showNameField: showNameField,
46
- sharingLink: sharingLink,
47
- nameLabel: t('SharedDrive.sharedDriveModal.nameLabel'),
48
- addPeopleLabel: t('SharedDrive.sharedDriveModal.addPeople'),
49
- addButtonLabel: t('SharedDrive.sharedDriveModal.add'),
50
- cancelLabel: t('SharedDrive.sharedDriveModal.cancel'),
51
- createLabel: t('SharedDrive.sharedDriveModal.create'),
52
- shareLabel: t('FederatedFolder.share'),
53
- saveLabel: t('SharedDrive.sharedDriveModal.save'),
54
- autoOpenShareRestriction: autoOpenShareRestriction,
55
- showGenerateLinkButton: showGenerateLinkButton
56
- });
57
- });
58
- DumbSharedDriveModal.propTypes = {
59
- title: PropTypes.string,
60
- document: PropTypes.object,
61
- sharedDriveName: PropTypes.string,
62
- handleSharedDriveNameChange: PropTypes.func,
63
- createContact: PropTypes.func.isRequired,
64
- recipients: PropTypes.array,
65
- currentRecipients: PropTypes.array,
66
- onRevoke: PropTypes.func.isRequired,
67
- onSetType: PropTypes.func.isRequired,
68
- onCreate: PropTypes.func,
69
- onSend: PropTypes.func,
70
- onClose: PropTypes.func.isRequired,
71
- onShare: PropTypes.func.isRequired,
72
- onRename: PropTypes.func,
73
- showNameField: PropTypes.bool,
74
- sharingLink: PropTypes.string,
75
- autoOpenShareRestriction: PropTypes.bool,
76
- showGenerateLinkButton: PropTypes.bool
77
- };
78
- export default DumbSharedDriveModal;
@@ -1,110 +0,0 @@
1
- import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
- import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
- import _regeneratorRuntime from "@babel/runtime/regenerator";
4
- import PropTypes from 'prop-types';
5
- import React, { useState } from 'react';
6
- import { useClient } from 'cozy-client';
7
- import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert';
8
- import { useI18n } from 'twake-i18n';
9
- import { DumbSharedDriveModal } from './DumbSharedDriveModal';
10
- import withLocales from '../../hoc/withLocales';
11
- import { useSharingContext } from '../../hooks/useSharingContext';
12
- import { Contact } from '../../models';
13
- export var SharedDriveEditModal = withLocales(function (_ref) {
14
- var document = _ref.document,
15
- sharing = _ref.sharing,
16
- recipients = _ref.recipients,
17
- onShare = _ref.onShare,
18
- onRevoke = _ref.onRevoke,
19
- onClose = _ref.onClose,
20
- autoOpenShareRestriction = _ref.autoOpenShareRestriction,
21
- showGenerateLinkButton = _ref.showGenerateLinkButton;
22
- var client = useClient();
23
-
24
- var _useI18n = useI18n(),
25
- t = _useI18n.t;
26
-
27
- var _useAlert = useAlert(),
28
- showAlert = _useAlert.showAlert;
29
-
30
- var _useSharingContext = useSharingContext(),
31
- renameSharedDrive = _useSharingContext.renameSharedDrive;
32
-
33
- var createContact = function createContact(contact) {
34
- return client.create(Contact.doctype, contact);
35
- };
36
-
37
- var _useState = useState(sharing === null || sharing === void 0 ? void 0 : sharing.description),
38
- _useState2 = _slicedToArray(_useState, 2),
39
- name = _useState2[0],
40
- setName = _useState2[1];
41
-
42
- var handleNameChange = function handleNameChange(event) {
43
- setName(event.target.value);
44
- };
45
-
46
- var onRename = /*#__PURE__*/function () {
47
- var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(value) {
48
- return _regeneratorRuntime.wrap(function _callee$(_context) {
49
- while (1) switch (_context.prev = _context.next) {
50
- case 0:
51
- _context.prev = 0;
52
- _context.next = 3;
53
- return renameSharedDrive(document, value);
54
-
55
- case 3:
56
- showAlert({
57
- message: t('SharedDrive.sharedDriveModal.successNotificationUpdate'),
58
- severity: 'success'
59
- });
60
- onClose();
61
- _context.next = 10;
62
- break;
63
-
64
- case 7:
65
- _context.prev = 7;
66
- _context.t0 = _context["catch"](0);
67
- showAlert({
68
- message: t('SharedDrive.sharedDriveModal.errorNotificationUpdate'),
69
- severity: 'error'
70
- });
71
-
72
- case 10:
73
- case "end":
74
- return _context.stop();
75
- }
76
- }, _callee, null, [[0, 7]]);
77
- }));
78
-
79
- return function onRename(_x) {
80
- return _ref2.apply(this, arguments);
81
- };
82
- }();
83
-
84
- return /*#__PURE__*/React.createElement(DumbSharedDriveModal, {
85
- title: t('Files.share.title', {
86
- name: sharing === null || sharing === void 0 ? void 0 : sharing.description
87
- }),
88
- document: document,
89
- createContact: createContact,
90
- recipients: recipients,
91
- onRevoke: onRevoke,
92
- onClose: onClose,
93
- onShare: onShare,
94
- sharedDriveName: name,
95
- handleSharedDriveNameChange: handleNameChange,
96
- onRename: onRename,
97
- autoOpenShareRestriction: autoOpenShareRestriction,
98
- showGenerateLinkButton: showGenerateLinkButton
99
- });
100
- });
101
- SharedDriveEditModal.propTypes = {
102
- document: PropTypes.object,
103
- sharing: PropTypes.object,
104
- recipients: PropTypes.array,
105
- onShare: PropTypes.func.isRequired,
106
- onRevoke: PropTypes.func.isRequired,
107
- onClose: PropTypes.func.isRequired,
108
- autoOpenShareRestriction: PropTypes.bool,
109
- showGenerateLinkButton: PropTypes.bool
110
- };
@@ -1,84 +0,0 @@
1
- import PropTypes from 'prop-types'
2
- import React from 'react'
3
-
4
- import { useI18n } from 'twake-i18n'
5
-
6
- import withLocales from '../../hoc/withLocales'
7
- import { DumbBatchSharedFolderModal } from '../SharedFolder/DumbBatchSharedFolderModal'
8
-
9
- export const DumbSharedDriveModal = withLocales(
10
- ({
11
- title,
12
- document,
13
- sharedDriveName,
14
- handleSharedDriveNameChange,
15
- createContact,
16
- recipients,
17
- currentRecipients,
18
- onRevoke,
19
- onSetType,
20
- onCreate,
21
- onSend,
22
- onClose,
23
- onShare,
24
- onRename,
25
- showNameField = true,
26
- sharingLink,
27
- autoOpenShareRestriction,
28
- showGenerateLinkButton
29
- }) => {
30
- const { t } = useI18n()
31
- return (
32
- <DumbBatchSharedFolderModal
33
- title={title}
34
- document={document}
35
- folderName={sharedDriveName}
36
- handleFolderNameChange={handleSharedDriveNameChange}
37
- createContact={createContact}
38
- recipients={recipients}
39
- currentRecipients={currentRecipients}
40
- onRevoke={onRevoke}
41
- onSetType={onSetType}
42
- onCreate={onCreate}
43
- onSend={onSend}
44
- onClose={onClose}
45
- onShare={onShare}
46
- onRename={onRename}
47
- showNameField={showNameField}
48
- sharingLink={sharingLink}
49
- nameLabel={t('SharedDrive.sharedDriveModal.nameLabel')}
50
- addPeopleLabel={t('SharedDrive.sharedDriveModal.addPeople')}
51
- addButtonLabel={t('SharedDrive.sharedDriveModal.add')}
52
- cancelLabel={t('SharedDrive.sharedDriveModal.cancel')}
53
- createLabel={t('SharedDrive.sharedDriveModal.create')}
54
- shareLabel={t('FederatedFolder.share')}
55
- saveLabel={t('SharedDrive.sharedDriveModal.save')}
56
- autoOpenShareRestriction={autoOpenShareRestriction}
57
- showGenerateLinkButton={showGenerateLinkButton}
58
- />
59
- )
60
- }
61
- )
62
-
63
- DumbSharedDriveModal.propTypes = {
64
- title: PropTypes.string,
65
- document: PropTypes.object,
66
- sharedDriveName: PropTypes.string,
67
- handleSharedDriveNameChange: PropTypes.func,
68
- createContact: PropTypes.func.isRequired,
69
- recipients: PropTypes.array,
70
- currentRecipients: PropTypes.array,
71
- onRevoke: PropTypes.func.isRequired,
72
- onSetType: PropTypes.func.isRequired,
73
- onCreate: PropTypes.func,
74
- onSend: PropTypes.func,
75
- onClose: PropTypes.func.isRequired,
76
- onShare: PropTypes.func.isRequired,
77
- onRename: PropTypes.func,
78
- showNameField: PropTypes.bool,
79
- sharingLink: PropTypes.string,
80
- autoOpenShareRestriction: PropTypes.bool,
81
- showGenerateLinkButton: PropTypes.bool
82
- }
83
-
84
- export default DumbSharedDriveModal
@@ -1,79 +0,0 @@
1
- import PropTypes from 'prop-types'
2
- import React, { useState } from 'react'
3
-
4
- import { useClient } from 'cozy-client'
5
- import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
6
- import { useI18n } from 'twake-i18n'
7
-
8
- import { DumbSharedDriveModal } from './DumbSharedDriveModal'
9
- import withLocales from '../../hoc/withLocales'
10
- import { useSharingContext } from '../../hooks/useSharingContext'
11
- import { Contact } from '../../models'
12
-
13
- export const SharedDriveEditModal = withLocales(
14
- ({
15
- document,
16
- sharing,
17
- recipients,
18
- onShare,
19
- onRevoke,
20
- onClose,
21
- autoOpenShareRestriction,
22
- showGenerateLinkButton
23
- }) => {
24
- const client = useClient()
25
- const { t } = useI18n()
26
- const { showAlert } = useAlert()
27
- const { renameSharedDrive } = useSharingContext()
28
-
29
- const createContact = contact => client.create(Contact.doctype, contact)
30
- const [name, setName] = useState(sharing?.description)
31
-
32
- const handleNameChange = event => {
33
- setName(event.target.value)
34
- }
35
- const onRename = async value => {
36
- try {
37
- await renameSharedDrive(document, value)
38
- showAlert({
39
- message: t('SharedDrive.sharedDriveModal.successNotificationUpdate'),
40
- severity: 'success'
41
- })
42
- onClose()
43
- } catch (_error) {
44
- showAlert({
45
- message: t('SharedDrive.sharedDriveModal.errorNotificationUpdate'),
46
- severity: 'error'
47
- })
48
- }
49
- }
50
-
51
- return (
52
- <DumbSharedDriveModal
53
- title={t('Files.share.title', { name: sharing?.description })}
54
- document={document}
55
- createContact={createContact}
56
- recipients={recipients}
57
- onRevoke={onRevoke}
58
- onClose={onClose}
59
- onShare={onShare}
60
- sharedDriveName={name}
61
- handleSharedDriveNameChange={handleNameChange}
62
- onRename={onRename}
63
- autoOpenShareRestriction={autoOpenShareRestriction}
64
- showGenerateLinkButton={showGenerateLinkButton}
65
- />
66
- )
67
- }
68
- )
69
-
70
- SharedDriveEditModal.propTypes = {
71
- document: PropTypes.object,
72
- sharing: PropTypes.object,
73
- recipients: PropTypes.array,
74
- onShare: PropTypes.func.isRequired,
75
- onRevoke: PropTypes.func.isRequired,
76
- onClose: PropTypes.func.isRequired,
77
- autoOpenShareRestriction: PropTypes.bool,
78
- showGenerateLinkButton: PropTypes.bool
79
- }