@strapi/admin 4.12.0-beta.1 → 4.12.0-beta.4

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 (166) hide show
  1. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +14 -38
  2. package/admin/src/content-manager/components/RelationInput/components/Option.js +6 -5
  3. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +7 -7
  4. package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFields.js +23 -21
  5. package/admin/src/content-manager/pages/EditSettingsView/components/ModalForm.js +21 -23
  6. package/admin/src/content-manager/pages/ListSettingsView/components/SortDisplayedFields.js +24 -22
  7. package/admin/src/content-manager/pages/ListView/components/Body/index.js +191 -0
  8. package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/ConfirmBulkActionDialog/index.js +164 -0
  9. package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/index.js +468 -0
  10. package/admin/src/content-manager/{components/DynamicTable/BulkActionsBar → pages/ListView/components/BulkActionButtons}/index.js +56 -132
  11. package/admin/src/content-manager/pages/ListView/components/CellContent/RelationMultiple/index.js +63 -69
  12. package/admin/src/content-manager/pages/ListView/components/CellContent/RepeatableComponent/index.js +28 -21
  13. package/admin/src/content-manager/pages/ListView/components/TableRows/index.js +7 -3
  14. package/admin/src/content-manager/pages/ListView/index.js +191 -132
  15. package/admin/src/layouts/UnauthenticatedLayout/LocaleToggle/index.js +7 -7
  16. package/admin/src/pages/AuthPage/components/Register/index.js +4 -0
  17. package/admin/src/translations/ca.json +4 -4
  18. package/admin/src/translations/de.json +4 -4
  19. package/admin/src/translations/dk.json +2 -2
  20. package/admin/src/translations/en.json +23 -4
  21. package/admin/src/translations/es.json +4 -4
  22. package/admin/src/translations/eu.json +4 -4
  23. package/admin/src/translations/fr.json +2 -2
  24. package/admin/src/translations/gu.json +4 -4
  25. package/admin/src/translations/hi.json +4 -4
  26. package/admin/src/translations/hu.json +4 -4
  27. package/admin/src/translations/ja.json +2 -2
  28. package/admin/src/translations/ko.json +2 -2
  29. package/admin/src/translations/ml.json +4 -4
  30. package/admin/src/translations/nl.json +4 -4
  31. package/admin/src/translations/pl.json +4 -4
  32. package/admin/src/translations/pt-BR.json +4 -4
  33. package/admin/src/translations/ru.json +4 -4
  34. package/admin/src/translations/sa.json +4 -4
  35. package/admin/src/translations/sk.json +4 -4
  36. package/admin/src/translations/sv.json +4 -4
  37. package/admin/src/translations/tr.json +4 -4
  38. package/admin/src/translations/zh-Hans.json +4 -4
  39. package/admin/src/translations/zh.json +4 -4
  40. package/build/2379.0ca87a89.chunk.js +1 -0
  41. package/build/2395.df7a044a.chunk.js +26 -0
  42. package/build/2801.b1140c9b.chunk.js +1 -0
  43. package/build/{3100.21c343fa.chunk.js → 3100.2ba4df95.chunk.js} +1 -1
  44. package/build/{3483.e182b190.chunk.js → 3483.e2ee2547.chunk.js} +1 -1
  45. package/build/{970.89601f27.chunk.js → 3739.63e352f1.chunk.js} +52 -20
  46. package/build/3984.dda474f7.chunk.js +1 -0
  47. package/build/502.8ae8ef60.chunk.js +1 -0
  48. package/build/5483.6dd2e776.chunk.js +6 -0
  49. package/build/6158.c974fd83.chunk.js +1 -0
  50. package/build/6691.f880a0b6.chunk.js +105 -0
  51. package/build/7065.99ca8ab1.chunk.js +112 -0
  52. package/build/7464.8a6c1e6c.chunk.js +1 -0
  53. package/build/8276.6c7b8e6e.chunk.js +26 -0
  54. package/build/{9932.b5a3bb3a.chunk.js → 9806.91360bb6.chunk.js} +47 -47
  55. package/build/{Admin-authenticatedApp.2ffa318a.chunk.js → Admin-authenticatedApp.24998de8.chunk.js} +2 -2
  56. package/build/Admin_settingsPage.8c600d1a.chunk.js +111 -0
  57. package/build/{admin-app.088bcd33.chunk.js → admin-app.c2e4e128.chunk.js} +9 -9
  58. package/build/{ca-json.1fed5d8b.chunk.js → ca-json.a53c10b6.chunk.js} +1 -1
  59. package/build/content-manager.8772445b.chunk.js +1103 -0
  60. package/build/{content-type-builder-translation-ar-json.56d8fcf4.chunk.js → content-type-builder-translation-ar-json.3e808e2f.chunk.js} +1 -1
  61. package/build/{content-type-builder-translation-cs-json.a5b299ca.chunk.js → content-type-builder-translation-cs-json.1ef9e106.chunk.js} +1 -1
  62. package/build/{content-type-builder-translation-de-json.393a76c0.chunk.js → content-type-builder-translation-de-json.63fcff7b.chunk.js} +1 -1
  63. package/build/{content-type-builder-translation-dk-json.fbd39bb7.chunk.js → content-type-builder-translation-dk-json.fd626b67.chunk.js} +1 -1
  64. package/build/{content-type-builder-translation-en-json.38e20391.chunk.js → content-type-builder-translation-en-json.ed29ff4d.chunk.js} +1 -1
  65. package/build/{content-type-builder-translation-es-json.9288474b.chunk.js → content-type-builder-translation-es-json.a4a361a9.chunk.js} +1 -1
  66. package/build/{content-type-builder-translation-fr-json.d35e269c.chunk.js → content-type-builder-translation-fr-json.499c3a46.chunk.js} +1 -1
  67. package/build/{content-type-builder-translation-id-json.f0513929.chunk.js → content-type-builder-translation-id-json.65255f93.chunk.js} +1 -1
  68. package/build/{content-type-builder-translation-it-json.aaf16753.chunk.js → content-type-builder-translation-it-json.e268ab74.chunk.js} +1 -1
  69. package/build/{content-type-builder-translation-ko-json.8fe21a7f.chunk.js → content-type-builder-translation-ko-json.04cb309d.chunk.js} +1 -1
  70. package/build/{content-type-builder-translation-ms-json.3b5d2d3e.chunk.js → content-type-builder-translation-ms-json.f6b743b9.chunk.js} +1 -1
  71. package/build/{content-type-builder-translation-nl-json.225ef5d3.chunk.js → content-type-builder-translation-nl-json.997fe8cc.chunk.js} +1 -1
  72. package/build/{content-type-builder-translation-pl-json.92f36be2.chunk.js → content-type-builder-translation-pl-json.634f638b.chunk.js} +1 -1
  73. package/build/{content-type-builder-translation-pt-BR-json.3bd10f89.chunk.js → content-type-builder-translation-pt-BR-json.6a95dc71.chunk.js} +1 -1
  74. package/build/{content-type-builder-translation-ru-json.9bfe47ce.chunk.js → content-type-builder-translation-ru-json.3af65503.chunk.js} +1 -1
  75. package/build/{content-type-builder-translation-sk-json.d03cc18a.chunk.js → content-type-builder-translation-sk-json.c6078082.chunk.js} +1 -1
  76. package/build/{content-type-builder-translation-sv-json.d23dcd32.chunk.js → content-type-builder-translation-sv-json.a6df2462.chunk.js} +1 -1
  77. package/build/{content-type-builder-translation-th-json.7ad256e2.chunk.js → content-type-builder-translation-th-json.122277cc.chunk.js} +1 -1
  78. package/build/{content-type-builder-translation-tr-json.926f6191.chunk.js → content-type-builder-translation-tr-json.41f44f77.chunk.js} +1 -1
  79. package/build/{content-type-builder-translation-uk-json.7bf19546.chunk.js → content-type-builder-translation-uk-json.e1315acd.chunk.js} +1 -1
  80. package/build/{content-type-builder-translation-zh-Hans-json.415577fb.chunk.js → content-type-builder-translation-zh-Hans-json.6ff57db6.chunk.js} +1 -1
  81. package/build/{content-type-builder-translation-zh-json.958d90e1.chunk.js → content-type-builder-translation-zh-json.3532b962.chunk.js} +1 -1
  82. package/build/{content-type-builder.3963fb2d.chunk.js → content-type-builder.40534de5.chunk.js} +21 -17
  83. package/build/{de-json.fcac7381.chunk.js → de-json.b3be02c7.chunk.js} +1 -1
  84. package/build/{dk-json.e34cad0d.chunk.js → dk-json.842aa391.chunk.js} +1 -1
  85. package/build/{en-json.fb9f6ddd.chunk.js → en-json.4c733bd1.chunk.js} +1 -1
  86. package/build/{es-json.42096084.chunk.js → es-json.f57b5335.chunk.js} +1 -1
  87. package/build/{eu-json.fb17c8f9.chunk.js → eu-json.633025f0.chunk.js} +1 -1
  88. package/build/{fr-json.69789980.chunk.js → fr-json.aa8839d2.chunk.js} +1 -1
  89. package/build/{gu-json.4d667d0c.chunk.js → gu-json.5bd62812.chunk.js} +1 -1
  90. package/build/{hi-json.323be97d.chunk.js → hi-json.9104eb78.chunk.js} +1 -1
  91. package/build/{hu-json.fe71e6c8.chunk.js → hu-json.9f4aae42.chunk.js} +1 -1
  92. package/build/index.html +1 -1
  93. package/build/{ja-json.81b6d1e3.chunk.js → ja-json.91286391.chunk.js} +1 -1
  94. package/build/{ko-json.4539f4ba.chunk.js → ko-json.fcf3ec4b.chunk.js} +1 -1
  95. package/build/main.ef5fb1a8.js +2856 -0
  96. package/build/{ml-json.8988e374.chunk.js → ml-json.557aa14c.chunk.js} +1 -1
  97. package/build/{nl-json.98345913.chunk.js → nl-json.b2b16eea.chunk.js} +1 -1
  98. package/build/{pl-json.59a5dab3.chunk.js → pl-json.f094a417.chunk.js} +1 -1
  99. package/build/{pt-BR-json.9410688b.chunk.js → pt-BR-json.dec7fb01.chunk.js} +1 -1
  100. package/build/review-workflows-settings-create-view.d4b5dbb8.chunk.js +1 -0
  101. package/build/review-workflows-settings-edit-view.77299c63.chunk.js +1 -0
  102. package/build/review-workflows-settings-list-view.3ee9190d.chunk.js +56 -0
  103. package/build/{ru-json.678cd48b.chunk.js → ru-json.8193d8c4.chunk.js} +1 -1
  104. package/build/{runtime~main.44bf2a37.js → runtime~main.c99f4c36.js} +2 -2
  105. package/build/{sa-json.6359a11c.chunk.js → sa-json.a56836f1.chunk.js} +1 -1
  106. package/build/{sk-json.2374f129.chunk.js → sk-json.bf2f057a.chunk.js} +1 -1
  107. package/build/sso-settings-page.3a1ed8c9.chunk.js +1 -0
  108. package/build/{sv-json.ae6e71ea.chunk.js → sv-json.fd0e86c6.chunk.js} +1 -1
  109. package/build/{tr-json.bac5dbd3.chunk.js → tr-json.56c32cf6.chunk.js} +1 -1
  110. package/build/upload.cbfeefa5.chunk.js +58 -0
  111. package/build/{zh-Hans-json.fada6f40.chunk.js → zh-Hans-json.36d81cdc.chunk.js} +1 -1
  112. package/build/{zh-json.3529f1e5.chunk.js → zh-json.1cc86ff0.chunk.js} +1 -1
  113. package/ee/admin/constants.js +3 -0
  114. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +12 -4
  115. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +2 -2
  116. package/ee/admin/hooks/index.js +1 -1
  117. package/ee/admin/hooks/useLicenseLimitNotification/index.js +1 -1
  118. package/ee/admin/hooks/useLicenseLimits/__mocks__/index.js +8 -0
  119. package/ee/admin/hooks/useLicenseLimits/index.js +1 -3
  120. package/ee/admin/hooks/useLicenseLimits/useLicenseLimits.js +3 -13
  121. package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +19 -4
  122. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +7 -0
  123. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +20 -16
  124. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +38 -23
  125. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +3 -0
  126. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +62 -14
  127. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/index.js +8 -3
  128. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +91 -33
  129. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/index.js +8 -3
  130. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +72 -55
  131. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/index.js +8 -3
  132. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +1 -7
  133. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/{getWorkflowValidationSchema.js → validateWorkflow.js} +33 -2
  134. package/ee/admin/pages/SettingsPage/pages/SingleSignOn/utils/schema.js +8 -5
  135. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/index.js +1 -1
  136. package/ee/server/constants/workflows.js +1 -0
  137. package/ee/server/services/review-workflows/review-workflows.js +1 -1
  138. package/ee/server/services/review-workflows/validation.js +12 -4
  139. package/ee/server/services/review-workflows/workflows/content-types.js +28 -19
  140. package/ee/server/services/review-workflows/workflows/index.js +14 -2
  141. package/ee/server/validation/authentication.js +14 -8
  142. package/ee/server/validation/review-workflows.js +6 -2
  143. package/package.json +9 -9
  144. package/build/190.66d89241.chunk.js +0 -117
  145. package/build/2379.d33a2e16.chunk.js +0 -1
  146. package/build/2395.b0419a54.chunk.js +0 -26
  147. package/build/2801.18ac397d.chunk.js +0 -1
  148. package/build/3984.ea7b8036.chunk.js +0 -1
  149. package/build/502.ccb38223.chunk.js +0 -1
  150. package/build/5483.ed2c7efa.chunk.js +0 -6
  151. package/build/6158.f9d82db9.chunk.js +0 -1
  152. package/build/6691.e6d5ac38.chunk.js +0 -105
  153. package/build/7464.c6d0565c.chunk.js +0 -1
  154. package/build/8276.23e0763b.chunk.js +0 -26
  155. package/build/Admin_settingsPage.3ad19487.chunk.js +0 -79
  156. package/build/content-manager.9b569036.chunk.js +0 -1094
  157. package/build/main.98c989b0.js +0 -2908
  158. package/build/review-workflows-settings-create-view.60bc516c.chunk.js +0 -1
  159. package/build/review-workflows-settings-edit-view.898ea409.chunk.js +0 -1
  160. package/build/review-workflows-settings-list-view.240cacdf.chunk.js +0 -56
  161. package/build/sso-settings-page.ed6f3f15.chunk.js +0 -1
  162. package/build/upload.8d01c525.chunk.js +0 -26
  163. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/ProtectedPage.js +0 -21
  164. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/ProtectedPage/index.js +0 -1
  165. /package/admin/src/content-manager/components/{DynamicTable → ListViewTable}/CellContent/PublicationState/PublicationState.js +0 -0
  166. /package/admin/src/content-manager/components/{DynamicTable → ListViewTable}/CellContent/PublicationState/index.js +0 -0
