cozy-sharing 33.0.2 → 33.1.1

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 CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [33.1.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.1.0...cozy-sharing@33.1.1) (2026-06-04)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Memoize ShareByEmail section in SharedDriveForm ([2ea5eb2](https://github.com/cozy/cozy-libs/commit/2ea5eb2a8d37eec582212b07b8617c36fd107930))
11
+
12
+ # [33.1.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.0.2...cozy-sharing@33.1.0) (2026-06-04)
13
+
14
+ ### Features
15
+
16
+ - Remove email sharing in recipient side ([f6a3f19](https://github.com/cozy/cozy-libs/commit/f6a3f19bd32f9c1d1d22284a59349a60e2ca147e))
17
+
6
18
  ## [33.0.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.0.1...cozy-sharing@33.0.2) (2026-06-02)
7
19
 
8
20
  ### Bug Fixes
@@ -14,6 +14,24 @@ import { getOrCreateFromArray } from '../../helpers/contacts';
14
14
  import withLocales from '../../hoc/withLocales';
15
15
  import { usePendingRecipients } from '../../hooks/usePendingRecipients';
16
16
  import { useSharingContext } from '../../hooks/useSharingContext';
17
+ var styles = {
18
+ "share-modal-content": "share__share-modal-content___1iqEq",
19
+ "coz-form": "share__coz-form___1ICST",
20
+ "coz-form-group": "share__coz-form-group___VsQp7",
21
+ "coz-form-desc": "share__coz-form-desc___2mYN4",
22
+ "coz-form-label": "share__coz-form-label___bogBw",
23
+ "coz-form-label--error": "share__coz-form-label--error___3YPkf",
24
+ "coz-form-errors": "share__coz-form-errors___2xYRz",
25
+ "coz-form-controls": "share__coz-form-controls___H8kGc",
26
+ "coz-form-controls--full": "share__coz-form-controls--full___1oQXF",
27
+ "coz-form-controls--dispatch": "share__coz-form-controls--dispatch___1zIxt",
28
+ "error": "share__error___pwqPO",
29
+ "share-byemail-onlybylink": "share__share-byemail-onlybylink___29W-t",
30
+ "aligned-dropdown-button": "share__aligned-dropdown-button___3crgG",
31
+ "share-details-created": "share__share-details-created___z3nvO",
32
+ "share-details-perm": "share__share-details-perm___3HjxK",
33
+ "share-details-perm-desc": "share__share-details-perm-desc___3HyHu"
34
+ };
17
35
  import AntivirusAlert from '../AntivirusAlert';
18
36
  import { default as DumbShareByEmail } from '../ShareByEmail';
19
37
  import ShareByLink from '../ShareByLink';
@@ -37,6 +55,8 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
37
55
  getOwner = _useSharingContext.getOwner,
38
56
  getSharingById = _useSharingContext.getSharingById,
39
57
  getRecipients = _useSharingContext.getRecipients,
58
+ hasSharedChild = _useSharingContext.hasSharedChild,
59
+ hasSharedParent = _useSharingContext.hasSharedParent,
40
60
  revoke = _useSharingContext.revoke;
41
61
 
42
62
  var _useAlert = useAlert(),
@@ -152,6 +172,10 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
152
172
  fetchSharingLink();
153
173
  }, [existingDocument, documentPermissions, getFederatedShareLink, getSharingLink, getOwner, getSharingById]);
154
174
  var folderName = (existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.name) || '';
175
+ var documentPath = existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.path;
176
+ var hasParentRestriction = Boolean((existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.driveId) || documentPath && hasSharedParent(documentPath));
177
+ var hasChildRestriction = Boolean(documentPath && hasSharedChild(documentPath));
178
+ var canShareByEmail = !hasParentRestriction && !hasChildRestriction;
155
179
 
156
180
  var onSend = /*#__PURE__*/function () {
157
181
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
@@ -248,7 +272,11 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
248
272
  className: "u-ph-2"
249
273
  }, /*#__PURE__*/React.createElement(AntivirusAlert, {
250
274
  document: isSending ? frozenDoc : existingDocument
251
- }), /*#__PURE__*/React.createElement(Typography, {
275
+ }), !canShareByEmail && /*#__PURE__*/React.createElement("div", {
276
+ className: styles['share-byemail-onlybylink']
277
+ }, t('Files.share.shareByEmail.onlyByLink', {
278
+ type: t("Files.share.shareByEmail.type.".concat((existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.type) === 'file' ? 'file' : 'folder'))
279
+ }), ' ', /*#__PURE__*/React.createElement("strong", null, t("Files.share.shareByEmail.".concat(hasParentRestriction ? 'hasSharedParent' : 'hasSharedChild')))), canShareByEmail && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Typography, {
252
280
  variant: "h6",
253
281
  className: "u-mt-1-half u-mb-half"
254
282
  }, t('Share.contacts.addUsers')), /*#__PURE__*/React.createElement(DumbShareByEmail, {
@@ -260,7 +288,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
260
288
  selectedOption: selectedOption,
261
289
  onSelectedOptionChange: setSelectedOption,
262
290
  enableCreateContact: true
263
- })), /*#__PURE__*/React.createElement(WhoHasAccess, {
291
+ }))), /*#__PURE__*/React.createElement(WhoHasAccess, {
264
292
  isOwner: true,
265
293
  isSharedDrive: true,
266
294
  recipients: isSending ? frozenRecipients : existingRecipients,
@@ -276,7 +304,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
276
304
  documentType: "Files",
277
305
  showGenerateLinkButton: showGenerateLinkButton,
278
306
  autoOpenShareRestriction: autoOpenShareRestriction
279
- }), /*#__PURE__*/React.createElement(Button, {
307
+ }), canShareByEmail && /*#__PURE__*/React.createElement(Button, {
280
308
  variant: "primary",
281
309
  label: t('FederatedFolder.share'),
282
310
  disabled: isSending,
@@ -1,5 +1,11 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
3
  import _extends from "@babel/runtime/helpers/extends";
4
+
5
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
6
+
7
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
8
+
3
9
  import _regeneratorRuntime from "@babel/runtime/regenerator";
4
10
  import { fireEvent, render, waitFor } from '@testing-library/react';
5
11
  import React from 'react';
@@ -11,7 +17,10 @@ import AppLike from '../SharingBanner/test/AppLike';
11
17
  var mockShare = jest.fn();
12
18
  var mockRevoke = jest.fn();
13
19
  var mockGetSharingLink = jest.fn();
20
+ var mockGetFederatedShareLink = jest.fn();
14
21
  var mockGetRecipients = jest.fn().mockReturnValue([]);
22
+ var mockHasSharedChild = jest.fn();
23
+ var mockHasSharedParent = jest.fn();
15
24
  var mockShowAlert = jest.fn();
16
25
  var mockOnClose = jest.fn();
17
26
  jest.mock('../../hooks/useSharingContext', function () {
@@ -21,8 +30,11 @@ jest.mock('../../hooks/useSharingContext', function () {
21
30
  share: mockShare,
22
31
  revoke: mockRevoke,
23
32
  getSharingLink: mockGetSharingLink,
33
+ getFederatedShareLink: mockGetFederatedShareLink,
24
34
  getDocumentPermissions: jest.fn().mockReturnValue([]),
25
- getRecipients: mockGetRecipients
35
+ getRecipients: mockGetRecipients,
36
+ hasSharedChild: mockHasSharedChild,
37
+ hasSharedParent: mockHasSharedParent
26
38
  };
27
39
  }
28
40
  };
@@ -97,7 +109,10 @@ describe('FederatedFolderModal', function () {
97
109
  beforeEach(function () {
98
110
  jest.clearAllMocks();
99
111
  mockGetSharingLink.mockReturnValue('https://example.com/share/abc123');
112
+ mockGetFederatedShareLink.mockResolvedValue('https://example.com/share/federated-abc123');
100
113
  mockGetRecipients.mockReturnValue([]);
114
+ mockHasSharedChild.mockReturnValue(false);
115
+ mockHasSharedParent.mockReturnValue(false);
101
116
  client = createTestClient();
102
117
  sharingContextValue = createSharingContextValue();
103
118
  usePendingRecipients.mockReturnValue({
@@ -175,27 +190,76 @@ describe('FederatedFolderModal', function () {
175
190
  }
176
191
  }, _callee3);
177
192
  })));
193
+ it('should hide share by email when document is in federated shared folder received by current user', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
194
+ var _setup4, findByText, queryByText;
195
+
196
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
197
+ while (1) switch (_context4.prev = _context4.next) {
198
+ case 0:
199
+ _setup4 = setup({
200
+ document: _objectSpread(_objectSpread({}, mockDocument), {}, {
201
+ driveId: 'federated-folder-id'
202
+ })
203
+ }), findByText = _setup4.findByText, queryByText = _setup4.queryByText;
204
+ _context4.next = 3;
205
+ return findByText('Copy link');
206
+
207
+ case 3:
208
+ expect(queryByText('Add users')).toBe(null);
209
+ expect(queryByText('Done')).toBe(null);
210
+
211
+ case 5:
212
+ case "end":
213
+ return _context4.stop();
214
+ }
215
+ }, _callee4);
216
+ })));
217
+ it('should show only by link message when document is in federated shared folder received by current user', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
218
+ var _setup5, findByText;
219
+
220
+ return _regeneratorRuntime.wrap(function _callee5$(_context5) {
221
+ while (1) switch (_context5.prev = _context5.next) {
222
+ case 0:
223
+ _setup5 = setup({
224
+ document: _objectSpread(_objectSpread({}, mockDocument), {}, {
225
+ driveId: 'federated-folder-id',
226
+ type: 'directory'
227
+ })
228
+ }), findByText = _setup5.findByText;
229
+ _context5.next = 3;
230
+ return findByText('This folder can only be shared by link, because');
231
+
232
+ case 3:
233
+ _context5.next = 5;
234
+ return findByText('it has a shared parent');
235
+
236
+ case 5:
237
+ case "end":
238
+ return _context5.stop();
239
+ }
240
+ }, _callee5);
241
+ })));
178
242
  });
