strapi-plugin-comments 2.0.0-beta.1 → 2.0.0-beta.5

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 +16 -5
  2. package/admin/src/components/DiscussionThread/index.js +3 -1
  3. package/admin/src/components/DiscussionThreadItem/index.js +4 -4
  4. package/admin/src/components/DiscussionThreadItem/styles.js +1 -0
  5. package/admin/src/components/DiscussionThreadItemActions/index.js +30 -44
  6. package/admin/src/components/DiscussionThreadItemActions/styles.js +0 -4
  7. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +72 -0
  8. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/styles.js +6 -0
  9. package/admin/src/components/DiscussionThreadItemWarnings/index.js +13 -9
  10. package/admin/src/components/Nav/index.js +5 -1
  11. package/admin/src/components/ReportsReviewModal/index.js +0 -2
  12. package/admin/src/components/ReportsReviewTable/index.js +9 -5
  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 +6 -4
  18. package/admin/src/pages/App/index.js +42 -3
  19. package/admin/src/pages/App/reducer/actions.js +8 -0
  20. package/admin/src/pages/App/reducer/constants.js +4 -0
  21. package/admin/src/pages/App/reducer/index.js +26 -0
  22. package/admin/src/pages/App/reducer/init.js +7 -0
  23. package/admin/src/pages/App/reducer/selectors.js +35 -0
  24. package/admin/src/pages/App/utils/api.js +12 -0
  25. package/admin/src/pages/Details/components/DetailsEntity/index.js +66 -0
  26. package/admin/src/pages/Details/components/DetailsFilters/index.js +36 -0
  27. package/admin/src/pages/Details/components/DetailsFilters/styles.js +8 -0
  28. package/admin/src/pages/Details/index.js +41 -14
  29. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +190 -0
  30. package/admin/src/pages/Discover/components/DiscoverTableRow/styles.js +8 -0
  31. package/admin/src/pages/Discover/index.js +25 -50
  32. package/admin/src/pages/Discover/utils/api.js +2 -11
  33. package/admin/src/pages/{Details/utils → utils}/api.js +15 -3
  34. package/admin/src/reducers.js +8 -0
  35. package/admin/src/translations/en.json +16 -4
  36. package/admin/src/utils/constants.js +3 -6
  37. package/admin/src/utils/getMessage.js +10 -3
  38. package/admin/src/utils/getRelatedGroups.js +1 -3
  39. package/admin/src/utils/handleAPIError.js +1 -1
  40. package/admin/src/utils/index.js +4 -2
  41. package/admin/src/utils/renderEntryTitle.js +14 -0
  42. package/admin/src/utils/resolveCommentStatus.js +30 -0
  43. package/admin/src/utils/resolveCommentStatusColor.js +22 -0
  44. package/package.json +7 -3
  45. package/server/config/index.js +6 -1
  46. package/server/controllers/admin.js +8 -24
  47. package/server/controllers/client.js +11 -2
  48. package/server/services/admin.js +75 -14
  49. package/server/services/client.js +22 -14
  50. package/server/services/common.js +41 -70
  51. package/server/services/utils/functions.js +7 -14
  52. package/server/utils/constants.js +3 -3
  53. package/admin/src/components/EntityDetails/index.js +0 -53
@@ -1,14 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const { getPluginService, parseParams } = require('./../utils/functions');
4
- const { getAssociationModel } = require('./common');
5
- const { first, isEmpty, isNil, uniq, get } = require('lodash');
4
+ const { isEmpty, isNil } = require('lodash');
6
5
  const PluginError = require('./../utils/error');
7
6
  const {
8
7
  getModelUid,
9
8
  getRelatedGroups,
10
9
  filterOurResolvedReports,
11
- convertContentTypeNameToSlug
12
10
  } = require('./utils/functions');
13
11
  const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
14
12
 
@@ -22,14 +20,36 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
22
20
  return getPluginService('common');
23
21
  },
24
22
 
23
+ // Config
24
+ async config() {
25
+ const entryLabel = this.getCommonService().getConfig('entryLabel');
26
+ const approvalFlow = this.getCommonService().getConfig('approvalFlow');
27
+ return {
28
+ entryLabel,
29
+ approvalFlow,
30
+ regex: Object.keys(REGEX).reduce((prev, curr) => ({
31
+ ...prev,
32
+ [curr]: REGEX[curr].toString(),
33
+ }), {}),
34
+ };
35
+ },
36
+
25
37
  // Find all comments
