strapi-plugin-comments 2.0.0-beta.2 → 2.0.0-beta.6

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 (53) hide show
  1. package/README.md +31 -5
  2. package/admin/src/components/DiscussionThread/index.js +10 -2
  3. package/admin/src/components/DiscussionThreadItem/index.js +25 -6
  4. package/admin/src/components/DiscussionThreadItem/styles.js +1 -0
  5. package/admin/src/components/DiscussionThreadItemActions/index.js +69 -56
  6. package/admin/src/components/DiscussionThreadItemActions/styles.js +0 -4
  7. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +81 -0
  8. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/styles.js +6 -0
  9. package/admin/src/components/DiscussionThreadItemWarnings/index.js +26 -16
  10. package/admin/src/components/Nav/index.js +5 -1
  11. package/admin/src/components/ReportsReviewModal/index.js +5 -2
  12. package/admin/src/components/ReportsReviewTable/index.js +69 -57
  13. package/admin/src/components/icons/index.js +6 -0
  14. package/admin/src/components/icons/lock.js +22 -0
  15. package/admin/src/components/icons/review.js +18 -0
  16. package/admin/src/components/icons/unlock.js +20 -0
  17. package/admin/src/index.js +1 -1
  18. package/admin/src/pages/App/index.js +20 -9
  19. package/admin/src/pages/App/reducer/index.js +0 -1
  20. package/admin/src/pages/App/reducer/selectors.js +1 -1
  21. package/admin/src/pages/App/utils/api.js +2 -10
  22. package/admin/src/pages/Details/components/DetailsEntity/index.js +66 -0
  23. package/admin/src/pages/Details/components/DetailsFilters/index.js +37 -0
  24. package/admin/src/pages/Details/components/DetailsFilters/styles.js +8 -0
  25. package/admin/src/pages/Details/index.js +75 -42
  26. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +210 -0
  27. package/admin/src/pages/Discover/components/DiscoverTableRow/styles.js +8 -0
  28. package/admin/src/pages/Discover/index.js +69 -102
  29. package/admin/src/pages/Discover/utils/api.js +2 -8
  30. package/admin/src/pages/{Details/utils → utils}/api.js +15 -3
  31. package/admin/src/permissions.js +10 -8
  32. package/admin/src/translations/en.json +17 -4
  33. package/admin/src/utils/constants.js +3 -6
  34. package/admin/src/utils/getMessage.js +10 -3
  35. package/admin/src/utils/getRelatedGroups.js +1 -3
  36. package/admin/src/utils/handleAPIError.js +1 -1
  37. package/admin/src/utils/index.js +4 -2
  38. package/admin/src/utils/renderEntryTitle.js +14 -0
  39. package/admin/src/utils/resolveCommentStatus.js +30 -0
  40. package/admin/src/utils/resolveCommentStatusColor.js +22 -0
  41. package/package.json +7 -3
  42. package/permissions.js +19 -0
  43. package/server/bootstrap.js +38 -2
  44. package/server/config/index.js +4 -3
  45. package/server/controllers/admin.js +3 -3
  46. package/server/controllers/client.js +11 -2
  47. package/server/services/admin.js +69 -13
  48. package/server/services/client.js +23 -15
  49. package/server/services/common.js +40 -69
  50. package/server/services/utils/functions.js +7 -14
  51. package/server/utils/constants.js +3 -3
  52. package/strapi-server.js +33 -0
  53. package/admin/src/components/EntityDetails/index.js +0 -53
