@strapi/content-releases 5.9.0 → 5.10.1

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 (69) hide show
  1. package/dist/admin/chunks/App-B9yCdSLJ.js +1866 -0
  2. package/dist/admin/chunks/App-B9yCdSLJ.js.map +1 -0
  3. package/dist/admin/chunks/App-CuOosufQ.mjs +1845 -0
  4. package/dist/admin/chunks/App-CuOosufQ.mjs.map +1 -0
  5. package/dist/admin/chunks/PurchaseContentReleases-BCME5SQU.js +55 -0
  6. package/dist/admin/chunks/PurchaseContentReleases-BCME5SQU.js.map +1 -0
  7. package/dist/admin/chunks/PurchaseContentReleases-S1ccDSwp.mjs +53 -0
  8. package/dist/admin/chunks/PurchaseContentReleases-S1ccDSwp.mjs.map +1 -0
  9. package/dist/admin/chunks/ReleasesSettingsPage-BzdLEfxa.mjs +206 -0
  10. package/dist/admin/chunks/ReleasesSettingsPage-BzdLEfxa.mjs.map +1 -0
  11. package/dist/admin/chunks/ReleasesSettingsPage-DFVGppsl.js +208 -0
  12. package/dist/admin/chunks/ReleasesSettingsPage-DFVGppsl.js.map +1 -0
  13. package/dist/admin/chunks/en-B2EeDoOz.mjs +101 -0
  14. package/dist/{_chunks/en-D9Q4YW03.mjs.map → admin/chunks/en-B2EeDoOz.mjs.map} +1 -1
  15. package/dist/admin/chunks/en-BzpFfVeO.js +103 -0
  16. package/dist/{_chunks/en-BWPPsSH-.js.map → admin/chunks/en-BzpFfVeO.js.map} +1 -1
  17. package/dist/admin/chunks/index-1nn-zHX-.js +1657 -0
  18. package/dist/admin/chunks/index-1nn-zHX-.js.map +1 -0
  19. package/dist/admin/chunks/index-CmAFGQWf.mjs +1618 -0
  20. package/dist/admin/chunks/index-CmAFGQWf.mjs.map +1 -0
  21. package/dist/admin/chunks/schemas-DMt8h1z-.mjs +43 -0
  22. package/dist/admin/chunks/schemas-DMt8h1z-.mjs.map +1 -0
  23. package/dist/admin/chunks/schemas-DS7NeFDN.js +65 -0
  24. package/dist/admin/chunks/schemas-DS7NeFDN.js.map +1 -0
  25. package/dist/admin/index.js +18 -3
  26. package/dist/admin/index.js.map +1 -1
  27. package/dist/admin/index.mjs +13 -5
  28. package/dist/admin/index.mjs.map +1 -1
  29. package/dist/admin/src/components/ReleaseListCell.d.ts +1 -1
  30. package/dist/server/index.js +2166 -1870
  31. package/dist/server/index.js.map +1 -1
  32. package/dist/server/index.mjs +2156 -1861
  33. package/dist/server/index.mjs.map +1 -1
  34. package/dist/server/src/destroy.d.ts +1 -1
  35. package/dist/server/src/destroy.d.ts.map +1 -1
  36. package/dist/server/src/middlewares/documents.d.ts +1 -1
  37. package/dist/server/src/middlewares/documents.d.ts.map +1 -1
  38. package/dist/server/src/services/scheduling.d.ts +1 -1
  39. package/dist/server/src/services/scheduling.d.ts.map +1 -1
  40. package/dist/server/src/services/validation.d.ts +1 -1
  41. package/dist/server/src/services/validation.d.ts.map +1 -1
  42. package/dist/shared/contracts/release-actions.d.ts +0 -1
  43. package/dist/shared/contracts/releases.d.ts +0 -1
  44. package/dist/shared/contracts/settings.d.ts +1 -2
  45. package/dist/shared/contracts/settings.d.ts.map +1 -1
  46. package/dist/shared/types.d.ts +0 -1
  47. package/package.json +16 -13
  48. package/dist/_chunks/App-CiZCkScI.mjs +0 -1558
  49. package/dist/_chunks/App-CiZCkScI.mjs.map +0 -1
  50. package/dist/_chunks/App-SGjO5UPV.js +0 -1578
  51. package/dist/_chunks/App-SGjO5UPV.js.map +0 -1
  52. package/dist/_chunks/PurchaseContentReleases--qQepXpP.js +0 -52
  53. package/dist/_chunks/PurchaseContentReleases--qQepXpP.js.map +0 -1
  54. package/dist/_chunks/PurchaseContentReleases-D-n-w-st.mjs +0 -52
  55. package/dist/_chunks/PurchaseContentReleases-D-n-w-st.mjs.map +0 -1
  56. package/dist/_chunks/ReleasesSettingsPage-Cto_NLUd.js +0 -178
  57. package/dist/_chunks/ReleasesSettingsPage-Cto_NLUd.js.map +0 -1
  58. package/dist/_chunks/ReleasesSettingsPage-DQT8N3A-.mjs +0 -178
  59. package/dist/_chunks/ReleasesSettingsPage-DQT8N3A-.mjs.map +0 -1
  60. package/dist/_chunks/en-BWPPsSH-.js +0 -102
  61. package/dist/_chunks/en-D9Q4YW03.mjs +0 -102
  62. package/dist/_chunks/index-BjvFfTtA.mjs +0 -1386
  63. package/dist/_chunks/index-BjvFfTtA.mjs.map +0 -1
  64. package/dist/_chunks/index-CyU534vL.js +0 -1404
  65. package/dist/_chunks/index-CyU534vL.js.map +0 -1
  66. package/dist/_chunks/schemas-DBYv9gK8.js +0 -61
  67. package/dist/_chunks/schemas-DBYv9gK8.js.map +0 -1
  68. package/dist/_chunks/schemas-DdA2ic2U.mjs +0 -44
  69. package/dist/_chunks/schemas-DdA2ic2U.mjs.map +0 -1
