@strapi/content-releases 0.0.0-next.f4ff842a3cb7b83db540bee67554b704e042b042 → 0.0.0-next.f698d55751345c4ca87477ef683475c1a68f310a

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