@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.78ea7925e0dad75936ae2e937a041a0666e3d65a

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 (29) hide show
  1. package/dist/_chunks/{App-L1jSxCiL.mjs → App-bpzO2Ljh.mjs} +762 -454
  2. package/dist/_chunks/App-bpzO2Ljh.mjs.map +1 -0
  3. package/dist/_chunks/{App-_20W9dYa.js → App-p8aKBitd.js} +754 -445
  4. package/dist/_chunks/App-p8aKBitd.js.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
  9. package/dist/_chunks/{en-MyLPoISH.mjs → en-WuuhP6Bn.mjs} +21 -6
  10. package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
  11. package/dist/_chunks/{en-gYDqKYFd.js → en-gcJJ5htG.js} +21 -6
  12. package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
  13. package/dist/_chunks/{index-c4zRX_sg.mjs → index-AECgcaDa.mjs} +171 -45
  14. package/dist/_chunks/index-AECgcaDa.mjs.map +1 -0
  15. package/dist/_chunks/{index-KJa1Rb5F.js → index-fP3qoWZ4.js} +159 -33
  16. package/dist/_chunks/index-fP3qoWZ4.js.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +2 -2
  19. package/dist/server/index.js +952 -397
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +951 -397
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -11
  24. package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
  25. package/dist/_chunks/App-_20W9dYa.js.map +0 -1
  26. package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
  27. package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
  28. package/dist/_chunks/index-KJa1Rb5F.js.map +0 -1
  29. package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
@@ -3,14 +3,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const helperPlugin = require("@strapi/helper-plugin");
5
5
  const reactRouterDom = require("react-router-dom");
6
- const index = require("./index-KJa1Rb5F.js");
6
+ const index = require("./index-fP3qoWZ4.js");
7
7
  const React = require("react");
8
8
  const strapiAdmin = require("@strapi/admin/strapi-admin");
9
9
  const designSystem = require("@strapi/design-system");
10
10
  const v2 = require("@strapi/design-system/v2");
11
11
  const icons = require("@strapi/icons");
12
+ const format = require("date-fns/format");
13
+ const dateFnsTz = require("date-fns-tz");
12
14
  const reactIntl = require("react-intl");
13
15
  const styled = require("styled-components");
16
+ const dateFns = require("date-fns");
14
17
  const formik = require("formik");
15
18
  const yup = require("yup");
16
19
  require("@reduxjs/toolkit/query");
@@ -37,10 +40,28 @@ function _interopNamespace(e) {
37
40
  return Object.freeze(n);
38
41
  }
39
42
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
43
+ const format__default = /* @__PURE__ */ _interopDefault(format);
40
44
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
41
45
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
42
46
  const RELEASE_SCHEMA = yup__namespace.object().shape({
43
- name: yup__namespace.string().trim().required()
47
+ name: yup__namespace.string().trim().required(),
48
+ scheduledAt: yup__namespace.string().nullable(),
49
+ isScheduled: yup__namespace.boolean().optional(),
50
+ time: yup__namespace.string().when("isScheduled", {
51
+ is: true,
52
+ then: yup__namespace.string().trim().required(),
53
+ otherwise: yup__namespace.string().nullable()
54
+ }),
55
+ timezone: yup__namespace.string().when("isScheduled", {
56
+ is: true,
57
+ then: yup__namespace.string().required().nullable(),
58
+ otherwise: yup__namespace.string().nullable()
59
+ }),
60
+ date: yup__namespace.string().when("isScheduled", {
61
+ is: true,
62
+ then: yup__namespace.string().required().nullable(),
63
+ otherwise: yup__namespace.string().nullable()
64
+ })
44
65
  }).required().noUnknown();
