strapi-plugin-comments 1.0.2 → 2.0.0-beta.3
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/.circleci/config.yml +2 -2
- package/README.md +70 -109
- package/admin/src/components/ConfirmationDialog/index.js +56 -0
- package/admin/src/components/DiscussionStatus/index.js +33 -0
- package/admin/src/components/DiscussionThread/index.js +59 -0
- package/admin/src/components/DiscussionThreadItem/index.js +87 -0
- package/admin/src/components/DiscussionThreadItem/styles.js +88 -0
- package/admin/src/components/DiscussionThreadItemActions/index.js +178 -0
- package/admin/src/components/DiscussionThreadItemActions/styles.js +22 -0
- package/admin/src/components/DiscussionThreadItemMeta/index.js +31 -0
- package/admin/src/components/DiscussionThreadItemWarnings/index.js +141 -0
- package/admin/src/components/DiscussionThreadItemWarnings/styles.js +7 -0
- package/admin/src/components/EntityDetails/index.js +53 -0
- package/admin/src/{containers → components}/Initializer/index.js +4 -4
- package/admin/src/components/LoadingIndicatorOverlay/index.js +15 -0
- package/admin/src/components/LoadingIndicatorOverlay/styles.js +20 -0
- package/admin/src/components/Nav/index.js +45 -0
- package/admin/src/components/PluginIcon/index.js +12 -0
- package/admin/src/components/ReportsReviewModal/index.js +43 -0
- package/admin/src/components/ReportsReviewTable/index.js +146 -0
- package/admin/src/components/TableFilters/index.js +53 -0
- package/admin/src/components/TablePagination/index.js +35 -0
- package/admin/src/components/icons/comments.js +9 -0
- 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 +58 -42
- package/admin/src/pages/App/index.js +71 -0
- package/admin/src/pages/App/reducer/actions.js +8 -0
- package/admin/src/pages/App/reducer/constants.js +4 -0
- package/admin/src/pages/App/reducer/index.js +26 -0
- package/admin/src/pages/App/reducer/init.js +7 -0
- package/admin/src/pages/App/reducer/selectors.js +35 -0
- package/admin/src/pages/App/utils/api.js +12 -0
- package/admin/src/pages/ComingSoonPage/index.js +25 -0
- package/admin/src/pages/Details/index.js +109 -0
- package/admin/src/pages/Details/utils/api.js +41 -0
- package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +196 -0
- package/admin/src/pages/Discover/index.js +157 -0
- package/admin/src/pages/Discover/utils/api.js +14 -0
- package/admin/src/pages/Discover/utils/filtersSchema.js +9 -0
- package/admin/src/permissions.js +13 -33
- package/admin/src/pluginId.js +2 -4
- package/admin/src/reducers.js +8 -0
- package/admin/src/translations/en.json +73 -33
- package/admin/src/translations/fr.json +1 -0
- package/admin/src/utils/axiosInstance.js +35 -0
- package/admin/src/utils/constants.js +28 -0
- package/admin/src/utils/getApiUrl.js +5 -0
- package/admin/src/utils/getMessage.js +21 -0
- package/admin/src/utils/getRelatedGroups.js +5 -0
- package/admin/src/utils/getUrl.js +5 -0
- package/admin/src/utils/handleAPIError.js +15 -0
- package/admin/src/utils/index.js +6 -0
- package/codecov.yml +2 -1
- package/content-types/comment.js +97 -0
- package/content-types/index.js +11 -0
- package/content-types/report.js +54 -0
- package/package.json +11 -25
- package/server/bootstrap.js +5 -0
- package/server/config/index.js +10 -0
- package/server/controllers/admin.js +134 -0
- package/server/controllers/client.js +117 -0
- package/server/controllers/index.js +9 -0
- package/{controllers → server/controllers}/utils/__tests__/functions.test.js +0 -0
- package/{controllers → server/controllers}/utils/functions.js +1 -1
- package/server/destroy.js +5 -0
- package/server/index.js +22 -0
- package/server/register.js +5 -0
- package/server/routes/admin.js +92 -0
- package/server/routes/client.js +52 -0
- package/server/routes/index.js +15 -0
- package/server/services/admin.js +215 -0
- package/server/services/client.js +227 -0
- package/server/services/common.js +197 -0
- package/server/services/index.js +11 -0
- package/{services → server/services}/utils/__tests__/functions.test.js +7 -9
- package/{services → server/services}/utils/functions.js +19 -8
- package/server/utils/constants.js +16 -0
- package/{services → server}/utils/error.js +0 -0
- package/server/utils/functions.js +19 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +9 -0
- package/admin/src/assets/images/icon-cross-blue.svg +0 -1
- package/admin/src/assets/images/icon_remove.svg +0 -19
- package/admin/src/assets/images/logo.svg +0 -5
- package/admin/src/components/AbuseReportsPopUp/ItemPreviewContainer.js +0 -8
- package/admin/src/components/AbuseReportsPopUp/ModalFooter.js +0 -31
- package/admin/src/components/AbuseReportsPopUp/TableContainer.js +0 -63
- package/admin/src/components/AbuseReportsPopUp/index.js +0 -148
- package/admin/src/components/Container/index.js +0 -7
- package/admin/src/components/EmptyView/index.js +0 -25
- package/admin/src/components/Filters/index.js +0 -151
- package/admin/src/components/Filters/index.styled.js +0 -82
- package/admin/src/components/Filters/utils.js +0 -21
- package/admin/src/components/Item/CardItem.js +0 -30
- package/admin/src/components/Item/CardWrapper.js +0 -10
- package/admin/src/components/Item/index.js +0 -92
- package/admin/src/components/ItemDetails/CardItem.js +0 -33
- package/admin/src/components/ItemDetails/CardLevelCounter.js +0 -46
- package/admin/src/components/ItemDetails/CardLevelCounterLink.js +0 -24
- package/admin/src/components/ItemDetails/CardLevelWrapper.js +0 -22
- package/admin/src/components/ItemDetails/CardWrapper.js +0 -52
- package/admin/src/components/ItemDetails/index.js +0 -151
- package/admin/src/components/ItemFooter/CardItemAuthor.js +0 -10
- package/admin/src/components/ItemFooter/CardItemRelation.js +0 -18
- package/admin/src/components/ItemFooter/Wrapper.js +0 -20
- package/admin/src/components/ItemFooter/index.js +0 -87
- package/admin/src/components/ItemHeader/CardHeaderBlocked.js +0 -24
- package/admin/src/components/ItemHeader/CardHeaderIndicator.js +0 -30
- package/admin/src/components/ItemHeader/CardHeaderIndicatorBlue.js +0 -11
- package/admin/src/components/ItemHeader/CardHeaderIndicatorRed.js +0 -11
- package/admin/src/components/ItemHeader/CardHeaderIndicatorsContainer.js +0 -18
- package/admin/src/components/ItemHeader/CardHeaderReports.js +0 -40
- package/admin/src/components/ItemHeader/Wrapper.js +0 -16
- package/admin/src/components/ItemHeader/index.js +0 -80
- package/admin/src/components/ItemModeration/Wrapper.js +0 -19
- package/admin/src/components/ItemModeration/index.js +0 -47
- package/admin/src/components/List/Container.js +0 -20
- package/admin/src/components/List/index.js +0 -21
- package/admin/src/components/Search/index.js +0 -83
- package/admin/src/containers/App/Wrapper.js +0 -14
- package/admin/src/containers/App/index.js +0 -47
- package/admin/src/containers/DataManagerProvider/actions.js +0 -11
- package/admin/src/containers/DataManagerProvider/index.js +0 -280
- package/admin/src/containers/DataManagerProvider/init.js +0 -5
- package/admin/src/containers/DataManagerProvider/reducer.js +0 -59
- package/admin/src/containers/DetailsView/Wrapper.js +0 -18
- package/admin/src/containers/DetailsView/index.js +0 -100
- package/admin/src/containers/ListView/FadedWrapper.js +0 -42
- package/admin/src/containers/ListView/Footer.js +0 -53
- package/admin/src/containers/ListView/Wrapper.js +0 -17
- package/admin/src/containers/ListView/components.js +0 -138
- package/admin/src/containers/ListView/index.js +0 -45
- package/admin/src/containers/Panel/actions.js +0 -5
- package/admin/src/containers/Panel/index.js +0 -191
- package/admin/src/containers/Panel/index.styled.js +0 -6
- package/admin/src/containers/Panel/reducer.js +0 -44
- package/admin/src/contexts/DataManagerContext.js +0 -5
- package/admin/src/hooks/useDataManager.js +0 -6
- package/admin/src/lifecycles.js +0 -3
- package/admin/src/translations/index.js +0 -7
- package/admin/src/utils/getTrad.js +0 -5
- package/admin/src/utils/getTradId.js +0 -5
- package/config/functions/bootstrap.js +0 -48
- package/config/routes.json +0 -116
- package/config/schema.graphql.js +0 -270
- package/controllers/comments.js +0 -211
- package/models/comment.js +0 -8
- package/models/comment.settings.json +0 -98
- package/models/report.js +0 -8
- package/models/report.settings.json +0 -49
- package/services/comments.js +0 -486
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Box } from '@strapi/design-system/Box';
|
|
3
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
4
|
+
|
|
5
|
+
export const AvatarWrapper = styled(Box)``;
|
|
6
|
+
export const InitialsWrapper = styled(Box)``;
|
|
7
|
+
|
|
8
|
+
export const DiscussionThreadItemLinkBox = styled(Flex)`
|
|
9
|
+
width: 100%;
|
|
10
|
+
margin-top: .5rem;
|
|
11
|
+
margin-bottom: .5rem;
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const DiscussionThreadItemStyled = styled(Box)`
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
width: 100%;
|
|
18
|
+
margin-bottom: 1rem;
|
|
19
|
+
|
|
20
|
+
${AvatarWrapper},
|
|
21
|
+
${InitialsWrapper} {
|
|
22
|
+
align-self: flex-start;
|
|
23
|
+
margin-top: .4rem;
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export const DiscussionThreadItemContent = styled(Box)`
|
|
28
|
+
max-width: 45%;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export const DiscussionThreadItemLeft = styled(DiscussionThreadItemStyled)`
|
|
32
|
+
align-items: flex-start;
|
|
33
|
+
padding-left: 4rem;
|
|
34
|
+
text-align: left;
|
|
35
|
+
|
|
36
|
+
${DiscussionThreadItemContent} {
|
|
37
|
+
align-self: flex-start;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
${AvatarWrapper},
|
|
41
|
+
${InitialsWrapper} {
|
|
42
|
+
margin-right: 1rem;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
export const DiscussionThreadItemRight = styled(DiscussionThreadItemStyled)`
|
|
47
|
+
align-items: flex-end;
|
|
48
|
+
padding-right: 4rem;
|
|
49
|
+
text-align: left;
|
|
50
|
+
|
|
51
|
+
${DiscussionThreadItemContent} {
|
|
52
|
+
align-self: flex-end;
|
|
53
|
+
flex-direction: row-reverse;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
${AvatarWrapper},
|
|
57
|
+
${InitialsWrapper} {
|
|
58
|
+
margin-left: 1rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
${DiscussionThreadItemLinkBox} {
|
|
62
|
+
margin-right: 3rem;
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
export const DiscussionThreadItemBox = styled(Box)`
|
|
67
|
+
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
export const DiscussionThreadItemBoxContent = styled(Box)`
|
|
71
|
+
width: 100%;
|
|
72
|
+
align-items: stretch;
|
|
73
|
+
`
|
|
74
|
+
|
|
75
|
+
export const DiscussionThreadItemRoot = styled(DiscussionThreadItemLeft)`
|
|
76
|
+
padding-left: 0;
|
|
77
|
+
padding-right: 0;
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
${DiscussionThreadItemContent} {
|
|
81
|
+
width: 100%;
|
|
82
|
+
max-width: 100%;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
${DiscussionThreadItemBox} {
|
|
86
|
+
flex-grow: 1;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Entity Details
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useEffect } from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import { useMutation, useQueryClient } from 'react-query';
|
|
10
|
+
import { isNil } from 'lodash';
|
|
11
|
+
import { Button } from '@strapi/design-system/Button';
|
|
12
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
13
|
+
import { IconButton } from '@strapi/design-system/IconButton';
|
|
14
|
+
import { Check, Cross } from '@strapi/icons';
|
|
15
|
+
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
16
|
+
import { getMessage, handleAPIError } from '../../utils';
|
|
17
|
+
import { DiscussionThreadItemActionsBadge, DiscussionThreadItemActionsGroupWrapper, DiscussionThreadItemActionsWrapper } from './styles';
|
|
18
|
+
import ConfirmationDialog from '../ConfirmationDialog';
|
|
19
|
+
import { blockItem, blockItemThread, unblockItem, unblockItemThread } from '../../pages/Details/utils/api';
|
|
20
|
+
import pluginId from '../../pluginId';
|
|
21
|
+
import { LockIcon, UnlockIcon } from '../icons';
|
|
22
|
+
|
|
23
|
+
const DiscussionThreadItemActions = ({ id, blocked, blockedThread, gotThread, pinned, approvalStatus, root }) => {
|
|
24
|
+
|
|
25
|
+
const [blockConfirmationVisible, setBlockConfirmationVisible] = useState(false);
|
|
26
|
+
const [blockThreadConfirmationVisible, setBlockThreadConfirmationVisible] = useState(false);
|
|
27
|
+
|
|
28
|
+
const toggleNotification = useNotification();
|
|
29
|
+
const queryClient = useQueryClient();
|
|
30
|
+
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
31
|
+
|
|
32
|
+
const onSuccess = (message, stateAction = () => {}) => async () => {
|
|
33
|
+
await queryClient.invalidateQueries('get-details-data');
|
|
34
|
+
toggleNotification({
|
|
35
|
+
type: 'success',
|
|
36
|
+
message: `${pluginId}.${message}`,
|
|
37
|
+
});
|
|
38
|
+
stateAction(false);
|
|
39
|
+
unlockApp();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
43
|
+
|
|
44
|
+
const blockItemMutation = useMutation(blockItem, {
|
|
45
|
+
onSuccess: onSuccess(
|
|
46
|
+
'page.details.actions.comment.block.confirmation.success',
|
|
47
|
+
setBlockConfirmationVisible
|
|
48
|
+
),
|
|
49
|
+
onError,
|
|
50
|
+
refetchActive: false,
|
|
51
|
+
});
|
|
52
|
+
const unblockItemMutation = useMutation(unblockItem, {
|
|
53
|
+
onSuccess: onSuccess('page.details.actions.comment.unblock.confirmation.success'),
|
|
54
|
+
onError,
|
|
55
|
+
refetchActive: false,
|
|
56
|
+
});
|
|
57
|
+
const blockItemThreadMutation = useMutation(blockItemThread, {
|
|
58
|
+
onSuccess: onSuccess(
|
|
59
|
+
'page.details.actions.thread.block.confirmation.success',
|
|
60
|
+
setBlockThreadConfirmationVisible
|
|
61
|
+
),
|
|
62
|
+
onError,
|
|
63
|
+
refetchActive: false,
|
|
64
|
+
});
|
|
65
|
+
const unblockItemThreadMutation = useMutation(unblockItemThread, {
|
|
66
|
+
onSuccess: onSuccess('page.details.actions.thread.unblock.confirmation.success'),
|
|
67
|
+
onError,
|
|
68
|
+
refetchActive: false,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const gotApprovalFlow = !isNil(approvalStatus);
|
|
72
|
+
const needsApproval = gotApprovalFlow && (approvalStatus === 'PENDING');
|
|
73
|
+
const isBlocked = blocked || blockedThread;
|
|
74
|
+
|
|
75
|
+
const resolveStatusBadge = () => {
|
|
76
|
+
let color = 'primary';
|
|
77
|
+
let label = '';
|
|
78
|
+
|
|
79
|
+
if (isBlocked) {
|
|
80
|
+
color = 'danger';
|
|
81
|
+
label = 'blocked';
|
|
82
|
+
} else if (gotApprovalFlow) {
|
|
83
|
+
label = approvalStatus.toLowerCase();
|
|
84
|
+
switch (approvalStatus) {
|
|
85
|
+
case 'APPROVED':
|
|
86
|
+
color = 'success';
|
|
87
|
+
break;
|
|
88
|
+
case 'REJECTED':
|
|
89
|
+
color = 'danger';
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
color = 'alternative';
|
|
93
|
+
break;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
badgeVisible: isBlocked || gotApprovalFlow,
|
|
99
|
+
badgeColor: color,
|
|
100
|
+
badgeLabel: label,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const {badgeVisible, badgeColor, badgeLabel } = resolveStatusBadge();
|
|
105
|
+
|
|
106
|
+
const handleBlockClick = () => setBlockConfirmationVisible(true);
|
|
107
|
+
const handleBlockConfirm = async () => {
|
|
108
|
+
lockApp();
|
|
109
|
+
blockItemMutation.mutate(id);
|
|
110
|
+
};
|
|
111
|
+
const handleBlockCancel = () => {
|
|
112
|
+
setBlockConfirmationVisible(false);
|
|
113
|
+
};
|
|
114
|
+
const handleUnblockClick = async () => {
|
|
115
|
+
lockApp();
|
|
116
|
+
unblockItemMutation.mutate(id);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleBlockThreadClick = () => setBlockThreadConfirmationVisible(true);
|
|
120
|
+
const handleBlockThreadConfirm = async () => {
|
|
121
|
+
lockApp();
|
|
122
|
+
blockItemThreadMutation.mutate(id);
|
|
123
|
+
};
|
|
124
|
+
const handleBlockThreadCancel = () => {
|
|
125
|
+
setBlockThreadConfirmationVisible(false);
|
|
126
|
+
};
|
|
127
|
+
const handleUnblockThreadClick = async () => {
|
|
128
|
+
lockApp();
|
|
129
|
+
unblockItemThreadMutation.mutate(id);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (<>
|
|
133
|
+
<DiscussionThreadItemActionsWrapper as={Flex} direction="row">
|
|
134
|
+
{ badgeVisible && (<DiscussionThreadItemActionsBadge color={badgeColor} backgroundColor={`${badgeColor}100`} textColor={`${badgeColor}600`}>{getMessage(`page.details.panel.discussion.status.${badgeLabel}`, badgeLabel)}</DiscussionThreadItemActionsBadge>)}
|
|
135
|
+
{ !blockedThread && !(blocked || needsApproval) && (<IconButton onClick={handleBlockClick} loading={blockItemMutation.isLoading} icon={<LockIcon />} label={getMessage('page.details.actions.comment.block', 'Block')} style={(!blockedThread && root) ? { marginTop: '1px', marginRight: '.5rem' } : {}} />)}
|
|
136
|
+
{ !blockedThread && blocked && (<IconButton onClick={handleUnblockClick} loading={unblockItemMutation.isLoading} icon={<UnlockIcon />} label={getMessage('page.details.actions.comment.unblock', 'Unblock')} />)}
|
|
137
|
+
{ (!blockedThread && (gotThread || pinned)) && (<Button onClick={handleBlockThreadClick} startIcon={<LockIcon />} loading={blockItemThreadMutation.isLoading} variant="danger">{ getMessage('page.details.actions.thread.block', 'Block thread') }</Button>) }
|
|
138
|
+
{ (blockedThread && (gotThread || pinned)) && (<Button onClick={handleUnblockThreadClick} startIcon={<UnlockIcon />} loading={unblockItemThreadMutation.isLoading} variant="success">{ getMessage('page.details.actions.thread.unblock', 'Unblock thread') }</Button>) }
|
|
139
|
+
{ needsApproval && (<DiscussionThreadItemActionsGroupWrapper>
|
|
140
|
+
<IconButton icon={<Check />} label={getMessage('page.details.actions.comment.approve', 'Approve')} />
|
|
141
|
+
<IconButton icon={<Cross />} label={getMessage('page.details.actions.comment.reject', 'Reject')} />
|
|
142
|
+
</DiscussionThreadItemActionsGroupWrapper>) }
|
|
143
|
+
</DiscussionThreadItemActionsWrapper>
|
|
144
|
+
{ !blocked && (<ConfirmationDialog
|
|
145
|
+
isVisible={blockConfirmationVisible}
|
|
146
|
+
isActionAsync={blockItemMutation.isLoading}
|
|
147
|
+
header={getMessage('page.details.actions.comment.block.confirmation.header')}
|
|
148
|
+
labelConfirm={getMessage('page.details.actions.comment.block.confirmation.button.confirm')}
|
|
149
|
+
iconConfirm={<LockIcon />}
|
|
150
|
+
onConfirm={handleBlockConfirm}
|
|
151
|
+
onCancel={handleBlockCancel}>
|
|
152
|
+
{ getMessage('page.details.actions.comment.block.confirmation.description') }
|
|
153
|
+
</ConfirmationDialog>) }
|
|
154
|
+
{ (!blockedThread && root) && (<ConfirmationDialog
|
|
155
|
+
isVisible={blockThreadConfirmationVisible}
|
|
156
|
+
isActionAsync={blockItemThreadMutation.isLoading}
|
|
157
|
+
header={getMessage('page.details.actions.thread.block.confirmation.header')}
|
|
158
|
+
labelConfirm={getMessage('page.details.actions.thread.block.confirmation.button.confirm')}
|
|
159
|
+
iconConfirm={<LockIcon />}
|
|
160
|
+
onConfirm={handleBlockThreadConfirm}
|
|
161
|
+
onCancel={handleBlockThreadCancel}>
|
|
162
|
+
{ getMessage('page.details.actions.thread.block.confirmation.description') }
|
|
163
|
+
</ConfirmationDialog>) }
|
|
164
|
+
</>);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
DiscussionThreadItemActions.propTypes = {
|
|
168
|
+
isRoot: PropTypes.bool,
|
|
169
|
+
blocked: PropTypes.bool.isRequired,
|
|
170
|
+
blockedThread: PropTypes.bool.isRequired,
|
|
171
|
+
approvalStatus: PropTypes.oneOfType([PropTypes.nullable, PropTypes.oneOf(['PENDING', 'APPROVED', 'REJECTED'])]),
|
|
172
|
+
onBlockClick: PropTypes.func.isRequired,
|
|
173
|
+
onUnBlockClick: PropTypes.func.isRequired,
|
|
174
|
+
onApprovalGroupClick: PropTypes.func.isRequired,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export default DiscussionThreadItemActions;
|
|
178
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Box } from '@strapi/design-system/Box';
|
|
3
|
+
import { Badge } from '@strapi/design-system/Badge';
|
|
4
|
+
import { IconButtonGroup } from '@strapi/design-system/IconButton';
|
|
5
|
+
|
|
6
|
+
export const DiscussionThreadItemActionsBadge = styled(Badge)`
|
|
7
|
+
border: 1px ${ ({ theme, color }) => theme.colors[`${color}200`] } solid;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
export const DiscussionThreadItemActionsWrapper = styled(Box)`
|
|
11
|
+
margin-left: 1rem;
|
|
12
|
+
align-items: start;
|
|
13
|
+
|
|
14
|
+
${DiscussionThreadItemActionsBadge} {
|
|
15
|
+
margin-top: .175rem;
|
|
16
|
+
margin-right: .5rem;
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export const DiscussionThreadItemActionsGroupWrapper = styled(IconButtonGroup)`
|
|
21
|
+
margin-left: .5rem;
|
|
22
|
+
`;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Entity Details
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import { useIntl } from 'react-intl';
|
|
10
|
+
import { Box } from '@strapi/design-system/Box';
|
|
11
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
12
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
13
|
+
|
|
14
|
+
const DiscussionThreadItemMeta = ({ authorName, authorEmail, createdAt, updatedAt }) => {
|
|
15
|
+
|
|
16
|
+
const { formatDate } = useIntl();
|
|
17
|
+
|
|
18
|
+
const dateTime = formatDate(updatedAt || createdAt, { dateStyle: 'short', timeStyle: 'short' });
|
|
19
|
+
|
|
20
|
+
return (<Flex as={Box} paddingTop={2} direction="row" justifyContent="space-between">
|
|
21
|
+
<Typography variant="pi" fontWeight="bold">{ authorName }</Typography>
|
|
22
|
+
<Typography variant="pi">{ dateTime }</Typography>
|
|
23
|
+
</Flex>);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
DiscussionThreadItemMeta.propTypes = {
|
|
27
|
+
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default DiscussionThreadItemMeta;
|
|
31
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Entity Details
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
|
+
import { useMutation, useQueryClient } from 'react-query';
|
|
9
|
+
import { isEmpty, orderBy } from 'lodash';
|
|
10
|
+
import { Badge } from '@strapi/design-system/Badge';
|
|
11
|
+
import { Box } from '@strapi/design-system/Box';
|
|
12
|
+
import { Button } from '@strapi/design-system/Button';
|
|
13
|
+
import { Divider } from '@strapi/design-system/Divider';
|
|
14
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
15
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
16
|
+
import { useNotification, useOverlayBlocker } from '@strapi/helper-plugin';
|
|
17
|
+
import { getMessage, handleAPIError } from '../../utils';
|
|
18
|
+
import { blockItem, blockItemThread, resolveReport } from '../../pages/Details/utils/api';
|
|
19
|
+
import { DiscussionThreadItemWarningsWrapper } from './styles';
|
|
20
|
+
import ReportsReviewModal from '../ReportsReviewModal';
|
|
21
|
+
import ReportsReviewTable from '../ReportsReviewTable';
|
|
22
|
+
import pluginId from '../../pluginId';
|
|
23
|
+
import { LockIcon, ReviewIcon } from '../icons';
|
|
24
|
+
|
|
25
|
+
const DiscussionThreadItemWarnings = (item) => {
|
|
26
|
+
const { id, reports, gotThread } = item;
|
|
27
|
+
|
|
28
|
+
const [reportsReviewVisible, setReportsReviewVisible] = useState(false);
|
|
29
|
+
const [blockButtonsDisabled, setBlockButtonsDisabled] = useState(false);
|
|
30
|
+
|
|
31
|
+
const toggleNotification = useNotification();
|
|
32
|
+
const queryClient = useQueryClient();
|
|
33
|
+
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
34
|
+
|
|
35
|
+
const onSuccess = (message, stateAction = () => {}, indalidate = true) => async () => {
|
|
36
|
+
if (indalidate) {
|
|
37
|
+
await queryClient.invalidateQueries('get-details-data');
|
|
38
|
+
}
|
|
39
|
+
toggleNotification({
|
|
40
|
+
type: 'success',
|
|
41
|
+
message: `${pluginId}.${message}`,
|
|
42
|
+
});
|
|
43
|
+
stateAction(false);
|
|
44
|
+
unlockApp();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const onError = err => { handleAPIError(err, toggleNotification); };
|
|
48
|
+
|
|
49
|
+
const resolveReportMutation = useMutation(resolveReport, {
|
|
50
|
+
onSuccess: onSuccess('page.details.panel.discussion.warnings.reports.dialog.confirmation.success', () => {}, false),
|
|
51
|
+
onError,
|
|
52
|
+
refetchActive: false,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const blockItemMutation = useMutation(blockItem, {
|
|
56
|
+
onSuccess: onSuccess(
|
|
57
|
+
'page.details.actions.comment.block.confirmation.success',
|
|
58
|
+
setReportsReviewVisible
|
|
59
|
+
),
|
|
60
|
+
onError,
|
|
61
|
+
refetchActive: false,
|
|
62
|
+
});
|
|
63
|
+
const blockItemThreadMutation = useMutation(blockItemThread, {
|
|
64
|
+
onSuccess: onSuccess(
|
|
65
|
+
'page.details.actions.thread.block.confirmation.success',
|
|
66
|
+
setReportsReviewVisible
|
|
67
|
+
),
|
|
68
|
+
onError,
|
|
69
|
+
refetchActive: false,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const handleReportsReviewClick = () => setReportsReviewVisible(true);
|
|
73
|
+
const handleBlockItemClick = async () => {
|
|
74
|
+
lockApp();
|
|
75
|
+
blockItemMutation.mutate(id);
|
|
76
|
+
};
|
|
77
|
+
const handleBlockItemThreadClick = async () => {
|
|
78
|
+
lockApp();
|
|
79
|
+
blockItemThreadMutation.mutate(id);
|
|
80
|
+
};
|
|
81
|
+
const handleReportsReviewClose = async () => {
|
|
82
|
+
await queryClient.invalidateQueries('get-details-data')
|
|
83
|
+
setReportsReviewVisible(false);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleBlockButtonsStateChange = disabled => setBlockButtonsDisabled(disabled);
|
|
87
|
+
|
|
88
|
+
const isLoading = blockItemMutation.isLoading || blockItemThreadMutation.isLoading || resolveReportMutation.isLoading;
|
|
89
|
+
const openReports = reports?.filter(_ => !_.resolved);
|
|
90
|
+
|
|
91
|
+
if (isEmpty(reports) || isEmpty(openReports)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (<>
|
|
96
|
+
<DiscussionThreadItemWarningsWrapper as={Flex} direction="row" padding={2} background="danger100" borderColor="danger200" border={1} hasRadius>
|
|
97
|
+
<Box as={Flex} paddingRight={2} direction="row" grow={1}>
|
|
98
|
+
<Badge textColor="neutral0" backgroundColor="warning600">{ openReports.length }</Badge>
|
|
99
|
+
<Box paddingLeft={2}>
|
|
100
|
+
<Typography textColor="warning600" fontWeight="bold">{getMessage(`page.details.panel.discussion.warnings.reports.description`, 'abuse reports')}</Typography>
|
|
101
|
+
</Box>
|
|
102
|
+
</Box>
|
|
103
|
+
<Button onClick={handleReportsReviewClick} variant="ghost" endIcon={<ReviewIcon />}>
|
|
104
|
+
{ getMessage(`page.details.actions.comment.report.review`, 'review') }
|
|
105
|
+
</Button>
|
|
106
|
+
</DiscussionThreadItemWarningsWrapper>
|
|
107
|
+
<ReportsReviewModal
|
|
108
|
+
isVisible={reportsReviewVisible}
|
|
109
|
+
isActionAsync={isLoading}
|
|
110
|
+
onClose={handleReportsReviewClose}
|
|
111
|
+
startActions={<>
|
|
112
|
+
<Button onClick={handleBlockItemClick} variant="danger-light" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
113
|
+
{ getMessage(`page.details.actions.comment.block`, 'Block comment') }
|
|
114
|
+
</Button>
|
|
115
|
+
{ gotThread && (<Button onClick={handleBlockItemThreadClick} variant="danger" startIcon={<LockIcon />} disabled={blockButtonsDisabled}>
|
|
116
|
+
{ getMessage(`page.details.actions.thread.block`, 'Block thread') }
|
|
117
|
+
</Button>) }
|
|
118
|
+
</>}
|
|
119
|
+
item={item}>
|
|
120
|
+
<Typography variant="sigma" textColor="neutral600" id="reports-list">
|
|
121
|
+
{ getMessage('page.details.panel.discussion.warnings.reports.list', 'List') }
|
|
122
|
+
</Typography>
|
|
123
|
+
<Box paddingTop={2} paddingBottom={6}>
|
|
124
|
+
<Divider />
|
|
125
|
+
</Box>
|
|
126
|
+
<ReportsReviewTable
|
|
127
|
+
commentId={item.id}
|
|
128
|
+
items={orderBy(reports, ['resolved', 'createdAt'], ['DESC', 'DESC'])}
|
|
129
|
+
mutation={resolveReportMutation}
|
|
130
|
+
onBlockButtonsStateChange={handleBlockButtonsStateChange}
|
|
131
|
+
/>
|
|
132
|
+
</ReportsReviewModal>
|
|
133
|
+
</>);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
DiscussionThreadItemWarnings.propTypes = {
|
|
137
|
+
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export default DiscussionThreadItemWarnings;
|
|
141
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Entity Details
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import { isNil } 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 EntityDetails = ({ data = {}, schema = {} }) => {
|
|
18
|
+
const { attributes = {} } = schema;
|
|
19
|
+
const keys = Object.keys(attributes);
|
|
20
|
+
|
|
21
|
+
if (keys.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const itemKeys = keys.filter(_ => (attributes[_].type === 'string') && !isNil(data[_]));
|
|
26
|
+
|
|
27
|
+
return (<Box padding={4}>
|
|
28
|
+
<Typography variant="sigma" textColor="neutral600" id="entity-details">
|
|
29
|
+
{ getMessage('page.details.panel.entity', 'Details') }
|
|
30
|
+
</Typography>
|
|
31
|
+
<Box paddingTop={2} paddingBottom={6}>
|
|
32
|
+
<Divider />
|
|
33
|
+
</Box>
|
|
34
|
+
<Stack size={itemKeys.length}>
|
|
35
|
+
{ itemKeys.map(_ =>
|
|
36
|
+
(<Flex justifyContent="space-between">
|
|
37
|
+
<Typography fontWeight="bold">
|
|
38
|
+
{ _ }
|
|
39
|
+
</Typography>
|
|
40
|
+
<Typography>{ data[_] }</Typography>
|
|
41
|
+
</Flex>))
|
|
42
|
+
}
|
|
43
|
+
</Stack>
|
|
44
|
+
</Box>);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
EntityDetails.propTypes = {
|
|
48
|
+
data: PropTypes.object.isRequired,
|
|
49
|
+
schema: PropTypes.object.isRequired,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default EntityDetails;
|
|
53
|
+
|
|
@@ -8,19 +8,19 @@ import { useEffect, useRef } from 'react';
|
|
|
8
8
|
import PropTypes from 'prop-types';
|
|
9
9
|
import pluginId from '../../pluginId';
|
|
10
10
|
|
|
11
|
-
const Initializer = ({
|
|
11
|
+
const Initializer = ({ setPlugin }) => {
|
|
12
12
|
const ref = useRef();
|
|
13
|
-
ref.current =
|
|
13
|
+
ref.current = setPlugin;
|
|
14
14
|
|
|
15
15
|
useEffect(() => {
|
|
16
|
-
ref.current(pluginId
|
|
16
|
+
ref.current(pluginId);
|
|
17
17
|
}, []);
|
|
18
18
|
|
|
19
19
|
return null;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
Initializer.propTypes = {
|
|
23
|
-
|
|
23
|
+
setPlugin: PropTypes.func.isRequired,
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export default Initializer;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Entity Details
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
|
|
9
|
+
import { LoadingIndicatorOverlayWrapper } from './styles';
|
|
10
|
+
|
|
11
|
+
const LoadingIndicatorOverlay = () => (<LoadingIndicatorOverlayWrapper>
|
|
12
|
+
<LoadingIndicatorPage />
|
|
13
|
+
</LoadingIndicatorOverlayWrapper>);
|
|
14
|
+
|
|
15
|
+
export default LoadingIndicatorOverlay;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Box } from '@strapi/design-system/Box';
|
|
3
|
+
|
|
4
|
+
export const LoadingIndicatorOverlayWrapper = styled(Box)`
|
|
5
|
+
height: 100% !important;
|
|
6
|
+
position: absolute;
|
|
7
|
+
top: 0;
|
|
8
|
+
left: 0;
|
|
9
|
+
width: 100%;
|
|
10
|
+
background: rgba(255,255,255,.7);
|
|
11
|
+
z-index: 1;
|
|
12
|
+
|
|
13
|
+
&>div {
|
|
14
|
+
height: 100% !important;
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const LoadingIndicatorContainer = styled((props) => props.as)`
|
|
19
|
+
position: relative;
|
|
20
|
+
`
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* Nav
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import {
|
|
9
|
+
SubNav,
|
|
10
|
+
SubNavHeader,
|
|
11
|
+
SubNavSection,
|
|
12
|
+
SubNavSections,
|
|
13
|
+
SubNavLink,
|
|
14
|
+
} from '@strapi/design-system/SubNav';
|
|
15
|
+
import { ExclamationMarkCircle, Apps } from '@strapi/icons';
|
|
16
|
+
import getMessage from '../../utils/getMessage';
|
|
17
|
+
|
|
18
|
+
// import PropTypes from 'prop-types';
|
|
19
|
+
import getUrl from '../../utils/getUrl';
|
|
20
|
+
|
|
21
|
+
const Nav = ({ hasNewComments, hasNewReports }) => {
|
|
22
|
+
return <SubNav ariaLabel="Comments sub nav">
|
|
23
|
+
<SubNavHeader label={getMessage('plugin.name')} />
|
|
24
|
+
<SubNavSections>
|
|
25
|
+
<SubNavLink to={ getUrl('dashboard') } withBullet={hasNewComments} icon={<Apps />}>
|
|
26
|
+
{ getMessage('nav.item.updates') }
|
|
27
|
+
</SubNavLink>
|
|
28
|
+
<SubNavSection label={getMessage('nav.header.moderation')}>
|
|
29
|
+
<SubNavLink to={ getUrl('discover') } icon={<Apps />}>
|
|
30
|
+
{ getMessage('nav.item.discover') }
|
|
31
|
+
</SubNavLink>
|
|
32
|
+
<SubNavLink to={ getUrl('reports') } withBullet={hasNewReports} icon={<Apps />}>
|
|
33
|
+
{ getMessage('nav.item.reports') }
|
|
34
|
+
</SubNavLink>
|
|
35
|
+
</SubNavSection>
|
|
36
|
+
<SubNavSection label={getMessage('nav.header.settings')}>
|
|
37
|
+
<SubNavLink to={ getUrl('settings') } icon={<Apps />}>
|
|
38
|
+
{ getMessage('nav.item.settings') }
|
|
39
|
+
</SubNavLink>
|
|
40
|
+
</SubNavSection>
|
|
41
|
+
</SubNavSections>
|
|
42
|
+
</SubNav>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default Nav;
|