@onehat/ui 0.3.334 → 0.3.335

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.3.334",
3
+ "version": "0.3.335",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -15,7 +15,7 @@ function Editor(props) {
15
15
  onEditorDelete: onDelete,
16
16
  editorMode,
17
17
  onEditMode,
18
- verifyCanEdit,
18
+ canRecordBeEdited,
19
19
  _viewer = {},
20
20
  _form = {
21
21
  containerProps: {}
@@ -36,7 +36,7 @@ function Editor(props) {
36
36
  const propsToPass = _.omit(props, ['self', 'reference', 'parent']);
37
37
 
38
38
  let canEdit = true;
39
- if (verifyCanEdit && !verifyCanEdit(selection)) {
39
+ if (canRecordBeEdited && !canRecordBeEdited(selection)) {
40
40
  canEdit = false;
41
41
  }
42
42
 
@@ -7,6 +7,9 @@ import {
7
7
  ScrollView,
8
8
  Text,
9
9
  } from 'native-base';
10
+ import {
11
+ VIEW,
12
+ } from '../../Constants/Commands.js';
10
13
  import {
11
14
  EDITOR_TYPE__INLINE,
12
15
  EDITOR_TYPE__WINDOWED,
@@ -106,6 +109,9 @@ function Form(props) {
106
109
 
107
110
  // withData
108
111
  Repository,
112
+
113
+ // withPermissions
114
+ canUser,
109
115
 
110
116
  // withEditor
111
117
  isEditorViewOnly = false,
@@ -922,7 +928,7 @@ function Form(props) {
922
928
  >Back</Button>}
923
929
  <Text fontSize={20} ml={2} color="trueGray.500">Edit Mode</Text>
924
930
  </Row>
925
- {onViewMode && !disableView &&
931
+ {onViewMode && !disableView && (!canUser || canUser(VIEW)) &&
926
932
  <Button
927
933
  {...testProps('toViewBtn')}
928
934
  key="viewBtn"
@@ -14,6 +14,10 @@ import {
14
14
  SELECTION_MODE_SINGLE,
15
15
  SELECTION_MODE_MULTI,
16
16
  } from '../../Constants/Selection.js';
17
+ import {
18
+ EDIT,
19
+ VIEW,
20
+ } from '../../Constants/Commands.js';
17
21
  import {
18
22
  DROP_POSITION_BEFORE,
19
23
  DROP_POSITION_AFTER,
@@ -59,6 +63,7 @@ import Toolbar from '../Toolbar/Toolbar.js';
59
63
  import NoReorderRows from '../Icons/NoReorderRows.js';
60
64
  import ReorderRows from '../Icons/ReorderRows.js';
61
65
  import ColumnSelectorWindow from './ColumnSelectorWindow.js';
66
+ import Unauthorized from '../Messages/Unauthorized.js';
62
67
  import _ from 'lodash';
63
68
 
64
69
 
@@ -129,7 +134,7 @@ function GridComponent(props) {
129
134
  h,
130
135
  flex,
131
136
  bg = '#fff',
132
- verifyCanEdit,
137
+ canRecordBeEdited,
133
138
  alternateRowBackgrounds = true,
134
139
  alternatingInterval = 2,
135
140
  defaultRowHeight = 48,
@@ -154,6 +159,9 @@ function GridComponent(props) {
154
159
  idIx,
155
160
  displayIx,
156
161
 
162
+ // withPermissions
163
+ canUser,
164
+
157
165
  // withDnd
158
166
  isDropTarget,
159
167
  canDrop,
@@ -372,11 +380,17 @@ function GridComponent(props) {
372
380
 
373
381
  if (UiGlobals.doubleClickingGridRowOpensEditorInViewMode) { // global setting
374
382
  if (onView) {
383
+ if (canUser && !canUser(VIEW)) { // permissions
384
+ return;
385
+ }
375
386
  onView(true);
376
387
  }
377
388
  } else {
378
389
  if (onEdit) {
379
- if (verifyCanEdit && !verifyCanEdit(selection)) {
390
+ if (canUser && !canUser(EDIT)) { // permissions
391
+ return;
392
+ }
393
+ if (canRecordBeEdited && !canRecordBeEdited(selection)) { // record can be edited
380
394
  return;
381
395
  }
382
396
  onEdit();
@@ -468,49 +482,53 @@ function GridComponent(props) {
468
482
  dragSelectionRef.current = selection;
469
483
  const getSelection = () => dragSelectionRef.current;
470
484
 
471
- // assign event handlers
472
- if (canRowsReorder && isReorderMode) {
473
- WhichRow = DragSourceGridRow;
474
- rowReorderProps.isDragSource = true;
475
- rowReorderProps.dragSourceType = 'row';
476
- const dragIx = showHeaders ? index - 1 : index;
477
- rowReorderProps.dragSourceItem = {
478
- id: item.id,
479
- getSelection,
480
- onDrag: (dragState) => {
481
- onRowReorderDrag(dragState, dragIx);
482
- },
483
- };
484
- rowReorderProps.onDragEnd = onRowReorderEnd;
485
- } else {
486
- // Don't allow drag/drop from withDnd while reordering
487
- if (areRowsDragSource) {
485
+ const userHasPermissionToDrag = (!canUser || canUser(EDIT));
486
+ if (userHasPermissionToDrag) {
487
+ // assign event handlers
488
+ if (canRowsReorder && isReorderMode) {
488
489
  WhichRow = DragSourceGridRow;
489
- rowDragProps.isDragSource = true;
490
- rowDragProps.dragSourceType = rowDragSourceType;
491
- if (getRowDragSourceItem) {
492
- rowDragProps.dragSourceItem = getRowDragSourceItem(item, getSelection, rowDragSourceType);
493
- } else {
494
- rowDragProps.dragSourceItem = {
495
- id: item.id,
496
- getSelection,
497
- type: rowDragSourceType,
490
+ rowReorderProps.isDragSource = true;
491
+ rowReorderProps.dragSourceType = 'row';
492
+ const dragIx = showHeaders ? index - 1 : index;
493
+ rowReorderProps.dragSourceItem = {
494
+ id: item.id,
495
+ getSelection,
496
+ onDrag: (dragState) => {
497
+ onRowReorderDrag(dragState, dragIx);
498
+ },
499
+ };
500
+ rowReorderProps.onDragEnd = onRowReorderEnd;
501
+ } else {
502
+ // Don't allow drag/drop from withDnd while reordering
503
+ if (areRowsDragSource) {
504
+ WhichRow = DragSourceGridRow;
505
+ rowDragProps.isDragSource = true;
506
+ rowDragProps.dragSourceType = rowDragSourceType;
507
+ if (getRowDragSourceItem) {
508
+ rowDragProps.dragSourceItem = getRowDragSourceItem(item, getSelection, rowDragSourceType);
509
+ } else {
510
+ rowDragProps.dragSourceItem = {
511
+ id: item.id,
512
+ getSelection,
513
+ type: rowDragSourceType,
514
+ };
515
+ }
516
+ }
517
+ if (areRowsDropTarget) {
518
+ WhichRow = DropTargetGridRow;
519
+ rowDragProps.isDropTarget = true;
520
+ rowDragProps.dropTargetAccept = dropTargetAccept;
521
+ rowDragProps.onDrop = (droppedItem) => {
522
+ // NOTE: item is sometimes getting destroyed, but it still as the id, so you can still use it
523
+ onRowDrop(item, droppedItem); // item is what it was dropped on; droppedItem is the dragSourceItem defined above
498
524
  };
499
525
  }
500
- }
501
- if (areRowsDropTarget) {
502
- WhichRow = DropTargetGridRow;
503
- rowDragProps.isDropTarget = true;
504
- rowDragProps.dropTargetAccept = dropTargetAccept;
505
- rowDragProps.onDrop = (droppedItem) => {
506
- // NOTE: item is sometimes getting destroyed, but it still as the id, so you can still use it
507
- onRowDrop(item, droppedItem); // item is what it was dropped on; droppedItem is the dragSourceItem defined above
508
- };
509
- }
510
- if (areRowsDragSource && areRowsDropTarget) {
511
- WhichRow = DragSourceDropTargetGridRow;
526
+ if (areRowsDragSource && areRowsDropTarget) {
527
+ WhichRow = DragSourceDropTargetGridRow;
528
+ }
512
529
  }
513
530
  }
531
+
514
532
  }
515
533
  return <WhichRow
516
534
  columnsConfig={localColumnsConfig}
@@ -942,6 +960,10 @@ function GridComponent(props) {
942
960
 
943
961
  }, [selectorSelected]);
944
962
 
963
+ if (canUser && !canUser('view')) {
964
+ return <Unauthorized />;
965
+ }
966
+
945
967
  if (self) {
946
968
  self.ref = containerRef;
947
969
  self.gridRef = gridRef;
@@ -57,6 +57,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
57
57
  // withSecondaryData
58
58
  SecondaryRepository,
59
59
 
60
+ // withPermissions
61
+ canUser,
62
+ showPermissionsError,
63
+
60
64
  // withSecondarySelection
61
65
  secondarySelection,
62
66
  secondarySetSelection,
@@ -69,6 +73,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
69
73
  secondaryListeners = useRef({}),
70
74
  secondaryEditorStateRef = useRef(),
71
75
  secondaryNewEntityDisplayValueRef = useRef(),
76
+ secondaryModel = SecondaryRepository?.schema?.name,
72
77
  [secondaryCurrentRecord, secondarySetCurrentRecord] = useState(null),
73
78
  [secondaryIsAdding, setIsAdding] = useState(false),
74
79
  [secondaryIsSaving, setIsSaving] = useState(false),
@@ -109,6 +114,11 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
109
114
  return secondaryNewEntityDisplayValueRef.current;
110
115
  },
111
116
  secondaryDoAdd = async (e, values) => {
117
+ if (canUser && !canUser(ADD, secondaryModel)) {
118
+ showPermissionsError(ADD, secondaryModel);
119
+ return;
120
+ }
121
+
112
122
  let addValues = values;
113
123
 
114
124
  if (SecondaryRepository?.isLoading) {
@@ -197,6 +207,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
197
207
  secondarySetIsEditorShown(true);
198
208
  },
199
209
  secondaryDoEdit = async () => {
210
+ if (canUser && !canUser(EDIT, secondaryModel)) {
211
+ showPermissionsError(EDIT, secondaryModel);
212
+ return;
213
+ }
200
214
  if (_.isEmpty(secondarySelection) || (_.isArray(secondarySelection) && (secondarySelection.length > 1 || secondarySelection[0]?.isDestroyed))) {
201
215
  return;
202
216
  }
@@ -211,6 +225,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
211
225
  secondarySetIsEditorShown(true);
212
226
  },
213
227
  secondaryDoDelete = async (args) => {
228
+ if (canUser && !canUser(DELETE, secondaryModel)) {
229
+ showPermissionsError(DELETE, secondaryModel);
230
+ return;
231
+ }
214
232
  let cb = null;
215
233
  if (_.isFunction(args)) {
216
234
  cb = args;
@@ -263,6 +281,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
263
281
  deleteRecord(false, cb);
264
282
  },
265
283
  deleteRecord = async (moveSubtreeUp, cb) => {
284
+ if (canUser && !canUser(DELETE, secondaryModel)) {
285
+ showPermissionsError(DELETE, secondaryModel);
286
+ return;
287
+ }
266
288
  if (getListeners().onBeforeDelete) {
267
289
  const listenerResult = await getListeners().onBeforeDelete(secondarySelection);
268
290
  if (listenerResult === false) {
@@ -292,6 +314,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
292
314
  if (!secondaryUserCanView) {
293
315
  return;
294
316
  }
317
+ if (canUser && !canUser(VIEW, secondaryModel)) {
318
+ showPermissionsError(VIEW, secondaryModel);
319
+ return;
320
+ }
295
321
  if (secondarySelection.length !== 1) {
296
322
  return;
297
323
  }
@@ -307,6 +333,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
307
333
  if (!secondaryUserCanEdit || secondaryDisableDuplicate) {
308
334
  return;
309
335
  }
336
+ if (canUser && !canUser(DUPLICATE, secondaryModel)) {
337
+ showPermissionsError(DUPLICATE, secondaryModel);
338
+ return;
339
+ }
310
340
  if (secondarySelection.length !== 1) {
311
341
  return;
312
342
  }
@@ -335,6 +365,12 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
335
365
  secondaryDoEdit();
336
366
  },
337
367
  secondaryDoEditorSave = async (data, e) => {
368
+ let mode = editorMode === EDITOR_MODE__ADD ? ADD : EDIT;
369
+ if (canUser && !canUser(mode, secondaryModel)) {
370
+ showPermissionsError(mode, secondaryModel);
371
+ return;
372
+ }
373
+
338
374
  // NOTE: The Form submits onSave for both adds (when not isAutoSsave) and edits.
339
375
  const isSingle = secondarySelection.length === 1;
340
376
  let useStaged = false;
@@ -391,7 +427,11 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
391
427
  await getListeners().onAfterAdd(secondarySelection);
392
428
  }
393
429
  setIsAdding(false);
394
- secondarySetEditorMode(EDITOR_MODE__EDIT);
430
+ if (!canUser || canUser(EDIT, secondaryModel)) {
431
+ secondarySetEditorMode(EDITOR_MODE__EDIT);
432
+ } else {
433
+ secondarySetEditorMode(EDITOR_MODE__VIEW);
434
+ }
395
435
  } else if (secondaryEditorMode === EDITOR_MODE__EDIT) {
396
436
  if (getListeners().onAfterEdit) {
397
437
  await getListeners().onAfterEdit(secondarySelection);
@@ -436,6 +476,11 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
436
476
  secondarySetIsEditorShown(false);
437
477
  },
438
478
  secondaryDoEditorDelete = async () => {
479
+ if (canUser && !canUser(DELETE, secondaryModel)) {
480
+ showPermissionsError(DELETE, secondaryModel);
481
+ return;
482
+ }
483
+
439
484
  secondaryDoDelete(() => {
440
485
  secondarySetEditorMode(EDITOR_MODE__VIEW);
441
486
  secondarySetIsEditorShown(false);
@@ -470,9 +515,19 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
470
515
  return mode;
471
516
  },
472
517
  secondarySetEditMode = () => {
518
+ if (canUser && !canUser(EDIT, secondaryModel)) {
519
+ showPermissionsError(EDIT, secondaryModel);
520
+ return;
521
+ }
522
+
473
523
  secondarySetEditorMode(EDITOR_MODE__EDIT);
474
524
  },
475
525
  secondarySetViewMode = () => {
526
+ if (canUser && !canUser(VIEW, secondaryModel)) {
527
+ showPermissionsError(VIEW, secondaryModel);
528
+ return;
529
+ }
530
+
476
531
  function doIt() {
477
532
  secondarySetEditorMode(EDITOR_MODE__VIEW);
478
533
  }
@@ -2,11 +2,22 @@ import { useEffect, useState, useRef, } from 'react';
2
2
  import {
3
3
  Button,
4
4
  } from 'native-base';
5
+ import {
6
+ ADD,
7
+ EDIT,
8
+ DELETE,
9
+ VIEW,
10
+ COPY,
11
+ DUPLICATE,
12
+ PRINT,
13
+ UPLOAD_DOWNLOAD,
14
+ } from '../../Constants/Commands.js';
5
15
  import {
6
16
  EDITOR_MODE__VIEW,
7
17
  EDITOR_MODE__ADD,
8
18
  EDITOR_MODE__EDIT,
9
19
  EDITOR_TYPE__SIDE,
20
+ EDITOR_TYPE__INLINE,
10
21
  } from '../../Constants/Editor.js';
11
22
  import UiGlobals from '../../UiGlobals.js';
12
23
  import _ from 'lodash';
@@ -56,6 +67,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
56
67
  // withData
57
68
  Repository,
58
69
 
70
+ // withPermissions
71
+ canUser,
72
+ showPermissionsError,
73
+
59
74
  // withSelection
60
75
  selection,
61
76
  setSelection,
@@ -108,6 +123,11 @@ export default function withEditor(WrappedComponent, isTree = false) {
108
123
  return newEntityDisplayValueRef.current;
109
124
  },
110
125
  doAdd = async (e, values) => {
126
+ if (canUser && !canUser(ADD)) {
127
+ showPermissionsError(ADD);
128
+ return;
129
+ }
130
+
111
131
  let addValues = values;
112
132
 
113
133
  if (Repository?.isLoading) {
@@ -201,6 +221,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
201
221
  setIsEditorShown(true);
202
222
  },
203
223
  doEdit = async () => {
224
+ if (canUser && !canUser(EDIT)) {
225
+ showPermissionsError(EDIT);
226
+ return;
227
+ }
204
228
  if (_.isEmpty(selection) || (_.isArray(selection) && (selection.length > 1 || selection[0]?.isDestroyed))) {
205
229
  return;
206
230
  }
@@ -215,6 +239,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
215
239
  setIsEditorShown(true);
216
240
  },
217
241
  doDelete = async (args) => {
242
+ if (canUser && !canUser(DELETE)) {
243
+ showPermissionsError(DELETE);
244
+ return;
245
+ }
218
246
  let cb = null;
219
247
  if (_.isFunction(args)) {
220
248
  cb = args;
@@ -267,6 +295,10 @@ export default function withEditor(WrappedComponent, isTree = false) {
267
295
  deleteRecord(false, cb);
268
296
  },
269
297
  deleteRecord = async (moveSubtreeUp, cb) => {
298
+ if (canUser && !canUser(DELETE)) {
299
+ showPermissionsError(DELETE);
300
+ return;
301
+ }
270
302
  if (getListeners().onBeforeDelete) {
271
303
  const listenerResult = await getListeners().onBeforeDelete(selection);
272
304
  if (listenerResult === false) {
@@ -296,6 +328,17 @@ export default function withEditor(WrappedComponent, isTree = false) {
296
328
  if (!userCanView) {
297
329
  return;
298
330
  }
331
+ if (canUser && !canUser(VIEW)) {
332
+ showPermissionsError(VIEW);
333
+ return;
334
+ }
335
+ if (editorType === EDITOR_TYPE__INLINE) {
336
+ alert('Cannot view in inline editor.');
337
+ return; // inline editor doesn't have a view mode
338
+ }
339
+
340
+ // check permissions for view
341
+
299
342
  if (selection.length !== 1) {
300
343
  return;
301
344
  }
@@ -311,6 +354,14 @@ export default function withEditor(WrappedComponent, isTree = false) {
311
354
  if (!userCanEdit || disableDuplicate) {
312
355
  return;
313
356
  }
357
+ if (canUser && !canUser(DUPLICATE)) {
358
+ showPermissionsError(DUPLICATE);
359
+ return;
360
+ }
361
+
362
+ // check permissions for duplicate
363
+
364
+
314
365
  if (selection.length !== 1) {
315
366
  return;
316
367
  }
@@ -339,6 +390,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
339
390
  doEdit();
340
391
  },
341
392
  doEditorSave = async (data, e) => {
393
+ let mode = editorMode === EDITOR_MODE__ADD ? ADD : EDIT;
394
+ if (canUser && !canUser(mode)) {
395
+ showPermissionsError(mode);
396
+ return;
397
+ }
398
+
342
399
  // NOTE: The Form submits onSave for both adds (when not isAutoSsave) and edits.
343
400
  const isSingle = selection.length === 1;
344
401
  let useStaged = false;
@@ -395,7 +452,11 @@ export default function withEditor(WrappedComponent, isTree = false) {
395
452
  await getListeners().onAfterAddSave(selection);
396
453
  }
397
454
  setIsAdding(false);
398
- setEditorMode(EDITOR_MODE__EDIT);
455
+ if (!canUser || canUser(EDIT)) {
456
+ setEditorMode(EDITOR_MODE__EDIT);
457
+ } else {
458
+ setEditorMode(EDITOR_MODE__VIEW);
459
+ }
399
460
  } else if (editorMode === EDITOR_MODE__EDIT) {
400
461
  if (getListeners().onAfterEdit) {
401
462
  await getListeners().onAfterEdit(selection);
@@ -440,6 +501,11 @@ export default function withEditor(WrappedComponent, isTree = false) {
440
501
  setIsEditorShown(false);
441
502
  },
442
503
  doEditorDelete = async () => {
504
+ if (canUser && !canUser(DELETE)) {
505
+ showPermissionsError(DELETE);
506
+ return;
507
+ }
508
+
443
509
  doDelete(() => {
444
510
  setEditorMode(EDITOR_MODE__VIEW);
445
511
  setIsEditorShown(false);
@@ -484,9 +550,19 @@ export default function withEditor(WrappedComponent, isTree = false) {
484
550
  return mode;
485
551
  },
486
552
  setEditMode = () => {
553
+ if (canUser && !canUser(EDIT)) {
554
+ showPermissionsError(EDIT);
555
+ return;
556
+ }
557
+
487
558
  setEditorMode(EDITOR_MODE__EDIT);
488
559
  },
489
560
  setViewMode = () => {
561
+ if (canUser && !canUser(VIEW)) {
562
+ showPermissionsError(VIEW);
563
+ return;
564
+ }
565
+
490
566
  function doIt() {
491
567
  setEditorMode(EDITOR_MODE__VIEW);
492
568
  }
@@ -4,6 +4,9 @@ import {
4
4
  Row,
5
5
  Text,
6
6
  } from 'native-base';
7
+ import {
8
+ VIEW,
9
+ } from '../../Constants/Commands.js';
7
10
  import * as yup from 'yup'; // https://github.com/jquense/yup#string
8
11
  import Inflector from 'inflector-js';
9
12
  import qs from 'qs';
@@ -19,15 +22,21 @@ import _ from 'lodash';
19
22
  export default function withPdfButtons(WrappedComponent) {
20
23
  return withModal((props) => {
21
24
 
25
+ let showButtons = true;
22
26
  if (!props.showPdfBtns) {
27
+ showButtons = false;
28
+ }
29
+ if (props.canUser && !props.canUser(VIEW)) { // permissions
30
+ showButtons = false;
31
+ }
32
+ if (!showButtons) {
23
33
  // bypass everything.
24
34
  // If we don't do this, we get an infinite recursion with Form
25
35
  // because this HOC wraps Form and uses Form itself.
26
36
  return <WrappedComponent {...props} />;
27
37
  }
28
38
 
29
- const
30
- {
39
+ const {
31
40
  additionalEditButtons = [],
32
41
  additionalViewButtons = [],
33
42
  items = [],
@@ -14,18 +14,21 @@ import UiGlobals from '../../UiGlobals.js';
14
14
  import _ from 'lodash';
15
15
 
16
16
 
17
- export default function withPermissions(WrappedComponent) {
17
+ export default function withPermissions(WrappedComponent, forceUsePermissions = false) {
18
18
  return (props) => {
19
19
 
20
- if (!props.usePermissions) {
20
+ if (!props.usePermissions && !forceUsePermissions) {
21
21
  return <WrappedComponent {...props} />;
22
22
  }
23
23
 
24
24
  const {
25
+ // withAlert
26
+ alert,
27
+
25
28
  // withData
26
29
  Repository,
27
30
  } = props,
28
- model = Repository.schema.name,
31
+ model = Repository?.schema?.name,
29
32
  checkPermission = (permission) => {
30
33
  const
31
34
  reduxState = UiGlobals.redux?.getState(),
@@ -36,6 +39,15 @@ export default function withPermissions(WrappedComponent) {
36
39
  return inArray(permission, permissions);
37
40
  },
38
41
 
42
+ showPermissionsError = (permission, modelForAlert = null) => {
43
+ if (!modelForAlert) {
44
+ modelForAlert = model; // use default model if none supplied
45
+ }
46
+ modelForAlert = Inflector.humanize(Inflector.underscore(modelForAlert)); // 'PmEvents' -> 'pm events'
47
+
48
+ alert(`You are not authorized to ${permission} ${modelForAlert}.`);
49
+ },
50
+
39
51
  /**
40
52
  * Check if user has permission to perform an action
41
53
  *
@@ -50,18 +62,28 @@ export default function withPermissions(WrappedComponent) {
50
62
  */
51
63
  canUser = (permission, modelToCheck = null) => {
52
64
 
53
- // in certain cases, shift permissions to standard CRUD operations
65
+ // deal with special cases that refer to other permissions
54
66
  switch(permission) {
55
- case COPY:
56
- case DUPLICATE:
57
- permission = ADD;
58
- break;
59
67
  case PRINT:
60
68
  permission = VIEW;
61
69
  break;
62
- case UPLOAD_DOWNLOAD:
63
- permission = EDIT;
64
- break;
70
+ case COPY:
71
+ case DUPLICATE: {
72
+ // user must have ADD _and_ EDIT permissions, so check both
73
+ const
74
+ hasAddPermission = canUser(ADD, modelToCheck),
75
+ hasEditPermission = canUser(EDIT, modelToCheck);
76
+ return hasAddPermission && hasEditPermission;
77
+ }
78
+ case UPLOAD_DOWNLOAD: {
79
+ // user must have VIEW, ADD, EDIT, and DELETE permissions, so check all of them
80
+ const
81
+ hasViewPermission = canUser(VIEW, modelToCheck),
82
+ hasAddPermission = canUser(ADD, modelToCheck),
83
+ hasEditPermission = canUser(EDIT, modelToCheck),
84
+ hasDeletePermission = canUser(DELETE, modelToCheck);
85
+ return hasViewPermission && hasAddPermission && hasEditPermission && hasDeletePermission;
86
+ }
65
87
  default:
66
88
  // do nothing
67
89
  break;
@@ -82,6 +104,7 @@ export default function withPermissions(WrappedComponent) {
82
104
  return <WrappedComponent
83
105
  {...props}
84
106
  canUser={canUser}
107
+ showPermissionsError={showPermissionsError}
85
108
  />;
86
109
  };
87
110
  }
@@ -52,9 +52,9 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
52
52
  additionalToolbarButtons = [],
53
53
  useUploadDownload = false,
54
54
  onChangeColumnsConfig,
55
- verifyCanEdit,
56
- verifyCanDelete,
57
- verifyCanDuplicate,
55
+ canRecordBeEdited,
56
+ canRecordBeDeleted,
57
+ canRecordBeDuplicated,
58
58
  ...propsToPass
59
59
  } = props,
60
60
  {
@@ -204,7 +204,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
204
204
  if (_.isEmpty(selection) || (_.isArray(selection) && selection.length > 1)) {
205
205
  isDisabled = true;
206
206
  }
207
- if (verifyCanEdit && !verifyCanEdit(selection)) {
207
+ if (canRecordBeEdited && !canRecordBeEdited(selection)) {
208
208
  isDisabled = true;
209
209
  }
210
210
  break;
@@ -219,7 +219,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
219
219
  if (_.isEmpty(selection) || (_.isArray(selection) && selection.length > 1)) {
220
220
  isDisabled = true;
221
221
  }
222
- if (verifyCanDelete && !verifyCanDelete(selection)) {
222
+ if (canRecordBeDeleted && !canRecordBeDeleted(selection)) {
223
223
  isDisabled = true;
224
224
  }
225
225
  if (isTree) {
@@ -267,7 +267,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
267
267
  if (_.isEmpty(selection) || selection.length > 1) {
268
268
  isDisabled = true;
269
269
  }
270
- if (verifyCanDuplicate && !verifyCanDuplicate(selection)) {
270
+ if (canRecordBeDuplicated && !canRecordBeDuplicated(selection)) {
271
271
  isDisabled = true;
272
272
  }
273
273
  break;
@@ -0,0 +1,17 @@
1
+ import {
2
+ Column,
3
+ Text,
4
+ } from 'native-base';
5
+
6
+ export default function Unauthorized(props) {
7
+ const
8
+ {
9
+ text = 'Unauthorized.',
10
+ } = props;
11
+ return <Column justifyContent="center" alignItems="center" w="100%" flex={1}>
12
+ <Text
13
+ textAlign="center"
14
+ color="#f00"
15
+ >{text}</Text>
16
+ </Column>;
17
+ }
@@ -13,6 +13,9 @@ import {
13
13
  SELECTION_MODE_SINGLE,
14
14
  SELECTION_MODE_MULTI,
15
15
  } from '../../Constants/Selection.js';
16
+ import {
17
+ EDIT,
18
+ } from '../../Constants/Commands.js';
16
19
  import {
17
20
  VERTICAL,
18
21
  } from '../../Constants/Directions.js';
@@ -56,6 +59,7 @@ import PaginationToolbar from '../Toolbar/PaginationToolbar.js';
56
59
  import NoRecordsFound from '../Grid/NoRecordsFound.js';
57
60
  import Toolbar from '../Toolbar/Toolbar.js';
58
61
  import Loading from '../Messages/Loading.js';
62
+ import Unauthorized from '../Messages/Unauthorized.js';
59
63
  import _ from 'lodash';
60
64
 
61
65
  const DEPTH_INDENT_PX = 25;
@@ -106,6 +110,7 @@ function TreeComponent(props) {
106
110
  reload = null, // Whenever this value changes after initial render, the tree will reload from scratch
107
111
  parentIdIx,
108
112
  initialSelection,
113
+ canRecordBeEdited,
109
114
  onTreeLoad,
110
115
 
111
116
  // withComponent
@@ -135,6 +140,9 @@ function TreeComponent(props) {
135
140
  idIx,
136
141
  displayIx,
137
142
 
143
+ // withPermissions
144
+ canUser,
145
+
138
146
  // withSelection
139
147
  selection,
140
148
  setSelection,
@@ -864,6 +872,12 @@ function TreeComponent(props) {
864
872
  onNodeClick(item, e); // so reselect it
865
873
  }
866
874
  if (onEdit) {
875
+ if (canUser && !canUser(EDIT)) { // permissions
876
+ return;
877
+ }
878
+ if (canRecordBeEdited && !canRecordBeEdited(selection)) { // record can be edited
879
+ return;
880
+ }
867
881
  onEdit();
868
882
  }
869
883
  break;
@@ -1177,6 +1191,10 @@ function TreeComponent(props) {
1177
1191
  }
1178
1192
  }, [selectorId, selectorSelected]);
1179
1193
 
1194
+ if (canUser && !canUser('view')) {
1195
+ return <Unauthorized />;
1196
+ }
1197
+
1180
1198
  if (setWithEditListeners) {
1181
1199
  setWithEditListeners({ // Update withEdit's listeners on every render
1182
1200
  onBeforeAdd,
@@ -6,6 +6,9 @@ import {
6
6
  Row,
7
7
  Text,
8
8
  } from 'native-base';
9
+ import {
10
+ EDIT,
11
+ } from '../../Constants/Commands.js';
9
12
  import {
10
13
  EDITOR_TYPE__SIDE,
11
14
  } from '../../Constants/Editor.js';
@@ -33,7 +36,7 @@ function Viewer(props) {
33
36
  columnDefaults = {}, // defaults for each Column defined in items (above)
34
37
  record,
35
38
  additionalViewButtons,
36
- verifyCanEdit,
39
+ canRecordBeEdited,
37
40
 
38
41
  // withComponent
39
42
  self,
@@ -41,6 +44,10 @@ function Viewer(props) {
41
44
  // withData
42
45
  Repository,
43
46
 
47
+ // withPermissions
48
+ canUser,
49
+ showPermissionsError,
50
+
44
51
  // withEditor
45
52
  editorType,
46
53
  onEditMode,
@@ -246,7 +253,7 @@ function Viewer(props) {
246
253
  }
247
254
 
248
255
  let canEdit = true;
249
- if (verifyCanEdit && !verifyCanEdit([record])) {
256
+ if (canRecordBeEdited && !canRecordBeEdited([record])) {
250
257
  canEdit = false;
251
258
  }
252
259
 
@@ -259,13 +266,14 @@ function Viewer(props) {
259
266
  <Row flex={1} alignItems="center">
260
267
  <Text fontSize={20} ml={2} color="trueGray.500">View Mode</Text>
261
268
  </Row>
262
- <Button
263
- {...testProps('toEditBtn')}
264
- key="editBtn"
265
- onPress={onEditMode}
266
- leftIcon={<Icon as={Pencil} color="#fff" size="sm" />}
267
- color="#fff"
268
- >To Edit</Button>
269
+ {(!canUser || canUser(EDIT)) &&
270
+ <Button
271
+ {...testProps('toEditBtn')}
272
+ key="editBtn"
273
+ onPress={onEditMode}
274
+ leftIcon={<Icon as={Pencil} color="#fff" size="sm" />}
275
+ color="#fff"
276
+ >To Edit</Button>}
269
277
  </Toolbar>}
270
278
  {!_.isEmpty(additionalButtons) &&
271
279
  <Toolbar justifyContent="flex-end" flexWrap="wrap">
@@ -0,0 +1,7 @@
1
+ export default function chunkArray(array, chunkSize) {
2
+ const result = [];
3
+ for (let i = 0; i < array.length; i += chunkSize) {
4
+ result.push(array.slice(i, i + chunkSize));
5
+ }
6
+ return result;
7
+ }