45
66
  const ReleaseModal = ({
46
67
  handleClose,
@@ -51,6 +72,24 @@ const ReleaseModal = ({
51
72
  const { formatMessage } = reactIntl.useIntl();
52
73
  const { pathname } = reactRouterDom.useLocation();
53
74
  const isCreatingRelease = pathname === `/plugins/${index.pluginId}`;
75
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
76
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
77
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
78
+ );
79
+ const getScheduledTimestamp = (values) => {
80
+ const { date, time, timezone } = values;
81
+ if (!date || !time || !timezone)
82
+ return null;
83
+ const formattedDate = dateFns.parse(time, "HH:mm", new Date(date));
84
+ const timezoneWithoutOffset = timezone.split("&")[1];
85
+ return dateFnsTz.zonedTimeToUtc(formattedDate, timezoneWithoutOffset);
86
+ };
87
+ const getTimezoneWithOffset = () => {
88
+ const currentTimezone = timezoneList.find(
89
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
90
+ );
91
+ return currentTimezone?.value || systemTimezone.value;
92
+ };
54
93
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
55
94
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
56
95
  {
@@ -62,45 +101,135 @@ const ReleaseModal = ({
62
101
  /* @__PURE__ */ jsxRuntime.jsx(
63
102
  formik.Formik,
64
103
  {
65
- validateOnChange: false,
66
- onSubmit: handleSubmit,
67
- initialValues,
104
+ onSubmit: (values) => {
105
+ handleSubmit({
106
+ ...values,
107
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
108
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
109
+ });
110
+ },
111
+ initialValues: {
112
+ ...initialValues,
113
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
114
+ },
68
115
  validationSchema: RELEASE_SCHEMA,
69
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
70
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
71
- designSystem.TextInput,
72
- {
73
- label: formatMessage({
74
- id: "content-releases.modal.form.input.label.release-name",
75
- defaultMessage: "Name"
76
- }),
77
- name: "name",
78
- value: values.name,
79
- error: errors.name,
80
- onChange: handleChange,
81
- required: true
82
- }
83
- ) }),
116
+ validateOnChange: false,
117
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx(
120
+ designSystem.TextInput,
121
+ {
122
+ label: formatMessage({
123
+ id: "content-releases.modal.form.input.label.release-name",
124
+ defaultMessage: "Name"
125
+ }),
126
+ name: "name",
127
+ value: values.name,
128
+ error: errors.name,
129
+ onChange: handleChange,
130
+ required: true
131
+ }
132
+ ),
133
+ IsSchedulingEnabled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "max-content", children: /* @__PURE__ */ jsxRuntime.jsx(
135
+ designSystem.Checkbox,
136
+ {
137
+ name: "isScheduled",
138
+ value: values.isScheduled,
139
+ onChange: (event) => {
140
+ setFieldValue("isScheduled", event.target.checked);
141
+ if (!event.target.checked) {
142
+ setFieldValue("date", null);
143
+ setFieldValue("time", "");
144
+ setFieldValue("timezone", null);
145
+ } else {
146
+ setFieldValue("date", initialValues.date);
147
+ setFieldValue("time", initialValues.time);
148
+ setFieldValue(
149
+ "timezone",
150
+ initialValues.timezone ?? systemTimezone?.value
151
+ );
152
+ }
153
+ },
154
+ children: /* @__PURE__ */ jsxRuntime.jsx(
155
+ designSystem.Typography,
156
+ {
157
+ textColor: values.isScheduled ? "primary600" : "neutral800",
158
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
159
+ children: formatMessage({
160
+ id: "modal.form.input.label.schedule-release",
161
+ defaultMessage: "Schedule release"
162
+ })
163
+ }
164
+ )
165
+ }
166
+ ) }),
167
+ values.isScheduled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
168
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "start", children: [
169
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
170
+ designSystem.DatePicker,
171
+ {
172
+ label: formatMessage({
173
+ id: "content-releases.modal.form.input.label.date",
174
+ defaultMessage: "Date"
175
+ }),
176
+ name: "date",
177
+ error: errors.date,
178
+ onChange: (date) => {
179
+ const isoFormatDate = date ? dateFns.formatISO(date, { representation: "date" }) : null;
180
+ setFieldValue("date", isoFormatDate);
181
+ },
182
+ clearLabel: formatMessage({
183
+ id: "content-releases.modal.form.input.clearLabel",
184
+ defaultMessage: "Clear"
185
+ }),
186
+ onClear: () => {
187
+ setFieldValue("date", null);
188
+ },
189
+ selectedDate: values.date || void 0,
190
+ required: true,
191
+ minDate: dateFnsTz.utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
192
+ }
193
+ ) }),
194
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
195
+ designSystem.TimePicker,
196
+ {
197
+ label: formatMessage({
198
+ id: "content-releases.modal.form.input.label.time",
199
+ defaultMessage: "Time"
200
+ }),
201
+ name: "time",
202
+ error: errors.time,
203
+ onChange: (time) => {
204
+ setFieldValue("time", time);
205
+ },
206
+ clearLabel: formatMessage({
207
+ id: "content-releases.modal.form.input.clearLabel",
208
+ defaultMessage: "Clear"
209
+ }),
210
+ onClear: () => {
211
+ setFieldValue("time", "");
212
+ },
213
+ value: values.time || void 0,
214
+ required: true
215
+ }
216
+ ) })
217
+ ] }),
218
+ /* @__PURE__ */ jsxRuntime.jsx(TimezoneComponent, { timezoneOptions: timezoneList })
219
+ ] })
220
+ ] })
221
+ ] }) }),
84
222
  /* @__PURE__ */ jsxRuntime.jsx(
85
223
  designSystem.ModalFooter,
86
224
  {
87
225
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
88
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
89
- designSystem.Button,
226
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
90
227
  {
91
- name: "submit",
92
- loading: isLoading,
93
- disabled: !values.name || values.name === initialValues.name,
94
- type: "submit",
95
- children: formatMessage(
96
- {
97
- id: "content-releases.modal.form.button.submit",
98
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
99
- },
100
- { isCreatingRelease }
101
- )
102
- }
103
- )
228
+ id: "content-releases.modal.form.button.submit",
229
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
230
+ },
231
+ { isCreatingRelease }
232
+ ) })
104
233
  }
105
234
  )
106
235
  ] })
@@ -108,33 +237,404 @@ const ReleaseModal = ({
108
237
  )
109
238
  ] });
110
239
  };