@@ -12,26 +12,27 @@ import { AddStage } from '../AddStage';
12
12
 
13
13
  import { Stage } from './Stage';
14
14
 
15
- const StagesContainer = styled(Box)`
16
- position: relative;
17
- `;
18
-
19
15
  const Background = styled(Box)`
20
- left: 50%;
21
- position: absolute;
22
- top: 0;
23
16
  transform: translateX(-50%);
24
17
  `;
25
18
 
26
- function Stages({ stages }) {
19
+ export function Stages({ canDelete, canUpdate, stages }) {
27
20
  const { formatMessage } = useIntl();
28
21
  const dispatch = useDispatch();
29
22
  const { trackUsage } = useTracking();
30
23
 
31
24
  return (
32
25
  <Flex direction="column" gap={6} width="100%">
33
- <StagesContainer spacing={4} width="100%">
34
- <Background background="neutral200" height="100%" width={2} zIndex={1} />
26
+ <Box position="relative" spacing={4} width="100%">
27
+ <Background
28
+ background="neutral200"
29
+ height="100%"
30
+ left="50%"
31
+ position="absolute"
32
+ top="0"
33
+ width={2}
34
+ zIndex={1}
35
+ />
35
36
 
36
37
  <Flex
37
38
  direction="column"
@@ -49,18 +50,19 @@ function Stages({ stages }) {
49
50
  <Stage
50
51
  id={id}
51
52
  index={index}
52
- canDelete={stages.length > 1}
53
53
  isOpen={!stage.id}
54
+ canDelete={stages.length > 1 && canDelete}
54
55
  canReorder={stages.length > 1}
56
+ canUpdate={canUpdate}
55
57
  stagesCount={stages.length}
56
58
  />
57
59
  </Box>
58
60
  );
59
61
  })}
