strapi-plugin-comments 1.0.4 → 2.0.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 (156) hide show
  1. package/.circleci/config.yml +2 -2
  2. package/README.md +53 -126
  3. package/admin/src/components/ConfirmationDialog/index.js +56 -0
  4. package/admin/src/components/DiscussionStatus/index.js +33 -0
  5. package/admin/src/components/DiscussionThread/index.js +59 -0
  6. package/admin/src/components/DiscussionThreadItem/index.js +87 -0
  7. package/admin/src/components/DiscussionThreadItem/styles.js +88 -0
  8. package/admin/src/components/DiscussionThreadItemActions/index.js +178 -0
  9. package/admin/src/components/DiscussionThreadItemActions/styles.js +22 -0
  10. package/admin/src/components/DiscussionThreadItemMeta/index.js +31 -0
  11. package/admin/src/components/DiscussionThreadItemWarnings/index.js +141 -0
  12. package/admin/src/components/DiscussionThreadItemWarnings/styles.js +7 -0
  13. package/admin/src/components/EntityDetails/index.js +53 -0
  14. package/admin/src/{containers → components}/Initializer/index.js +4 -4
  15. package/admin/src/components/LoadingIndicatorOverlay/index.js +15 -0
  16. package/admin/src/components/LoadingIndicatorOverlay/styles.js +20 -0
  17. package/admin/src/components/Nav/index.js +45 -0
  18. package/admin/src/components/PluginIcon/index.js +12 -0
  19. package/admin/src/components/ReportsReviewModal/index.js +43 -0
  20. package/admin/src/components/ReportsReviewTable/index.js +146 -0
  21. package/admin/src/components/TableFilters/index.js +53 -0
  22. package/admin/src/components/TablePagination/index.js +35 -0
  23. package/admin/src/components/icons/comments.js +9 -0
  24. package/admin/src/components/icons/index.js +6 -0
  25. package/admin/src/components/icons/lock.js +22 -0
  26. package/admin/src/components/icons/review.js +18 -0
  27. package/admin/src/components/icons/unlock.js +20 -0
  28. package/admin/src/index.js +58 -42
  29. package/admin/src/pages/App/index.js +71 -0
  30. package/admin/src/pages/App/reducer/actions.js +8 -0
  31. package/admin/src/pages/App/reducer/constants.js +4 -0
  32. package/admin/src/pages/App/reducer/index.js +26 -0
  33. package/admin/src/pages/App/reducer/init.js +7 -0
  34. package/admin/src/pages/App/reducer/selectors.js +35 -0
  35. package/admin/src/pages/App/utils/api.js +12 -0
  36. package/admin/src/pages/ComingSoonPage/index.js +25 -0
  37. package/admin/src/pages/Details/index.js +109 -0
  38. package/admin/src/pages/Details/utils/api.js +41 -0
  39. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +196 -0
  40. package/admin/src/pages/Discover/index.js +157 -0
  41. package/admin/src/pages/Discover/utils/api.js +14 -0
  42. package/admin/src/pages/Discover/utils/filtersSchema.js +9 -0
  43. package/admin/src/permissions.js +13 -37
  44. package/admin/src/pluginId.js +2 -4
  45. package/admin/src/reducers.js +8 -0
  46. package/admin/src/translations/en.json +73 -37
  47. package/admin/src/translations/fr.json +1 -0
  48. package/admin/src/utils/axiosInstance.js +35 -0
  49. package/admin/src/utils/constants.js +25 -6
  50. package/admin/src/utils/getApiUrl.js +5 -0
  51. package/admin/src/utils/getMessage.js +21 -0
  52. package/admin/src/utils/getRelatedGroups.js +5 -0
  53. package/admin/src/utils/getUrl.js +5 -0
  54. package/admin/src/utils/handleAPIError.js +15 -0
  55. package/admin/src/utils/index.js +6 -0
  56. package/codecov.yml +2 -1
  57. package/content-types/comment.js +97 -0
  58. package/content-types/index.js +11 -0
  59. package/content-types/report.js +54 -0
  60. package/package.json +11 -25
  61. package/server/bootstrap.js +5 -0
  62. package/server/config/index.js +10 -0
  63. package/server/controllers/admin.js +134 -0
  64. package/server/controllers/client.js +117 -0
  65. package/server/controllers/index.js +9 -0
  66. package/{controllers → server/controllers}/utils/__tests__/functions.test.js +0 -0
  67. package/{controllers → server/controllers}/utils/functions.js +1 -1
  68. package/server/destroy.js +5 -0
  69. package/server/index.js +22 -0
  70. package/server/register.js +5 -0
  71. package/server/routes/admin.js +92 -0
  72. package/server/routes/client.js +52 -0
  73. package/server/routes/index.js +15 -0
  74. package/server/services/admin.js +215 -0
  75. package/server/services/client.js +227 -0
  76. package/server/services/common.js +197 -0
  77. package/server/services/index.js +11 -0
  78. package/{services → server/services}/utils/__tests__/functions.test.js +0 -0
  79. package/{services → server/services}/utils/functions.js +17 -6
  80. package/server/utils/constants.js +16 -0
  81. package/{services → server}/utils/error.js +0 -0
  82. package/server/utils/functions.js +19 -0
  83. package/strapi-admin.js +3 -0
  84. package/strapi-server.js +9 -0
  85. package/admin/src/assets/images/icon-cross-blue.svg +0 -1
  86. package/admin/src/assets/images/icon_remove.svg +0 -19
  87. package/admin/src/assets/images/logo.svg +0 -5
  88. package/admin/src/components/AbuseReportsPopUp/ItemPreviewContainer.js +0 -8
  89. package/admin/src/components/AbuseReportsPopUp/ModalFooter.js +0 -31
  90. package/admin/src/components/AbuseReportsPopUp/TableContainer.js +0 -63
  91. package/admin/src/components/AbuseReportsPopUp/index.js +0 -148
  92. package/admin/src/components/Container/index.js +0 -7
  93. package/admin/src/components/EmptyView/index.js +0 -25
  94. package/admin/src/components/Filters/index.js +0 -151
  95. package/admin/src/components/Filters/index.styled.js +0 -82
  96. package/admin/src/components/Filters/utils.js +0 -21
  97. package/admin/src/components/Item/CardItem.js +0 -30
  98. package/admin/src/components/Item/CardWrapper.js +0 -10
  99. package/admin/src/components/Item/index.js +0 -107
  100. package/admin/src/components/ItemDetails/CardItem.js +0 -33
  101. package/admin/src/components/ItemDetails/CardLevelCounter.js +0 -46
  102. package/admin/src/components/ItemDetails/CardLevelCounterLink.js +0 -24
  103. package/admin/src/components/ItemDetails/CardLevelWrapper.js +0 -22
  104. package/admin/src/components/ItemDetails/CardWrapper.js +0 -52
  105. package/admin/src/components/ItemDetails/index.js +0 -167
  106. package/admin/src/components/ItemFooter/CardItemAuthor.js +0 -10
  107. package/admin/src/components/ItemFooter/CardItemRelation.js +0 -18
  108. package/admin/src/components/ItemFooter/Wrapper.js +0 -20
  109. package/admin/src/components/ItemFooter/index.js +0 -87
  110. package/admin/src/components/ItemHeader/CardHeaderBlocked.js +0 -24
  111. package/admin/src/components/ItemHeader/CardHeaderIndicator.js +0 -30
  112. package/admin/src/components/ItemHeader/CardHeaderIndicatorBlue.js +0 -11
  113. package/admin/src/components/ItemHeader/CardHeaderIndicatorRed.js +0 -11
  114. package/admin/src/components/ItemHeader/CardHeaderIndicatorsContainer.js +0 -18
  115. package/admin/src/components/ItemHeader/CardHeaderPending.js +0 -11
  116. package/admin/src/components/ItemHeader/CardHeaderReports.js +0 -40
  117. package/admin/src/components/ItemHeader/Wrapper.js +0 -16
  118. package/admin/src/components/ItemHeader/index.js +0 -97
  119. package/admin/src/components/ItemModeration/Wrapper.js +0 -19
  120. package/admin/src/components/ItemModeration/index.js +0 -84
  121. package/admin/src/components/List/Container.js +0 -20
  122. package/admin/src/components/List/index.js +0 -21
  123. package/admin/src/components/Search/index.js +0 -83
  124. package/admin/src/containers/App/Wrapper.js +0 -14
  125. package/admin/src/containers/App/index.js +0 -47
  126. package/admin/src/containers/DataManagerProvider/actions.js +0 -15
  127. package/admin/src/containers/DataManagerProvider/index.js +0 -332
  128. package/admin/src/containers/DataManagerProvider/init.js +0 -5
  129. package/admin/src/containers/DataManagerProvider/reducer.js +0 -59
  130. package/admin/src/containers/DetailsView/Wrapper.js +0 -18
  131. package/admin/src/containers/DetailsView/index.js +0 -106
  132. package/admin/src/containers/ListView/FadedWrapper.js +0 -42
  133. package/admin/src/containers/ListView/Footer.js +0 -53
  134. package/admin/src/containers/ListView/Wrapper.js +0 -17
  135. package/admin/src/containers/ListView/components.js +0 -138
  136. package/admin/src/containers/ListView/index.js +0 -45
  137. package/admin/src/containers/Panel/actions.js +0 -5
  138. package/admin/src/containers/Panel/index.js +0 -191
  139. package/admin/src/containers/Panel/index.styled.js +0 -6
  140. package/admin/src/containers/Panel/reducer.js +0 -44
  141. package/admin/src/contexts/DataManagerContext.js +0 -5
  142. package/admin/src/hooks/useDataManager.js +0 -6
  143. package/admin/src/lifecycles.js +0 -3
  144. package/admin/src/translations/index.js +0 -7
  145. package/admin/src/utils/getTrad.js +0 -5
  146. package/admin/src/utils/getTradId.js +0 -5
  147. package/config/functions/bootstrap.js +0 -62
  148. package/config/routes.json +0 -142
  149. package/config/schema.graphql.js +0 -268
  150. package/controllers/comments.js +0 -228
  151. package/models/comment.js +0 -8
  152. package/models/comment.settings.json +0 -102
  153. package/models/report.js +0 -8
  154. package/models/report.settings.json +0 -49
  155. package/services/comments.js +0 -539
  156. package/services/utils/constants.js +0 -9