240
+ const getTimezones = (selectedDate) => {
241
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
242
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
243
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
244
+ });
245
+ const systemTimezone = timezoneList.find(
246
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
247
+ );
248
+ return { timezoneList, systemTimezone };
249
+ };
250
+ const TimezoneComponent = ({ timezoneOptions }) => {
251
+ const { values, errors, setFieldValue } = formik.useFormikContext();
252
+ const { formatMessage } = reactIntl.useIntl();
253
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
254
+ React__namespace.useEffect(() => {
255
+ if (values.date) {
256
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
257
+ setTimezoneList(timezoneList2);
258
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
259
+ if (updatedTimezone) {
260
+ setFieldValue("timezone", updatedTimezone.value);
261
+ }
262
+ }
263
+ }, [setFieldValue, values.date, values.timezone]);
264
+ return /* @__PURE__ */ jsxRuntime.jsx(
265
+ designSystem.Combobox,
266
+ {
267
+ label: formatMessage({
268
+ id: "content-releases.modal.form.input.label.timezone",
269
+ defaultMessage: "Timezone"
270
+ }),
271
+ autocomplete: { type: "list", filter: "contains" },
272
+ name: "timezone",
273
+ value: values.timezone || void 0,
274
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
275
+ onChange: (timezone) => {
276
+ setFieldValue("timezone", timezone);
277
+ },
278
+ onTextValueChange: (timezone) => {
279
+ setFieldValue("timezone", timezone);
280
+ },
281
+ onClear: () => {
282
+ setFieldValue("timezone", "");
283
+ },
284
+ error: errors.timezone,
285
+ required: true,
286
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
287
+ }
288
+ );
289
+ };
290
+ const LinkCard = styled__default.default(v2.Link)`
291
+ display: block;
292
+ `;
293
+ const CapitalizeRelativeTime = styled__default.default(helperPlugin.RelativeTime)`
294
+ text-transform: capitalize;
295
+ `;
296
+ const getBadgeProps = (status) => {
297
+ let color;
298
+ switch (status) {
299
+ case "ready":
300
+ color = "success";
301
+ break;
302
+ case "blocked":
303
+ color = "warning";
304
+ break;
305
+ case "failed":
306
+ color = "danger";
307
+ break;
308
+ case "done":
309
+ color = "primary";
310
+ break;
311
+ case "empty":
312
+ default:
313
+ color = "neutral";
314
+ }
315
+ return {
316
+ textColor: `${color}600`,
317
+ backgroundColor: `${color}100`,
318
+ borderColor: `${color}200`
319
+ };
320
+ };
321
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
322
+ const { formatMessage } = reactIntl.useIntl();
323
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
324
+ if (isError) {
325
+ return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
326
+ }
327
+ if (releases?.length === 0) {
328
+ return /* @__PURE__ */ jsxRuntime.jsx(
329
+ designSystem.EmptyStateLayout,
330
+ {
331
+ content: formatMessage(
332
+ {
333
+ id: "content-releases.page.Releases.tab.emptyEntries",
334
+ defaultMessage: "No releases"
335
+ },
336
+ {
337
+ target: sectionTitle
338
+ }
339
+ ),
340
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
341
+ }
342
+ );
343
+ }
344
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, actions, scheduledAt, status }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxRuntime.jsxs(
345
+ designSystem.Flex,
346
+ {
347
+ direction: "column",
348
+ justifyContent: "space-between",
349
+ padding: 4,
350
+ hasRadius: true,
351
+ background: "neutral0",
352
+ shadow: "tableShadow",
353
+ height: "100%",
354
+ width: "100%",
355
+ alignItems: "start",
356
+ gap: 4,
357
+ children: [
358
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", gap: 1, children: [
359
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
360
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: IsSchedulingEnabled ? scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(CapitalizeRelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
361
+ id: "content-releases.pages.Releases.not-scheduled",
362
+ defaultMessage: "Not scheduled"
363
+ }) : formatMessage(
364
+ {
365
+ id: "content-releases.page.Releases.release-item.entries",
366
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
367
+ },
368
+ { number: actions.meta.count }
369
+ ) })
370
+ ] }),
371
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(status), children: status })
372
+ ]
373
+ }
374
+ ) }) }, id)) });
375
+ };
376
+ const StyledAlert = styled__default.default(designSystem.Alert)`
377
+ button {
378
+ display: none;
379
+ }
380
+ p + div {
381
+ margin-left: auto;
382
+ }
383
+ `;
384
+ const INITIAL_FORM_VALUES = {
385
+ name: "",
386
+ date: null,
387
+ time: "",
388
+ // Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
389
+ isScheduled: window.strapi.future.isEnabled("contentReleasesScheduling"),
390
+ scheduledAt: null,
391
+ timezone: null
392
+ };
393
+ const ReleasesPage = () => {
394
+ const tabRef = React__namespace.useRef(null);
395
+ const location = reactRouterDom.useLocation();
396
+ const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
397
+ const toggleNotification = helperPlugin.useNotification();
398
+ const { formatMessage } = reactIntl.useIntl();
399
+ const { push, replace } = reactRouterDom.useHistory();
400
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
401
+ const [{ query }, setQuery] = helperPlugin.useQueryParams();
402
+ const response = index.useGetReleasesQuery(query);
403
+ const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
404
+ const { getFeature } = strapiAdmin.useLicenseLimits();
405
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
406
+ const { trackUsage } = helperPlugin.useTracking();
407
+ const { isLoading, isSuccess, isError } = response;
408
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
409
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
410
+ React__namespace.useEffect(() => {
411
+ if (location?.state?.errors) {
412
+ toggleNotification({
413
+ type: "warning",
414
+ title: formatMessage({
415
+ id: "content-releases.pages.Releases.notification.error.title",
416
+ defaultMessage: "Your request could not be processed."
417
+ }),
418
+ message: formatMessage({
419
+ id: "content-releases.pages.Releases.notification.error.message",
420
+ defaultMessage: "Please try again or open another release."
421
+ })
422
+ });
423
+ replace({ state: null });
424
+ }
425
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
426
+ React__namespace.useEffect(() => {
427
+ if (tabRef.current) {
428
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
429
+ }
430
+ }, [activeTabIndex]);
431
+ const toggleAddReleaseModal = () => {
432
+ setReleaseModalShown((prev) => !prev);
433
+ };
434
+ if (isLoading) {
435
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
436
+ }
437
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
438
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
439
+ const handleTabChange = (index2) => {
440
+ setQuery({
441
+ ...query,
442
+ page: 1,
443
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
444
+ filters: {
445
+ releasedAt: {
446
+ $notNull: index2 === 0 ? false : true
447
+ }
448
+ }
449
+ });
450
+ };
451
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
452
+ const response2 = await createRelease({
453
+ name,
454
+ scheduledAt,
455
+ timezone
456
+ });
457
+ if ("data" in response2) {
458
+ toggleNotification({
459
+ type: "success",
460
+ message: formatMessage({
461
+ id: "content-releases.modal.release-created-notification-success",
462
+ defaultMessage: "Release created."
463
+ })
464
+ });
465
+ trackUsage("didCreateRelease");
466
+ push(`/plugins/content-releases/${response2.data.data.id}`);
467
+ } else if (index.isAxiosError(response2.error)) {
468
+ toggleNotification({
469
+ type: "warning",
470
+ message: formatAPIError(response2.error)
471
+ });
472
+ } else {
473
+ toggleNotification({
474
+ type: "warning",
475
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
476
+ });
477
+ }
478
+ };
479
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx(
481
+ designSystem.HeaderLayout,
482
+ {
483
+ title: formatMessage({
484
+ id: "content-releases.pages.Releases.title",
485
+ defaultMessage: "Releases"
486
+ }),
487
+ subtitle: formatMessage({
488
+ id: "content-releases.pages.Releases.header-subtitle",
489
+ defaultMessage: "Create and manage content updates"
490
+ }),
491
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
492
+ designSystem.Button,
493
+ {
494
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
495
+ onClick: toggleAddReleaseModal,
496
+ disabled: hasReachedMaximumPendingReleases,
497
+ children: formatMessage({
498
+ id: "content-releases.header.actions.add-release",
499
+ defaultMessage: "New release"
500
+ })
501
+ }
502
+ ) })
503
+ }
504
+ ),
505
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
506
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
507
+ StyledAlert,
508
+ {
509
+ marginBottom: 6,
510
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
511
+ id: "content-releases.pages.Releases.max-limit-reached.action",
512
+ defaultMessage: "Explore plans"
513
+ }) }),
514
+ title: formatMessage(
515
+ {
516
+ id: "content-releases.pages.Releases.max-limit-reached.title",
517
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
518
+ },
519
+ { number: maximumReleases }
520
+ ),
521
+ onClose: () => {
522
+ },
523
+ closeLabel: "",
524
+ children: formatMessage({
525
+ id: "content-releases.pages.Releases.max-limit-reached.message",
526
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
527
+ })
528
+ }
529
+ ),
530
+ /* @__PURE__ */ jsxRuntime.jsxs(
531
+ designSystem.TabGroup,
532
+ {
533
+ label: formatMessage({
534
+ id: "content-releases.pages.Releases.tab-group.label",
535
+ defaultMessage: "Releases list"
536
+ }),
537
+ variant: "simple",
538
+ initialSelectedTabIndex: activeTabIndex,
539
+ onTabChange: handleTabChange,
540
+ ref: tabRef,
541
+ children: [
542
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
543
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
544
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage(
545
+ {
546
+ id: "content-releases.pages.Releases.tab.pending",
547
+ defaultMessage: "Pending ({count})"
548
+ },
549
+ {
550
+ count: totalPendingReleases
551
+ }
552
+ ) }),
553
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
554
+ id: "content-releases.pages.Releases.tab.done",
555
+ defaultMessage: "Done"
556
+ }) })
557
+ ] }),
558
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
559
+ ] }),
560
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
561
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
562
+ ReleasesGrid,
563
+ {
564
+ sectionTitle: "pending",
565
+ releases: response?.currentData?.data,
566
+ isError
567
+ }
568
+ ) }),
569
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
570
+ ReleasesGrid,
571
+ {
572
+ sectionTitle: "done",
573
+ releases: response?.currentData?.data,
574
+ isError
575
+ }
576
+ ) })
577
+ ] })
578
+ ]
579
+ }
580
+ ),
581
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
582
+ /* @__PURE__ */ jsxRuntime.jsx(
583
+ helperPlugin.PageSizeURLQuery,
584
+ {
585
+ options: ["8", "16", "32", "64"],
586
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
587
+ }
588
+ ),
589
+ /* @__PURE__ */ jsxRuntime.jsx(
590
+ helperPlugin.PaginationURLQuery,
591
+ {
592
+ pagination: {
593
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
594
+ }
595
+ }
596
+ )
597
+ ] }) : null
598
+ ] }) }),
599
+ releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
600
+ ReleaseModal,
601
+ {
602
+ handleClose: toggleAddReleaseModal,
603
+ handleSubmit: handleAddRelease,
604
+ isLoading: isSubmittingForm,
605
+ initialValues: INITIAL_FORM_VALUES
606
+ }
607
+ )
608
+ ] });
609
+ };
111
610
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
112
611
  align-self: stretch;
