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.
- package/README.md +31 -5
- package/admin/src/components/DiscussionThread/index.js +10 -2
- package/admin/src/components/DiscussionThreadItem/index.js +25 -6
- package/admin/src/components/DiscussionThreadItem/styles.js +1 -0
- package/admin/src/components/DiscussionThreadItemActions/index.js +69 -56
- package/admin/src/components/DiscussionThreadItemActions/styles.js +0 -4
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +81 -0
- package/admin/src/components/DiscussionThreadItemApprovalFlowActions/styles.js +6 -0
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +26 -16
- package/admin/src/components/Nav/index.js +5 -1
- package/admin/src/components/ReportsReviewModal/index.js +5 -2
- package/admin/src/components/ReportsReviewTable/index.js +69 -57
- package/admin/src/components/icons/index.js +6 -0
- package/admin/src/components/icons/lock.js +22 -0
- package/admin/src/components/icons/review.js +18 -0
- package/admin/src/components/icons/unlock.js +20 -0
- package/admin/src/index.js +1 -1
- package/admin/src/pages/App/index.js +20 -9
- package/admin/src/pages/App/reducer/index.js +0 -1
- package/admin/src/pages/App/reducer/selectors.js +1 -1
- package/admin/src/pages/App/utils/api.js +2 -10
- package/admin/src/pages/Details/components/DetailsEntity/index.js +66 -0
- package/admin/src/pages/Details/components/DetailsFilters/index.js +37 -0
- package/admin/src/pages/Details/components/DetailsFilters/styles.js +8 -0
- package/admin/src/pages/Details/index.js +75 -42
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +210 -0
- package/admin/src/pages/Discover/components/DiscoverTableRow/styles.js +8 -0
- package/admin/src/pages/Discover/index.js +69 -102
- package/admin/src/pages/Discover/utils/api.js +2 -8
- package/admin/src/pages/{Details/utils → utils}/api.js +15 -3
- package/admin/src/permissions.js +10 -8
- package/admin/src/translations/en.json +17 -4
- package/admin/src/utils/constants.js +3 -6
- package/admin/src/utils/getMessage.js +10 -3
- package/admin/src/utils/getRelatedGroups.js +1 -3
- package/admin/src/utils/handleAPIError.js +1 -1
- package/admin/src/utils/index.js +4 -2
- package/admin/src/utils/renderEntryTitle.js +14 -0
- package/admin/src/utils/resolveCommentStatus.js +30 -0
- package/admin/src/utils/resolveCommentStatusColor.js +22 -0
- package/package.json +7 -3
- package/permissions.js +19 -0
- package/server/bootstrap.js +38 -2
- package/server/config/index.js +4 -3
- package/server/controllers/admin.js +3 -3
- package/server/controllers/client.js +11 -2
- package/server/services/admin.js +69 -13
- package/server/services/client.js +23 -15
- package/server/services/common.js +40 -69
- package/server/services/utils/functions.js +7 -14
- package/server/utils/constants.js +3 -3
- package/strapi-server.js +33 -0
- 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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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,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;
|
package/admin/src/index.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import 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
|
-
|
|
35
|
+
setConfig(response);
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
);
|
|
@@ -57,4 +55,17 @@ const App = () => {
|
|
|
57
55
|
);
|
|
58
56
|
};
|
|
59
57
|
|
|
60
|
-
|
|
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);
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
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;
|
|
@@ -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 '
|
|
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
|
|
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
|
-
|
|
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: {
|
|
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 &&
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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}> </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));
|