@@ -0,0 +1,1845 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { useNotification, useAPIErrorHandler, useQueryParams, useTracking, useRBAC, Page, Layouts, Pagination, isFetchError, ConfirmDialog, BackButton, useStrapiApp, Table } from '@strapi/admin/strapi-admin';
3
+ import { Link, useLocation, useNavigate, NavLink, useParams, Navigate, Routes, Route } from 'react-router-dom';
4
+ import { g as getTimezones, p as pluginId, u as useGetReleasesQuery, a as useGetReleaseSettingsQuery, b as useCreateReleaseMutation, P as PERMISSIONS, c as useGetReleaseQuery, d as useUpdateReleaseMutation, e as useDeleteReleaseMutation, f as usePublishReleaseMutation, h as getTimezoneOffset, i as useGetReleaseActionsQuery, j as useUpdateReleaseActionMutation, R as ReleaseActionOptions, k as ReleaseActionMenu, r as releaseApi } from './index-CmAFGQWf.mjs';
5
+ import * as React from 'react';
6
+ import { Flex, Popover, Button, Typography, LinkButton, Modal, Field, TextInput, Box, Checkbox, DatePicker, TimePicker, Combobox, ComboboxOption, Link as Link$1, Alert, Main, Tabs, Divider, EmptyStateLayout, Grid, Badge, MenuItem, SimpleMenu, Dialog, SingleSelect, SingleSelectOption, Tr, Td } from '@strapi/design-system';
7
+ import { CrossCircle, CaretDown, CheckCircle, ArrowsCounterClockwise, Plus, Pencil, Trash, More } from '@strapi/icons';
8
+ import { EmptyDocuments } from '@strapi/icons/symbols';
9
+ import format$1 from 'date-fns/format';
10
+ import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
11
+ import { useIntl } from 'react-intl';
12
+ import { styled } from 'styled-components';
13
+ import { unstable_useDocument } from '@strapi/content-manager/strapi-admin';
14
+ import { stringify } from 'qs';
15
+ import { intervalToDuration, isPast, formatISO, format } from 'date-fns';
16
+ import { Formik, Form, useFormikContext } from 'formik';
17
+ import { R as RELEASE_SCHEMA } from './schemas-DMt8h1z-.mjs';
18
+ import { useDispatch } from 'react-redux';
19
+ import { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';
20
+ import 'yup';
21
+
22
+ const StyledPopoverFlex = styled(Flex)`
23
+ width: 100%;
24
+ max-width: 256px;
25
+
26
+ & > * {
27
+ border-bottom: 1px solid ${({ theme })=>theme.colors.neutral150};
28
+ }
29
+
30
+ & > *:last-child {
31
+ border-bottom: none;
32
+ }
33
+ `;
34
+ const EntryStatusTrigger = ({ action, status, hasErrors, requiredStage, entryStage })=>{
35
+ const { formatMessage } = useIntl();
36
+ if (action === 'publish') {
37
+ if (hasErrors || requiredStage && requiredStage.id !== entryStage?.id) {
38
+ return /*#__PURE__*/ jsx(Popover.Trigger, {
39
+ children: /*#__PURE__*/ jsx(Button, {
40
+ variant: "ghost",
41
+ startIcon: /*#__PURE__*/ jsx(CrossCircle, {
42
+ fill: "danger600"
43
+ }),
44
+ endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
45
+ children: /*#__PURE__*/ jsx(Typography, {
46
+ textColor: "danger600",
47
+ variant: "omega",
48
+ fontWeight: "bold",
49
+ children: formatMessage({
50
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.not-ready',
51
+ defaultMessage: 'Not ready to publish'
52
+ })
53
+ })
54
+ })
55
+ });
56
+ }
57
+ if (status === 'draft') {
58
+ return /*#__PURE__*/ jsx(Popover.Trigger, {
59
+ children: /*#__PURE__*/ jsx(Button, {
60
+ variant: "ghost",
61
+ startIcon: /*#__PURE__*/ jsx(CheckCircle, {
62
+ fill: "success600"
63
+ }),
64
+ endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
65
+ children: /*#__PURE__*/ jsx(Typography, {
66
+ textColor: "success600",
67
+ variant: "omega",
68
+ fontWeight: "bold",
69
+ children: formatMessage({
70
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish',
71
+ defaultMessage: 'Ready to publish'
72
+ })
73
+ })
74
+ })
75
+ });
76
+ }
77
+ if (status === 'modified') {
78
+ return /*#__PURE__*/ jsx(Popover.Trigger, {
79
+ children: /*#__PURE__*/ jsx(Button, {
80
+ variant: "ghost",
81
+ startIcon: /*#__PURE__*/ jsx(ArrowsCounterClockwise, {
82
+ fill: "alternative600"
83
+ }),
84
+ endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
85
+ children: /*#__PURE__*/ jsx(Typography, {
86
+ variant: "omega",
87
+ fontWeight: "bold",
88
+ textColor: "alternative600",
89
+ children: formatMessage({
90
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.modified',
91
+ defaultMessage: 'Ready to publish changes'
92
+ })
93
+ })
94
+ })
95
+ });
96
+ }
97
+ return /*#__PURE__*/ jsx(Popover.Trigger, {
98
+ children: /*#__PURE__*/ jsx(Button, {
99
+ variant: "ghost",
100
+ startIcon: /*#__PURE__*/ jsx(CheckCircle, {
101
+ fill: "success600"
102
+ }),
103
+ endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
104
+ children: /*#__PURE__*/ jsx(Typography, {
105
+ textColor: "success600",
106
+ variant: "omega",
107
+ fontWeight: "bold",
108
+ children: formatMessage({
109
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.already-published',
110
+ defaultMessage: 'Already published'
111
+ })
112
+ })
113
+ })
114
+ });
115
+ }
116
+ if (status === 'published') {
117
+ return /*#__PURE__*/ jsx(Popover.Trigger, {
118
+ children: /*#__PURE__*/ jsx(Button, {
119
+ variant: "ghost",
120
+ startIcon: /*#__PURE__*/ jsx(CheckCircle, {
121
+ fill: "success600"
122
+ }),
123
+ endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
124
+ children: /*#__PURE__*/ jsx(Typography, {
125
+ textColor: "success600",
126
+ variant: "omega",
127
+ fontWeight: "bold",
128
+ children: formatMessage({
129
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish',
130
+ defaultMessage: 'Ready to unpublish'
131
+ })
132
+ })
133
+ })
134
+ });
135
+ }
136
+ return /*#__PURE__*/ jsx(Popover.Trigger, {
137
+ children: /*#__PURE__*/ jsx(Button, {
138
+ variant: "ghost",
139
+ startIcon: /*#__PURE__*/ jsx(CheckCircle, {
140
+ fill: "success600"
141
+ }),
142
+ endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
143
+ children: /*#__PURE__*/ jsx(Typography, {
144
+ textColor: "success600",
145
+ variant: "omega",
146
+ fontWeight: "bold",
147
+ children: formatMessage({
148
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.already-unpublished',
149
+ defaultMessage: 'Already unpublished'
150
+ })
151
+ })
152
+ })
153
+ });
154
+ };
155
+ const FieldsValidation = ({ hasErrors, errors, kind, contentTypeUid, documentId, locale })=>{
156
+ const { formatMessage } = useIntl();
157
+ return /*#__PURE__*/ jsxs(Flex, {
158
+ direction: "column",
159
+ gap: 1,
160
+ width: "100%",
161
+ padding: 5,
162
+ children: [
163
+ /*#__PURE__*/ jsxs(Flex, {
164
+ gap: 2,
165
+ width: "100%",
166
+ children: [
167
+ /*#__PURE__*/ jsx(Typography, {
168
+ fontWeight: "bold",
169
+ children: formatMessage({
170
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.fields',
171
+ defaultMessage: 'Fields'
172
+ })
173
+ }),
174
+ hasErrors ? /*#__PURE__*/ jsx(CrossCircle, {
175
+ fill: "danger600"
176
+ }) : /*#__PURE__*/ jsx(CheckCircle, {
177
+ fill: "success600"
178
+ })
179
+ ]
180
+ }),
181
+ /*#__PURE__*/ jsx(Typography, {
182
+ width: "100%",
183
+ textColor: "neutral600",
184
+ children: hasErrors ? formatMessage({
185
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.error',
186
+ defaultMessage: '{errors} errors on fields.'
187
+ }, {
188
+ errors: errors ? Object.keys(errors).length : 0
189
+ }) : formatMessage({
190
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.success',
191
+ defaultMessage: 'All fields are filled correctly.'
192
+ })
193
+ }),
194
+ hasErrors && /*#__PURE__*/ jsx(LinkButton, {
195
+ tag: Link,
196
+ to: {
197
+ pathname: `/content-manager/${kind === 'collectionType' ? 'collection-types' : 'single-types'}/${contentTypeUid}/${documentId}`,
198
+ search: locale ? stringify({
199
+ plugins: {
200
+ i18n: {
201
+ locale
202
+ }
203
+ }
204
+ }) : ''
205
+ },
206
+ variant: "secondary",
207
+ fullWidth: true,
208
+ state: {
209
+ forceValidation: true
210
+ },
211
+ children: formatMessage({
212
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.see-errors',
213
+ defaultMessage: 'See errors'
214
+ })
215
+ })
216
+ ]
217
+ });
218
+ };
219
+ const getReviewStageIcon = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
220
+ if (!contentTypeHasReviewWorkflow) {
221
+ return /*#__PURE__*/ jsx(CheckCircle, {
222
+ fill: "neutral200"
223
+ });
224
+ }
225
+ if (requiredStage && requiredStage.id !== entryStage?.id) {
226
+ return /*#__PURE__*/ jsx(CrossCircle, {
227
+ fill: "danger600"
228
+ });
229
+ }
230
+ return /*#__PURE__*/ jsx(CheckCircle, {
231
+ fill: "success600"
232
+ });
233
+ };
234
+ const getReviewStageMessage = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage, formatMessage })=>{
235
+ if (!contentTypeHasReviewWorkflow) {
236
+ return formatMessage({
237
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-enabled',
238
+ defaultMessage: 'This entry is not associated to any workflow.'
239
+ });
240
+ }
241
+ if (requiredStage && requiredStage.id !== entryStage?.id) {
242
+ return formatMessage({
243
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-ready',
244
+ defaultMessage: 'This entry is not at the required stage for publishing. ({stageName})'
245
+ }, {
246
+ stageName: requiredStage?.name ?? ''
247
+ });
248
+ }
249
+ if (requiredStage && requiredStage.id === entryStage?.id) {
250
+ return formatMessage({
251
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.ready',
252
+ defaultMessage: 'This entry is at the required stage for publishing. ({stageName})'
253
+ }, {
254
+ stageName: requiredStage?.name ?? ''
255
+ });
256
+ }
257
+ return formatMessage({
258
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.stage-not-required',
259
+ defaultMessage: 'No required stage for publication'
260
+ });
261
+ };
262
+ const ReviewStageValidation = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
263
+ const { formatMessage } = useIntl();
264
+ const Icon = getReviewStageIcon({
265
+ contentTypeHasReviewWorkflow,
266
+ requiredStage,
267
+ entryStage
268
+ });
269
+ return /*#__PURE__*/ jsxs(Flex, {
270
+ direction: "column",
271
+ gap: 1,
272
+ width: "100%",
273
+ padding: 5,
274
+ children: [
275
+ /*#__PURE__*/ jsxs(Flex, {
276
+ gap: 2,
277
+ width: "100%",
278
+ children: [
279
+ /*#__PURE__*/ jsx(Typography, {
280
+ fontWeight: "bold",
281
+ children: formatMessage({
282
+ id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage',
283
+ defaultMessage: 'Review stage'
284
+ })
285
+ }),
286
+ Icon
287
+ ]
288
+ }),
289
+ /*#__PURE__*/ jsx(Typography, {
290
+ textColor: "neutral600",
291
+ children: getReviewStageMessage({
292
+ contentTypeHasReviewWorkflow,
293
+ requiredStage,
294
+ entryStage,
295
+ formatMessage
296
+ })
297
+ })
298
+ ]
299
+ });
300
+ };
301
+ const EntryValidationPopover = ({ schema, entry, status, action })=>{
302
+ const { validate, isLoading } = unstable_useDocument({
303
+ collectionType: schema?.kind ?? '',
304
+ model: schema?.uid ?? ''
305
+ }, {
306
+ // useDocument makes a request to get more data about the entry, but we only want to have the validation function so we skip the request
307
+ skip: true
308
+ });
309
+ // Validation errors
310
+ const errors = isLoading ? null : validate(entry);
311
+ const hasErrors = errors ? Object.keys(errors).length > 0 : false;
312
+ // Entry stage
313
+ const contentTypeHasReviewWorkflow = schema?.hasReviewWorkflow ?? false;
314
+ const requiredStage = schema?.stageRequiredToPublish;
315
+ const entryStage = entry.strapi_stage;
316
+ if (isLoading) {
317
+ return null;
318
+ }
319
+ return /*#__PURE__*/ jsxs(Popover.Root, {
320
+ children: [
321
+ /*#__PURE__*/ jsx(EntryStatusTrigger, {
322
+ action: action,
323
+ status: status,
324
+ hasErrors: hasErrors,
325
+ requiredStage: requiredStage,
326
+ entryStage: entryStage
327
+ }),
328
+ /*#__PURE__*/ jsx(Popover.Content, {
329
+ children: /*#__PURE__*/ jsxs(StyledPopoverFlex, {
330
+ direction: "column",
331
+ children: [
332
+ /*#__PURE__*/ jsx(FieldsValidation, {
333
+ hasErrors: hasErrors,
334
+ errors: errors,
335
+ contentTypeUid: schema?.uid,
336
+ kind: schema?.kind,
337
+ documentId: entry.documentId,
338
+ locale: entry.locale
339
+ }),
340
+ /*#__PURE__*/ jsx(ReviewStageValidation, {
341
+ contentTypeHasReviewWorkflow: contentTypeHasReviewWorkflow,
342
+ requiredStage: requiredStage,
343
+ entryStage: entryStage
344
+ })
345
+ ]
346
+ })
347
+ })
348
+ ]
349
+ });
350
+ };
351
+
352
+ const intervals = [
353
+ 'years',
354
+ 'months',
355
+ 'days',
356
+ 'hours',
357
+ 'minutes',
358
+ 'seconds'
359
+ ];
360
+ /**
361
+ * Displays the relative time between a given timestamp and the current time.
362
+ * You can display a custom message for given time intervals by passing an array of custom intervals.
363
+ *
364
+ * @example
365
+ * ```jsx
366
+ * <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
367
+ * <RelativeTime
368
+ * timestamp={new Date('2021-01-01')}
369
+ * customIntervals={[
370
+ * { unit: 'hours', threshold: 1, text: 'last hour' },
371
+ * ]}
372
+ * ```
373
+ */ const RelativeTime$1 = /*#__PURE__*/ React.forwardRef(({ timestamp, customIntervals = [], ...restProps }, forwardedRef)=>{
374
+ const { formatRelativeTime, formatDate, formatTime } = useIntl();
375
+ /**
376
+ * TODO: make this auto-update, like a clock.
377
+ */ const interval = intervalToDuration({
378
+ start: timestamp,
379
+ end: Date.now()
380
+ });
381
+ const unit = intervals.find((intervalUnit)=>{
382
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
383
+ });
384
+ const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
385
+ // Display custom text if interval is less than the threshold
386
+ const customInterval = customIntervals.find((custom)=>interval[custom.unit] < custom.threshold);
387
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, {
388
+ numeric: 'auto'
389
+ });
390
+ return /*#__PURE__*/ jsx("time", {
391
+ ref: forwardedRef,
392
+ dateTime: timestamp.toISOString(),
393
+ role: "time",
394
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
395
+ ...restProps,
396
+ children: displayText
397
+ });
398
+ });
399
+
400
+ const ReleaseModal = ({ handleClose, open, handleSubmit, initialValues, isLoading = false })=>{
401
+ const { formatMessage } = useIntl();
402
+ const { pathname } = useLocation();
403
+ const isCreatingRelease = pathname === `/plugins/${pluginId}`;
404
+ // Set default first timezone from the list if no system timezone detected
405
+ const { timezoneList, systemTimezone = {
406
+ value: 'UTC+00:00-Africa/Abidjan '
407
+ } } = getTimezones(initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date());
408
+ /**
409
+ * Generate scheduled time using selected date, time and timezone
410
+ */ const getScheduledTimestamp = (values)=>{
411
+ const { date, time, timezone } = values;
412
+ if (!date || !time || !timezone) return null;
413
+ const timezoneWithoutOffset = timezone.split('&')[1];
414
+ return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
415
+ };
416
+ /**
417
+ * Get timezone with offset to show the selected value in the dropdown
418
+ */ const getTimezoneWithOffset = ()=>{
419
+ const currentTimezone = timezoneList.find((timezone)=>timezone.value.split('&')[1] === initialValues.timezone);
420
+ return currentTimezone?.value || systemTimezone.value;
421
+ };
422
+ return /*#__PURE__*/ jsx(Modal.Root, {
423
+ open: open,
424
+ onOpenChange: handleClose,
425
+ children: /*#__PURE__*/ jsxs(Modal.Content, {
426
+ children: [
427
+ /*#__PURE__*/ jsx(Modal.Header, {
428
+ children: /*#__PURE__*/ jsx(Modal.Title, {
429
+ children: formatMessage({
430
+ id: 'content-releases.modal.title',
431
+ defaultMessage: '{isCreatingRelease, select, true {New release} other {Edit release}}'
432
+ }, {
433
+ isCreatingRelease: isCreatingRelease
434
+ })
435
+ })
436
+ }),
437
+ /*#__PURE__*/ jsx(Formik, {
438
+ onSubmit: (values)=>{
439
+ handleSubmit({
440
+ ...values,
441
+ timezone: values.timezone ? values.timezone.split('&')[1] : null,
442
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
443
+ });
444
+ },
445
+ initialValues: {
446
+ ...initialValues,
447
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
448
+ },
449
+ validationSchema: RELEASE_SCHEMA,
450
+ validateOnChange: false,
451
+ children: ({ values, errors, handleChange, setFieldValue })=>{
452
+ return /*#__PURE__*/ jsxs(Form, {
453
+ children: [
454
+ /*#__PURE__*/ jsx(Modal.Body, {
455
+ children: /*#__PURE__*/ jsxs(Flex, {
456
+ direction: "column",
457
+ alignItems: "stretch",
458
+ gap: 6,
459
+ children: [
460
+ /*#__PURE__*/ jsxs(Field.Root, {
461
+ name: "name",
462
+ error: errors.name && formatMessage({
463
+ id: errors.name,
464
+ defaultMessage: errors.name
465
+ }),
466
+ required: true,
467
+ children: [
468
+ /*#__PURE__*/ jsx(Field.Label, {
469
+ children: formatMessage({
470
+ id: 'content-releases.modal.form.input.label.release-name',
471
+ defaultMessage: 'Name'
472
+ })
473
+ }),
474
+ /*#__PURE__*/ jsx(TextInput, {
475
+ value: values.name,
476
+ onChange: handleChange
477
+ }),
478
+ /*#__PURE__*/ jsx(Field.Error, {})
479
+ ]
480
+ }),
481
+ /*#__PURE__*/ jsx(Box, {
482
+ width: "max-content",
483
+ children: /*#__PURE__*/ jsx(Checkbox, {
484
+ name: "isScheduled",
485
+ checked: values.isScheduled,
486
+ onCheckedChange: (checked)=>{
487
+ setFieldValue('isScheduled', checked);
488
+ if (!checked) {
489
+ // Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB
490
+ setFieldValue('date', null);
491
+ setFieldValue('time', '');
492
+ setFieldValue('timezone', null);
493
+ } else {
494
+ // On ticking back schedule release date, time and timezone should be restored to the initial state
495
+ setFieldValue('date', initialValues.date);
496
+ setFieldValue('time', initialValues.time);
497
+ setFieldValue('timezone', initialValues.timezone ?? systemTimezone?.value);
498
+ }
499
+ },
500
+ children: /*#__PURE__*/ jsx(Typography, {
501
+ textColor: values.isScheduled ? 'primary600' : 'neutral800',
502
+ fontWeight: values.isScheduled ? 'semiBold' : 'regular',
503
+ children: formatMessage({
504
+ id: 'modal.form.input.label.schedule-release',
505
+ defaultMessage: 'Schedule release'
506
+ })
507
+ })
508
+ })
509
+ }),
510
+ values.isScheduled && /*#__PURE__*/ jsxs(Fragment, {
511
+ children: [
512
+ /*#__PURE__*/ jsxs(Flex, {
513
+ gap: 4,
514
+ alignItems: "start",
515
+ children: [
516
+ /*#__PURE__*/ jsx(Box, {
517
+ width: "100%",
518
+ children: /*#__PURE__*/ jsxs(Field.Root, {
519
+ name: "date",
520
+ error: errors.date && formatMessage({
521
+ id: errors.date,
522
+ defaultMessage: errors.date
523
+ }),
524
+ required: true,
525
+ children: [
526
+ /*#__PURE__*/ jsx(Field.Label, {
527
+ children: formatMessage({
528
+ id: 'content-releases.modal.form.input.label.date',
529
+ defaultMessage: 'Date'
530
+ })
531
+ }),
532
+ /*#__PURE__*/ jsx(DatePicker, {
533
+ onChange: (date)=>{
534
+ const isoFormatDate = date ? formatISO(date, {
535
+ representation: 'date'
536
+ }) : null;
537
+ setFieldValue('date', isoFormatDate);
538
+ },
539
+ clearLabel: formatMessage({
540
+ id: 'content-releases.modal.form.input.clearLabel',
541
+ defaultMessage: 'Clear'
542
+ }),
543
+ onClear: ()=>{
544
+ setFieldValue('date', null);
545
+ },
546
+ value: values.date ? new Date(values.date) : new Date(),
547
+ minDate: utcToZonedTime(new Date(), values.timezone.split('&')[1])
548
+ }),
549
+ /*#__PURE__*/ jsx(Field.Error, {})
550
+ ]
551
+ })
552
+ }),
553
+ /*#__PURE__*/ jsx(Box, {
554
+ width: "100%",
555
+ children: /*#__PURE__*/ jsxs(Field.Root, {
556
+ name: "time",
557
+ error: errors.time && formatMessage({
558
+ id: errors.time,
559
+ defaultMessage: errors.time
560
+ }),
561
+ required: true,
562
+ children: [
563
+ /*#__PURE__*/ jsx(Field.Label, {
564
+ children: formatMessage({
565
+ id: 'content-releases.modal.form.input.label.time',
566
+ defaultMessage: 'Time'
567
+ })
568
+ }),
569
+ /*#__PURE__*/ jsx(TimePicker, {
570
+ onChange: (time)=>{
571
+ setFieldValue('time', time);
572
+ },
573
+ clearLabel: formatMessage({
574
+ id: 'content-releases.modal.form.input.clearLabel',
575
+ defaultMessage: 'Clear'
576
+ }),
577
+ onClear: ()=>{
578
+ setFieldValue('time', '');
579
+ },
580
+ value: values.time || undefined
581
+ }),
582
+ /*#__PURE__*/ jsx(Field.Error, {})
583
+ ]
584
+ })
585
+ })
586
+ ]
587
+ }),
588
+ /*#__PURE__*/ jsx(TimezoneComponent, {
589
+ timezoneOptions: timezoneList
590
+ })
591
+ ]
592
+ })
593
+ ]
594
+ })
595
+ }),
596
+ /*#__PURE__*/ jsxs(Modal.Footer, {
597
+ children: [
598
+ /*#__PURE__*/ jsx(Modal.Close, {
599
+ children: /*#__PURE__*/ jsx(Button, {
600
+ variant: "tertiary",
601
+ name: "cancel",
602
+ children: formatMessage({
603
+ id: 'cancel',
604
+ defaultMessage: 'Cancel'
605
+ })
606
+ })
607
+ }),
608
+ /*#__PURE__*/ jsx(Button, {
609
+ name: "submit",
610
+ loading: isLoading,
611
+ type: "submit",
612
+ children: formatMessage({
613
+ id: 'content-releases.modal.form.button.submit',
614
+ defaultMessage: '{isCreatingRelease, select, true {Continue} other {Save}}'
615
+ }, {
616
+ isCreatingRelease: isCreatingRelease
617
+ })
618
+ })
619
+ ]
620
+ })
621
+ ]
622
+ });
623
+ }
624
+ })
625
+ ]
626
+ })
627
+ });
628
+ };
629
+ const TimezoneComponent = ({ timezoneOptions })=>{
630
+ const { values, errors, setFieldValue } = useFormikContext();
631
+ const { formatMessage } = useIntl();
632
+ const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
633
+ React.useEffect(()=>{
634
+ if (values.date) {
635
+ // Update the timezone offset which varies with DST based on the date selected
636
+ const { timezoneList } = getTimezones(new Date(values.date));
637
+ setTimezoneList(timezoneList);
638
+ const updatedTimezone = values.timezone && timezoneList.find((tz)=>tz.value.split('&')[1] === values.timezone.split('&')[1]);
639
+ if (updatedTimezone) {
640
+ setFieldValue('timezone', updatedTimezone.value);
641
+ }
642
+ }
643
+ }, [
644
+ setFieldValue,
645
+ values.date,
646
+ values.timezone
647
+ ]);
648
+ return /*#__PURE__*/ jsxs(Field.Root, {
649
+ name: "timezone",
650
+ error: errors.timezone && formatMessage({
651
+ id: errors.timezone,
652
+ defaultMessage: errors.timezone
653
+ }),
654
+ required: true,
655
+ children: [
656
+ /*#__PURE__*/ jsx(Field.Label, {
657
+ children: formatMessage({
658
+ id: 'content-releases.modal.form.input.label.timezone',
659
+ defaultMessage: 'Timezone'
660
+ })
661
+ }),
662
+ /*#__PURE__*/ jsx(Combobox, {
663
+ autocomplete: {
664
+ type: 'list',
665
+ filter: 'contains'
666
+ },
667
+ value: values.timezone || undefined,
668
+ textValue: values.timezone ? values.timezone.replace(/&/, ' ') : undefined,
669
+ onChange: (timezone)=>{
670
+ setFieldValue('timezone', timezone);
671
+ },
672
+ onTextValueChange: (timezone)=>{
673
+ setFieldValue('timezone', timezone);
674
+ },
675
+ onClear: ()=>{
676
+ setFieldValue('timezone', '');
677
+ },
678
+ children: timezoneList.map((timezone)=>/*#__PURE__*/ jsx(ComboboxOption, {
679
+ value: timezone.value,
680
+ children: timezone.value.replace(/&/, ' ')
681
+ }, timezone.value))
682
+ }),
683
+ /*#__PURE__*/ jsx(Field.Error, {})
684
+ ]
685
+ });
686
+ };
687
+
688
+ const useTypedDispatch = useDispatch;
689
+
690
+ const isBaseQueryError = (error)=>{
691
+ return typeof error !== 'undefined' && error.name !== undefined;
692
+ };
693
+
694
+ const LinkCard = styled(Link$1)`
695
+ display: block;
696
+ `;
697
+ const RelativeTime = styled(RelativeTime$1)`
698
+ display: inline-block;
699
+ &::first-letter {
700
+ text-transform: uppercase;
701
+ }
702
+ `;
703
+ const getBadgeProps = (status)=>{
704
+ let color;
705
+ switch(status){
706
+ case 'ready':
707
+ color = 'success';
708
+ break;
709
+ case 'blocked':
710
+ color = 'warning';
711
+ break;
712
+ case 'failed':
713
+ color = 'danger';
714
+ break;
715
+ case 'done':
716
+ color = 'primary';
717
+ break;
718
+ case 'empty':
719
+ default:
720
+ color = 'neutral';
721
+ }
722
+ return {
723
+ textColor: `${color}600`,
724
+ backgroundColor: `${color}100`,
725
+ borderColor: `${color}200`
726
+ };
727
+ };
728
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false })=>{
729
+ const { formatMessage } = useIntl();
730
+ if (isError) {
731
+ return /*#__PURE__*/ jsx(Page.Error, {});
732
+ }
733
+ if (releases?.length === 0) {
734
+ return /*#__PURE__*/ jsx(EmptyStateLayout, {
735
+ content: formatMessage({
736
+ id: 'content-releases.page.Releases.tab.emptyEntries',
737
+ defaultMessage: 'No releases'
738
+ }, {
739
+ target: sectionTitle
740
+ }),
741
+ icon: /*#__PURE__*/ jsx(EmptyDocuments, {
742
+ width: "16rem"
743
+ })
744
+ });
745
+ }
746
+ return /*#__PURE__*/ jsx(Grid.Root, {
747
+ gap: 4,
748
+ children: releases.map(({ id, name, scheduledAt, status })=>/*#__PURE__*/ jsx(Grid.Item, {
749
+ col: 3,
750
+ s: 6,
751
+ xs: 12,
752
+ direction: "column",
753
+ alignItems: "stretch",
754
+ children: /*#__PURE__*/ jsx(LinkCard, {
755
+ tag: NavLink,
756
+ to: `${id}`,
757
+ isExternal: false,
758
+ children: /*#__PURE__*/ jsxs(Flex, {
759
+ direction: "column",
760
+ justifyContent: "space-between",
761
+ padding: 4,
762
+ hasRadius: true,
763
+ background: "neutral0",
764
+ shadow: "tableShadow",
765
+ height: "100%",
766
+ width: "100%",
767
+ alignItems: "start",
768
+ gap: 4,
769
+ children: [
770
+ /*#__PURE__*/ jsxs(Flex, {
771
+ direction: "column",
772
+ alignItems: "start",
773
+ gap: 1,
774
+ children: [
775
+ /*#__PURE__*/ jsx(Typography, {
776
+ textColor: "neutral800",
777
+ tag: "h3",
778
+ variant: "delta",
779
+ fontWeight: "bold",
780
+ children: name
781
+ }),
782
+ /*#__PURE__*/ jsx(Typography, {
783
+ variant: "pi",
784
+ textColor: "neutral600",
785
+ children: scheduledAt ? /*#__PURE__*/ jsx(RelativeTime, {
786
+ timestamp: new Date(scheduledAt)
787
+ }) : formatMessage({
788
+ id: 'content-releases.pages.Releases.not-scheduled',
789
+ defaultMessage: 'Not scheduled'
790
+ })
791
+ })
792
+ ]
793
+ }),
794
+ /*#__PURE__*/ jsx(Badge, {
795
+ ...getBadgeProps(status),
796
+ children: status
797
+ })
798
+ ]
799
+ })
800
+ })
801
+ }, id))
802
+ });
803
+ };
804
+ /* -------------------------------------------------------------------------------------------------
805
+ * ReleasesPage
806
+ * -----------------------------------------------------------------------------------------------*/ const StyledAlert = styled(Alert)`
807
+ button {
808
+ display: none;
809
+ }
810
+ p + div {
811
+ margin-left: auto;
812
+ }
813
+ `;
814
+ const INITIAL_FORM_VALUES = {
815
+ name: '',
816
+ date: format(new Date(), 'yyyy-MM-dd'),
817
+ time: '',
818
+ isScheduled: true,
819
+ scheduledAt: null,
820
+ timezone: null
821
+ };
822
+ const ReleasesPage = ()=>{
823
+ const location = useLocation();
824
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
825
+ const { toggleNotification } = useNotification();
826
+ const { formatMessage } = useIntl();
827
+ const navigate = useNavigate();
828
+ const { formatAPIError } = useAPIErrorHandler();
829
+ const [{ query }, setQuery] = useQueryParams();
830
+ const response = useGetReleasesQuery(query);
831
+ const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery();
832
+ const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
833
+ const { getFeature } = useLicenseLimits();
834
+ const { maximumReleases = 3 } = getFeature('cms-content-releases');
835
+ const { trackUsage } = useTracking();
836
+ const { allowedActions: { canCreate } } = useRBAC(PERMISSIONS);
837
+ const { isLoading: isLoadingReleases, isSuccess, isError } = response;
838
+ const activeTab = response?.currentData?.meta?.activeTab || 'pending';
839
+ // Check if we have some errors and show a notification to the user to explain the error
840
+ React.useEffect(()=>{
841
+ if (location?.state?.errors) {
842
+ toggleNotification({
843
+ type: 'danger',
844
+ title: formatMessage({
845
+ id: 'content-releases.pages.Releases.notification.error.title',
846
+ defaultMessage: 'Your request could not be processed.'
847
+ }),
848
+ message: formatMessage({
849
+ id: 'content-releases.pages.Releases.notification.error.message',
850
+ defaultMessage: 'Please try again or open another release.'
851
+ })
852
+ });
853
+ navigate('', {
854
+ replace: true,
855
+ state: null
856
+ });
857
+ }
858
+ }, [
859
+ formatMessage,
860
+ location?.state?.errors,
861
+ navigate,
862
+ toggleNotification
863
+ ]);
864
+ const toggleAddReleaseModal = ()=>{
865
+ setReleaseModalShown((prev)=>!prev);
866
+ };
867
+ if (isLoadingReleases || isLoadingSettings) {
868
+ return /*#__PURE__*/ jsx(Page.Loading, {});
869
+ }
870
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
871
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
872
+ const handleTabChange = (tabValue)=>{
873
+ setQuery({
874
+ ...query,
875
+ page: 1,
876
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
877
+ filters: {
878
+ releasedAt: {
879
+ $notNull: tabValue !== 'pending'
880
+ }
881
+ }
882
+ });
883
+ };
884
+ const handleAddRelease = async ({ name, scheduledAt, timezone })=>{
885
+ const response = await createRelease({
886
+ name,
887
+ scheduledAt,
888
+ timezone
889
+ });
890
+ if ('data' in response) {
891
+ // When the response returns an object with 'data', handle success
892
+ toggleNotification({
893
+ type: 'success',
894
+ message: formatMessage({
895
+ id: 'content-releases.modal.release-created-notification-success',
896
+ defaultMessage: 'Release created.'
897
+ })
898
+ });
899
+ trackUsage('didCreateRelease');
900
+ navigate(response.data.data.id.toString());
901
+ } else if (isFetchError(response.error)) {
902
+ // When the response returns an object with 'error', handle fetch error
903
+ toggleNotification({
904
+ type: 'danger',
905
+ message: formatAPIError(response.error)
906
+ });
907
+ } else {
908
+ // Otherwise, the response returns an object with 'error', handle a generic error
909
+ toggleNotification({
910
+ type: 'danger',
911
+ message: formatMessage({
912
+ id: 'notification.error',
913
+ defaultMessage: 'An error occurred'
914
+ })
915
+ });
916
+ }
917
+ };
918
+ return /*#__PURE__*/ jsxs(Main, {
919
+ "aria-busy": isLoadingReleases || isLoadingSettings,
920
+ children: [
921
+ /*#__PURE__*/ jsx(Layouts.Header, {
922
+ title: formatMessage({
923
+ id: 'content-releases.pages.Releases.title',
924
+ defaultMessage: 'Releases'
925
+ }),
926
+ subtitle: formatMessage({
927
+ id: 'content-releases.pages.Releases.header-subtitle',
928
+ defaultMessage: 'Create and manage content updates'
929
+ }),
930
+ primaryAction: canCreate ? /*#__PURE__*/ jsx(Button, {
931
+ startIcon: /*#__PURE__*/ jsx(Plus, {}),
932
+ onClick: toggleAddReleaseModal,
933
+ disabled: hasReachedMaximumPendingReleases,
934
+ children: formatMessage({
935
+ id: 'content-releases.header.actions.add-release',
936
+ defaultMessage: 'New release'
937
+ })
938
+ }) : null
939
+ }),
940
+ /*#__PURE__*/ jsx(Layouts.Content, {
941
+ children: /*#__PURE__*/ jsxs(Fragment, {
942
+ children: [
943
+ hasReachedMaximumPendingReleases && /*#__PURE__*/ jsx(StyledAlert, {
944
+ marginBottom: 6,
945
+ action: /*#__PURE__*/ jsx(Link$1, {
946
+ href: "https://strapi.io/pricing-cloud",
947
+ isExternal: true,
948
+ children: formatMessage({
949
+ id: 'content-releases.pages.Releases.max-limit-reached.action',
950
+ defaultMessage: 'Explore plans'
951
+ })
952
+ }),
953
+ title: formatMessage({
954
+ id: 'content-releases.pages.Releases.max-limit-reached.title',
955
+ defaultMessage: 'You have reached the {number} pending {number, plural, one {release} other {releases}} limit.'
956
+ }, {
957
+ number: maximumReleases
958
+ }),
959
+ onClose: ()=>{},
960
+ closeLabel: "",
961
+ children: formatMessage({
962
+ id: 'content-releases.pages.Releases.max-limit-reached.message',
963
+ defaultMessage: 'Upgrade to manage an unlimited number of releases.'
964
+ })
965
+ }),
966
+ /*#__PURE__*/ jsxs(Tabs.Root, {
967
+ variant: "simple",
968
+ onValueChange: handleTabChange,
969
+ value: activeTab,
970
+ children: [
971
+ /*#__PURE__*/ jsxs(Box, {
972
+ paddingBottom: 8,
973
+ children: [
974
+ /*#__PURE__*/ jsxs(Tabs.List, {
975
+ "aria-label": formatMessage({
976
+ id: 'content-releases.pages.Releases.tab-group.label',
977
+ defaultMessage: 'Releases list'
978
+ }),
979
+ children: [
980
+ /*#__PURE__*/ jsx(Tabs.Trigger, {
981
+ value: "pending",
982
+ children: formatMessage({
983
+ id: 'content-releases.pages.Releases.tab.pending',
984
+ defaultMessage: 'Pending ({count})'
985
+ }, {
986
+ count: totalPendingReleases
987
+ })
988
+ }),
989
+ /*#__PURE__*/ jsx(Tabs.Trigger, {
990
+ value: "done",
991
+ children: formatMessage({
992
+ id: 'content-releases.pages.Releases.tab.done',
993
+ defaultMessage: 'Done'
994
+ })
995
+ })
996
+ ]
997
+ }),
998
+ /*#__PURE__*/ jsx(Divider, {})
999
+ ]
1000
+ }),
1001
+ /*#__PURE__*/ jsx(Tabs.Content, {
1002
+ value: "pending",
1003
+ children: /*#__PURE__*/ jsx(ReleasesGrid, {
1004
+ sectionTitle: "pending",
1005
+ releases: response?.currentData?.data,
1006
+ isError: isError
1007
+ })
1008
+ }),
1009
+ /*#__PURE__*/ jsx(Tabs.Content, {
1010
+ value: "done",
1011
+ children: /*#__PURE__*/ jsx(ReleasesGrid, {
1012
+ sectionTitle: "done",
1013
+ releases: response?.currentData?.data,
1014
+ isError: isError
1015
+ })
1016
+ })
1017
+ ]
1018
+ }),
1019
+ /*#__PURE__*/ jsxs(Pagination.Root, {
1020
+ ...response?.currentData?.meta?.pagination,
1021
+ defaultPageSize: response?.currentData?.meta?.pagination?.pageSize,
1022
+ children: [
1023
+ /*#__PURE__*/ jsx(Pagination.PageSize, {
1024
+ options: [
1025
+ '8',
1026
+ '16',
1027
+ '32',
1028
+ '64'
1029
+ ]
1030
+ }),
1031
+ /*#__PURE__*/ jsx(Pagination.Links, {})
1032
+ ]
1033
+ })
1034
+ ]
1035
+ })
1036
+ }),
1037
+ /*#__PURE__*/ jsx(ReleaseModal, {
1038
+ open: releaseModalShown,
1039
+ handleClose: toggleAddReleaseModal,
1040
+ handleSubmit: handleAddRelease,
1041
+ isLoading: isSubmittingForm,
1042
+ initialValues: {
1043
+ ...INITIAL_FORM_VALUES,
1044
+ timezone: data?.data.defaultTimezone ? data.data.defaultTimezone.split('&')[1] : null
1045
+ }
1046
+ })
1047
+ ]
1048
+ });
1049
+ };
1050
+
1051
+ /* -------------------------------------------------------------------------------------------------
1052
+ * ReleaseDetailsLayout
1053
+ * -----------------------------------------------------------------------------------------------*/ const ReleaseInfoWrapper = styled(Flex)`
1054
+ align-self: stretch;
1055
+ border-bottom-right-radius: ${({ theme })=>theme.borderRadius};
1056
+ border-bottom-left-radius: ${({ theme })=>theme.borderRadius};
1057
+ border-top: 1px solid ${({ theme })=>theme.colors.neutral150};
1058
+ `;
1059
+ const StyledMenuItem = styled(MenuItem)`
1060
+ svg path {
1061
+ fill: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
1062
+ }
1063
+ span {
1064
+ color: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
1065
+ }
1066
+
1067
+ &:hover {
1068
+ background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
1069
+ }
1070
+ `;
1071
+ const PencilIcon = styled(Pencil)`
1072
+ width: ${({ theme })=>theme.spaces[4]};
1073
+ height: ${({ theme })=>theme.spaces[4]};
1074
+ path {
1075
+ fill: ${({ theme })=>theme.colors.neutral600};
1076
+ }
1077
+ `;
1078
+ const TrashIcon = styled(Trash)`
1079
+ width: ${({ theme })=>theme.spaces[4]};
1080
+ height: ${({ theme })=>theme.spaces[4]};
1081
+ path {
1082
+ fill: ${({ theme })=>theme.colors.danger600};
1083
+ }
1084
+ `;
1085
+ const ReleaseDetailsLayout = ({ toggleEditReleaseModal, toggleWarningSubmit, children })=>{
1086
+ const { formatMessage, formatDate, formatTime } = useIntl();
1087
+ const { releaseId } = useParams();
1088
+ const { data, isLoading: isLoadingDetails, error } = useGetReleaseQuery({
1089
+ id: releaseId
1090
+ }, {
1091
+ skip: !releaseId
1092
+ });
1093
+ const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
1094
+ const { toggleNotification } = useNotification();
1095
+ const { formatAPIError } = useAPIErrorHandler();
1096
+ const { allowedActions } = useRBAC(PERMISSIONS);
1097
+ const { canUpdate, canDelete, canPublish } = allowedActions;
1098
+ const dispatch = useTypedDispatch();
1099
+ const { trackUsage } = useTracking();
1100
+ const release = data?.data;
1101
+ const handlePublishRelease = (id)=>async ()=>{
1102
+ const response = await publishRelease({
1103
+ id
1104
+ });
1105
+ if ('data' in response) {
1106
+ // When the response returns an object with 'data', handle success
1107
+ toggleNotification({
1108
+ type: 'success',
1109
+ message: formatMessage({
1110
+ id: 'content-releases.pages.ReleaseDetails.publish-notification-success',
1111
+ defaultMessage: 'Release was published successfully.'
1112
+ })
1113
+ });
1114
+ const { totalEntries, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
1115
+ trackUsage('didPublishRelease', {
1116
+ totalEntries,
1117
+ totalPublishedEntries,
1118
+ totalUnpublishedEntries
1119
+ });
1120
+ } else if (isFetchError(response.error)) {
1121
+ // When the response returns an object with 'error', handle fetch error
1122
+ toggleNotification({
1123
+ type: 'danger',
1124
+ message: formatAPIError(response.error)
1125
+ });
1126
+ } else {
1127
+ // Otherwise, the response returns an object with 'error', handle a generic error
1128
+ toggleNotification({
1129
+ type: 'danger',
1130
+ message: formatMessage({
1131
+ id: 'notification.error',
1132
+ defaultMessage: 'An error occurred'
1133
+ })
1134
+ });
1135
+ }
1136
+ };
1137
+ const handleRefresh = ()=>{
1138
+ dispatch(releaseApi.util.invalidateTags([
1139
+ {
1140
+ type: 'ReleaseAction',
1141
+ id: 'LIST'
1142
+ },
1143
+ {
1144
+ type: 'Release',
1145
+ id: releaseId
1146
+ }
1147
+ ]));
1148
+ };
1149
+ const getCreatedByUser = ()=>{
1150
+ if (!release?.createdBy) {
1151
+ return null;
1152
+ }
1153
+ // Favor the username
1154
+ if (release.createdBy.username) {
1155
+ return release.createdBy.username;
1156
+ }
1157
+ // Firstname may not exist if created with SSO
1158
+ if (release.createdBy.firstname) {
1159
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ''}`.trim();
1160
+ }
1161
+ // All users must have at least an email
1162
+ return release.createdBy.email;
1163
+ };
1164
+ if (isLoadingDetails) {
1165
+ return /*#__PURE__*/ jsx(Page.Loading, {});
1166
+ }
1167
+ if (isBaseQueryError(error) && 'code' in error || !release) {
1168
+ return /*#__PURE__*/ jsx(Navigate, {
1169
+ to: "..",
1170
+ state: {
1171
+ errors: [
1172
+ {
1173
+ // @ts-expect-error – TODO: fix this weird error flow
1174
+ code: error?.code
1175
+ }
1176
+ ]
1177
+ }
1178
+ });
1179
+ }
1180
+ const totalEntries = release.actions.meta.count || 0;
1181
+ const hasCreatedByUser = Boolean(getCreatedByUser());
1182
+ const isScheduled = release.scheduledAt && release.timezone;
1183
+ const numberOfEntriesText = formatMessage({
1184
+ id: 'content-releases.pages.Details.header-subtitle',
1185
+ defaultMessage: '{number, plural, =0 {No entries} one {# entry} other {# entries}}'
1186
+ }, {
1187
+ number: totalEntries
1188
+ });
1189
+ const scheduledText = isScheduled ? formatMessage({
1190
+ id: 'content-releases.pages.ReleaseDetails.header-subtitle.scheduled',
1191
+ defaultMessage: 'Scheduled for {date} at {time} ({offset})'
1192
+ }, {
1193
+ date: formatDate(new Date(release.scheduledAt), {
1194
+ weekday: 'long',
1195
+ day: 'numeric',
1196
+ month: 'long',
1197
+ year: 'numeric',
1198
+ timeZone: release.timezone
1199
+ }),
1200
+ time: formatTime(new Date(release.scheduledAt), {
1201
+ timeZone: release.timezone,
1202
+ hourCycle: 'h23'
1203
+ }),
1204
+ offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
1205
+ }) : '';
1206
+ return /*#__PURE__*/ jsxs(Main, {
1207
+ "aria-busy": isLoadingDetails,
1208
+ children: [
1209
+ /*#__PURE__*/ jsx(Layouts.Header, {
1210
+ title: release.name,
1211
+ subtitle: /*#__PURE__*/ jsxs(Flex, {
1212
+ gap: 2,
1213
+ lineHeight: 6,
1214
+ children: [
1215
+ /*#__PURE__*/ jsx(Typography, {
1216
+ textColor: "neutral600",
1217
+ variant: "epsilon",
1218
+ children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : '')
1219
+ }),
1220
+ /*#__PURE__*/ jsx(Badge, {
1221
+ ...getBadgeProps(release.status),
1222
+ children: release.status
1223
+ })
1224
+ ]
1225
+ }),
1226
+ navigationAction: /*#__PURE__*/ jsx(BackButton, {
1227
+ fallback: ".."
1228
+ }),
1229
+ primaryAction: !release.releasedAt && /*#__PURE__*/ jsxs(Flex, {
1230
+ gap: 2,
1231
+ children: [
1232
+ /*#__PURE__*/ jsxs(SimpleMenuButton, {
1233
+ label: /*#__PURE__*/ jsx(More, {}),
1234
+ variant: "tertiary",
1235
+ endIcon: null,
1236
+ paddingLeft: "7px",
1237
+ paddingRight: "7px",
1238
+ "aria-label": formatMessage({
1239
+ id: 'content-releases.header.actions.open-release-actions',
1240
+ defaultMessage: 'Release edit and delete menu'
1241
+ }),
1242
+ popoverPlacement: "bottom-end",
1243
+ children: [
1244
+ /*#__PURE__*/ jsx(StyledMenuItem, {
1245
+ disabled: !canUpdate,
1246
+ onSelect: toggleEditReleaseModal,
1247
+ children: /*#__PURE__*/ jsxs(Flex, {
1248
+ alignItems: "center",
1249
+ gap: 2,
1250
+ hasRadius: true,
1251
+ width: "100%",
1252
+ children: [
1253
+ /*#__PURE__*/ jsx(PencilIcon, {}),
1254
+ /*#__PURE__*/ jsx(Typography, {
1255
+ ellipsis: true,
1256
+ children: formatMessage({
1257
+ id: 'content-releases.header.actions.edit',
1258
+ defaultMessage: 'Edit'
1259
+ })
1260
+ })
1261
+ ]
1262
+ })
1263
+ }),
1264
+ /*#__PURE__*/ jsx(StyledMenuItem, {
1265
+ disabled: !canDelete,
1266
+ onSelect: toggleWarningSubmit,
1267
+ $variant: "danger",
1268
+ children: /*#__PURE__*/ jsxs(Flex, {
1269
+ alignItems: "center",
1270
+ gap: 2,
1271
+ hasRadius: true,
1272
+ width: "100%",
1273
+ children: [
1274
+ /*#__PURE__*/ jsx(TrashIcon, {}),
1275
+ /*#__PURE__*/ jsx(Typography, {
1276
+ ellipsis: true,
1277
+ textColor: "danger600",
1278
+ children: formatMessage({
1279
+ id: 'content-releases.header.actions.delete',
1280
+ defaultMessage: 'Delete'
1281
+ })
1282
+ })
1283
+ ]
1284
+ })
1285
+ }),
1286
+ /*#__PURE__*/ jsxs(ReleaseInfoWrapper, {
1287
+ direction: "column",
1288
+ justifyContent: "center",
1289
+ alignItems: "flex-start",
1290
+ gap: 1,
1291
+ padding: 4,
1292
+ children: [
1293
+ /*#__PURE__*/ jsx(Typography, {
1294
+ variant: "pi",
1295
+ fontWeight: "bold",
1296
+ children: formatMessage({
1297
+ id: 'content-releases.header.actions.created',
1298
+ defaultMessage: 'Created'
1299
+ })
1300
+ }),
1301
+ /*#__PURE__*/ jsxs(Typography, {
1302
+ variant: "pi",
1303
+ color: "neutral300",
1304
+ children: [
1305
+ /*#__PURE__*/ jsx(RelativeTime$1, {
1306
+ timestamp: new Date(release.createdAt)
1307
+ }),
1308
+ formatMessage({
1309
+ id: 'content-releases.header.actions.created.description',
1310
+ defaultMessage: '{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}'
1311
+ }, {
1312
+ createdBy: getCreatedByUser(),
1313
+ hasCreatedByUser
1314
+ })
1315
+ ]
1316
+ })
1317
+ ]
1318
+ })
1319
+ ]
1320
+ }),
1321
+ /*#__PURE__*/ jsx(Button, {
1322
+ size: "S",
1323
+ variant: "tertiary",
1324
+ onClick: handleRefresh,
1325
+ children: formatMessage({
1326
+ id: 'content-releases.header.actions.refresh',
1327
+ defaultMessage: 'Refresh'
1328
+ })
1329
+ }),
1330
+ canPublish ? /*#__PURE__*/ jsx(Button, {
1331
+ size: "S",
1332
+ variant: "default",
1333
+ onClick: handlePublishRelease(release.id.toString()),
1334
+ loading: isPublishing,
1335
+ disabled: release.actions.meta.count === 0,
1336
+ children: formatMessage({
1337
+ id: 'content-releases.header.actions.publish',
1338
+ defaultMessage: 'Publish'
1339
+ })
1340
+ }) : null
1341
+ ]
1342
+ })
1343
+ }),
1344
+ children
1345
+ ]
1346
+ });
1347
+ };
1348
+ const SimpleMenuButton = styled(SimpleMenu)`
1349
+ & > span {
1350
+ display: flex;
1351
+ }
1352
+ `;
1353
+ /* -------------------------------------------------------------------------------------------------
1354
+ * ReleaseDetailsBody
1355
+ * -----------------------------------------------------------------------------------------------*/ const GROUP_BY_OPTIONS = [
1356
+ 'contentType',
1357
+ 'locale',
1358
+ 'action'
1359
+ ];
1360
+ const GROUP_BY_OPTIONS_NO_LOCALE = [
1361
+ 'contentType',
1362
+ 'action'
1363
+ ];
1364
+ const getGroupByOptionLabel = (value)=>{
1365
+ if (value === 'locale') {
1366
+ return {
1367
+ id: 'content-releases.pages.ReleaseDetails.groupBy.option.locales',
1368
+ defaultMessage: 'Locales'
1369
+ };
1370
+ }
1371
+ if (value === 'action') {
1372
+ return {
1373
+ id: 'content-releases.pages.ReleaseDetails.groupBy.option.actions',
1374
+ defaultMessage: 'Actions'
1375
+ };
1376
+ }
1377
+ return {
1378
+ id: 'content-releases.pages.ReleaseDetails.groupBy.option.content-type',
1379
+ defaultMessage: 'Content-Types'
1380
+ };
1381
+ };
1382
+ const ReleaseDetailsBody = ({ releaseId })=>{
1383
+ const { formatMessage } = useIntl();
1384
+ const [{ query }, setQuery] = useQueryParams();
1385
+ const { toggleNotification } = useNotification();
1386
+ const { formatAPIError } = useAPIErrorHandler();
1387
+ const { data: releaseData, isLoading: isReleaseLoading, error: releaseError } = useGetReleaseQuery({
1388
+ id: releaseId
1389
+ });
1390
+ const { allowedActions: { canUpdate } } = useRBAC(PERMISSIONS);
1391
+ const runHookWaterfall = useStrapiApp('ReleaseDetailsPage', (state)=>state.runHookWaterfall);
1392
+ // TODO: Migrated displayedHeader to v5
1393
+ const { displayedHeaders, hasI18nEnabled } = runHookWaterfall('ContentReleases/pages/ReleaseDetails/add-locale-in-releases', {
1394
+ displayedHeaders: [
1395
+ {
1396
+ label: {
1397
+ id: 'content-releases.page.ReleaseDetails.table.header.label.name',
1398
+ defaultMessage: 'name'
1399
+ },
1400
+ name: 'name'
1401
+ }
1402
+ ],
1403
+ hasI18nEnabled: false
1404
+ });
1405
+ const release = releaseData?.data;
1406
+ const selectedGroupBy = query?.groupBy || 'contentType';
1407
+ const { isLoading, isFetching, isError, data, error: releaseActionsError } = useGetReleaseActionsQuery({
1408
+ ...query,
1409
+ releaseId
1410
+ });
1411
+ const [updateReleaseAction] = useUpdateReleaseActionMutation();
1412
+ const handleChangeType = async (e, actionId, actionPath)=>{
1413
+ const response = await updateReleaseAction({
1414
+ params: {
1415
+ releaseId,
1416
+ actionId
1417
+ },
1418
+ body: {
1419
+ type: e.target.value
1420
+ },
1421
+ query,
1422
+ actionPath
1423
+ });
1424
+ if ('error' in response) {
1425
+ if (isFetchError(response.error)) {
1426
+ // When the response returns an object with 'error', handle fetch error
1427
+ toggleNotification({
1428
+ type: 'danger',
1429
+ message: formatAPIError(response.error)
1430
+ });
1431
+ } else {
1432
+ // Otherwise, the response returns an object with 'error', handle a generic error
1433
+ toggleNotification({
1434
+ type: 'danger',
1435
+ message: formatMessage({
1436
+ id: 'notification.error',
1437
+ defaultMessage: 'An error occurred'
1438
+ })
1439
+ });
1440
+ }
1441
+ }
1442
+ };
1443
+ if (isLoading || isReleaseLoading) {
1444
+ return /*#__PURE__*/ jsx(Page.Loading, {});
1445
+ }
1446
+ const releaseActions = data?.data;
1447
+ const releaseMeta = data?.meta;
1448
+ const contentTypes = releaseMeta?.contentTypes || {};
1449
+ releaseMeta?.components || {};
1450
+ if (isBaseQueryError(releaseError) || !release) {
1451
+ const errorsArray = [];
1452
+ if (releaseError && 'code' in releaseError) {
1453
+ errorsArray.push({
1454
+ code: releaseError.code
1455
+ });
1456
+ }
1457
+ if (releaseActionsError && 'code' in releaseActionsError) {
1458
+ errorsArray.push({
1459
+ code: releaseActionsError.code
1460
+ });
1461
+ }
1462
+ return /*#__PURE__*/ jsx(Navigate, {
1463
+ to: "..",
1464
+ state: {
1465
+ errors: errorsArray
1466
+ }
1467
+ });
1468
+ }
1469
+ if (isError || !releaseActions) {
1470
+ return /*#__PURE__*/ jsx(Page.Error, {});
1471
+ }
1472
+ if (Object.keys(releaseActions).length === 0) {
1473
+ return /*#__PURE__*/ jsx(Layouts.Content, {
1474
+ children: /*#__PURE__*/ jsx(EmptyStateLayout, {
1475
+ action: /*#__PURE__*/ jsx(LinkButton, {
1476
+ tag: Link,
1477
+ to: {
1478
+ pathname: '/content-manager'
1479
+ },
1480
+ style: {
1481
+ textDecoration: 'none'
1482
+ },
1483
+ variant: "secondary",
1484
+ children: formatMessage({
1485
+ id: 'content-releases.page.Details.button.openContentManager',
1486
+ defaultMessage: 'Open the Content Manager'
1487
+ })
1488
+ }),
1489
+ icon: /*#__PURE__*/ jsx(EmptyDocuments, {
1490
+ width: "16rem"
1491
+ }),
1492
+ content: formatMessage({
1493
+ id: 'content-releases.pages.Details.tab.emptyEntries',
1494
+ defaultMessage: 'This release is empty. Open the Content Manager, select an entry and add it to the release.'
1495
+ })
1496
+ })
1497
+ });
1498
+ }
1499
+ const groupByLabel = formatMessage({
1500
+ id: 'content-releases.pages.ReleaseDetails.groupBy.aria-label',
1501
+ defaultMessage: 'Group by'
1502
+ });
1503
+ const headers = [
1504
+ ...displayedHeaders,
1505
+ {
1506
+ label: {
1507
+ id: 'content-releases.page.ReleaseDetails.table.header.label.content-type',
1508
+ defaultMessage: 'content-type'
1509
+ },
1510
+ name: 'content-type'
1511
+ },
1512
+ {
1513
+ label: {
1514
+ id: 'content-releases.page.ReleaseDetails.table.header.label.action',
1515
+ defaultMessage: 'action'
1516
+ },
1517
+ name: 'action'
1518
+ },
1519
+ ...!release.releasedAt ? [
1520
+ {
1521
+ label: {
1522
+ id: 'content-releases.page.ReleaseDetails.table.header.label.status',
1523
+ defaultMessage: 'status'
1524
+ },
1525
+ name: 'status'
1526
+ }
1527
+ ] : []
1528
+ ];
1529
+ const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
1530
+ return /*#__PURE__*/ jsx(Layouts.Content, {
1531
+ children: /*#__PURE__*/ jsxs(Flex, {
1532
+ gap: 8,
1533
+ direction: "column",
1534
+ alignItems: "stretch",
1535
+ children: [
1536
+ /*#__PURE__*/ jsx(Flex, {
1537
+ children: /*#__PURE__*/ jsx(SingleSelect, {
1538
+ placeholder: groupByLabel,
1539
+ "aria-label": groupByLabel,
1540
+ customizeContent: (value)=>formatMessage({
1541
+ id: `content-releases.pages.ReleaseDetails.groupBy.label`,
1542
+ defaultMessage: `Group by {groupBy}`
1543
+ }, {
1544
+ groupBy: value
1545
+ }),
1546
+ value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
1547
+ onChange: (value)=>setQuery({
1548
+ groupBy: value
1549
+ }),
1550
+ children: options.map((option)=>/*#__PURE__*/ jsx(SingleSelectOption, {
1551
+ value: option,
1552
+ children: formatMessage(getGroupByOptionLabel(option))
1553
+ }, option))
1554
+ })
1555
+ }),
1556
+ Object.keys(releaseActions).map((key)=>/*#__PURE__*/ jsxs(Flex, {
1557
+ gap: 4,
1558
+ direction: "column",
1559
+ alignItems: "stretch",
1560
+ children: [
1561
+ /*#__PURE__*/ jsx(Flex, {
1562
+ role: "separator",
1563
+ "aria-label": key,
1564
+ children: /*#__PURE__*/ jsx(Badge, {
1565
+ children: key
1566
+ })
1567
+ }),
1568
+ /*#__PURE__*/ jsx(Table.Root, {
1569
+ rows: releaseActions[key].map((item)=>({
1570
+ ...item,
1571
+ id: Number(item.entry.id)
1572
+ })),
1573
+ headers: headers,
1574
+ isLoading: isLoading || isFetching,
1575
+ children: /*#__PURE__*/ jsxs(Table.Content, {
1576
+ children: [
1577
+ /*#__PURE__*/ jsx(Table.Head, {
1578
+ children: headers.map(({ label, name })=>/*#__PURE__*/ jsx(Table.HeaderCell, {
1579
+ label: formatMessage(label),
1580
+ name: name
1581
+ }, name))
1582
+ }),
1583
+ /*#__PURE__*/ jsx(Table.Loading, {}),
1584
+ /*#__PURE__*/ jsx(Table.Body, {
1585
+ children: releaseActions[key].map(({ id, contentType, locale, type, entry, status }, actionIndex)=>/*#__PURE__*/ jsxs(Tr, {
1586
+ children: [
1587
+ /*#__PURE__*/ jsx(Td, {
1588
+ width: "25%",
1589
+ maxWidth: "200px",
1590
+ children: /*#__PURE__*/ jsx(Typography, {
1591
+ ellipsis: true,
1592
+ children: `${contentType.mainFieldValue || entry.id}`
1593
+ })
1594
+ }),
1595
+ hasI18nEnabled && /*#__PURE__*/ jsx(Td, {
1596
+ width: "10%",
1597
+ children: /*#__PURE__*/ jsx(Typography, {
1598
+ children: `${locale?.name ? locale.name : '-'}`
1599
+ })
1600
+ }),
1601
+ /*#__PURE__*/ jsx(Td, {
1602
+ width: "10%",
1603
+ children: /*#__PURE__*/ jsx(Typography, {
1604
+ children: contentType.displayName || ''
1605
+ })
1606
+ }),
1607
+ /*#__PURE__*/ jsx(Td, {
1608
+ width: "20%",
1609
+ children: release.releasedAt ? /*#__PURE__*/ jsx(Typography, {
1610
+ children: formatMessage({
1611
+ id: 'content-releases.page.ReleaseDetails.table.action-published',
1612
+ defaultMessage: 'This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>.'
1613
+ }, {
1614
+ isPublish: type === 'publish',
1615
+ b: (children)=>/*#__PURE__*/ jsx(Typography, {
1616
+ fontWeight: "bold",
1617
+ children: children
1618
+ })
1619
+ })
1620
+ }) : /*#__PURE__*/ jsx(ReleaseActionOptions, {
1621
+ selected: type,
1622
+ handleChange: (e)=>handleChangeType(e, id, [
1623
+ key,
1624
+ actionIndex
1625
+ ]),
1626
+ name: `release-action-${id}-type`,
1627
+ disabled: !canUpdate
1628
+ })
1629
+ }),
1630
+ !release.releasedAt && /*#__PURE__*/ jsxs(Fragment, {
1631
+ children: [
1632
+ /*#__PURE__*/ jsx(Td, {
1633
+ width: "20%",
1634
+ minWidth: "200px",
1635
+ children: /*#__PURE__*/ jsx(EntryValidationPopover, {
1636
+ action: type,
1637
+ schema: contentTypes?.[contentType.uid],
1638
+ entry: entry,
1639
+ status: status
1640
+ })
1641
+ }),
1642
+ /*#__PURE__*/ jsx(Td, {
1643
+ children: /*#__PURE__*/ jsx(Flex, {
1644
+ justifyContent: "flex-end",
1645
+ children: /*#__PURE__*/ jsxs(ReleaseActionMenu.Root, {
1646
+ children: [
1647
+ /*#__PURE__*/ jsx(ReleaseActionMenu.ReleaseActionEntryLinkItem, {
1648
+ contentTypeUid: contentType.uid,
1649
+ documentId: entry.documentId,
1650
+ locale: locale?.code
1651
+ }),
1652
+ /*#__PURE__*/ jsx(ReleaseActionMenu.DeleteReleaseActionItem, {
1653
+ releaseId: release.id,
1654
+ actionId: id
1655
+ })
1656
+ ]
1657
+ })
1658
+ })
1659
+ })
1660
+ ]
1661
+ })
1662
+ ]
1663
+ }, id))
1664
+ })
1665
+ ]
1666
+ })
1667
+ })
1668
+ ]
1669
+ }, `releases-group-${key}`)),
1670
+ /*#__PURE__*/ jsxs(Pagination.Root, {
1671
+ ...releaseMeta?.pagination,
1672
+ defaultPageSize: releaseMeta?.pagination?.pageSize,
1673
+ children: [
1674
+ /*#__PURE__*/ jsx(Pagination.PageSize, {}),
1675
+ /*#__PURE__*/ jsx(Pagination.Links, {})
1676
+ ]
1677
+ })
1678
+ ]
1679
+ })
1680
+ });
1681
+ };
1682
+ /* -------------------------------------------------------------------------------------------------
1683
+ * ReleaseDetailsPage
1684
+ * -----------------------------------------------------------------------------------------------*/ const ReleaseDetailsPage = ()=>{
1685
+ const { formatMessage } = useIntl();
1686
+ const { releaseId } = useParams();
1687
+ const { toggleNotification } = useNotification();
1688
+ const { formatAPIError } = useAPIErrorHandler();
1689
+ const navigate = useNavigate();
1690
+ const [releaseModalShown, setReleaseModalShown] = React.useState(false);
1691
+ const [showWarningSubmit, setWarningSubmit] = React.useState(false);
1692
+ const { isLoading: isLoadingDetails, data, isSuccess: isSuccessDetails } = useGetReleaseQuery({
1693
+ id: releaseId
1694
+ }, {
1695
+ skip: !releaseId
1696
+ });
1697
+ const { data: dataTimezone, isLoading: isLoadingTimezone } = useGetReleaseSettingsQuery();
1698
+ const [updateRelease, { isLoading: isSubmittingForm }] = useUpdateReleaseMutation();
1699
+ const [deleteRelease] = useDeleteReleaseMutation();
1700
+ const toggleEditReleaseModal = ()=>{
1701
+ setReleaseModalShown((prev)=>!prev);
1702
+ };
1703
+ const getTimezoneValue = ()=>{
1704
+ if (releaseData?.timezone) {
1705
+ return releaseData.timezone;
1706
+ } else {
1707
+ if (dataTimezone?.data.defaultTimezone) {
1708
+ return dataTimezone.data.defaultTimezone;
1709
+ }
1710
+ return null;
1711
+ }
1712
+ };
1713
+ const toggleWarningSubmit = ()=>setWarningSubmit((prevState)=>!prevState);
1714
+ if (isLoadingDetails || isLoadingTimezone) {
1715
+ return /*#__PURE__*/ jsx(ReleaseDetailsLayout, {
1716
+ toggleEditReleaseModal: toggleEditReleaseModal,
1717
+ toggleWarningSubmit: toggleWarningSubmit,
1718
+ children: /*#__PURE__*/ jsx(Page.Loading, {})
1719
+ });
1720
+ }
1721
+ if (!releaseId) {
1722
+ return /*#__PURE__*/ jsx(Navigate, {
1723
+ to: ".."
1724
+ });
1725
+ }
1726
+ const releaseData = isSuccessDetails && data?.data || null;
1727
+ const title = releaseData?.name || '';
1728
+ const timezone = getTimezoneValue();
1729
+ const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1730
+ // Just get the date and time to display without considering updated timezone time
1731
+ const date = scheduledAt ? format$1(scheduledAt, 'yyyy-MM-dd') : undefined;
1732
+ const time = scheduledAt ? format$1(scheduledAt, 'HH:mm') : '';
1733
+ const handleEditRelease = async (values)=>{
1734
+ const response = await updateRelease({
1735
+ id: releaseId,
1736
+ name: values.name,
1737
+ scheduledAt: values.scheduledAt,
1738
+ timezone: values.timezone
1739
+ });
1740
+ if ('data' in response) {
1741
+ // When the response returns an object with 'data', handle success
1742
+ toggleNotification({
1743
+ type: 'success',
1744
+ message: formatMessage({
1745
+ id: 'content-releases.modal.release-updated-notification-success',
1746
+ defaultMessage: 'Release updated.'
1747
+ })
1748
+ });
1749
+ toggleEditReleaseModal();
1750
+ } else if (isFetchError(response.error)) {
1751
+ // When the response returns an object with 'error', handle fetch error
1752
+ toggleNotification({
1753
+ type: 'danger',
1754
+ message: formatAPIError(response.error)
1755
+ });
1756
+ } else {
1757
+ // Otherwise, the response returns an object with 'error', handle a generic error
1758
+ toggleNotification({
1759
+ type: 'danger',
1760
+ message: formatMessage({
1761
+ id: 'notification.error',
1762
+ defaultMessage: 'An error occurred'
1763
+ })
1764
+ });
1765
+ }
1766
+ };
1767
+ const handleDeleteRelease = async ()=>{
1768
+ const response = await deleteRelease({
1769
+ id: releaseId
1770
+ });
1771
+ if ('data' in response) {
1772
+ navigate('..');
1773
+ } else if (isFetchError(response.error)) {
1774
+ // When the response returns an object with 'error', handle fetch error
1775
+ toggleNotification({
1776
+ type: 'danger',
1777
+ message: formatAPIError(response.error)
1778
+ });
1779
+ } else {
1780
+ // Otherwise, the response returns an object with 'error', handle a generic error
1781
+ toggleNotification({
1782
+ type: 'danger',
1783
+ message: formatMessage({
1784
+ id: 'notification.error',
1785
+ defaultMessage: 'An error occurred'
1786
+ })
1787
+ });
1788
+ }
1789
+ };
1790
+ return /*#__PURE__*/ jsxs(ReleaseDetailsLayout, {
1791
+ toggleEditReleaseModal: toggleEditReleaseModal,
1792
+ toggleWarningSubmit: toggleWarningSubmit,
1793
+ children: [
1794
+ /*#__PURE__*/ jsx(ReleaseDetailsBody, {
1795
+ releaseId: releaseId
1796
+ }),
1797
+ /*#__PURE__*/ jsx(ReleaseModal, {
1798
+ open: releaseModalShown,
1799
+ handleClose: toggleEditReleaseModal,
1800
+ handleSubmit: handleEditRelease,
1801
+ isLoading: isLoadingDetails || isSubmittingForm,
1802
+ initialValues: {
1803
+ name: title || '',
1804
+ scheduledAt,
1805
+ date,
1806
+ time,
1807
+ isScheduled: Boolean(scheduledAt),
1808
+ timezone
1809
+ }
1810
+ }),
1811
+ /*#__PURE__*/ jsx(Dialog.Root, {
1812
+ open: showWarningSubmit,
1813
+ onOpenChange: toggleWarningSubmit,
1814
+ children: /*#__PURE__*/ jsx(ConfirmDialog, {
1815
+ onConfirm: handleDeleteRelease,
1816
+ children: formatMessage({
1817
+ id: 'content-releases.dialog.confirmation-message',
1818
+ defaultMessage: 'Are you sure you want to delete this release?'
1819
+ })
1820
+ })
1821
+ })
1822
+ ]
1823
+ });
1824
+ };
1825
+
1826
+ const App = ()=>{
1827
+ return /*#__PURE__*/ jsx(Page.Protect, {
1828
+ permissions: PERMISSIONS.main,
1829
+ children: /*#__PURE__*/ jsxs(Routes, {
1830
+ children: [
1831
+ /*#__PURE__*/ jsx(Route, {
1832
+ index: true,
1833
+ element: /*#__PURE__*/ jsx(ReleasesPage, {})
1834
+ }),
1835
+ /*#__PURE__*/ jsx(Route, {
1836
+ path: ':releaseId',
1837
+ element: /*#__PURE__*/ jsx(ReleaseDetailsPage, {})
1838
+ })
1839
+ ]
1840
+ })
1841
+ });
1842
+ };
1843
+
1844
+ export { App };
1845
+ //# sourceMappingURL=App-CuOosufQ.mjs.map