@@ -23,9 +23,10 @@ import { REPORT_REASON, REPORT_STATUS } from '../../utils/constants';
23
23
  const ReportsReviewTable = ({
24
24
  commentId,
25
25
  items,
26
- mutation
26
+ mutation,
27
+ allowedActions: { canAccessReports, canReviewReports },
28
+ onBlockButtonsStateChange,
27
29
  }) => {
28
-
29
30
  const [storedItems, setStoredItems] = useState([]);
30
31
 
31
32
  const { formatDate } = useIntl();
@@ -58,18 +59,21 @@ const ReportsReviewTable = ({
58
59
  };
59
60
 
60
61
  const handleClickResolve = async (reportId) => {
62
+ if (canReviewReports) {
61
63
  lockApp();
62
64
  const item = await mutation.mutateAsync({
63
65
  id: commentId,
64
66
  reportId,
65
67
  });
66
- console.log(item);
67
68
  if (item) {
68
- setStoredItems(storedItems.map(_ => ({
69
- ..._,
70
- resolved: reportId === _.id ? true : _.resolved,
71
- })));
69
+ const updatedItems = storedItems.map(_ => ({
70
+ ..._,
71
+ resolved: reportId === _.id ? true : _.resolved,
72
+ }));
73
+ setStoredItems(updatedItems);
74
+ onBlockButtonsStateChange(updatedItems.filter(_ => !_.resolved).length === 0);
72
75
  }
76
+ }
73
77
  }
74
78
 
75
79
  useEffect(() => {
@@ -82,61 +86,69 @@ const ReportsReviewTable = ({
82
86
 
83
87
  const COL_COUNT = 6;
84
88
 
85
- return (<Table colCount={COL_COUNT} rowCount={storedItems.length}>
86
- <Thead>
87
- <Tr>
88
- <Th>
89
- <BaseCheckbox aria-label="Select all entries" />
90
- </Th>
91
- <Th>
92
- <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.reason') }</Typography>
93
- </Th>
94
- <Th>
95
- <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.content') }</Typography>
96
- </Th>
97
- <Th>
98
- <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.createdAt') }</Typography>
99
- </Th>
100
- <Th>
101
- <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.status') }</Typography>
102
- </Th>
103
- <Th>
104
- <VisuallyHidden>{ getMessage('page.details.panel.discussion.warnings.reports.dialog.actions') }</VisuallyHidden>
105
- </Th>
106
- </Tr>
107
- </Thead>
108
- <Tbody>
109
- {storedItems.map(entry => <Tr key={entry.id}>
110
- <Td>
111
- <BaseCheckbox aria-label={`Select report`} />
112
- </Td>
113
- <Td>
114
- <Typography textColor="neutral800">{renderReason(entry.reason)}</Typography>
115
- </Td>
116
- <Td>
117
- <Typography textColor="neutral800">{entry.content}</Typography>
118
- </Td>
119
- <Td>
120
- <Typography textColor="neutral800">{ formatDate(entry.createdAt, { dateStyle: 'short', timeStyle: 'short' }) }</Typography>
121
- </Td>
122
- <Td>
123
- <Typography textColor="neutral800">{ renderStatus(entry.resolved) }</Typography>
124
- </Td>
125
- <Td>
126
- { !entry.resolved && (<Flex direction="column" alignItems="flex-end">
127
- <Button onClick={() => handleClickResolve(entry.id)} startIcon={<Check />} variant="success">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.actions.resolve', 'resolve') }</Button>
128
- </Flex>) }
129
- </Td>
130
- </Tr>)}
131
- </Tbody>
132
- {/* <TFooter /> */}
133
- </Table>)
89
+ if (canAccessReports) {
90
+ return (<Table colCount={COL_COUNT} rowCount={storedItems.length}>
91
+ <Thead>
92
+ <Tr>
93
+ <Th>
94
+ <BaseCheckbox aria-label="Select all entries" />
95
+ </Th>
96
+ <Th>
97
+ <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.reason') }</Typography>
98
+ </Th>
99
+ <Th>
100
+ <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.content') }</Typography>
101
+ </Th>
102
+ <Th>
103
+ <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.createdAt') }</Typography>
104
+ </Th>
105
+ <Th>
106
+ <Typography variant="sigma">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.status') }</Typography>
107
+ </Th>
108
+ { canReviewReports && (<Th>
109
+ <VisuallyHidden>{ getMessage('page.details.panel.discussion.warnings.reports.dialog.actions') }</VisuallyHidden>
110
+ </Th>)}
111
+ </Tr>
112
+ </Thead>
113
+ <Tbody>
114
+ {storedItems.map(entry => <Tr key={entry.id}>
115
+ <Td>
116
+ <BaseCheckbox aria-label={`Select report`} />
117
+ </Td>
118
+ <Td>
119
+ <Typography textColor="neutral800">{renderReason(entry.reason)}</Typography>
120
+ </Td>
121
+ <Td>
122
+ <Typography textColor="neutral800">{entry.content}</Typography>
123
+ </Td>
124
+ <Td>
125
+ <Typography textColor="neutral800">{ formatDate(entry.createdAt, { dateStyle: 'short', timeStyle: 'short' }) }</Typography>
126
+ </Td>
127
+ <Td>
128
+ <Typography textColor="neutral800">{ renderStatus(entry.resolved) }</Typography>
129
+ </Td>
130
+ { canReviewReports && (<Td>
131
+ { !entry.resolved && (<Flex direction="column" alignItems="flex-end">
132
+ <Button onClick={() => handleClickResolve(entry.id)} startIcon={<Check />} variant="success">{ getMessage('page.details.panel.discussion.warnings.reports.dialog.actions.resolve', 'resolve') }</Button>
133
+ </Flex>) }
134
+ </Td>)}
135
+ </Tr>)}
136
+ </Tbody>
137
+ {/* <TFooter /> */}
138
+ </Table>);
139
+ }
140
+ return null;
134
141
  };
135
142
 
136
143
  ReportsReviewTable.propTypes = {
137
- commentId: PropTypes.string.isRequired,
144
+ commentId: PropTypes.oneOfType(PropTypes.string, PropTypes.number).isRequired,
138
145
  items: PropTypes.array.isRequired,
139
146
  mutation: PropTypes.func.isRequired,
147
+ allowedActions: PropTypes.shape({
148
+ canAccessReports: PropTypes.bool,
149
+ canReviewReports: PropTypes.bool,
150
+ }),
151
+ onBlockButtonsStateChange: PropTypes.func.isRequired,
140
152
  };
141
153
 
142
154
  export default ReportsReviewTable;
@@ -0,0 +1,6 @@
1
+ import CommentsIcon from './comments';
2
+ import ReviewIcon from './review';
3
+ import LockIcon from './lock';
4
+ import UnlockIcon from './unlock';
5
+
6
+ export { CommentsIcon, ReviewIcon, LockIcon, UnlockIcon };
@@ -0,0 +1,22 @@
1
+
2
+ import React from 'react';
3
+
4
+ const initSize = 330;
5
+
6
+ const LockIcon = ({ width, height, size = 16 }) =>
7
+ <svg
8
+ width={width || size}
9
+ height={height || size}
10
+ viewBox={`0 0 ${width || size} ${height || size}`}
11
+ style={{ width: `${width || size}px`, height: `${height || size}px`, }}
12
+ xmlns="http://www.w3.org/2000/svg">
13
+ <g style={ { transform: `scale(${(width || size)/initSize})` } }>
14
+ <path d="M165,330c63.411,0,115-51.589,115-115c0-29.771-11.373-56.936-30-77.379V85c0-46.869-38.131-85-85-85
15
+ S80.001,38.131,80.001,85v52.619C61.373,158.064,50,185.229,50,215C50,278.411,101.589,330,165,330z M180,219.986V240
16
+ c0,8.284-6.716,15-15,15s-15-6.716-15-15v-20.014c-6.068-4.565-10-11.824-10-19.986c0-13.785,11.215-25,25-25s25,11.215,25,25
17
+ C190,208.162,186.068,215.421,180,219.986z M110.001,85c0-30.327,24.673-55,54.999-55c30.327,0,55,24.673,55,55v29.029
18
+ C203.652,105.088,184.91,100,165,100c-19.909,0-38.651,5.088-54.999,14.028V85z"/>
19
+ </g>
20
+ </svg>
21
+
22
+ export default LockIcon;
@@ -0,0 +1,18 @@
1
+
2
+ import React from 'react';
3
+
4
+ const initSize = 16;
5
+
6
+ const ReviewIcon = ({ width, height, size = 16 }) =>
7
+ <svg
8
+ width={width || size}
9
+ height={height || size}
10
+ viewBox={`0 0 ${width || size} ${height || size}`}
11
+ style={{ width: `${width || size}px`, height: `${height || size}px`, }}
12
+ xmlns="http://www.w3.org/2000/svg">
13
+ <g style={ {transform: `scale(${(width || size)/initSize})` } }>
14
+ <path fillRule="evenodd" clipRule="evenodd" d="M0 4a4 4 0 014-4h8a4 4 0 014 4v8a4 4 0 01-4 4H4a4 4 0 01-4-4V4zm6.996.165a1.017 1.017 0 112.012 0L8 11 6.996 4.165zM8 11a1 1 0 110 2 1 1 0 010-2z" />
15
+ </g>
16
+ </svg>
17
+
18
+ export default ReviewIcon;
@@ -0,0 +1,20 @@
1
+
2
+ import React from 'react';
3
+
4
+ const initSize = 330;
5
+
6
+ const UnlockIcon = ({ width, height, size = 16 }) =>
7
+ <svg
8
+ width={width || size}
9
+ height={height || size}
10
+ viewBox={`0 0 ${width || size} ${height || size}`}
11
+ style={{ width: `${width || size}px`, height: `${height || size}px`, }}
12
+ xmlns="http://www.w3.org/2000/svg">
13
+ <g style={ { transform: `scale(${(width || size)/initSize})` } }>
14
+ <path d="M15,160c8.283,0,15-6.716,15-15V85c0-30.327,24.672-55,55-55c30.326,0,55,24.673,55,55v42.893
15
+ c-24.479,21.105-40,52.327-40,87.107c0,63.411,51.588,115,115,115c63.41,0,115-51.589,115-115s-51.59-115-115-115
16
+ c-15.961,0-31.172,3.271-45,9.174V85c0-46.869-38.131-85-85-85S0,38.131,0,85v60C0,153.284,6.715,160,15,160z"/>
17
+ </g>
18
+ </svg>
19
+
20
+ export default UnlockIcon;
@@ -23,7 +23,7 @@ export default {
23
23
 
24
24
  return component;
25
25
  },
26
- permissions: pluginPermissions,
26
+ permissions: pluginPermissions.access,
27
27
  });
28
28
  app.addReducers(reducers);
29
29
  app.registerPlugin({
@@ -5,7 +5,9 @@
5
5
  *
6
6
  */
7
7
 
8
- import React, { useReducer } from 'react';
8
+ import React from 'react';
9
+ import { connect } from 'react-redux';
10
+ import { bindActionCreators, compose } from 'redux';
9
11
  import { Switch, Route } from 'react-router-dom';
10
12
  import { useQuery } from 'react-query';
11
13
  import {
@@ -18,23 +20,19 @@ import getUrl from '../../utils/getUrl';
18
20
  import Discover from '../Discover';
19
21
  import Details from '../Details';
20
22
  import { fetchConfig } from './utils/api';
21
- import initReducer from './reducer/init';
22
- import reducer, { initialState } from './reducer';
23
23
  import { setConfig } from './reducer/actions';
24
+ import makeAppView from './reducer/selectors';
24
25
 
25
- const App = () => {
26
+ const App = ({ setConfig }) => {
26
27
 
27
28
  const toggleNotification = useNotification();
28
- const [reducerState, dispatch] = useReducer(reducer, initialState, () =>
29
- initReducer(initialState)
30
- );
31
29
 
32
30
  const { isLoading, isFetching } = useQuery(
33
31
  'get-config', () => fetchConfig(toggleNotification),
34
32
  {
35
33
  initialData: { },
36
34
  onSuccess: (response) => {
37
- dispatch(setConfig(response));
35
+ setConfig(response);
38
36
  }
39
37
  }
40
38
  );
@@ -57,4 +55,17 @@ const App = () => {
57
55
  );
58
56
  };
59
57
 
60
- export default App;
58
+ const mapStateToProps = makeAppView();
59
+
60
+ export function mapDispatchToProps(dispatch) {
61
+ return bindActionCreators(
62
+ { setConfig },
63
+ dispatch
64
+ );
65
+ }
66
+ const withConnect = connect(
67
+ mapStateToProps,
68
+ mapDispatchToProps
69
+ );
70
+
71
+ export default compose(withConnect)(App);
@@ -18,7 +18,6 @@ const reducer = (state = initialState, action) =>
18
18
  return draftState?.config;
19
19
  }
20
20
  default:
21
- console.log('draftState', draftState);
22
21
  return draftState;
23
22
  }
24
23
  });
@@ -26,7 +26,7 @@ const makeAppView = () =>
26
26
  );
27
27
 
28
28
  const selectConfig = state => {
29
- const { config } = state[REDUCER_NAME];
29
+ const { config } = state;
30
30
 
31
31
  return config;
32
32
  };
@@ -1,7 +1,4 @@
1
- import { stringify } from 'qs';
2
- import { isEmpty } from 'lodash';
3
-
4
- import { getApiURL, axiosInstance } from '../../../utils';
1
+ import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
5
2
 
6
3
  // eslint-disable-next-line import/prefer-default-export
7
4
  export const fetchConfig = async (toggleNotification) => {
@@ -10,11 +7,6 @@ export const fetchConfig = async (toggleNotification) => {
10
7
 
11
8
  return data;
12
9
  } catch (err) {
13
- toggleNotification({
14
- type: 'warning',
15
- message: { id: 'notification.error' },
16
- });
17
-
18
- throw new Error('error');
10
+ handleAPIError(err, toggleNotification);
19
11
  }
20
12
  };
@@ -0,0 +1,66 @@
1
+ /**
2
+ *
3
+ * Entity Details
4
+ *
5
+ */
6
+
7
+ import React from 'react';
8
+ import PropTypes from 'prop-types';
9
+ import { capitalize, isNil, first, take } from 'lodash';
10
+ import { Box } from '@strapi/design-system/Box';
11
+ import { Divider } from '@strapi/design-system/Divider';
12
+ import { Flex } from '@strapi/design-system/Flex';
13
+ import { Stack } from '@strapi/design-system/Stack';
14
+ import { Typography } from '@strapi/design-system/Typography';
15
+ import { getMessage } from '../../../../utils';
16
+
17
+ const DetailsEntity = ({ data = {}, schema = {}, config = {} }) => {
18
+ const { entryLabel = {} } = config;
19
+ const { attributes = {} } = schema;
20
+ const keys = Object.keys(attributes);
21
+ const entityLabelKey = first(entryLabel[data?.uid]);
22
+
23
+ if (keys.length === 0) {
24
+ return null;
25
+ }
26
+
27
+ const FIELDS_LIMIT = 5;
28
+ const itemKeys = take(
29
+ keys.filter(_ => (attributes[_].type === 'string') && !isNil(data[_]) && (_ !== entityLabelKey)),
30
+ FIELDS_LIMIT
31
+ );
32
+
33
+ const formatLabel = (label = '') => label.split('_')
34
+ .map(_ => capitalize(_))
35
+ .join(' ');
36
+
37
+ return (<Box padding={4}>
38
+ <Typography variant="sigma" textColor="neutral600" id="entity-details">
39
+ { getMessage('page.details.panel.entity', 'Details') }
40
+ </Typography>
41
+ <Box paddingTop={2} paddingBottom={6}>
42
+ <Divider />
43
+ </Box>
44
+ <Stack size={itemKeys.length}>
45
+ <Flex direction="column" alignItems="flex-start">
46
+ <Typography fontWeight="bold">{ formatLabel(entityLabelKey) }</Typography>
47
+ <Typography>{ data[entityLabelKey] }</Typography>
48
+ </Flex>
49
+ { itemKeys.map(_ =>
50
+ (<Flex key={`prop_${_}`} direction="column" alignItems="flex-start">
51
+ <Typography fontWeight="bold">{ formatLabel(_) }</Typography>
52
+ <Typography>{ data[_] }</Typography>
53
+ </Flex>))
54
+ }
55
+ </Stack>
56
+ </Box>);
57
+ };
58
+
59
+ DetailsEntity.propTypes = {
60
+ data: PropTypes.object.isRequired,
61
+ schema: PropTypes.object.isRequired,
62
+ config: PropTypes.object.isRequired,
63
+ };
64
+
65
+ export default DetailsEntity;
66
+
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Box } from '@strapi/design-system/Box';
4
+ import { Flex } from '@strapi/design-system/Flex';
5
+ import { Stack } from '@strapi/design-system/Stack';
6
+ import { Switch } from '@strapi/design-system/Switch';
7
+ import { Typography } from '@strapi/design-system/Typography';
8
+ import { getMessage } from '../../../../utils';
9
+
10
+ const DetailsFilters = ({ data, onChange }) => {
11
+
12
+ const { removed = false } = data;
13
+
14
+ const handleOnChange = (prop, value) => onChange({
15
+ ...data,
16
+ [prop]: !value ? undefined : value,
17
+ });
18
+
19
+ return (<Box as={Flex}>
20
+ <Stack size={2} horizontal>
21
+ <Typography variant="epsilon">{getMessage('page.details.filters.label', '')}</Typography>
22
+ <Switch
23
+ label={getMessage('page.details.filters.removed.label', 'Comments visibility')}
24
+ onLabel={getMessage('page.details.filters.removed.show', 'Show')}
25
+ offLabel={getMessage('page.details.filters.removed.hide', 'Hide')}
26
+ selected={removed} onChange={() => handleOnChange('removed', !removed)}
27
+ visibleLabels />
28
+ </Stack>
29
+ </Box>);
30
+ };
31
+
32
+ DetailsFilters.propTypes = {
33
+ data: PropTypes.object.isRequired,
34
+ onChange: PropTypes.func.isRequired,
35
+ };
36
+
37
+ export default DetailsFilters;
@@ -0,0 +1,8 @@
1
+ import styled from 'styled-components';
2
+ import { Link } from '@strapi/design-system/Link';
3
+
4
+ export const TableLink = styled(Link)`
5
+ display: block;
6
+ text-overflow: ellipsis;
7
+ overflow: hidden;
8
+ `
@@ -4,11 +4,15 @@
4
4
  *
5
5
  */
6
6
 
7
- import React, { useRef, useMemo } from 'react';
7
+ import React, { memo, useRef, useMemo, useState } from 'react';
8
+ import { connect } from 'react-redux';
9
+ import { bindActionCreators, compose } from 'redux';
10
+ import isEqual from 'react-fast-compare';
8
11
  import { useQuery } from 'react-query';
9
12
  import { useRouteMatch } from 'react-router-dom';
10
13
  import { Layout, HeaderLayout, TwoColsLayout, ContentLayout } from '@strapi/design-system/Layout';
11
14
  import { Box } from '@strapi/design-system/Box';
15
+ import { Flex } from '@strapi/design-system/Flex';
12
16
  import { Link } from '@strapi/design-system/Link';
13
17
  import { Loader } from '@strapi/design-system/Loader';
14
18
  import { Stack } from '@strapi/design-system/Stack';
@@ -24,16 +28,17 @@ import {
24
28
  useFocusWhenNavigate,
25
29
  } from '@strapi/helper-plugin';
26
30
  import getMessage from '../../utils/getMessage';
27
- import { fetchDetailsData, fetchContentTypeData } from './utils/api';
31
+ import { fetchDetailsData, fetchContentTypeData } from '../utils/api';
28
32
  import pluginPermissions from '../../permissions';
29
33
  import { getUrl } from '../../utils';
30
34
  import Nav from '../../components/Nav';
31
- import { REGEX } from '../../utils/constants';
32
- import EntityDetails from '../../components/EntityDetails';
35
+ import DetailsEntity from './components/DetailsEntity';
33
36
  import DiscussionThread from '../../components/DiscussionThread';
34
37
  import DiscussionStatus from '../../components/DiscussionStatus';
38
+ import makeAppView from '../App/reducer/selectors';
39
+ import DetailsFilters from './components/DetailsFilters';
35
40
 
36
- const Details = () => {
41
+ const Details = ({ config }) => {
37
42
  useFocusWhenNavigate();
38
43
 
39
44
  const {
@@ -45,28 +50,35 @@ const Details = () => {
45
50
  const trackUsageRef = useRef(trackUsage);
46
51
  const toggleNotification = useNotification();
47
52
 
48
- const viewPermissions = useMemo(() => {
49
- return { view: pluginPermissions.view };
50
- }, []);
53
+ const viewPermissions = useMemo(() => ({
54
+ access: pluginPermissions.access,
55
+ moderate: pluginPermissions.moderate,
56
+ accessReports: pluginPermissions.reports,
57
+ reviewReports: pluginPermissions.reportsReview,
58
+ }), []);
51
59
 
52
60
  const {
53
61
  isLoading: isLoadingForPermissions,
54
- allowedActions: { canView },
62
+ allowedActions: { canAccess, canModerate, canAccessReports, canReviewReports },
55
63
  } = useRBAC(viewPermissions);
56
64
 
65
+ const [filters, setFilters] = useState({});
66
+
67
+ const regexUID = new RegExp(config.regex.uid);
68
+
57
69
  const { isLoading: isLoadingForData, data, isFetching } = useQuery(
58
- ['get-details-data', id],
59
- () => fetchDetailsData(id, toggleNotification),
70
+ ['get-details-data', id, filters, canAccess],
71
+ () => fetchDetailsData(id, filters, toggleNotification),
60
72
  {
61
73
  initialData: { },
62
74
  }
63
75
  );
64
76
 
65
77
  const { entity, level, selected } = data;
66
- const entityUidValid = entity?.uid && REGEX.uid.test(entity?.uid);
78
+ const entityUidValid = entity?.uid && regexUID.test(entity.uid);
67
79
 
68
80
  const { data: contentTypeData } = useQuery(
69
- ['get-additional-data', entity?.uid],
81
+ ['get-additional-data', entity?.uid, canAccess],
70
82
  () => fetchContentTypeData(entity?.uid, toggleNotification),
71
83
  {
72
84
  enabled: !!entity?.uid,
@@ -74,36 +86,57 @@ const Details = () => {
74
86
  }
75
87
  )
76
88
 
89
+ const handleChangeFilters = props => setFilters(props);
90
+
77
91
  const isLoading = isLoadingForData || isFetching;
78
92
 
79
- return <Box background="neutral100">
80
- <Layout sideNav={<Nav />}>
81
- {(isLoading || isLoadingForPermissions) && isEmpty(data) ? (<LoadingIndicatorPage />) : (
82
- <>
83
- <HeaderLayout
84
- navigationAction={
85
- <Link startIcon={<ArrowLeft />} to={getUrl(`discover`)}>
86
- { getMessage('button.back.label', '', false) }
87
- </Link>}
88
- title={ getMessage('page.details.header') }
89
- subtitle={ getMessage('page.details.header.description') } as="h2" />
90
- <ContentLayout>
91
- <TwoColsLayout
92
- startCol={<DiscussionThread level={level} selected={selected} isReloading={isLoading} />}
93
- endCol={ <Stack size={2}>
94
- <DiscussionStatus item={selected} />
95
- { !contentTypeData?.schema ? (<Box padding={4}>
96
- <Loader small>Fetching entity...</Loader>
97
- </Box>) :
98
- (<EntityDetails data={entity} schema={contentTypeData?.schema} />)
99
- }
100
- </Stack>
101
- }
102
- />
103
- </ContentLayout>
104
- </>) }
105
- </Layout>
106
- </Box>;
93
+ if (canAccess) {
94
+ return <Box background="neutral100">
95
+ <Layout>
96
+ {(isLoading || isLoadingForPermissions) && isEmpty(data) ? (<LoadingIndicatorPage />) : (
97
+ <>
98
+ <HeaderLayout
99
+ navigationAction={
100
+ <Link startIcon={<ArrowLeft />} to={getUrl(`discover`)}>
101
+ { getMessage('HeaderLayout.link.go-back', 'Back', false) }
102
+ </Link>}
103
+ title={ getMessage('page.details.header') }
104
+ subtitle={ getMessage('page.details.header.description') }
105
+ primaryAction={<DetailsFilters data={filters} onChange={handleChangeFilters} />}
106
+ secondaryAction={<Box as={Flex}>&nbsp;</Box>}
107
+ as="h2" />
108
+ <ContentLayout>
109
+ <TwoColsLayout
110
+ startCol={<DiscussionThread level={level} selected={selected} isReloading={isLoading} allowedActions={{ canModerate, canAccessReports, canReviewReports }} />}
111
+ endCol={ <Stack size={2}>
112
+ <DiscussionStatus item={selected} />
113
+ { !contentTypeData?.schema ? (<Box padding={4}>
114
+ <Loader small>{ getMessage('page.details.panel.loading', 'Loading...') }</Loader>
115
+ </Box>) :
116
+ (<DetailsEntity data={entity} schema={contentTypeData?.schema} config={config} />)
117
+ }
118
+ </Stack>
119
+ }
120
+ />
121
+ </ContentLayout>
122
+ </>) }
123
+ </Layout>
124
+ </Box>;
125
+ }
126
+ return null;
127
+ }
128
+
129
+ const mapStateToProps = makeAppView();
130
+
131
+ export function mapDispatchToProps(dispatch) {
132
+ return bindActionCreators(
133
+ { },
134
+ dispatch
135
+ );
107
136
  }
137
+ const withConnect = connect(
138
+ mapStateToProps,
139
+ mapDispatchToProps
140
+ );
108
141
 
109
- export default Details;
142
+ export default compose(withConnect)(memo(Details, isEqual));