113
612
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
114
613
  border-bottom-left-radius: ${({ theme }) => theme.borderRadius};
115
614
  border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
116
615
  `;
117
- const StyledFlex = styled__default.default(designSystem.Flex)`
118
- align-self: stretch;
119
- cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
120
-
616
+ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
121
617
  svg path {
122
618
  fill: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
123
619
  }
124
620
  span {
125
621
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
126
622
  }
623
+
624
+ &:hover {
625
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
626
+ }
127
627
  `;
128
628
  const PencilIcon = styled__default.default(icons.Pencil)`
129
- width: ${({ theme }) => theme.spaces[4]};
130
- height: ${({ theme }) => theme.spaces[4]};
629
+ width: ${({ theme }) => theme.spaces[3]};
630
+ height: ${({ theme }) => theme.spaces[3]};
131
631
  path {
132
632
  fill: ${({ theme }) => theme.colors.neutral600};
133
633
  }
134
634
  `;
135
635
  const TrashIcon = styled__default.default(icons.Trash)`
136
- width: ${({ theme }) => theme.spaces[4]};
137
- height: ${({ theme }) => theme.spaces[4]};
636
+ width: ${({ theme }) => theme.spaces[3]};
637
+ height: ${({ theme }) => theme.spaces[3]};
138
638
  path {
139
639
  fill: ${({ theme }) => theme.colors.danger600};
140
640
  }
@@ -142,24 +642,6 @@ const TrashIcon = styled__default.default(icons.Trash)`
142
642
  const TypographyMaxWidth = styled__default.default(designSystem.Typography)`
143
643
  max-width: 300px;
