@strapi/content-releases 0.0.0-next.eedb036f0a7ac282d2a645d8a40625091bd28b1e → 0.0.0-next.ef9237644b07791c05e1b7edc54d1921e59565ae

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