60
62
  </Flex>
61
- </StagesContainer>
63
+ </Box>
62
64
 
63
- <Flex direction="column" gap={6}>
65
+ {canUpdate && (
64
66
  <AddStage
65
67
  type="button"
66
68
  onClick={() => {
@@ -73,18 +75,20 @@ function Stages({ stages }) {
73
75
  defaultMessage: 'Add new stage',
74
76
  })}
75
77
  </AddStage>
76
- </Flex>
78
+ )}
77
79
  </Flex>
78
80
  );
79
81
  }
80
82
 
81
- export { Stages };
82
-
83
83
  Stages.defaultProps = {
84
+ canDelete: true,
85
+ canUpdate: true,
84
86
  stages: [],
85
87
  };
86
88
 
87
89
  Stages.propTypes = {
90
+ canDelete: PropTypes.bool,
91
+ canUpdate: PropTypes.bool,
88
92
  stages: PropTypes.arrayOf(
89
93
  PropTypes.shape({
90
94
  id: PropTypes.number,
@@ -9,7 +9,7 @@ import { useDispatch } from 'react-redux';
9
9
 
10
10
  import { updateWorkflow } from '../../actions';
11
11
 
12
- export function WorkflowAttributes({ contentTypes: { collectionTypes, singleTypes } }) {
12
+ export function WorkflowAttributes({ canUpdate, contentTypes: { collectionTypes, singleTypes } }) {
13
13
  const { formatMessage, locale } = useIntl();
14
14
  const dispatch = useDispatch();
15
15
  const [nameField, nameMeta, nameHelper] = useField('name');
@@ -24,6 +24,7 @@ export function WorkflowAttributes({ contentTypes: { collectionTypes, singleType
24
24
  <TextInput
25
25
  {...nameField}
26
26
  id={nameField.name}
27
+ disabled={!canUpdate}
27
28
  label={formatMessage({
28
29
  id: 'Settings.review-workflows.workflow.name.label',
29
30
  defaultMessage: 'Workflow Name',
@@ -50,6 +51,7 @@ export function WorkflowAttributes({ contentTypes: { collectionTypes, singleType
50
51
  { count: value.length }
51
52
  )
52
53
  }
54
+ disabled={!canUpdate}
53
55
  error={contentTypesMeta.error ?? false}
54
56
  id={contentTypesField.name}
55
57
  label={formatMessage({
@@ -61,29 +63,37 @@ export function WorkflowAttributes({ contentTypes: { collectionTypes, singleType
61
63
  contentTypesHelper.setValue(values);
62
64
  }}
63
65
  options={[
64
- {
65
- label: formatMessage({
66
- id: 'Settings.review-workflows.workflow.contentTypes.collectionTypes.label',
67
- defaultMessage: 'Collection Types',
68
- }),
69
- children: collectionTypes
70
- .sort((a, b) => formatter.compare(a.info.displayName, b.info.displayName))
71
- .map((contentType) => ({
72
- label: contentType.info.displayName,
73
- value: contentType.uid,
74
- })),
75
- },
66
+ ...(collectionTypes.length > 0
67
+ ? [
68
+ {
69
+ label: formatMessage({
70
+ id: 'Settings.review-workflows.workflow.contentTypes.collectionTypes.label',
71
+ defaultMessage: 'Collection Types',
72
+ }),
73
+ children: collectionTypes
74
+ .sort((a, b) => formatter.compare(a.info.displayName, b.info.displayName))
75
+ .map((contentType) => ({
76
+ label: contentType.info.displayName,
77
+ value: contentType.uid,
78
+ })),
79
+ },
80
+ ]
81
+ : []),
76
82
 
77
- {
78
- label: formatMessage({
79
- id: 'Settings.review-workflows.workflow.contentTypes.singleTypes.label',
80
- defaultMessage: 'Single Types',
81
- }),
82
- children: singleTypes.map((contentType) => ({
83
- label: contentType.info.displayName,
84
- value: contentType.uid,
85
- })),
86
- },
83
+ ...(singleTypes.length > 0
84
+ ? [
85
+ {
86
+ label: formatMessage({
87
+ id: 'Settings.review-workflows.workflow.contentTypes.singleTypes.label',
88
+ defaultMessage: 'Single Types',
89
+ }),
90
+ children: singleTypes.map((contentType) => ({
91
+ label: contentType.info.displayName,
92
+ value: contentType.uid,
93
+ })),
94
+ },
95
+ ]
96
+ : []),
87
97
  ]}
88
98
  placeholder={formatMessage({
89
99
  id: 'Settings.review-workflows.workflow.contentTypes.placeholder',
@@ -102,7 +112,12 @@ const ContentTypeType = PropTypes.shape({
102
112
  }).isRequired,
103
113
  });
104
114
 
115
+ WorkflowAttributes.defaultProps = {
116
+ canUpdate: true,
117
+ };
118
+
105
119
  WorkflowAttributes.propTypes = {
120
+ canUpdate: PropTypes.bool,
106
121
  contentTypes: PropTypes.shape({
107
122
  collectionTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
108
123
  singleTypes: PropTypes.arrayOf(ContentTypeType).isRequired,
@@ -32,3 +32,6 @@ export const STAGE_COLOR_DEFAULT = lightTheme.colors.primary600;
32
32
  export const DRAG_DROP_TYPES = {
33
33
  STAGE: 'stage',
34
34
  };
35
+
36
+ export const CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME = 'numberOfWorkflows';
37
+ export const CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME = 'stagesPerWorkflow';
@@ -1,9 +1,15 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { Button, Flex, Loader } from '@strapi/design-system';
4
- import { useAPIErrorHandler, useFetchClient, useNotification } from '@strapi/helper-plugin';
4
+ import {
5
+ useAPIErrorHandler,
6
+ useFetchClient,
7
+ useNotification,
8
+ useRBAC,
9
+ } from '@strapi/helper-plugin';
5
10
  import { Check } from '@strapi/icons';
6
11
  import { useFormik, Form, FormikProvider } from 'formik';
12
+ import set from 'lodash/set';
7
13
  import { useIntl } from 'react-intl';
8
14
  import { useMutation } from 'react-query';
9
15
  import { useDispatch, useSelector } from 'react-redux';
@@ -11,16 +17,21 @@ import { useHistory } from 'react-router-dom';
11
17
 
12
18
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
13
19
  import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
20
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
14
21
  import { useLicenseLimits } from '../../../../../../hooks';
15
- import { resetWorkflow } from '../../actions';
22
+ import { addStage, resetWorkflow } from '../../actions';
16
23
  import * as Layout from '../../components/Layout';
17
24
  import * as LimitsModal from '../../components/LimitsModal';
18
25
  import { Stages } from '../../components/Stages';
19
26
  import { WorkflowAttributes } from '../../components/WorkflowAttributes';
20
- import { REDUX_NAMESPACE } from '../../constants';
27
+ import {
28
+ CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
29
+ CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME,
30
+ REDUX_NAMESPACE,
31
+ } from '../../constants';
21
32
  import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
22
33
  import { reducer, initialState } from '../../reducer';
23
- import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
34
+ import { validateWorkflow } from '../../utils/validateWorkflow';
24
35
 
25
36
  export function ReviewWorkflowsCreateView() {
26
37
  const { formatMessage } = useIntl();
@@ -28,6 +39,7 @@ export function ReviewWorkflowsCreateView() {
28
39
  const { push } = useHistory();
29
40
  const { formatAPIError } = useAPIErrorHandler();
30
41
  const dispatch = useDispatch();
42
+ const permissions = useSelector(selectAdminPermissions);
31
43
  const toggleNotification = useNotification();
32
44
  const { collectionTypes, singleTypes, isLoading: isLoadingModels } = useContentTypes();
33
45
  const {
@@ -35,9 +47,13 @@ export function ReviewWorkflowsCreateView() {
35
47
  currentWorkflow: { data: currentWorkflow, isDirty: currentWorkflowIsDirty },
36
48
  },
37
49
  } = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
50
+ const {
51
+ allowedActions: { canCreate },
52
+ } = useRBAC(permissions.settings['review-workflows']);
38
53
  const [showLimitModal, setShowLimitModal] = React.useState(false);
39
54
  const { isLoading: isLicenseLoading, getFeature } = useLicenseLimits();
40
55
  const { meta, isLoading: isWorkflowLoading } = useReviewWorkflows();
56
+ const [initialErrors, setInitialErrors] = React.useState(null);
41
57
 
42
58
  const { mutateAsync, isLoading } = useMutation(
43
59
  async ({ workflow }) => {
@@ -70,6 +86,21 @@ export function ReviewWorkflowsCreateView() {
70
86
 
71
87
  return workflow;
72
88
  } catch (error) {
89
+ // TODO: this would benefit from a utility to get a formik error
90
+ // representation from an API error
91
+ if (
92
+ error.response.data?.error?.name === 'ValidationError' &&
93
+ error.response.data?.error?.details?.errors?.length > 0
94
+ ) {
95
+ setInitialErrors(
96
+ error.response.data?.error?.details?.errors.reduce((acc, error) => {
97
+ set(acc, error.path, error.message);
98
+
99
+ return acc;
100
+ }, {})
101
+ );
102
+ }
103
+
73
104
  toggleNotification({
74
105
  type: 'warning',
75
106
  message: formatAPIError(error),
@@ -83,6 +114,7 @@ export function ReviewWorkflowsCreateView() {
83
114
 
84
115
  const formik = useFormik({
85
116
  enableReinitialize: true,
117
+ initialErrors,
86
118
  initialValues: currentWorkflow,
87
119
  async onSubmit() {
88
120
  /**
@@ -91,7 +123,10 @@ export function ReviewWorkflowsCreateView() {
91
123
  * update, because it would throw an API error.
92
124
  */
93
125
 
94
- if (limits?.workflows && meta?.workflowCount >= parseInt(limits.workflows, 10)) {
126
+ if (
127
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
128
+ meta?.workflowCount >= parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
129
+ ) {
95
130
  setShowLimitModal('workflow');
96
131
 
97
132
  /**
@@ -100,21 +135,31 @@ export function ReviewWorkflowsCreateView() {
100
135
  * update, because it would throw an API error.
101
136
  */
102
137
  } else if (
103
- limits?.stagesPerWorkflow &&
104
- currentWorkflow.stages.length >= parseInt(limits.stagesPerWorkflow, 10)
138
+ limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
139
+ currentWorkflow.stages.length >=
140
+ parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
105
141
  ) {
106
142
  setShowLimitModal('stage');
107
143
  } else {
108
144
  submitForm();
109
145
  }
110
146
  },
111
- validationSchema: getWorkflowValidationSchema({ formatMessage }),
147
+ validate(values) {
148
+ return validateWorkflow({ values, formatMessage });
149
+ },
112
150
  });
113
151
 
114
152
  useInjectReducer(REDUX_NAMESPACE, reducer);
115
153
 
116
154
  React.useEffect(() => {
117
155
  dispatch(resetWorkflow());
156
+
157
+ // Create an empty default stage
158
+ dispatch(
159
+ addStage({
160
+ name: '',
161
+ })
162
+ );
118
163
  }, [dispatch]);
119
164
 
120
165
  /**
@@ -132,11 +177,15 @@ export function ReviewWorkflowsCreateView() {
132
177
 
133
178
  React.useEffect(() => {
134
179
  if (!isWorkflowLoading && !isLicenseLoading) {
135
- if (limits.workflows && meta?.workflowsTotal >= parseInt(limits.workflows, 10)) {
180
+ if (
181
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
182
+ meta?.workflowsTotal >= parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
183
+ ) {
136
184
  setShowLimitModal('workflow');
137
185
  } else if (
138
- limits.stagesPerWorkflow &&
139
- currentWorkflow.stages.length >= parseInt(limits.stagesPerWorkflow, 10)
186
+ limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
187
+ currentWorkflow.stages.length >=
188
+ parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
140
189
  ) {
141
190
  setShowLimitModal('stage');
142
191
  }
@@ -144,8 +193,7 @@ export function ReviewWorkflowsCreateView() {
144
193
  }, [
145
194
  isLicenseLoading,
146
195
  isWorkflowLoading,
147
- limits.stagesPerWorkflow,
148
- limits?.workflows,
196
+ limits,
149
197
  meta?.workflowsTotal,
150
198
  currentWorkflow.stages.length,
151
199
  ]);
@@ -163,7 +211,7 @@ export function ReviewWorkflowsCreateView() {
163
211
  startIcon={<Check />}
164
212
  type="submit"
165
213
  size="M"
166
- disabled={!currentWorkflowIsDirty}
214
+ disabled={!currentWorkflowIsDirty || !canCreate}
167
215
  isLoading={isLoading}
168
216
  >
169
217
  {formatMessage({
@@ -1,13 +1,18 @@
1
1
  import React from 'react';
2
2
 
3
- import { ProtectedPage } from '../../components/ProtectedPage';
3
+ import { CheckPagePermissions } from '@strapi/helper-plugin';
4
+ import { useSelector } from 'react-redux';
5
+
6
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
4
7
 
5
8
  import { ReviewWorkflowsCreateView } from './CreateView';
6
9
 
7
10
  export default function () {
11
+ const permissions = useSelector(selectAdminPermissions);
12
+
8
13
  return (
9
- <ProtectedPage>
14
+ <CheckPagePermissions permissions={permissions.settings['review-workflows'].create}>
10
15
  <ReviewWorkflowsCreateView />
11
- </ProtectedPage>
16
+ </CheckPagePermissions>
12
17
  );
13
18
  }
@@ -6,9 +6,11 @@ import {
6
6
  useAPIErrorHandler,
7
7
  useFetchClient,
8
8
  useNotification,
9
+ useRBAC,
9
10
  } from '@strapi/helper-plugin';
10
11
  import { Check } from '@strapi/icons';
11
12
  import { useFormik, Form, FormikProvider } from 'formik';
13
+ import set from 'lodash/set';
12
14
  import { useIntl } from 'react-intl';
13
15
  import { useMutation } from 'react-query';
14
16
  import { useSelector, useDispatch } from 'react-redux';
@@ -16,19 +18,25 @@ import { useParams } from 'react-router-dom';
16
18
 
17
19
  import { useContentTypes } from '../../../../../../../../admin/src/hooks/useContentTypes';
18
20
  import { useInjectReducer } from '../../../../../../../../admin/src/hooks/useInjectReducer';
21
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
19
22
  import { useLicenseLimits } from '../../../../../../hooks';
20
- import { setWorkflow } from '../../actions';
23
+ import { resetWorkflow, setWorkflow } from '../../actions';
21
24
  import * as Layout from '../../components/Layout';
22
25
  import * as LimitsModal from '../../components/LimitsModal';
23
26
  import { Stages } from '../../components/Stages';
24
27
  import { WorkflowAttributes } from '../../components/WorkflowAttributes';
25
- import { REDUX_NAMESPACE } from '../../constants';
28
+ import {
29
+ CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME,
30
+ CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME,
31
+ REDUX_NAMESPACE,
32
+ } from '../../constants';
26
33
  import { useReviewWorkflows } from '../../hooks/useReviewWorkflows';
27
34
  import { reducer, initialState } from '../../reducer';
28
- import { getWorkflowValidationSchema } from '../../utils/getWorkflowValidationSchema';
35
+ import { validateWorkflow } from '../../utils/validateWorkflow';
29
36
 
30
37
  export function ReviewWorkflowsEditView() {
31
38
  const { workflowId } = useParams();
39
+ const permissions = useSelector(selectAdminPermissions);
32
40
  const { formatMessage } = useIntl();
33
41
  const dispatch = useDispatch();
34
42
  const { put } = useFetchClient();
@@ -52,9 +60,13 @@ export function ReviewWorkflowsEditView() {
52
60
  },
53
61
  },
54
62
  } = useSelector((state) => state?.[REDUX_NAMESPACE] ?? initialState);
63
+ const {
64
+ allowedActions: { canDelete, canUpdate },
65
+ } = useRBAC(permissions.settings['review-workflows']);
55
66
  const [isConfirmDeleteDialogOpen, setIsConfirmDeleteDialogOpen] = React.useState(false);
56
67
  const { getFeature, isLoading: isLicenseLoading } = useLicenseLimits();
57
68
  const [showLimitModal, setShowLimitModal] = React.useState(false);
69
+ const [initialErrors, setInitialErrors] = React.useState(null);
58
70
 
59
71
  const { mutateAsync, isLoading } = useMutation(
60
72
  async ({ workflow }) => {
@@ -77,11 +89,29 @@ export function ReviewWorkflowsEditView() {
77
89
  );
78
90
 
79
91
  const updateWorkflow = async (workflow) => {
92
+ // reset the error messages
93
+ setInitialErrors(null);
94
+
80
95
  try {
81
96
  const res = await mutateAsync({ workflow });
82
97
 
83
98
  return res;
84
99
  } catch (error) {
100
+ // TODO: this would benefit from a utility to get a formik error
101
+ // representation from an API error
102
+ if (
103
+ error.response.data?.error?.name === 'ValidationError' &&
104
+ error.response.data?.error?.details?.errors?.length > 0
105
+ ) {
106
+ setInitialErrors(
107
+ error.response.data?.error?.details?.errors.reduce((acc, error) => {
108
+ set(acc, error.path, error.message);
109
+
110
+ return acc;
111
+ }, {})
112
+ );
113
+ }
114
+
85
115
  toggleNotification({
86
116
  type: 'warning',
87
117
  message: formatAPIError(error),
@@ -108,16 +138,20 @@ export function ReviewWorkflowsEditView() {
108
138
 
109
139
  const formik = useFormik({
110
140
  enableReinitialize: true,
141
+ initialErrors,
111
142
  initialValues: currentWorkflow,
112
143
  async onSubmit() {
113
144
  if (currentWorkflowHasDeletedServerStages) {
114
145
  setIsConfirmDeleteDialogOpen(true);
115
- } else if (limits?.workflows && meta?.workflowCount > parseInt(limits.workflows, 10)) {
116
- /**
117
- * If the current license has a limit, check if the total count of workflows
118
- * exceeds that limit and display the limits modal instead of sending the
119
- * update, because it would throw an API error.
120
- */
146
+ } else if (
147
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
148
+ meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
149
+ ) {
150
+ /**
151
+ * If the current license has a limit, check if the total count of workflows
152
+ * exceeds that limit and display the limits modal instead of sending the
153
+ * update, because it would throw an API error.
154
+ */
121
155
  setShowLimitModal('workflow');
122
156
 
123
157
  /**
@@ -126,15 +160,18 @@ export function ReviewWorkflowsEditView() {
126
160
  * update, because it would throw an API error.
127
161
  */
128
162
  } else if (
129
- limits?.stagesPerWorkflow &&
130
- currentWorkflow.stages.length > parseInt(limits.stagesPerWorkflow, 10)
163
+ limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
164
+ currentWorkflow.stages.length >
165
+ parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
131
166
  ) {
132
167
  setShowLimitModal('stage');
133
168
  } else {
134
169
  submitForm();
135
170
  }
136
171
  },
137
- validationSchema: getWorkflowValidationSchema({ formatMessage }),
172
+ validate(values) {
173
+ return validateWorkflow({ values, formatMessage });
174
+ },
138
175
  });
139
176
 
140
177
  useInjectReducer(REDUX_NAMESPACE, reducer);
@@ -143,6 +180,12 @@ export function ReviewWorkflowsEditView() {
143
180
 
144
181
  React.useEffect(() => {
145
182
  dispatch(setWorkflow({ status: workflowStatus, data: workflow }));
183
+
184
+ // reset the state to the initial state to avoid flashes if a user
185
+ // navigates from an edit-view to a create-view
186
+ return () => {
187
+ dispatch(resetWorkflow());
188
+ };
146
189
  }, [workflowStatus, workflow, dispatch]);
147
190
 
148
191
  /**
@@ -160,11 +203,15 @@ export function ReviewWorkflowsEditView() {
160
203
 
161
204
  React.useEffect(() => {
162
205
  if (!isWorkflowLoading && !isLicenseLoading) {
163
- if (limits?.workflows && meta?.workflowCount > parseInt(limits.workflows, 10)) {
206
+ if (
207
+ limits?.[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME] &&
208
+ meta?.workflowCount > parseInt(limits[CHARGEBEE_WORKFLOW_ENTITLEMENT_NAME], 10)
209
+ ) {
164
210
  setShowLimitModal('workflow');
165
211
  } else if (
166
- limits?.stagesPerWorkflow &&
167
- currentWorkflow.stages.length > parseInt(limits.stagesPerWorkflow, 10)
212
+ limits?.[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME] &&
213
+ currentWorkflow.stages.length >
214
+ parseInt(limits[CHARGEBEE_STAGES_PER_WORKFLOW_ENTITLEMENT_NAME], 10)
168
215
  ) {
169
216
  setShowLimitModal('stage');
170
217
  }
@@ -173,8 +220,7 @@ export function ReviewWorkflowsEditView() {
173
220
  currentWorkflow.stages.length,
174
221
  isLicenseLoading,
175
222
  isWorkflowLoading,
176
- limits.stagesPerWorkflow,
177
- limits.workflows,
223
+ limits,
178
224
  meta?.workflowCount,
179
225
  meta.workflowsTotal,
180
226
  ]);
@@ -194,7 +240,7 @@ export function ReviewWorkflowsEditView() {
194
240
  startIcon={<Check />}
195
241
  type="submit"
196
242
  size="M"
197
- disabled={!currentWorkflowIsDirty}
243
+ disabled={!currentWorkflowIsDirty || !canUpdate}
198
244
  // if the confirm dialog is open the loading state is on
199
245
  // the confirm button already
200
246
  loading={!isConfirmDeleteDialogOpen && isLoading}
@@ -205,28 +251,40 @@ export function ReviewWorkflowsEditView() {
205
251
  })}
206
252
  </Button>
207
253
  }
208
- subtitle={formatMessage(
209
- {
210
- id: 'Settings.review-workflows.page.subtitle',
211
- defaultMessage: '{count, plural, one {# stage} other {# stages}}',
212
- },
213
- { count: currentWorkflow?.stages?.length ?? 0 }
214
- )}
254
+ subtitle={
255
+ currentWorkflow.stages.length > 0 &&
256
+ formatMessage(
257
+ {
258
+ id: 'Settings.review-workflows.page.subtitle',
259
+ defaultMessage: '{count, plural, one {# stage} other {# stages}}',
260
+ },
261
+ { count: currentWorkflow.stages.length }
262
+ )
263
+ }
215
264
  title={currentWorkflow.name}
216
265
  />
217
266
 
218
267
  <Layout.Root>
219
268
  {isLoadingModels || status === 'loading' ? (
220
- <Loader>
221
- {formatMessage({
222
- id: 'Settings.review-workflows.page.isLoading',
223
- defaultMessage: 'Workflow is loading',
224
- })}
225
- </Loader>
269
+ <Flex justifyContent="center">
270
+ <Loader>
271
+ {formatMessage({
272
+ id: 'Settings.review-workflows.page.isLoading',
273
+ defaultMessage: 'Workflow is loading',
274
+ })}
275
+ </Loader>
276
+ </Flex>
226
277
  ) : (
227
278
  <Flex alignItems="stretch" direction="column" gap={7}>
228
- <WorkflowAttributes contentTypes={{ collectionTypes, singleTypes }} />
229
- <Stages stages={formik.values?.stages} />
279
+ <WorkflowAttributes
280
+ canUpdate={canUpdate}
281
+ contentTypes={{ collectionTypes, singleTypes }}
282
+ />
283
+ <Stages
284
+ canDelete={canDelete}
285
+ canUpdate={canUpdate}
286
+ stages={formik.values?.stages}
287
+ />
230
288
  </Flex>
231
289
  )}
232
290
  </Layout.Root>
@@ -1,13 +1,18 @@
1
1
  import React from 'react';
2
2
 
3
- import { ProtectedPage } from '../../components/ProtectedPage';
3
+ import { CheckPagePermissions } from '@strapi/helper-plugin';
4
+ import { useSelector } from 'react-redux';
5
+
6
+ import { selectAdminPermissions } from '../../../../../../../../admin/src/pages/App/selectors';
4
7
 
5
8
  import { ReviewWorkflowsEditView } from './EditView';
6
9
 
7
10
  export default function () {
11
+ const permissions = useSelector(selectAdminPermissions);
12
+
8
13
  return (
9
- <ProtectedPage>
14
+ <CheckPagePermissions permissions={permissions.settings['review-workflows'].main}>
10
15
  <ReviewWorkflowsEditView />
11
- </ProtectedPage>
16
+ </CheckPagePermissions>
12
17
  );
13
18
  }