144
644
  `;
145
- const PopoverButton = ({ onClick, disabled, children }) => {
146
- return /* @__PURE__ */ jsxRuntime.jsx(
147
- StyledFlex,
148
- {
149
- paddingTop: 2,
150
- paddingBottom: 2,
151
- paddingLeft: 4,
152
- paddingRight: 4,
153
- alignItems: "center",
154
- gap: 2,
155
- as: "button",
156
- hasRadius: true,
157
- onClick,
158
- disabled,
159
- children
160
- }
161
- );
162
- };
163
645
  const EntryValidationText = ({ action, schema, components, entry }) => {
164
646
  const { formatMessage } = reactIntl.useIntl();
165
647
  const { validate } = strapiAdmin.unstable_useDocument();
@@ -208,10 +690,8 @@ const ReleaseDetailsLayout = ({
208
690
  toggleWarningSubmit,
209
691
  children
210
692
  }) => {
211
- const { formatMessage } = reactIntl.useIntl();
693
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
212
694
  const { releaseId } = reactRouterDom.useParams();
213
- const [isPopoverVisible, setIsPopoverVisible] = React__namespace.useState(false);
214
- const moreButtonRef = React__namespace.useRef(null);
215
695
  const {
216
696
  data,
217
697
  isLoading: isLoadingDetails,
@@ -225,14 +705,8 @@ const ReleaseDetailsLayout = ({
225
705
  allowedActions: { canUpdate, canDelete }
226
706
  } = helperPlugin.useRBAC(index.PERMISSIONS);
227
707
  const dispatch = index.useTypedDispatch();
708
+ const { trackUsage } = helperPlugin.useTracking();
228
709
  const release = data?.data;
229
- const handleTogglePopover = () => {
230
- setIsPopoverVisible((prev) => !prev);
231
- };
232
- const openReleaseModal = () => {
233
- toggleEditReleaseModal();
234
- handleTogglePopover();
235
- };
236
710
  const handlePublishRelease = async () => {
237
711
  const response = await publishRelease({ id: releaseId });
238
712
  if ("data" in response) {
@@ -243,6 +717,12 @@ const ReleaseDetailsLayout = ({
243
717
  defaultMessage: "Release was published successfully."
244
718
  })
245
719
  });
720
+ const { totalEntries: totalEntries2, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
721
+ trackUsage("didPublishRelease", {
722
+ totalEntries: totalEntries2,
723
+ totalPublishedEntries,
724
+ totalUnpublishedEntries
725
+ });
246
726
  } else if (index.isAxiosError(response.error)) {
247
727
  toggleNotification({
248
728
  type: "warning",
@@ -255,13 +735,21 @@ const ReleaseDetailsLayout = ({
255
735
  });
256
736
  }
257
737
  };
258
- const openWarningConfirmDialog = () => {
259
- toggleWarningSubmit();
260
- handleTogglePopover();
261
- };
262
738
  const handleRefresh = () => {
263
739
  dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
264
740
  };
741
+ const getCreatedByUser = () => {
742
+ if (!release?.createdBy) {
743
+ return null;
744
+ }
745
+ if (release.createdBy.username) {
746
+ return release.createdBy.username;
747
+ }
748
+ if (release.createdBy.firstname) {
749
+ return `${release.createdBy.firstname} ${release.createdBy.lastname || ""}`.trim();
750
+ }
751
+ return release.createdBy.email;
752
+ };
265
753
  if (isLoadingDetails) {
266
754
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoadingDetails, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
267
755
  }
@@ -283,90 +771,128 @@ const ReleaseDetailsLayout = ({
283
771
  );
284
772
  }
285
773
  const totalEntries = release.actions.meta.count || 0;
286
- const createdBy = release.createdBy.lastname ? `${release.createdBy.firstname} ${release.createdBy.lastname}` : `${release.createdBy.firstname}`;
774
+ const hasCreatedByUser = Boolean(getCreatedByUser());
775
+ const IsSchedulingEnabled = window.strapi.future.isEnabled("contentReleasesScheduling");
776
+ const isScheduled = release.scheduledAt && release.timezone;
777
+ const numberOfEntriesText = formatMessage(
778
+ {
779
+ id: "content-releases.pages.Details.header-subtitle",
780
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
781
+ },
782
+ { number: totalEntries }
783
+ );
784
+ const scheduledText = isScheduled ? formatMessage(
785
+ {
786
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
787
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
788
+ },
789
+ {
790
+ date: formatDate(new Date(release.scheduledAt), {
791
+ weekday: "long",
792
+ day: "numeric",
793
+ month: "long",
794
+ year: "numeric",
795
+ timeZone: release.timezone
796
+ }),
797
+ time: formatTime(new Date(release.scheduledAt), {
798
+ timeZone: release.timezone,
799
+ hourCycle: "h23"
800
+ }),
801
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
802
+ }
803
+ ) : "";
287
804
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
288
805
  /* @__PURE__ */ jsxRuntime.jsx(
289
806
  designSystem.HeaderLayout,
290
807
  {
291
808
  title: release.name,
292
- subtitle: formatMessage(
293
- {
294
- id: "content-releases.pages.Details.header-subtitle",
295
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
296
- },
297
- { number: totalEntries }
298
- ),
809
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, lineHeight: 6, children: [
810
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : "") }),
811
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(release.status), children: release.status })
812
+ ] }),
299
813
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
300
814
  id: "global.back",
301
815
  defaultMessage: "Back"
302
816
  }) }),
303
817
  primaryAction: !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
304
- /* @__PURE__ */ jsxRuntime.jsx(
305
- designSystem.IconButton,
306
- {
307
- label: formatMessage({
308
- id: "content-releases.header.actions.open-release-actions",
309
- defaultMessage: "Release actions"
310
- }),
311
- ref: moreButtonRef,
312
- onClick: handleTogglePopover,
313
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {})
314
- }
315
- ),
316
- isPopoverVisible && /* @__PURE__ */ jsxRuntime.jsxs(
317
- designSystem.Popover,
318
- {
319
- source: moreButtonRef,
320
- placement: "bottom-end",
321
- onDismiss: handleTogglePopover,
322
- spacing: 4,
323
- minWidth: "242px",
324
- children: [
325
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "center", direction: "column", padding: 1, children: [
326
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canUpdate, onClick: openReleaseModal, children: [
327
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
328
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
329
- id: "content-releases.header.actions.edit",
330
- defaultMessage: "Edit"
331
- }) })
332
- ] }),
333
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverButton, { disabled: !canDelete, onClick: openWarningConfirmDialog, children: [
334
- /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
335
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
336
- id: "content-releases.header.actions.delete",
337
- defaultMessage: "Delete"
338
- }) })
339
- ] })
340
- ] }),
341
- /* @__PURE__ */ jsxRuntime.jsxs(
342
- ReleaseInfoWrapper,
343
- {
344
- direction: "column",
345
- justifyContent: "center",
346
- alignItems: "flex-start",
347
- gap: 1,
348
- padding: 5,
349
- children: [
350
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
351
- id: "content-releases.header.actions.created",
352
- defaultMessage: "Created"
353
- }) }),
354
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
355
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
356
- formatMessage(
357
- {
358
- id: "content-releases.header.actions.created.description",
359
- defaultMessage: " by {createdBy}"
360
- },
361
- { createdBy }
362
- )
363
- ] })
364
- ]
365
- }
366
- )
367
- ]
368
- }
369
- ),
818
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Root, { children: [
819
+ /* @__PURE__ */ jsxRuntime.jsx(
820
+ v2.Menu.Trigger,
821
+ {
822
+ as: designSystem.IconButton,
823
+ paddingLeft: 2,
824
+ paddingRight: 2,
825
+ "aria-label": formatMessage({
826
+ id: "content-releases.header.actions.open-release-actions",
827
+ defaultMessage: "Release edit and delete menu"
828
+ }),
829
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.More, {}),
830
+ variant: "tertiary"
831
+ }
832
+ ),
833
+ /* @__PURE__ */ jsxRuntime.jsxs(v2.Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: [
834
+ /* @__PURE__ */ jsxRuntime.jsxs(
835
+ designSystem.Flex,
836
+ {
837
+ alignItems: "center",
838
+ justifyContent: "center",
839
+ direction: "column",
840
+ padding: 1,
841
+ width: "100%",
842
+ children: [
843
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
844
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
845
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
846
+ id: "content-releases.header.actions.edit",
847
+ defaultMessage: "Edit"
848
+ }) })
849
+ ] }) }),
850
+ /* @__PURE__ */ jsxRuntime.jsx(
851
+ StyledMenuItem,
852
+ {
853
+ disabled: !canDelete,
854
+ onSelect: toggleWarningSubmit,
855
+ variant: "danger",
856
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
857
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
858
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
859
+ id: "content-releases.header.actions.delete",
860
+ defaultMessage: "Delete"
861
+ }) })
862
+ ] })
863
+ }
864
+ )
865
+ ]
866
+ }
867
+ ),
868
+ /* @__PURE__ */ jsxRuntime.jsxs(
869
+ ReleaseInfoWrapper,
870
+ {
871
+ direction: "column",
872
+ justifyContent: "center",
873
+ alignItems: "flex-start",
874
+ gap: 1,
875
+ padding: 5,
876
+ children: [
877
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({
878
+ id: "content-releases.header.actions.created",
879
+ defaultMessage: "Created"
880
+ }) }),
881
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", color: "neutral300", children: [
882
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.RelativeTime, { timestamp: new Date(release.createdAt) }),
883
+ formatMessage(
884
+ {
885
+ id: "content-releases.header.actions.created.description",
886
+ defaultMessage: "{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}"
887
+ },
888
+ { createdBy: getCreatedByUser(), hasCreatedByUser }
889
+ )
890
+ ] })
891
+ ]
892
+ }
893
+ )
894
+ ] })
895
+ ] }),
370
896
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "tertiary", onClick: handleRefresh, children: formatMessage({
371
897
  id: "content-releases.header.actions.refresh",
372
898
  defaultMessage: "Refresh"
@@ -422,6 +948,9 @@ const ReleaseDetailsBody = () => {
422
948
  isError: isReleaseError,
423
949
  error: releaseError
424
950
  } = index.useGetReleaseQuery({ id: releaseId });
951
+ const {
952
+ allowedActions: { canUpdate }
953
+ } = helperPlugin.useRBAC(index.PERMISSIONS);
425
954
  const release = releaseData?.data;
426
955
  const selectedGroupBy = query?.groupBy || "contentType";
427
956
  const {
@@ -435,7 +964,7 @@ const ReleaseDetailsBody = () => {
435
964
  releaseId
436
965
  });
437
966
  const [updateReleaseAction] = index.useUpdateReleaseActionMutation();
438
- const handleChangeType = async (e, actionId) => {
967
+ const handleChangeType = async (e, actionId, actionPath) => {
439
968
  const response = await updateReleaseAction({
440
969
  params: {
441
970
  releaseId,
@@ -443,7 +972,11 @@ const ReleaseDetailsBody = () => {
443
972
  },
444
973
  body: {
445
974
  type: e.target.value
446
- }
975
+ },
976
+ query,
977
+ // We are passing the query params to make optimistic updates
978
+ actionPath
979
+ // We are passing the action path to found the position in the cache of the action for optimistic updates
447
980
  });
448
981
  if ("error" in response) {
449
982
  if (index.isAxiosError(response.error)) {
@@ -524,7 +1057,7 @@ const ReleaseDetailsBody = () => {
524
1057
  designSystem.SingleSelect,
525
1058
  {
526
1059
  "aria-label": formatMessage({
527
- id: "content-releases.pages.ReleaseDetails.groupBy.label",
1060
+ id: "content-releases.pages.ReleaseDetails.groupBy.aria-label",
528
1061
  defaultMessage: "Group by"
529
1062
  }),
530
1063
  customizeContent: (value) => formatMessage(
@@ -542,7 +1075,7 @@ const ReleaseDetailsBody = () => {
542
1075
  }
543
1076
  ) }),
544
1077
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
545
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
1078
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { role: "separator", "aria-label": key, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: key }) }),
546
1079
  /* @__PURE__ */ jsxRuntime.jsx(
547
1080
  helperPlugin.Table.Root,
548
1081
  {
@@ -612,56 +1145,59 @@ const ReleaseDetailsBody = () => {
612
1145
  )
613
1146
  ] }),
614
1147
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.LoadingBody, {}),
615
- /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(({ id, contentType, locale, type, entry }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
616
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
617
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
618
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
619
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
620
- {
621
- id: "content-releases.page.ReleaseDetails.table.action-published",
622
- defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
623
- },
624
- {
625
- isPublish: type === "publish",
626
- b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
627
- }
628
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
629
- index.ReleaseActionOptions,
630
- {
631
- selected: type,
632
- handleChange: (e) => handleChangeType(e, id),
633
- name: `release-action-${id}-type`
634
- }
635
- ) }),
636
- !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
637
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
638
- EntryValidationText,
1148
+ /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
1149
+ ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1150
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
1151
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1152
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
1153
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
1154
+ {
1155
+ id: "content-releases.page.ReleaseDetails.table.action-published",
1156
+ defaultMessage: "This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>."
1157
+ },
1158
+ {
1159
+ isPublish: type === "publish",
1160
+ b: (children) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children })
1161
+ }
1162
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
1163
+ index.ReleaseActionOptions,
639
1164
  {
640
- action: type,
641
- schema: contentTypes?.[contentType.uid],
642
- components,
643
- entry
1165
+ selected: type,
1166
+ handleChange: (e) => handleChangeType(e, id, [key, actionIndex]),
1167
+ name: `release-action-${id}-type`,
1168
+ disabled: !canUpdate
644
1169
  }
645
1170
  ) }),
646
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
647
- /* @__PURE__ */ jsxRuntime.jsx(
648
- index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
1171
+ !release.releasedAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1172
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(
1173
+ EntryValidationText,
649
1174
  {
650
- contentTypeUid: contentType.uid,
651
- entryId: entry.id,
652
- locale: locale?.code
1175
+ action: type,
1176
+ schema: contentTypes?.[contentType.uid],
1177
+ components,
1178
+ entry
653
1179
  }
654
- ),
655
- /* @__PURE__ */ jsxRuntime.jsx(
656
- index.ReleaseActionMenu.DeleteReleaseActionItem,
657
- {
658
- releaseId: release.id,
659
- actionId: id
660
- }
661
- )
662
- ] }) }) })
663
- ] })
664
- ] }, id)) })
1180
+ ) }),
1181
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(index.ReleaseActionMenu.Root, { children: [
1182
+ /* @__PURE__ */ jsxRuntime.jsx(
1183
+ index.ReleaseActionMenu.ReleaseActionEntryLinkItem,
1184
+ {
1185
+ contentTypeUid: contentType.uid,
1186
+ entryId: entry.id,
1187
+ locale: locale?.code
1188
+ }
1189
+ ),
1190
+ /* @__PURE__ */ jsxRuntime.jsx(
1191
+ index.ReleaseActionMenu.DeleteReleaseActionItem,
1192
+ {
1193
+ releaseId: release.id,
1194
+ actionId: id
1195
+ }
1196
+ )
1197
+ ] }) }) })
1198
+ ] })
1199
+ ] }, id)
1200
+ ) })
665
1201
  ] })
666
1202
  }
667
1203
  )
@@ -684,7 +1220,7 @@ const ReleaseDetailsPage = () => {
684
1220
  const { releaseId } = reactRouterDom.useParams();
685
1221
  const toggleNotification = helperPlugin.useNotification();
686
1222
  const { formatAPIError } = helperPlugin.useAPIErrorHandler();
687
- const { push } = reactRouterDom.useHistory();
1223
+ const { replace } = reactRouterDom.useHistory();
688
1224
  const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
689
1225
  const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
690
1226
  const {
@@ -708,11 +1244,18 @@ const ReleaseDetailsPage = () => {
708
1244
  }
709
1245
  );
710
1246
  }
711
- const title = isSuccessDetails && data?.data?.name || "";
1247
+ const releaseData = isSuccessDetails && data?.data || null;
1248
+ const title = releaseData?.name || "";
1249
+ const timezone = releaseData?.timezone ?? null;
1250
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1251
+ const date = scheduledAt ? new Date(format__default.default(scheduledAt, "yyyy-MM-dd")) : null;
1252
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
712
1253
  const handleEditRelease = async (values) => {
713
1254
  const response = await updateRelease({
714
1255
  id: releaseId,
715
- name: values.name
1256
+ name: values.name,
1257
+ scheduledAt: values.scheduledAt,
1258
+ timezone: values.timezone
716
1259
  });
717
1260
  if ("data" in response) {
718
1261
  toggleNotification({
@@ -740,7 +1283,7 @@ const ReleaseDetailsPage = () => {
740
1283
  id: releaseId
741
1284
  });
742
1285
  if ("data" in response) {
743
- push("/plugins/content-releases");
1286
+ replace("/plugins/content-releases");
744
1287
  } else if (index.isAxiosError(response.error)) {
745
1288
  toggleNotification({
746
1289
  type: "warning",
@@ -766,7 +1309,14 @@ const ReleaseDetailsPage = () => {
766
1309
  handleClose: toggleEditReleaseModal,
767
1310
  handleSubmit: handleEditRelease,
768
1311
  isLoading: isLoadingDetails || isSubmittingForm,
769
- initialValues: { name: title || "" }
1312
+ initialValues: {
1313
+ name: title || "",
1314
+ scheduledAt,
1315
+ date,
1316
+ time,
1317
+ isScheduled: Boolean(scheduledAt),
1318
+ timezone
1319
+ }
770
1320
  }
771
1321
  ),
772
1322
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -786,247 +1336,6 @@ const ReleaseDetailsPage = () => {
786
1336
  }
787
1337
  );
788
1338
  };
789
- const ReleasesLayout = ({
790
- isLoading,
791
- totalReleases,
792
- onClickAddRelease,
793
- children
794
- }) => {
795
- const { formatMessage } = reactIntl.useIntl();
796
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
797
- /* @__PURE__ */ jsxRuntime.jsx(
798
- designSystem.HeaderLayout,
799
- {
800
- title: formatMessage({
801
- id: "content-releases.pages.Releases.title",
802
- defaultMessage: "Releases"
803
- }),
804
- subtitle: !isLoading && formatMessage(
805
- {
806
- id: "content-releases.pages.Releases.header-subtitle",
807
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
808
- },
809
- { number: totalReleases }
810
- ),
811
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}), onClick: onClickAddRelease, children: formatMessage({
812
- id: "content-releases.header.actions.add-release",
813
- defaultMessage: "New release"
814
- }) }) })
815
- }
816
- ),
817
- children
818
- ] });
819
- };
820
- const LinkCard = styled__default.default(v2.Link)`
821
- display: block;
822
- `;
823
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
824
- const { formatMessage } = reactIntl.useIntl();
825
- if (isError) {
826
- return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
827
- }
828
- if (releases?.length === 0) {
829
- return /* @__PURE__ */ jsxRuntime.jsx(
830
- designSystem.EmptyStateLayout,
831
- {
832
- content: formatMessage(
833
- {
834
- id: "content-releases.page.Releases.tab.emptyEntries",
835
- defaultMessage: "No releases"
836
- },
837
- {
838
- target: sectionTitle
839
- }
840
- ),
841
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
842
- }
843
- );
844
- }
845
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, actions }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.GridItem, { col: 3, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(LinkCard, { href: `content-releases/${id}`, isExternal: false, children: /* @__PURE__ */ jsxRuntime.jsxs(
846
- designSystem.Flex,
847
- {
848
- direction: "column",
849
- justifyContent: "space-between",
850
- padding: 4,
851
- hasRadius: true,
852
- background: "neutral0",
853
- shadow: "tableShadow",
854
- height: "100%",
855
- width: "100%",
856
- alignItems: "start",
857
- gap: 2,
858
- children: [
859
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
860
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
861
- {
862
- id: "content-releases.page.Releases.release-item.entries",
863
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
864
- },
865
- { number: actions.meta.count }
866
- ) })
867
- ]
868
- }
869
- ) }) }, id)) });
870
- };
871
- const INITIAL_FORM_VALUES = {
872
- name: ""
873
- };
874
- const ReleasesPage = () => {
875
- const tabRef = React__namespace.useRef(null);
876
- const location = reactRouterDom.useLocation();
877
- const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
878
- const toggleNotification = helperPlugin.useNotification();
879
- const { formatMessage } = reactIntl.useIntl();
880
- const { push, replace } = reactRouterDom.useHistory();
881
- const { formatAPIError } = helperPlugin.useAPIErrorHandler();
882
- const [{ query }, setQuery] = helperPlugin.useQueryParams();
883
- const response = index.useGetReleasesQuery(query);
884
- const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
885
- const { isLoading, isSuccess, isError } = response;
886
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
887
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
888
- React__namespace.useEffect(() => {
889
- if (location?.state?.errors) {
890
- toggleNotification({
891
- type: "warning",
892
- title: formatMessage({
893
- id: "content-releases.pages.Releases.notification.error.title",
894
- defaultMessage: "Your request could not be processed."
895
- }),
896
- message: formatMessage({
897
- id: "content-releases.pages.Releases.notification.error.message",
898
- defaultMessage: "Please try again or open another release."
899
- })
900
- });
901
- replace({ state: null });
902
- }
903
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
904
- React__namespace.useEffect(() => {
905
- if (tabRef.current) {
906
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
907
- }
908
- }, [activeTabIndex]);
909
- const toggleAddReleaseModal = () => {
910
- setReleaseModalShown((prev) => !prev);
911
- };
912
- if (isLoading) {
913
- return /* @__PURE__ */ jsxRuntime.jsx(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, isLoading: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) }) });
914
- }
915
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
916
- const handleTabChange = (index2) => {
917
- setQuery({
918
- ...query,
919
- page: 1,
920
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
921
- filters: {
922
- releasedAt: {
923
- $notNull: index2 === 0 ? false : true
924
- }
925
- }
926
- });
927
- };
928
- const handleAddRelease = async (values) => {
929
- const response2 = await createRelease({
930
- name: values.name
931
- });
932
- if ("data" in response2) {
933
- toggleNotification({
934
- type: "success",
935
- message: formatMessage({
936
- id: "content-releases.modal.release-created-notification-success",
937
- defaultMessage: "Release created."
938
- })
939
- });
940
- push(`/plugins/content-releases/${response2.data.data.id}`);
941
- } else if (index.isAxiosError(response2.error)) {
942
- toggleNotification({
943
- type: "warning",
944
- message: formatAPIError(response2.error)
945
- });
946
- } else {
947
- toggleNotification({
948
- type: "warning",
949
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
950
- });
951
- }
952
- };
953
- return /* @__PURE__ */ jsxRuntime.jsxs(ReleasesLayout, { onClickAddRelease: toggleAddReleaseModal, totalReleases, children: [
954
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
955
- /* @__PURE__ */ jsxRuntime.jsxs(
956
- designSystem.TabGroup,
957
- {
958
- label: formatMessage({
959
- id: "content-releases.pages.Releases.tab-group.label",
960
- defaultMessage: "Releases list"
961
- }),
962
- variant: "simple",
963
- initialSelectedTabIndex: activeTabIndex,
964
- onTabChange: handleTabChange,
965
- ref: tabRef,
966
- children: [
967
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
968
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
969
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
970
- id: "content-releases.pages.Releases.tab.pending",
971
- defaultMessage: "Pending"
972
- }) }),
973
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
974
- id: "content-releases.pages.Releases.tab.done",
975
- defaultMessage: "Done"
976
- }) })
977
- ] }),
978
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
979
- ] }),
980
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
981
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
982
- ReleasesGrid,
983
- {
984
- sectionTitle: "pending",
985
- releases: response?.currentData?.data,
986
- isError
987
- }
988
- ) }),
989
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
990
- ReleasesGrid,
991
- {
992
- sectionTitle: "done",
993
- releases: response?.currentData?.data,
994
- isError
995
- }
996
- ) })
997
- ] })
998
- ]
999
- }
1000
- ),
1001
- totalReleases > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1002
- /* @__PURE__ */ jsxRuntime.jsx(
1003
- helperPlugin.PageSizeURLQuery,
1004
- {
1005
- options: ["8", "16", "32", "64"],
1006
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1007
- }
1008
- ),
1009
- /* @__PURE__ */ jsxRuntime.jsx(
1010
- helperPlugin.PaginationURLQuery,
1011
- {
1012
- pagination: {
1013
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1014
- }
1015
- }
1016
- )
1017
- ] })
1018
- ] }) }),
1019
- releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
1020
- ReleaseModal,
1021
- {
1022
- handleClose: toggleAddReleaseModal,
1023
- handleSubmit: handleAddRelease,
1024
- isLoading: isSubmittingForm,
1025
- initialValues: INITIAL_FORM_VALUES
1026
- }
1027
- )
1028
- ] });
1029
- };
1030
1339
  const App = () => {
1031
1340
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
1032
1341
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
@@ -1034,4 +1343,4 @@ const App = () => {
1034
1343
  ] }) });
1035
1344
  };
1036
1345
  exports.App = App;
1037
- //# sourceMappingURL=App-_20W9dYa.js.map
1346
+ //# sourceMappingURL=App-p8aKBitd.js.map