179
243
  describe('onSend callback', function () {
180
244
  var pendingRecipient = {
181
245
  name: 'Alice',
182
246
  email: 'alice@example.com'
183
247
  };
184
- it('should call onClose and skip share when there are no pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
185
- var _setup4, findByText, sendButton;
248
+ it('should call onClose and skip share when there are no pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
249
+ var _setup6, findByText, sendButton;
186
250
 
187
- return _regeneratorRuntime.wrap(function _callee4$(_context4) {
188
- while (1) switch (_context4.prev = _context4.next) {
251
+ return _regeneratorRuntime.wrap(function _callee6$(_context6) {
252
+ while (1) switch (_context6.prev = _context6.next) {
189
253
  case 0:
190
254
  // pendingRecipients defaults to [] via beforeEach
191
- _setup4 = setup(), findByText = _setup4.findByText;
192
- _context4.next = 3;
255
+ _setup6 = setup(), findByText = _setup6.findByText;
256
+ _context6.next = 3;
193
257
  return findByText('Done');
194
258
 
195
259
  case 3:
196
- sendButton = _context4.sent;
260
+ sendButton = _context6.sent;
197
261
  fireEvent.click(sendButton);
198
- _context4.next = 7;
262
+ _context6.next = 7;
199
263
  return waitFor(function () {
200
264
  expect(mockOnClose).toHaveBeenCalled();
201
265
  expect(mockShare).not.toHaveBeenCalled();
@@ -203,15 +267,15 @@ describe('FederatedFolderModal', function () {
203
267
 
204
268
  case 7:
205
269
  case "end":
206
- return _context4.stop();
270
+ return _context6.stop();
207
271
  }
208
- }, _callee4);
272
+ }, _callee6);
209
273
  })));
210
- it('should call share with correct parameters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
211
- var _setup5, findByText, sendButton;
274
+ it('should call share with correct parameters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
275
+ var _setup7, findByText, sendButton;
212
276
 
213
- return _regeneratorRuntime.wrap(function _callee5$(_context5) {
214
- while (1) switch (_context5.prev = _context5.next) {
277
+ return _regeneratorRuntime.wrap(function _callee7$(_context7) {
278
+ while (1) switch (_context7.prev = _context7.next) {
215
279
  case 0:
216
280
  usePendingRecipients.mockReturnValue({
217
281
  pendingRecipients: [pendingRecipient],
@@ -221,14 +285,14 @@ describe('FederatedFolderModal', function () {
221
285
  });
222
286
  getOrCreateFromArray.mockResolvedValue([pendingRecipient]);
223
287
  mockShare.mockResolvedValueOnce({});
224
- _setup5 = setup(), findByText = _setup5.findByText;
225
- _context5.next = 6;
288
+ _setup7 = setup(), findByText = _setup7.findByText;
289
+ _context7.next = 6;
226
290
  return findByText('Done');
227
291
 
228
292
  case 6:
229
- sendButton = _context5.sent;
293
+ sendButton = _context7.sent;
230
294
  fireEvent.click(sendButton);
231
- _context5.next = 10;
295
+ _context7.next = 10;
232
296
  return waitFor(function () {
233
297
  expect(mockShare).toHaveBeenCalledWith({
234
298
  description: 'My Test Folder',
@@ -242,15 +306,15 @@ describe('FederatedFolderModal', function () {
242
306
 
243
307
  case 10:
244
308
  case "end":
245
- return _context5.stop();
309
+ return _context7.stop();
246
310
  }
247
- }, _callee5);
311
+ }, _callee7);
248
312
  })));
249
- it('should show success alert and close modal on successful share', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
250
- var _setup6, findByText, sendButton;
313
+ it('should show success alert and close modal on successful share', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
314
+ var _setup8, findByText, sendButton;
251
315
 
252
- return _regeneratorRuntime.wrap(function _callee6$(_context6) {
253
- while (1) switch (_context6.prev = _context6.next) {
316
+ return _regeneratorRuntime.wrap(function _callee8$(_context8) {
317
+ while (1) switch (_context8.prev = _context8.next) {
254
318
  case 0:
255
319
  usePendingRecipients.mockReturnValue({
256
320
  pendingRecipients: [pendingRecipient],
@@ -260,14 +324,14 @@ describe('FederatedFolderModal', function () {
260
324
  });
261
325
  getOrCreateFromArray.mockResolvedValue([pendingRecipient]);
262
326
  mockShare.mockResolvedValueOnce({});
263
- _setup6 = setup(), findByText = _setup6.findByText;
264
- _context6.next = 6;
327
+ _setup8 = setup(), findByText = _setup8.findByText;
328
+ _context8.next = 6;
265
329
  return findByText('Done');
266
330
 
267
331
  case 6:
268
- sendButton = _context6.sent;
332
+ sendButton = _context8.sent;
269
333
  fireEvent.click(sendButton);
270
- _context6.next = 10;
334
+ _context8.next = 10;
271
335
  return waitFor(function () {
272
336
  expect(mockShowAlert).toHaveBeenCalledWith({
273
337
  message: 'Folder has been shared',
@@ -279,15 +343,15 @@ describe('FederatedFolderModal', function () {
279
343
 
280
344
  case 10:
281
345
  case "end":
282
- return _context6.stop();
346
+ return _context8.stop();
283
347
  }
284
- }, _callee6);
348
+ }, _callee8);
285
349
  })));
286
- it('should show error alert when share fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
287
- var _setup7, findByText, sendButton;
350
+ it('should show error alert when share fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
351
+ var _setup9, findByText, sendButton;
288
352
 
289
- return _regeneratorRuntime.wrap(function _callee7$(_context7) {
290
- while (1) switch (_context7.prev = _context7.next) {
353
+ return _regeneratorRuntime.wrap(function _callee9$(_context9) {
354
+ while (1) switch (_context9.prev = _context9.next) {
291
355
  case 0:
292
356
  usePendingRecipients.mockReturnValue({
293
357
  pendingRecipients: [pendingRecipient],
@@ -297,14 +361,14 @@ describe('FederatedFolderModal', function () {
297
361
  });
298
362
  getOrCreateFromArray.mockResolvedValue([pendingRecipient]);
299
363
  mockShare.mockRejectedValueOnce(new Error('Share failed'));
300
- _setup7 = setup(), findByText = _setup7.findByText;
301
- _context7.next = 6;
364
+ _setup9 = setup(), findByText = _setup9.findByText;
365
+ _context9.next = 6;
302
366
  return findByText('Done');
303
367
 
304
368
  case 6:
305
- sendButton = _context7.sent;
369
+ sendButton = _context9.sent;
306
370
  fireEvent.click(sendButton);
307
- _context7.next = 10;
371
+ _context9.next = 10;
308
372
  return waitFor(function () {
309
373
  expect(mockShowAlert).toHaveBeenCalledWith({
310
374
  message: 'Error while sharing folder',
@@ -316,40 +380,40 @@ describe('FederatedFolderModal', function () {
316
380
 
317
381
  case 10:
318
382
  case "end":
319
- return _context7.stop();
383
+ return _context9.stop();
320
384
  }
321
- }, _callee7);
385
+ }, _callee9);
322
386
  })));
323
387
  });
324
388
  describe('onClose callback', function () {
325
- it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
326
- var _setup8, findByRole, closeButton;
389
+ it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
390
+ var _setup10, findByRole, closeButton;
327
391
 
328
- return _regeneratorRuntime.wrap(function _callee8$(_context8) {
329
- while (1) switch (_context8.prev = _context8.next) {
392
+ return _regeneratorRuntime.wrap(function _callee10$(_context10) {
393
+ while (1) switch (_context10.prev = _context10.next) {
330
394
  case 0:
331
- _setup8 = setup(), findByRole = _setup8.findByRole;
332
- _context8.next = 3;
395
+ _setup10 = setup(), findByRole = _setup10.findByRole;
396
+ _context10.next = 3;
333
397
  return findByRole('button', {
334
398
  name: /close/i
335
399
  });
336
400
 
337
401
  case 3:
338
- closeButton = _context8.sent;
402
+ closeButton = _context10.sent;
339
403
  fireEvent.click(closeButton);
340
404
  expect(mockOnClose).toHaveBeenCalled();
341
405
 
342
406
  case 6:
343
407
  case "end":
344
- return _context8.stop();
408
+ return _context10.stop();
345
409
  }
346
- }, _callee8);
410
+ }, _callee10);
347
411
  })));
348
- it('should ask confirmation when close button is clicked with pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
349
- var _setup9, getByRole, findByRole, getByText, closeButton;
412
+ it('should ask confirmation when close button is clicked with pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
413
+ var _setup11, getByRole, findByRole, getByText, closeButton;
350
414
 
351
- return _regeneratorRuntime.wrap(function _callee9$(_context9) {
352
- while (1) switch (_context9.prev = _context9.next) {
415
+ return _regeneratorRuntime.wrap(function _callee11$(_context11) {
416
+ while (1) switch (_context11.prev = _context11.next) {
353
417
  case 0:
354
418
  usePendingRecipients.mockReturnValue({
355
419
  pendingRecipients: [{
@@ -360,14 +424,14 @@ describe('FederatedFolderModal', function () {
360
424
  selectedOption: 'readWrite',
361
425
  setSelectedOption: jest.fn()
362
426
  });
363
- _setup9 = setup(), getByRole = _setup9.getByRole, findByRole = _setup9.findByRole, getByText = _setup9.getByText;
364
- _context9.next = 4;
427
+ _setup11 = setup(), getByRole = _setup11.getByRole, findByRole = _setup11.findByRole, getByText = _setup11.getByText;
428
+ _context11.next = 4;
365
429
  return findByRole('button', {
366
430
  name: /close/i
367
431
  });
368
432
 
369
433
  case 4:
370
- closeButton = _context9.sent;
434
+ closeButton = _context11.sent;
371
435
  fireEvent.click(closeButton);
372
436
  expect(getByText("Discard the changes you haven't saved?")).toBeTruthy();
373
437
  expect(mockOnClose).not.toHaveBeenCalled();
@@ -378,15 +442,15 @@ describe('FederatedFolderModal', function () {
378
442
 
379
443
  case 10:
380
444
  case "end":
381
- return _context9.stop();
445
+ return _context11.stop();
382
446
  }
383
- }, _callee9);
447
+ }, _callee11);
384
448
  })));
385
- it('should close after discard confirmation', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
386
- var _setup10, getByRole, findByRole, getByText, closeButton;
449
+ it('should close after discard confirmation', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
450
+ var _setup12, getByRole, findByRole, getByText, closeButton;
387
451
 
388
- return _regeneratorRuntime.wrap(function _callee10$(_context10) {
389
- while (1) switch (_context10.prev = _context10.next) {
452
+ return _regeneratorRuntime.wrap(function _callee12$(_context12) {
453
+ while (1) switch (_context12.prev = _context12.next) {
390
454
  case 0:
391
455
  usePendingRecipients.mockReturnValue({
392
456
  pendingRecipients: [{
@@ -397,14 +461,14 @@ describe('FederatedFolderModal', function () {
397
461
  selectedOption: 'readWrite',
398
462
  setSelectedOption: jest.fn()
399
463
  });
400
- _setup10 = setup(), getByRole = _setup10.getByRole, findByRole = _setup10.findByRole, getByText = _setup10.getByText;
401
- _context10.next = 4;
464
+ _setup12 = setup(), getByRole = _setup12.getByRole, findByRole = _setup12.findByRole, getByText = _setup12.getByText;
465
+ _context12.next = 4;
402
466
  return findByRole('button', {
403
467
  name: /close/i
404
468
  });
405
469
 
406
470
  case 4:
407
- closeButton = _context10.sent;
471
+ closeButton = _context12.sent;
408
472
  fireEvent.click(closeButton);
409
473
  expect(getByText("Discard the changes you haven't saved?")).toBeTruthy();
410
474
  fireEvent.click(getByRole('button', {
@@ -414,9 +478,9 @@ describe('FederatedFolderModal', function () {
414
478
 
415
479
  case 9:
416
480
  case "end":
417
- return _context10.stop();
481
+ return _context12.stop();
418
482
  }
419
- }, _callee10);
483
+ }, _callee12);
420
484
  })));
421
485
  });
422
486
  });
@@ -1,5 +1,5 @@
1
1
  import PropTypes from 'prop-types';
2
- import React from 'react';
2
+ import React, { memo } from 'react';
3
3
  import Button from 'cozy-ui/transpiled/react/Buttons';
4
4
  import TextField from 'cozy-ui/transpiled/react/TextField';
5
5
  import Typography from 'cozy-ui/transpiled/react/Typography';
@@ -9,9 +9,23 @@ import withLocales from '../../hoc/withLocales';
9
9
  import { default as DumbShareByEmail } from '../ShareByEmail';
10
10
  import SharedDriveHeader from './SharedDriveHeader';
11
11
  import { useSharedDrive } from './useSharedDrive';
12
- export var SharedDriveForm = withLocales(function (_ref) {
13
- var onSuccess = _ref.onSuccess,
14
- onCancel = _ref.onCancel;
12
+ var ShareByEmailSection = /*#__PURE__*/memo(function ShareByEmailSection(_ref) {
13
+ var pendingRecipients = _ref.pendingRecipients,
14
+ setPendingRecipients = _ref.setPendingRecipients,
15
+ selectedOption = _ref.selectedOption,
16
+ setSelectedOption = _ref.setSelectedOption;
17
+ return /*#__PURE__*/React.createElement(DumbShareByEmail, {
18
+ currentRecipients: [],
19
+ documentType: "Files",
20
+ pendingRecipients: pendingRecipients,
21
+ onPendingRecipientsChange: setPendingRecipients,
22
+ selectedOption: selectedOption,
23
+ onSelectedOptionChange: setSelectedOption
24
+ });
25
+ });
26
+ export var SharedDriveForm = withLocales(function (_ref2) {
27
+ var onSuccess = _ref2.onSuccess,
28
+ onCancel = _ref2.onCancel;
15
29
 
16
30
  var _useI18n = useI18n(),
17
31
  t = _useI18n.t;
@@ -44,13 +58,11 @@ export var SharedDriveForm = withLocales(function (_ref) {
44
58
  }), /*#__PURE__*/React.createElement(Typography, {
45
59
  variant: "h6",
46
60
  className: "u-mb-half"
47
- }, t('Share.contacts.addUsers')), /*#__PURE__*/React.createElement(DumbShareByEmail, {
48
- currentRecipients: [],
49
- documentType: "Files",
61
+ }, t('Share.contacts.addUsers')), /*#__PURE__*/React.createElement(ShareByEmailSection, {
50
62
  pendingRecipients: pendingRecipients,
51
- onPendingRecipientsChange: setPendingRecipients,
63
+ setPendingRecipients: setPendingRecipients,
52
64
  selectedOption: selectedOption,
53
- onSelectedOptionChange: setSelectedOption
65
+ setSelectedOption: setSelectedOption
54
66
  })), /*#__PURE__*/React.createElement("div", {
55
67
  className: "u-flex u-ph-2 u-pv-1"
56
68
  }, /*#__PURE__*/React.createElement(Button, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.0.2",
3
+ "version": "33.1.1",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "0a14cfca6a608f4a40d2301c370b176ecbaeee4c"
86
+ "gitHead": "a6e1d756c4f41496383878fd9ce59afbb443f5b7"
87
87
  }
@@ -15,6 +15,7 @@ import { getOrCreateFromArray } from '../../helpers/contacts'
15
15
  import withLocales from '../../hoc/withLocales'
16
16
  import { usePendingRecipients } from '../../hooks/usePendingRecipients'
17
17
  import { useSharingContext } from '../../hooks/useSharingContext'
18
+ import styles from '../../styles/share.styl'
18
19
  import AntivirusAlert from '../AntivirusAlert'
19
20
  import { default as DumbShareByEmail } from '../ShareByEmail'
20
21
  import ShareByLink from '../ShareByLink'
@@ -36,6 +37,8 @@ const FederatedFolderModalContent = ({
36
37
  getOwner,
37
38
  getSharingById,
38
39
  getRecipients,
40
+ hasSharedChild,
41
+ hasSharedParent,
39
42
  revoke
40
43
  } = useSharingContext()
41
44
  const { showAlert } = useAlert()
@@ -124,6 +127,14 @@ const FederatedFolderModalContent = ({
124
127
  ])
125
128
 
126
129
  const folderName = existingDocument?.name || ''
130
+ const documentPath = existingDocument?.path
131
+ const hasParentRestriction = Boolean(
132
+ existingDocument?.driveId || (documentPath && hasSharedParent(documentPath))
133
+ )
134
+ const hasChildRestriction = Boolean(
135
+ documentPath && hasSharedChild(documentPath)
136
+ )
137
+ const canShareByEmail = !hasParentRestriction && !hasChildRestriction
127
138
 
128
139
  const onSend = async () => {
129
140
  if (isSending || pendingRecipients.length === 0) {
@@ -193,21 +204,45 @@ const FederatedFolderModalContent = ({
193
204
  <AntivirusAlert
194
205
  document={isSending ? frozenDoc : existingDocument}
195
206
  />
196
- <Typography variant="h6" className="u-mt-1-half u-mb-half">
197
- {t('Share.contacts.addUsers')}
198
- </Typography>
199
- <DumbShareByEmail
200
- currentRecipients={
201
- isSending ? frozenRecipients : existingRecipients
202
- }
203
- document={isSending ? frozenDoc : existingDocument}
204
- documentType="Files"
205
- pendingRecipients={pendingRecipients}
206
- onPendingRecipientsChange={setPendingRecipients}
207
- selectedOption={selectedOption}
208
- onSelectedOptionChange={setSelectedOption}
209
- enableCreateContact
210
- />
207
+ {!canShareByEmail && (
208
+ <div className={styles['share-byemail-onlybylink']}>
209
+ {t('Files.share.shareByEmail.onlyByLink', {
210
+ type: t(
211
+ `Files.share.shareByEmail.type.${
212
+ existingDocument?.type === 'file' ? 'file' : 'folder'
213
+ }`
214
+ )
215
+ })}{' '}
216
+ <strong>
217
+ {t(
218
+ `Files.share.shareByEmail.${
219
+ hasParentRestriction
220
+ ? 'hasSharedParent'
221
+ : 'hasSharedChild'
222
+ }`
223
+ )}
224
+ </strong>
225
+ </div>
226
+ )}
227
+ {canShareByEmail && (
228
+ <>
229
+ <Typography variant="h6" className="u-mt-1-half u-mb-half">
230
+ {t('Share.contacts.addUsers')}
231
+ </Typography>
232
+ <DumbShareByEmail
233
+ currentRecipients={
234
+ isSending ? frozenRecipients : existingRecipients
235
+ }
236
+ document={isSending ? frozenDoc : existingDocument}
237
+ documentType="Files"
238
+ pendingRecipients={pendingRecipients}
239
+ onPendingRecipientsChange={setPendingRecipients}
240
+ selectedOption={selectedOption}
241
+ onSelectedOptionChange={setSelectedOption}
242
+ enableCreateContact
243
+ />
244
+ </>
245
+ )}
211
246
  </div>
212
247
  <WhoHasAccess
213
248
  isOwner
@@ -230,12 +265,14 @@ const FederatedFolderModalContent = ({
230
265
  showGenerateLinkButton={showGenerateLinkButton}
231
266
  autoOpenShareRestriction={autoOpenShareRestriction}
232
267
  />
233
- <Button
234
- variant="primary"
235
- label={t('FederatedFolder.share')}
236
- disabled={isSending}
237
- onClick={onSend}
238
- />
268
+ {canShareByEmail && (
269
+ <Button
270
+ variant="primary"
271
+ label={t('FederatedFolder.share')}
272
+ disabled={isSending}
273
+ onClick={onSend}
274
+ />
275
+ )}
239
276
  </>
240
277
  }
241
278
  />
@@ -11,7 +11,10 @@ import AppLike from '../SharingBanner/test/AppLike'
11
11
  const mockShare = jest.fn()
12
12
  const mockRevoke = jest.fn()
13
13
  const mockGetSharingLink = jest.fn()
14
+ const mockGetFederatedShareLink = jest.fn()
14
15
  const mockGetRecipients = jest.fn().mockReturnValue([])
16
+ const mockHasSharedChild = jest.fn()
17
+ const mockHasSharedParent = jest.fn()
15
18
  const mockShowAlert = jest.fn()
16
19
  const mockOnClose = jest.fn()
17
20
 
@@ -20,8 +23,11 @@ jest.mock('../../hooks/useSharingContext', () => ({
20
23
  share: mockShare,
21
24
  revoke: mockRevoke,
22
25
  getSharingLink: mockGetSharingLink,
26
+ getFederatedShareLink: mockGetFederatedShareLink,
23
27
  getDocumentPermissions: jest.fn().mockReturnValue([]),
24
- getRecipients: mockGetRecipients
28
+ getRecipients: mockGetRecipients,
29
+ hasSharedChild: mockHasSharedChild,
30
+ hasSharedParent: mockHasSharedParent
25
31
  })
26
32
  }))
27
33
 
@@ -84,7 +90,12 @@ describe('FederatedFolderModal', () => {
84
90
  beforeEach(() => {
85
91
  jest.clearAllMocks()
86
92
  mockGetSharingLink.mockReturnValue('https://example.com/share/abc123')
93
+ mockGetFederatedShareLink.mockResolvedValue(
94
+ 'https://example.com/share/federated-abc123'
95
+ )
87
96
  mockGetRecipients.mockReturnValue([])
97
+ mockHasSharedChild.mockReturnValue(false)
98
+ mockHasSharedParent.mockReturnValue(false)
88
99
  client = createTestClient()
89
100
  sharingContextValue = createSharingContextValue()
90
101
  usePendingRecipients.mockReturnValue({
@@ -127,6 +138,30 @@ describe('FederatedFolderModal', () => {
127
138
 
128
139
  await findByText('Copy link')
129
140
  })
141
+
142
+ it('should hide share by email when document is in federated shared folder received by current user', async () => {
143
+ const { findByText, queryByText } = setup({
144
+ document: { ...mockDocument, driveId: 'federated-folder-id' }
145
+ })
146
+
147
+ await findByText('Copy link')
148
+
149
+ expect(queryByText('Add users')).toBe(null)
150
+ expect(queryByText('Done')).toBe(null)
151
+ })
152
+
153
+ it('should show only by link message when document is in federated shared folder received by current user', async () => {
154
+ const { findByText } = setup({
155
+ document: {
156
+ ...mockDocument,
157
+ driveId: 'federated-folder-id',
158
+ type: 'directory'
159
+ }
160
+ })
161
+
162
+ await findByText('This folder can only be shared by link, because')
163
+ await findByText('it has a shared parent')
164
+ })
130
165
  })
131
166
 
132
167
  describe('onSend callback', () => {
@@ -1,5 +1,5 @@
1
1
  import PropTypes from 'prop-types'
2
- import React from 'react'
2
+ import React, { memo } from 'react'
3
3
 
4
4
  import Button from 'cozy-ui/transpiled/react/Buttons'
5
5
  import TextField from 'cozy-ui/transpiled/react/TextField'
@@ -12,6 +12,24 @@ import { default as DumbShareByEmail } from '../ShareByEmail'
12
12
  import SharedDriveHeader from './SharedDriveHeader'
13
13
  import { useSharedDrive } from './useSharedDrive'
14
14
 
15
+ const ShareByEmailSection = memo(function ShareByEmailSection({
16
+ pendingRecipients,
17
+ setPendingRecipients,
18
+ selectedOption,
19
+ setSelectedOption
20
+ }) {
21
+ return (
22
+ <DumbShareByEmail
23
+ currentRecipients={[]}
24
+ documentType="Files"
25
+ pendingRecipients={pendingRecipients}
26
+ onPendingRecipientsChange={setPendingRecipients}
27
+ selectedOption={selectedOption}
28
+ onSelectedOptionChange={setSelectedOption}
29
+ />
30
+ )
31
+ })
32
+
15
33
  export const SharedDriveForm = withLocales(({ onSuccess, onCancel }) => {
16
34
  const { t } = useI18n()
17
35
  const {
@@ -41,13 +59,11 @@ export const SharedDriveForm = withLocales(({ onSuccess, onCancel }) => {
41
59
  <Typography variant="h6" className="u-mb-half">
42
60
  {t('Share.contacts.addUsers')}
43
61
  </Typography>
44
- <DumbShareByEmail
45
- currentRecipients={[]}
46
- documentType="Files"
62
+ <ShareByEmailSection
47
63
  pendingRecipients={pendingRecipients}
48
- onPendingRecipientsChange={setPendingRecipients}
64
+ setPendingRecipients={setPendingRecipients}
49
65
  selectedOption={selectedOption}
50
- onSelectedOptionChange={setSelectedOption}
66
+ setSelectedOption={setSelectedOption}
51
67
  />
52
68
  </div>
53
69
  <div className="u-flex u-ph-2 u-pv-1">