@@ -0,0 +1,109 @@
1
+ /*
2
+ *
3
+ * Details
4
+ *
5
+ */
6
+
7
+ import React, { useRef, useMemo } from 'react';
8
+ import { useQuery } from 'react-query';
9
+ import { useRouteMatch } from 'react-router-dom';
10
+ import { Layout, HeaderLayout, TwoColsLayout, ContentLayout } from '@strapi/design-system/Layout';
11
+ import { Box } from '@strapi/design-system/Box';
12
+ import { Link } from '@strapi/design-system/Link';
13
+ import { Loader } from '@strapi/design-system/Loader';
14
+ import { Stack } from '@strapi/design-system/Stack';
15
+ import { useNotifyAT } from '@strapi/design-system/LiveRegions';
16
+ import { ArrowLeft } from '@strapi/icons';
17
+
18
+ import { isEmpty } from 'lodash';
19
+ import {
20
+ LoadingIndicatorPage,
21
+ useTracking,
22
+ useNotification,
23
+ useRBAC,
24
+ useFocusWhenNavigate,
25
+ } from '@strapi/helper-plugin';
26
+ import getMessage from '../../utils/getMessage';
27
+ import { fetchDetailsData, fetchContentTypeData } from './utils/api';
28
+ import pluginPermissions from '../../permissions';
29
+ import { getUrl } from '../../utils';
30
+ import Nav from '../../components/Nav';
31
+ import { REGEX } from '../../utils/constants';
32
+ import EntityDetails from '../../components/EntityDetails';
33
+ import DiscussionThread from '../../components/DiscussionThread';
34
+ import DiscussionStatus from '../../components/DiscussionStatus';
35
+
36
+ const Details = () => {
37
+ useFocusWhenNavigate();
38
+
39
+ const {
40
+ params: { id },
41
+ } = useRouteMatch(getUrl(`discover/:id`));
42
+
43
+ const { notifyStatus } = useNotifyAT();
44
+ const { trackUsage } = useTracking();
45
+ const trackUsageRef = useRef(trackUsage);
46
+ const toggleNotification = useNotification();
47
+
48
+ const viewPermissions = useMemo(() => {
49
+ return { view: pluginPermissions.view };
50
+ }, []);
51
+
52
+ const {
53
+ isLoading: isLoadingForPermissions,
54
+ allowedActions: { canView },
55
+ } = useRBAC(viewPermissions);
56
+
57
+ const { isLoading: isLoadingForData, data, isFetching } = useQuery(
58
+ ['get-details-data', id],
59
+ () => fetchDetailsData(id, toggleNotification),
60
+ {
61
+ initialData: { },
62
+ }
63
+ );
64
+
65
+ const { entity, level, selected } = data;
66
+ const entityUidValid = entity?.uid && REGEX.uid.test(entity?.uid);
67
+
68
+ const { data: contentTypeData } = useQuery(
69
+ ['get-additional-data', entity?.uid],
70
+ () => fetchContentTypeData(entity?.uid, toggleNotification),
71
+ {
72
+ enabled: !!entity?.uid,
73
+ suspense: !entityUidValid,
74
+ }
75
+ )
76
+
77
+ const isLoading = isLoadingForData || isFetching;
78
+
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('HeaderLayout.link.go-back', 'Back', 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>;
107
+ }
108
+
109
+ export default Details;
@@ -0,0 +1,41 @@
1
+ import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
2
+
3
+ // eslint-disable-next-line import/prefer-default-export
4
+ export const fetchDetailsData = async (id, toggleNotification) => {
5
+ try {
6
+ const { data } = await axiosInstance.get(getApiURL(`moderate/single/${id}`));
7
+
8
+ return data;
9
+ } catch (err) {
10
+ handleAPIError(err, toggleNotification);
11
+ }
12
+ };
13
+
14
+ export const fetchContentTypeData = async (uid, toggleNotification) => {
15
+ try {
16
+ const { data } = await axiosInstance.get(`/content-type-builder/content-types/${uid}`);
17
+ return data?.data;
18
+ } catch (err) {
19
+ handleAPIError(err, toggleNotification);
20
+ }
21
+ };
22
+
23
+ export const blockItem = id => {
24
+ return axiosInstance.patch(getApiURL(`moderate/single/${id}/block`));
25
+ };
26
+
27
+ export const unblockItem = id => {
28
+ return axiosInstance.patch(getApiURL(`moderate/single/${id}/unblock`));
29
+ };
30
+
31
+ export const blockItemThread = id => {
32
+ return axiosInstance.patch(getApiURL(`moderate/thread/${id}/block`));
33
+ };
34
+
35
+ export const unblockItemThread = id => {
36
+ return axiosInstance.patch(getApiURL(`moderate/thread/${id}/unblock`));
37
+ };
38
+
39
+ export const resolveReport = ({ id, reportId }) => {
40
+ return axiosInstance.patch(getApiURL(`moderate/single/${id}/report/${reportId}/resolve`));
41
+ };
@@ -0,0 +1,196 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useIntl } from 'react-intl';
4
+ import { useMutation, useQueryClient } from 'react-query';
5
+ import { first, isEmpty, orderBy } from 'lodash';
6
+ import { Badge } from '@strapi/design-system/Badge';
7
+ import { Box } from '@strapi/design-system/Box';
8
+ import { Button } from '@strapi/design-system/Button';
9
+ import { Divider } from '@strapi/design-system/Divider';
10
+ import { Flex } from '@strapi/design-system/Flex';
11
+ import { IconButton } from '@strapi/design-system/IconButton';
12
+ import { Link } from '@strapi/design-system/Link';
13
+ import { Stack } from '@strapi/design-system/Stack';
14
+ import { Tr, Td } from '@strapi/design-system/Table';
15
+ import { Typography } from '@strapi/design-system/Typography';
16
+ import { Eye, Lock } from '@strapi/icons';
17
+ import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
18
+ import { getMessage, handleAPIError } from '../../../../utils';
19
+ import { COMMENT_STATUS } from '../../../../utils/constants';
20
+ import ReportsReviewModal from '../../../../components/ReportsReviewModal';
21
+ import ReportsReviewTable from '../../../../components/ReportsReviewTable';
22
+ import { blockItem, blockItemThread, resolveReport } from '../../../Details/utils/api';
23
+ import pluginId from '../../../../pluginId';
24
+ import { ReviewIcon, LockIcon } from '../../../../components/icons';
25
+
26
+ const DiscoverTableRow = ({ config, item, onClick }) => {
27
+
28
+ const { id, reports } = item;
29
+
30
+ const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
31
+ const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
32
+
33
+ const { formatDate } = useIntl();
34
+ const toggleNotification = useNotification();
35
+ const queryClient = useQueryClient();
36
+ const { lockApp, unlockApp } = useOverlayBlocker();
37
+
38
+ const onSuccess = (message, stateAction = () => {}, indalidate = true) => async () => {
39
+ if (indalidate) {
40
+ await queryClient.invalidateQueries('get-data');
41
+ }
42
+ toggleNotification({
43
+ type: 'success',
44
+ message: `${pluginId}.${message}`,
45
+ });
46
+ stateAction(false);
47
+ unlockApp();
48
+ };
49
+
50
+ const onError = err => { handleAPIError(err, toggleNotification); };
51
+
52
+ const resolveReportMutation = useMutation(resolveReport, {
53
+ onSuccess: onSuccess('page.details.panel.discussion.warnings.reports.dialog.confirmation.success', () => {}, false),
54
+ onError,
55
+ refetchActive: false,
56
+ });
57
+
58
+ const blockItemMutation = useMutation(blockItem, {
59
+ onSuccess: onSuccess(
60
+ 'page.details.actions.comment.block.confirmation.success',
61
+ setReportsReviewVisible
62
+ ),
63
+ onError,
64
+ refetchActive: false,
65
+ });
66
+ const blockItemThreadMutation = useMutation(blockItemThread, {
67
+ onSuccess: onSuccess(
68
+ 'page.details.actions.thread.block.confirmation.success',
69
+ setReportsReviewVisible
70
+ ),
71
+ onError,
72
+ refetchActive: false,
73
+ });
74
+
75
+ const handleReportsReviewClick = () => setReportsReviewVisible(true);
76
+ const handleBlockItemClick = async () => {
77
+ lockApp();
78
+ blockItemMutation.mutate(id);
79
+ };
80
+ const handleBlockItemThreadClick = async () => {
81
+ lockApp();
82
+ blockItemThreadMutation.mutate(id);
83
+ };
84
+ const handleReportsReviewClose = async () => {
85
+ await queryClient.invalidateQueries('get-data');
86
+ setReportsReviewVisible(false);
87
+ };
88
+
89
+ const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
90
+ const openReports = reports?.filter(_ => !_.resolved);
91
+ const hasReports = !isEmpty(openReports);
92
+ const reviewFlowEnabled = hasReports && !(item.blocked || item.blockedThread);
93
+
94
+ const handleClick = () => onClick();
95
+ const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
96
+
97
+ const renderStatus = ({ blocked, blockedThread }) => {
98
+ let status = blocked || blockedThread ? COMMENT_STATUS.BLOCKED : COMMENT_STATUS.OPEN;
99
+ if (reviewFlowEnabled) {
100
+ status = COMMENT_STATUS.TO_REVIEW;
101
+ }
102
+ let color = 'secondary';
103
+ switch (status) {
104
+ case COMMENT_STATUS.OPEN:
105
+ color = 'success';
106
+ break;
107
+ case COMMENT_STATUS.TO_REVIEW:
108
+ color = 'warning';
109
+ break;
110
+ case COMMENT_STATUS.BLOCKED:
111
+ color = 'danger';
112
+ break;
113
+ };
114
+ return (<Badge backgroundColor={`${color}100`} textColor={`${color}600`}>{ getMessage({
115
+ id: `page.discover.table.header.status.${status}`,
116
+ props: {
117
+ count: openReports.length
118
+ }
119
+ }, status) }</Badge>);
120
+ };
121
+
122
+ const renderEntryTitle = entry => {
123
+ const { entryLabel } = config;
124
+ const rule = entry.uid in entryLabel ? entryLabel[entry.uid] : entryLabel['*'];
125
+ return first(
126
+ Object.keys(entry)
127
+ .filter(_ => (rule === _) || rule.includes(_))
128
+ .map(_ => entry[_])
129
+ .filter(_ => _)
130
+ );
131
+ };
132
+
133
+ const renderEntryUrl = entry => `/content-manager/collectionType/${entry.uid}/${entry.id}`;
134
+
135
+ return (
136
+ <Tr key={item.id}>
137
+ <Td style={{ maxWidth: '40vw' }}>
138
+ <Typography textColor="neutral800" ellipsis>{item.content}</Typography>
139
+ </Td>
140
+ <Td>
141
+ <Typography textColor="neutral800">{item.threadOf?.id || '-'}</Typography>
142
+ </Td>
143
+ <Td>
144
+ <Link to={ renderEntryUrl(item.related) }>{ renderEntryTitle(item.related) }</Link>
145
+ </Td>
146
+ <Td>
147
+ <Typography textColor="neutral800">{ formatDate(item.updatedAt || item.createdAt, { dateStyle: 'long', timeStyle: 'short' }) }</Typography>
148
+ </Td>
149
+ <Td>
150
+ <Typography textColor="neutral800">{ renderStatus(item) }</Typography>
151
+ </Td>
152
+ <Td>
153
+ <Flex direction="column" alignItems="flex-end">
154
+ <Stack size={reviewFlowEnabled ? 2 : 1} horizontal>
155
+ { reviewFlowEnabled && (<IconButton onClick={handleReportsReviewClick} label={ getMessage('page.discover.table.reports.review') } icon={<ReviewIcon />} />)}
156
+ <IconButton onClick={handleClick} label={ getMessage('page.discover.table.action.display') } icon={<Eye />} />
157
+ </Stack>
158
+ </Flex>
159
+ { reviewFlowEnabled && (<ReportsReviewModal
160
+ isVisible={reportsReviewVisible}
161
+ isActionAsync={isLoading}
162
+ onClose={handleReportsReviewClose}
163
+ startActions={<>
164
+ <Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
165
+ { getMessage(`page.details.actions.comment.block`, 'Block comment') }
166
+ </Button>
167
+ { item.gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
168
+ { getMessage(`page.details.actions.thread.block`, 'Block thread') }
169
+ </Button>) }
170
+ </>}
171
+ item={item}>
172
+ <Typography variant="sigma" textColor="neutral600" id="reports-list">
173
+ { getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
174
+ </Typography>
175
+ <Box paddingTop={2} paddingBottom={6}>
176
+ <Divider />
177
+ </Box>
178
+ <ReportsReviewTable
179
+ commentId={item.id}
180
+ items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
181
+ mutation={resolveReportMutation}
182
+ onBlockButtonsStateChange={handleBlockButtonsStateChange}
183
+ />
184
+ </ReportsReviewModal>) }
185
+ </Td>
186
+ </Tr>
187
+ );
188
+ };
189
+
190
+ DiscoverTableRow.propTypes = {
191
+ config: PropTypes.object.isRequired,
192
+ item: PropTypes.object.isRequired,
193
+ onClick: PropTypes.func.isRequired,
194
+ };
195
+
196
+ export default DiscoverTableRow;
@@ -0,0 +1,157 @@
1
+ /*
2
+ *
3
+ * Discover
4
+ *
5
+ */
6
+
7
+ import React, { memo, useRef, useMemo } from 'react';
8
+ import { connect } from 'react-redux';
9
+ import { bindActionCreators, compose } from 'redux';
10
+ import isEqual from 'react-fast-compare';
11
+ import { useQuery } from 'react-query';
12
+ import { useHistory } from 'react-router-dom';
13
+ import { isEmpty } from 'lodash';
14
+ import { Box } from '@strapi/design-system/Box';
15
+ import { Layout, HeaderLayout, ActionLayout, ContentLayout } from '@strapi/design-system/Layout';
16
+ import { Table, Thead, Tbody, Tr, Th, Td } from '@strapi/design-system/Table';
17
+ import { Typography } from '@strapi/design-system/Typography';
18
+ import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
19
+ import { useNotifyAT } from '@strapi/design-system/LiveRegions';
20
+
21
+ import {
22
+ EmptyStateLayout,
23
+ SearchURLQuery,
24
+ LoadingIndicatorPage,
25
+ useTracking,
26
+ useNotification,
27
+ useRBAC,
28
+ useFocusWhenNavigate,
29
+ useQueryParams,
30
+ } from '@strapi/helper-plugin';
31
+ import getMessage from '../../utils/getMessage';
32
+ import { fetchData } from './utils/api';
33
+ import pluginPermissions from '../../permissions';
34
+
35
+ import getUrl from '../../utils/getUrl';
36
+ import Nav from '../../components/Nav';
37
+ import TablePagination from '../../components/TablePagination';
38
+ import filtersSchema from './utils/filtersSchema';
39
+ import TableFilters from '../../components/TableFilters';
40
+ import makeAppView from '../App/reducer/selectors';
41
+ import DiscoverTableRow from './components/DiscoverTableRow';
42
+
43
+ const Discover = ({ config }) => {
44
+ useFocusWhenNavigate();
45
+
46
+ const { push } = useHistory();
47
+ const { notifyStatus } = useNotifyAT();
48
+ const { trackUsage } = useTracking();
49
+ const trackUsageRef = useRef(trackUsage);
50
+ const toggleNotification = useNotification();
51
+ const [{ query: queryParams }] = useQueryParams();
52
+ const _q = queryParams?._q || '';
53
+
54
+ const viewPermissions = useMemo(() => {
55
+ return { view: pluginPermissions.view };
56
+ }, []);
57
+
58
+ const {
59
+ isLoading: isLoadingForPermissions,
60
+ allowedActions: { canView },
61
+ } = useRBAC(viewPermissions);
62
+
63
+ const { isLoading: isLoadingForData, data: { result, pagination = {} }, isFetching } = useQuery(
64
+ ['get-data', queryParams],
65
+ () => fetchData(queryParams, toggleNotification),
66
+ {
67
+ initialData: { },
68
+ }
69
+ );
70
+
71
+ const handleClickDisplay = id => {
72
+ push(getUrl(`discover/${id}`));
73
+ };
74
+
75
+ const isLoading = isLoadingForData || isFetching;
76
+ const { total } = pagination;
77
+
78
+ const emptyLayout = {
79
+ comments: {
80
+ id: getMessage('page.discover.table.empty'),
81
+ defaultMessage: "You don't have any comments yet.",
82
+ },
83
+ search: {
84
+ id: getMessage('page.discover.table.empty.search'),
85
+ defaultMessage: 'No comments match the search.',
86
+ },
87
+ };
88
+
89
+
90
+
91
+ const emptyContent = _q ? 'search' : 'comments';
92
+
93
+ const COL_COUNT = 6;
94
+
95
+ return <Box background="neutral100">
96
+ <Layout sideNav={<Nav />}>
97
+ {isLoading || isLoadingForPermissions ? (<LoadingIndicatorPage />) : (
98
+ <>
99
+ <HeaderLayout title={ getMessage('page.discover.header') } subtitle={ `${total} ${ getMessage('page.discover.header.count')}` } as="h2" />
100
+ <ActionLayout
101
+ startActions={<>
102
+ <SearchURLQuery
103
+ label={ getMessage('search.label', 'Search', false) }
104
+ />
105
+ <TableFilters displayedFilters={filtersSchema} />
106
+ </>} />
107
+ <ContentLayout>
108
+ { !isEmpty(result) ? (<>
109
+ <Table colCount={COL_COUNT} rowCount={result.length}>
110
+ <Thead>
111
+ <Tr>
112
+ <Th>
113
+ <Typography variant="sigma">{ getMessage('page.discover.table.header.message') }</Typography>
114
+ </Th>
115
+ <Th>
116
+ <Typography variant="sigma">{ getMessage('page.discover.table.header.thread') }</Typography>
117
+ </Th>
118
+ <Th>
119
+ <Typography variant="sigma">{ getMessage('page.discover.table.header.entry') }</Typography>
120
+ </Th>
121
+ <Th>
122
+ <Typography variant="sigma">{ getMessage('page.discover.table.header.lastUpdate') }</Typography>
123
+ </Th>
124
+ <Th>
125
+ <Typography variant="sigma">{ getMessage('page.discover.table.header.status') }</Typography>
126
+ </Th>
127
+ <Th>
128
+ <VisuallyHidden>{ getMessage('page.discover.table.header.actions') }</VisuallyHidden>
129
+ </Th>
130
+ </Tr>
131
+ </Thead>
132
+ <Tbody>
133
+ {result.map(entry => (<DiscoverTableRow config={config} item={entry} onClick={() => handleClickDisplay(entry.id)}/>))}
134
+ </Tbody>
135
+ </Table>
136
+ <TablePagination pagination={{ pageCount: pagination?.pageCount || 1 }} />
137
+ </>) : (<EmptyStateLayout content={emptyLayout[emptyContent]} />) }
138
+ </ContentLayout>
139
+ </>) }
140
+ </Layout>
141
+ </Box>;
142
+ }
143
+
144
+ const mapStateToProps = makeAppView();
145
+
146
+ export function mapDispatchToProps(dispatch) {
147
+ return bindActionCreators(
148
+ { },
149
+ dispatch
150
+ );
151
+ }
152
+ const withConnect = connect(
153
+ mapStateToProps,
154
+ mapDispatchToProps
155
+ );
156
+
157
+ export default compose(withConnect)(memo(Discover, isEqual));
@@ -0,0 +1,14 @@
1
+ import { stringify } from 'qs';
2
+
3
+ import { getApiURL, axiosInstance, handleAPIError } from '../../../utils';
4
+
5
+ // eslint-disable-next-line import/prefer-default-export
6
+ export const fetchData = async (queryParams, toggleNotification) => {
7
+ try {
8
+ const { data } = await axiosInstance.get(getApiURL(`moderate/all${queryParams ? `?${stringify(queryParams, { encode: false })}` : ''}`));
9
+
10
+ return data;
11
+ } catch (err) {
12
+ handleAPIError(err, toggleNotification);
13
+ }
14
+ };
@@ -0,0 +1,9 @@
1
+ const filtersSchema = [
2
+ {
3
+ name: 'content',
4
+ metadatas: { label: 'Message' },
5
+ fieldSchema: { type: 'string' },
6
+ },
7
+ ];
8
+
9
+ export default filtersSchema;
@@ -1,38 +1,14 @@
1
1
  const pluginPermissions = {
2
- // This permission regards the main component (App) and is used to tell
3
- // If the plugin link should be displayed in the menu
4
- // And also if the plugin is accessible. This use case is found when a user types the url of the
5
- // plugin directly in the browser
6
- main: [
7
- { action: 'plugins::comments.read', subject: null },
8
- { action: 'plugins::comments.moderate.block.comment', subject: null },
9
- { action: 'plugins::comments.moderate.block.thread', subject: null },
10
- { action: 'plugins::comments.moderate.reports', subject: null },
11
- ],
12
- open: [
13
- { action: 'plugins::comments.read', subject: null },
14
- ],
15
- moderate: [
16
- { action: 'plugins::comments.read', subject: null },
17
- { action: 'plugins::comments.moderate.block.comment', subject: null },
18
- { action: 'plugins::comments.moderate.block.thread', subject: null },
19
- { action: 'plugins::comments.moderate.approve.comment', subject: null },
20
- { action: 'plugins::comments.moderate.reject.comment', subject: null },
21
- ],
22
- moderateComments: [
23
- { action: 'plugins::comments.read', subject: null },
24
- { action: 'plugins::comments.moderate.block.comment', subject: null },
25
- { action: 'plugins::comments.moderate.approve.comment', subject: null },
26
- { action: 'plugins::comments.moderate.reject.comment', subject: null },
27
- ],
28
- moderateThreads: [
29
- { action: 'plugins::comments.read', subject: null },
30
- { action: 'plugins::comments.moderate.block.thread', subject: null },
31
- ],
32
- moderateReports: [
33
- { action: 'plugins::comments.read', subject: null },
34
- { action: 'plugins::comments.moderate.reports', subject: null },
35
- ],
36
- };
37
-
38
- export default pluginPermissions;
2
+ access: [
3
+ { action: 'plugin::comments.read', subject: null },
4
+ { action: 'plugin::comments.reports.read', subject: null },
5
+ { action: 'plugin::comments.discovery.read', subject: null },
6
+ { action: 'plugin::comments.settings.read', subject: null },
7
+ ],
8
+ discover: [{ action: 'plugin::comments.read', subject: null }],
9
+ moderate: [{ action: 'plugin::comments.moderate', subject: null }],
10
+ reports: [{ action: 'plugin::comments.reports.read', subject: null }],
11
+ reportsReview: [{ action: 'plugin::comments.reports.action', subject: null }],
12
+ };
13
+
14
+ export default pluginPermissions;
@@ -1,7 +1,5 @@
1
1
  const pluginPkg = require('../../package.json');
2
- const pluginId = pluginPkg.name.replace(
3
- /^strapi-plugin-/i,
4
- ''
5
- );
2
+
3
+ const pluginId = pluginPkg.name.replace(/^strapi-plugin-/i, '');
6
4
 
7
5
  module.exports = pluginId;
@@ -0,0 +1,8 @@
1
+ import appReducer from './pages/App/reducer';
2
+ import { REDUCER_NAME } from './pages/App/reducer/constants';
3
+
4
+ const reducers = {
5
+ [`${REDUCER_NAME}`]: appReducer,
6
+ };
7
+
8
+ export default reducers;