26
38
  async findAll({ related, entity, ...query }) {
27
39
  const { _q, orderBy, pageSize = 10, page = 1, filters, ...rest } = parseParams(query);
28
40
 
41
+ const defaultWhere = {
42
+ $or: [
43
+ { removed: false },
44
+ { removed: null },
45
+ ],
46
+ };
47
+
29
48
  let params = {
30
49
  where: !isEmpty(filters) ? {
50
+ ...defaultWhere,
31
51
  ...filters,
32
- } : undefined,
52
+ } : { ...defaultWhere },
33
53
  offset: (page - 1)*pageSize,
34
54
  limit: pageSize,
35
55
  orderBy: orderBy || [{ createdAt: 'desc' }],
@@ -47,7 +67,18 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
47
67
  }
48
68
 
49
69
  const entities = await strapi.db.query(getModelUid('comment'))
50
- .findMany(params, ['authorUser', 'reports']);
70
+ .findMany({
71
+ ...params,
72
+ populate: {
73
+ authorUser: true,
74
+ threadOf: true,
75
+ reports: {
76
+ where: {
77
+ resolved: false
78
+ },
79
+ },
80
+ },
81
+ });
51
82
  const total = await strapi.db.query(getModelUid('comment'))
52
83
  .count({
53
84
  where: params.where,
@@ -57,8 +88,6 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
57
88
  .map(_ => filterOurResolvedReports(this.getCommonService().sanitizeCommentEntity(_)))
58
89
  .map(_ => this.getCommonService().mergeRelatedEntityTo(_, relatedEntities));
59
90
 
60
- console.log(relatedEntities);
61
-
62
91
  return {
63
92
  result,
64
93
  pagination: {
@@ -71,12 +100,35 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
71
100
  },
72
101
 
73
102
  // Find single comment
74
- async findOneAndThread(id) {
103
+ async findOneAndThread(id, { removed, ...query }) {
104
+ const defaultWhere = !removed ? {
105
+ $or: [
106
+ { removed: false },
107
+ { removed: null },
108
+ ],
109
+ } : {};
110
+
111
+ const reportsPopulation = {
112
+ reports: {
113
+ where: {
114
+ resolved: false,
115
+ },
116
+ }
117
+ };
118
+
75
119
  const entity = await strapi.db.query(getModelUid('comment')).findOne({
76
120
  where: {
77
- id
121
+ id,
122
+ },
123
+ populate: {
124
+ authorUser: true,
125
+ threadOf: {
126
+ populate: {
127
+ ...reportsPopulation
128
+ },
129
+ },
130
+ ...reportsPopulation,
78
131
  },
79
- populate: ['threadOf', 'threadOf.reports', 'authorUser', 'reports']
80
132
  });
81
133
 
82
134
  if (!entity){
@@ -100,22 +152,27 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
100
152
  const entitiesOnSameLevel = await this.getCommonService()
101
153
  .findAllInHierarchy({
102
154
  query: {
155
+ ...defaultWhere,
156
+ ...query,
103
157
  threadOf: levelThreadId,
104
158
  related: entity.related,
105
159
  },
160
+ populate: {
161
+ ...reportsPopulation,
162
+ },
106
163
  startingFromId: levelThreadId,
107
164
  isAdmin: true
108
165
  }, false);
109
166
 
110
- const selectedEntity = filterOurResolvedReports(this.getCommonService().sanitizeCommentEntity(entity));
167
+ const selectedEntity = this.getCommonService().sanitizeCommentEntity(entity);
111
168
 
112
169
  return {
113
170
  entity: relatedEntity,
114
171
  selected: {
115
172
  ...selectedEntity,
116
- threadOf: selectedEntity.threadOf ? filterOurResolvedReports(selectedEntity.threadOf) : null,
173
+ threadOf: selectedEntity.threadOf ? selectedEntity.threadOf : null,
117
174
  },
118
- level: entitiesOnSameLevel.map(_ => filterOurResolvedReports(_))
175
+ level: entitiesOnSameLevel,
119
176
  };
120
177
  },
121
178
 
@@ -132,9 +189,13 @@ const { APPROVAL_STATUS, REGEX } = require('./../utils/constants')
132
189
  // Block / Unblock a comment thread
133
190
  async blockCommentThread(id, forceStatus) {
134
191
  const existingEntity = await this.getCommonService().findOne({ id });
192
+ const status = !isNil(forceStatus) ? forceStatus : !existingEntity.blockedThread;
135
193
  const changedEntity = await strapi.db.query(getModelUid('comment')).update({
136
194
  where: { id },
137
- data: { blockedThread: !isNil(forceStatus) ? forceStatus : !existingEntity.blockedThread }
195
+ data: {
196
+ blocked: status,
197
+ blockedThread: status,
198
+ },
138
199
  });
139
200
  await this.blockNestedThreads(id, changedEntity.blockedThread);
140
201
 
@@ -26,18 +26,14 @@ module.exports = ({ strapi }) => ({
26
26
 
27
27
  // Create a comment
28
28
  async create(relation, data, user = undefined) {
29
- const { content, threadOf, related } = data;
30
- const singleRelationFulfilled = related && REGEX.relatedUid.test(related);
31
-
32
- if (relation !== related) {
33
- throw new PluginError(400, `Insonsistent relation uid`);
34
- }
35
-
29
+ const { content, threadOf } = data;
30
+ const singleRelationFulfilled = relation && REGEX.relatedUid.test(relation);
31
+
36
32
  if(!singleRelationFulfilled) {
37
33
  throw new PluginError(400, `Field "related" got incorrect format, use format like "api::<collection name>.<content type name>:<entity id>"`);
38
34
  }
39
35
 
40
- const [ uid, relatedStringId ] = getRelatedGroups(related);
36
+ const [ uid, relatedStringId ] = getRelatedGroups(relation);
41
37
  const relatedId = parseInt(relatedStringId, 10);
42
38
 
43
39
  const relatedEntity = await strapi.db.query(uid).findOne(relatedId);
@@ -45,9 +41,12 @@ module.exports = ({ strapi }) => ({
45
41
  throw new PluginError(400, `Relation for field "related" does not exist. Check your payload please.`);
46
42
  }
47
43
 
44
+ const isApprovalFlowEnabled = this.getCommonService().getConfig('approvalFlow', []).includes(uid) ||
45
+ relatedEntity.requireCommentsApproval;
46
+
48
47
  const linkToThread = !isNil(threadOf) ? !!await this.getCommonService().findOne({
49
48
  id: threadOf,
50
- related,
49
+ related: relation,
51
50
  }) : true;
52
51
  const validContext = isValidUserContext(user);
53
52
 
@@ -87,7 +86,8 @@ module.exports = ({ strapi }) => ({
87
86
  throw new PluginError(400, 'Object: "author" is invalid. Check your payload');
88
87
  }
89
88
 
90
- if (relatedEntity.requireCommentsApproval && data.approvalStatus !== APPROVAL_STATUS.PENDING) {
89
+ if (isApprovalFlowEnabled &&
90
+ (!isNil(data.approvalStatus) && (data.approvalStatus !== APPROVAL_STATUS.PENDING))) {
91
91
  throw new PluginError(400, 'Invalid approval status');
92
92
  }
93
93
 
@@ -95,7 +95,8 @@ module.exports = ({ strapi }) => ({
95
95
  data: {
96
96
  ...rest,
97
97
  ...authorData,
98
- related,
98
+ related: relation,
99
+ approvalStatus: isApprovalFlowEnabled ? APPROVAL_STATUS.PENDING : null,
99
100
  }
100
101
  });
101
102
  return this.getCommonService().sanitizeCommentEntity(entity);
@@ -160,7 +161,7 @@ module.exports = ({ strapi }) => ({
160
161
  try {
161
162
  await this.sendAbuseReportEmail(entity.reason, entity.content); // Could also add some info about relation
162
163
  } catch (err) {
163
- throw err;
164
+ return entity;
164
165
  }
165
166
  } else {
166
167
  throw new PluginError(500, 'Report cannot be created');
@@ -177,10 +178,17 @@ module.exports = ({ strapi }) => ({
177
178
  $or: [{ authorUser: authorId }, { authorId }],
178
179
  });
179
180
  if (!entity){
180
- throw strapi.errors.notFound('Entity not exist');
181
+ throw new PluginError(404, 'Entity not exist');
181
182
  }
183
+
182
184
  return strapi.db.query(getModelUid('comment'))
183
- .update({ id: commentId, relatedSlug: relationId }, { removed: true })
185
+ .update({
186
+ where: {
187
+ id: commentId,
188
+ related: relationId
189
+ },
190
+ data: { removed: true }
191
+ })
184
192
  .then(({ id }) => this.markAsRemovedNested(id, true))
185
193
  .then(() => ({ id: commentId }));
186
194
  },
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const { isArray, isEmpty, uniq, get, first } = require('lodash');
3
+ const { isArray, uniq, first } = require('lodash');
4
4
  // const { sanitizeEntity } = require('strapi-utils');
5
5
  const PluginError = require('./../utils/error');
6
6
  const {
7
- extractMeta,
8
7
  getModelUid,
9
8
  getRelatedGroups,
10
9
  buildNestedStructure,
@@ -16,71 +15,31 @@ const {
16
15
  */
17
16
 
18
17
  module.exports = ({ strapi }) => ({
19
- getAssociationModel(ormModel, related) {
20
- const relatedAssociation = ormModel.associations.find(_ => _.alias === 'related');
21
- if (relatedAssociation) {
22
- return relatedAssociation.related.find(rel => rel.collectionName === related);
23
- }
24
- return null;
25
- },
26
-
27
- getMorphModel() {
28
- const ormModel = strapi.query(getModelUid('comment'));
29
- return ormModel.model.morph;
30
- },
31
-
32
- isMongoDB(){
33
- return strapi.query(getModelUid('comment'))?.model?.orm === 'mongoose';
34
- },
35
-
36
- getMorphData(query) {
37
- return this
38
- .getMorphModel()
39
- .query(function () {
40
- this.where(query);
41
- })
42
- .fetchAll()
43
- .then((entities) => entities.map(entity => entity.toJSON()));
44
- },
45
-
46
- checkEntityRelation(uid, entityId) {
47
- const result = parsedRelation.some(ref => {
48
- const model = strapi.query(ref);
49
- return model && model.associations.length &&
50
- model.associations.some(_ => _.plugin === 'comments' && _.collection === 'comment');
51
- });
52
- if (!result) {
53
- throw new PluginError(400, 'Comment not have relation to this type content type.');
54
- }
55
- },
56
18
 
57
- getConfig(prop) {
19
+ getConfig(prop, defaultValue) {
58
20
  let queryProp = prop;
59
21
  if (prop && isArray(prop)) {
60
22
  queryProp = prop.join('.');
61
23
  }
62
- return strapi.config.get(`plugin.comments${ queryProp ? '.' + queryProp : ''}`);
24
+ return strapi.config.get(`plugin.comments${ queryProp ? '.' + queryProp : ''}`) || defaultValue;
63
25
  },
64
26
 
65
27
  // Find comments in the flat structure
66
- async findAllFlat(query, isAdmin = false, relatedEntity = null) {
67
- const criteria = {
68
- ...query,
69
- $or: [{ removed: { $null: true } }, { removed: false }],
70
- }
71
-
72
- if (isAdmin){
73
- delete criteria.$or;
74
- }
28
+ async findAllFlat({ query = {}, populate = {} }, relatedEntity = null) {
75
29
 
76
- const populate = { authorUser: true, reports: true };
30
+ const defaultPopulate = {
31
+ authorUser: true,
32
+ };
77
33
 
78
34
  const entries = await strapi.db.query(getModelUid('comment'))
79
35
  .findMany({
80
36
  where: {
81
- ...criteria,
37
+ ...query,
38
+ },
39
+ populate: {
40
+ ...defaultPopulate,
41
+ ...populate,
82
42
  },
83
- populate,
84
43
  });
85
44
 
86
45
  const entriesWithThreads = await Promise.all(entries.map(async _ => {
@@ -93,16 +52,16 @@ module.exports = ({ strapi }) => ({
93
52
  return { id: _.id, itemsInTread: count, firstThreadItemId: first(nestedEntries)?.id }
94
53
  }));
95
54
 
96
- const relatedEntities = relatedEntity !== null ? [relatedEntity] : await this.findRelatedEntitiesFor([...entitiesRoot, ...entitiesNested]);
55
+ const relatedEntities = relatedEntity !== null ? [relatedEntity] : await this.findRelatedEntitiesFor([...entries]);
97
56
  const result = entries
98
57
  .map(_ => {
99
58
  const threadedItem = entriesWithThreads.find(item => item.id === _.id);
100
- return filterOurResolvedReports(this.sanitizeCommentEntity({
59
+ return this.sanitizeCommentEntity({
101
60
  ..._,
102
61
  threadOf: query.threadOf || _.threadOf || null,
103
62
  gotThread: (threadedItem?.itemsInTread || 0) > 0,
104
63
  threadFirstItemId: threadedItem?.firstThreadItemId,
105
- }))
64
+ });
106
65
  });
107
66
 
108
67
  if (relatedEntities.filter(_ => _).length > 0) {
@@ -114,11 +73,11 @@ module.exports = ({ strapi }) => ({
114
73
  // Find comments and create relations tree structure
115
74
  async findAllInHierarchy ({
116
75
  query,
76
+ populate = {},
117
77
  startingFromId = null,
118
78
  dropBlockedThreads = false,
119
- isAdmin = false,
120
79
  }, relatedEntity) {
121
- const entities = await this.findAllFlat(query, isAdmin, relatedEntity);
80
+ const entities = await this.findAllFlat({ query, populate }, relatedEntity);
122
81
  return buildNestedStructure(entities, startingFromId, 'threadOf', dropBlockedThreads, false);
123
82
  },
124
83
 
@@ -129,24 +88,36 @@ module.exports = ({ strapi }) => ({
129
88
  where: criteria
130
89
  }, ['related', 'reports']);
131
90
  if (!entity){
132
- throw strapi.errors.notFound();
91
+ throw new PluginError(404, 'Not found');
133
92
  }
134
93
  return filterOurResolvedReports(this.sanitizeCommentEntity(entity));
135
94
  },
136
95
 
137
96
  // Find all related entiries
138
97
  async findRelatedEntitiesFor(entities = []) {
139
- return Promise.all(uniq(entities.map(_ => _.related)).map(async _ => {
140
- const [relatedUid, relatedStringId] = getRelatedGroups(_);
141
- const relatedId = parseInt(relatedStringId, 10);
142
- const relatedEntity = await strapi.db.query(relatedUid).findOne({
143
- where: { id: relatedId }
144
- });
145
- return {
146
- ...relatedEntity,
147
- uid: relatedUid,
148
- };
149
- }))
98
+ const data = entities.reduce((acc, cur) => {
99
+ const [relatedUid, relatedStringId] = getRelatedGroups(cur.related);
100
+ return {
101
+ ...acc,
102
+ [relatedUid]: [...(acc[relatedUid] || []), parseInt(relatedStringId, 10)]
103
+ };
104
+ },
105
+ {}
106
+ );
107
+ return Promise.all(
108
+ Object.entries(data)
109
+ .map(async ([relatedUid, relatedStringIds]) =>
110
+ strapi.db.query(relatedUid).findMany({
111
+ where: { id: Array.from(new Set(relatedStringIds)) }
112
+ }).then(relatedEntities => relatedEntities.map(_ =>
113
+ ({
114
+ ..._,
115
+ uid: relatedUid,
116
+ })
117
+ ))
118
+ )
119
+ )
120
+ .then(result => result.flat(2));
150
121
  },
151
122
 
152
123
  // Merge related entity with comment
@@ -34,9 +34,14 @@ const buildNestedStructure = (
34
34
  module.exports = {
35
35
  isEqualEntity: (existing, data, user) => {
36
36
  const { authorUser, authorId } = existing;
37
- console.log('data', data);
38
- console.log(existing);
39
37
  const { author } = data;
38
+
39
+ // Disallow approval status change by Client
40
+ if (data.approvalStatus && (existing.approvalStatus !== data.approvalStatus)) {
41
+ return false;
42
+ }
43
+
44
+ // Make sure that author is exact the same
40
45
  if (authorUser) {
41
46
  const existingUserId = authorUser?.id || authorUser;
42
47
  const receivedUserId = user?.id || author;
@@ -45,18 +50,6 @@ module.exports = {
45
50
  return authorId === author?.id;
46
51
  },
47
52
 
48
- extractMeta: plugins => {
49
- const { comments: plugin } = plugins;
50
- const { comments: service } = plugin.services;
51
- const { comment: model} = plugin.models;
52
- return {
53
- model,
54
- service,
55
- plugin,
56
- pluginName: plugin.package.strapi.name.toLowerCase()
57
- };
58
- },
59
-
60
53
  getRelatedGroups: related => related.split(REGEX.relatedUid).filter(s => s && s.length > 0),
61
54
 
62
55
  getModelUid: name => {
@@ -5,9 +5,9 @@ const APPROVAL_STATUS = {
5
5
  };
6
6
 
7
7
  const REGEX = {
8
- uid: new RegExp(/^(?<type>[a-z]+)\:{2}(?<api>[a-z]+)\.{1}(?<contentType>[a-z]+)$/gmi),
9
- relatedUid: new RegExp(/^(?<uid>[a-z]+\:{2}[a-z]+\.[a-z]+)\:{1}(?<id>[0-9]+)$/gmi),
10
- email: new RegExp(/\S+@\S+\.\S+/),
8
+ uid: /^(?<type>[a-z0-9-]+)\:{2}(?<api>[a-z0-9-]+)\.{1}(?<contentType>[a-z0-9-]+)$/i,
9
+ relatedUid: /^(?<uid>[a-z0-9-]+\:{2}[a-z0-9-]+\.[a-z0-9-]+)\:{1}(?<id>[a-z0-9-]+)$/i,
10
+ email: /\S+@\S+\.\S+/,
11
11
  };
12
12
 
13
13
  module.exports = {
@@ -1,53 +0,0 @@
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
-