@strapi/content-releases 0.0.0-next.d470b4f75cf00f24f440b80300f1c833c322b871 → 0.0.0-next.d54a672641d83234230d3d80d92aaf87f60f8c75

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 (32) hide show
  1. package/LICENSE +17 -1
  2. package/dist/_chunks/{App-1hHIqUoZ.js → App-dLXY5ei3.js} +636 -382
  3. package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
  4. package/dist/_chunks/{App-U6GbyLIE.mjs → App-jrh58sXY.mjs} +647 -394
  5. package/dist/_chunks/App-jrh58sXY.mjs.map +1 -0
  6. package/dist/_chunks/{PurchaseContentReleases-Clm0iACO.mjs → PurchaseContentReleases-3tRbmbY3.mjs} +2 -2
  7. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  8. package/dist/_chunks/{PurchaseContentReleases-YhAPgpG9.js → PurchaseContentReleases-bpIYXOfu.js} +2 -2
  9. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  10. package/dist/_chunks/{en-bDhIlw-B.js → en-HrREghh3.js} +23 -6
  11. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  12. package/dist/_chunks/{en-GqXgfmzl.mjs → en-ltT1TlKQ.mjs} +23 -6
  13. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  14. package/dist/_chunks/{index-l-FvkQlQ.js → index-CVO0Rqdm.js} +385 -20
  15. package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
  16. package/dist/_chunks/{index-gkExFBa0.mjs → index-PiOGBETy.mjs} +401 -36
  17. package/dist/_chunks/index-PiOGBETy.mjs.map +1 -0
  18. package/dist/admin/index.js +1 -1
  19. package/dist/admin/index.mjs +2 -2
  20. package/dist/server/index.js +948 -450
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/index.mjs +947 -450
  23. package/dist/server/index.mjs.map +1 -1
  24. package/package.json +15 -13
  25. package/dist/_chunks/App-1hHIqUoZ.js.map +0 -1
  26. package/dist/_chunks/App-U6GbyLIE.mjs.map +0 -1
  27. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +0 -1
  28. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +0 -1
  29. package/dist/_chunks/en-GqXgfmzl.mjs.map +0 -1
  30. package/dist/_chunks/en-bDhIlw-B.js.map +0 -1
  31. package/dist/_chunks/index-gkExFBa0.mjs.map +0 -1
  32. package/dist/_chunks/index-l-FvkQlQ.js.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-l-FvkQlQ.js");
6
+ const index = require("./index-CVO0Rqdm.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,15 +40,26 @@ 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
47
  name: yup__namespace.string().trim().required(),
44
- // scheduledAt is a date, but we always receive strings from the client
45
48
  scheduledAt: yup__namespace.string().nullable(),
46
- timezone: yup__namespace.string().when("scheduledAt", {
47
- is: (scheduledAt) => !!scheduledAt,
48
- then: yup__namespace.string().required(),
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(),
49
63
  otherwise: yup__namespace.string().nullable()
50
64
  })
51
65
  }).required().noUnknown();
@@ -58,6 +72,22 @@ const ReleaseModal = ({
58
72
  const { formatMessage } = reactIntl.useIntl();
59
73
  const { pathname } = reactRouterDom.useLocation();
60
74
  const isCreatingRelease = pathname === `/plugins/${index.pluginId}`;
75
+ const { timezoneList, systemTimezone = { value: "UTC+00:00-Africa/Abidjan " } } = getTimezones(
76
+ initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : /* @__PURE__ */ new Date()
77
+ );
78
+ const getScheduledTimestamp = (values) => {
79
+ const { date, time, timezone } = values;
80
+ if (!date || !time || !timezone)
81
+ return null;
82
+ const timezoneWithoutOffset = timezone.split("&")[1];
83
+ return dateFnsTz.zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
84
+ };
85
+ const getTimezoneWithOffset = () => {
86
+ const currentTimezone = timezoneList.find(
87
+ (timezone) => timezone.value.split("&")[1] === initialValues.timezone
88
+ );
89
+ return currentTimezone?.value || systemTimezone.value;
90
+ };
61
91
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
62
92
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
63
93
  {
@@ -69,45 +99,130 @@ const ReleaseModal = ({
69
99
  /* @__PURE__ */ jsxRuntime.jsx(
70
100
  formik.Formik,
71
101
  {
72
- validateOnChange: false,
73
- onSubmit: handleSubmit,
74
- initialValues,
102
+ onSubmit: (values) => {
103
+ handleSubmit({
104
+ ...values,
105
+ timezone: values.timezone ? values.timezone.split("&")[1] : null,
106
+ scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
107
+ });
108
+ },
109
+ initialValues: {
110
+ ...initialValues,
111
+ timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
112
+ },
75
113
  validationSchema: RELEASE_SCHEMA,
76
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
77
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
78
- designSystem.TextInput,
79
- {
80
- label: formatMessage({
81
- id: "content-releases.modal.form.input.label.release-name",
82
- defaultMessage: "Name"
83
- }),
84
- name: "name",
85
- value: values.name,
86
- error: errors.name,
87
- onChange: handleChange,
88
- required: true
89
- }
90
- ) }),
114
+ validateOnChange: false,
115
+ children: ({ values, errors, handleChange, setFieldValue }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
116
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
117
+ /* @__PURE__ */ jsxRuntime.jsx(
118
+ designSystem.TextInput,
119
+ {
120
+ label: formatMessage({
121
+ id: "content-releases.modal.form.input.label.release-name",
122
+ defaultMessage: "Name"
123
+ }),
124
+ name: "name",
125
+ value: values.name,
126
+ error: errors.name,
127
+ onChange: handleChange,
128
+ required: true
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "max-content", children: /* @__PURE__ */ jsxRuntime.jsx(
132
+ designSystem.Checkbox,
133
+ {
134
+ name: "isScheduled",
135
+ value: values.isScheduled,
136
+ onChange: (event) => {
137
+ setFieldValue("isScheduled", event.target.checked);
138
+ if (!event.target.checked) {
139
+ setFieldValue("date", null);
140
+ setFieldValue("time", "");
141
+ setFieldValue("timezone", null);
142
+ } else {
143
+ setFieldValue("date", initialValues.date);
144
+ setFieldValue("time", initialValues.time);
145
+ setFieldValue("timezone", initialValues.timezone ?? systemTimezone?.value);
146
+ }
147
+ },
148
+ children: /* @__PURE__ */ jsxRuntime.jsx(
149
+ designSystem.Typography,
150
+ {
151
+ textColor: values.isScheduled ? "primary600" : "neutral800",
152
+ fontWeight: values.isScheduled ? "semiBold" : "regular",
153
+ children: formatMessage({
154
+ id: "modal.form.input.label.schedule-release",
155
+ defaultMessage: "Schedule release"
156
+ })
157
+ }
158
+ )
159
+ }
160
+ ) }),
161
+ values.isScheduled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
162
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "start", children: [
163
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
164
+ designSystem.DatePicker,
165
+ {
166
+ label: formatMessage({
167
+ id: "content-releases.modal.form.input.label.date",
168
+ defaultMessage: "Date"
169
+ }),
170
+ name: "date",
171
+ error: errors.date,
172
+ onChange: (date) => {
173
+ const isoFormatDate = date ? dateFns.formatISO(date, { representation: "date" }) : null;
174
+ setFieldValue("date", isoFormatDate);
175
+ },
176
+ clearLabel: formatMessage({
177
+ id: "content-releases.modal.form.input.clearLabel",
178
+ defaultMessage: "Clear"
179
+ }),
180
+ onClear: () => {
181
+ setFieldValue("date", null);
182
+ },
183
+ selectedDate: values.date || void 0,
184
+ required: true,
185
+ minDate: dateFnsTz.utcToZonedTime(/* @__PURE__ */ new Date(), values.timezone.split("&")[1])
186
+ }
187
+ ) }),
188
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
189
+ designSystem.TimePicker,
190
+ {
191
+ label: formatMessage({
192
+ id: "content-releases.modal.form.input.label.time",
193
+ defaultMessage: "Time"
194
+ }),
195
+ name: "time",
196
+ error: errors.time,
197
+ onChange: (time) => {
198
+ setFieldValue("time", time);
199
+ },
200
+ clearLabel: formatMessage({
201
+ id: "content-releases.modal.form.input.clearLabel",
202
+ defaultMessage: "Clear"
203
+ }),
204
+ onClear: () => {
205
+ setFieldValue("time", "");
206
+ },
207
+ value: values.time || void 0,
208
+ required: true
209
+ }
210
+ ) })
211
+ ] }),
212
+ /* @__PURE__ */ jsxRuntime.jsx(TimezoneComponent, { timezoneOptions: timezoneList })
213
+ ] })
214
+ ] }) }),
91
215
  /* @__PURE__ */ jsxRuntime.jsx(
92
216
  designSystem.ModalFooter,
93
217
  {
94
218
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
95
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
96
- designSystem.Button,
219
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
97
220
  {
98
- name: "submit",
99
- loading: isLoading,
100
- disabled: !values.name || values.name === initialValues.name,
101
- type: "submit",
102
- children: formatMessage(
103
- {
104
- id: "content-releases.modal.form.button.submit",
105
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
106
- },
107
- { isCreatingRelease }
108
- )
109
- }
110
- )
221
+ id: "content-releases.modal.form.button.submit",
222
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
223
+ },
224
+ { isCreatingRelease }
225
+ ) })
111
226
  }
112
227
  )
113
228
  ] })
@@ -115,6 +230,371 @@ const ReleaseModal = ({
115
230
  )
116
231
  ] });
117
232
  };
233
+ const getTimezones = (selectedDate) => {
234
+ const timezoneList = Intl.supportedValuesOf("timeZone").map((timezone) => {
235
+ const utcOffset = index.getTimezoneOffset(timezone, selectedDate);
236
+ return { offset: utcOffset, value: `${utcOffset}&${timezone}` };
237
+ });
238
+ const systemTimezone = timezoneList.find(
239
+ (timezone) => timezone.value.split("&")[1] === Intl.DateTimeFormat().resolvedOptions().timeZone
240
+ );
241
+ return { timezoneList, systemTimezone };
242
+ };
243
+ const TimezoneComponent = ({ timezoneOptions }) => {
244
+ const { values, errors, setFieldValue } = formik.useFormikContext();
245
+ const { formatMessage } = reactIntl.useIntl();
246
+ const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
247
+ React__namespace.useEffect(() => {
248
+ if (values.date) {
249
+ const { timezoneList: timezoneList2 } = getTimezones(new Date(values.date));
250
+ setTimezoneList(timezoneList2);
251
+ const updatedTimezone = values.timezone && timezoneList2.find((tz) => tz.value.split("&")[1] === values.timezone.split("&")[1]);
252
+ if (updatedTimezone) {
253
+ setFieldValue("timezone", updatedTimezone.value);
254
+ }
255
+ }
256
+ }, [setFieldValue, values.date, values.timezone]);
257
+ return /* @__PURE__ */ jsxRuntime.jsx(
258
+ designSystem.Combobox,
259
+ {
260
+ label: formatMessage({
261
+ id: "content-releases.modal.form.input.label.timezone",
262
+ defaultMessage: "Timezone"
263
+ }),
264
+ autocomplete: { type: "list", filter: "contains" },
265
+ name: "timezone",
266
+ value: values.timezone || void 0,
267
+ textValue: values.timezone ? values.timezone.replace(/&/, " ") : void 0,
268
+ onChange: (timezone) => {
269
+ setFieldValue("timezone", timezone);
270
+ },
271
+ onTextValueChange: (timezone) => {
272
+ setFieldValue("timezone", timezone);
273
+ },
274
+ onClear: () => {
275
+ setFieldValue("timezone", "");
276
+ },
277
+ error: errors.timezone,
278
+ required: true,
279
+ children: timezoneList.map((timezone) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: timezone.value, children: timezone.value.replace(/&/, " ") }, timezone.value))
280
+ }
281
+ );
282
+ };
283
+ const LinkCard = styled__default.default(v2.Link)`
284
+ display: block;
285
+ `;
286
+ const RelativeTime = styled__default.default(helperPlugin.RelativeTime)`
287
+ display: inline-block;
288
+ &::first-letter {
289
+ text-transform: uppercase;
290
+ }
291
+ `;
292
+ const getBadgeProps = (status) => {
293
+ let color;
294
+ switch (status) {
295
+ case "ready":
296
+ color = "success";
297
+ break;
298
+ case "blocked":
299
+ color = "warning";
300
+ break;
301
+ case "failed":
302
+ color = "danger";
303
+ break;
304
+ case "done":
305
+ color = "primary";
306
+ break;
307
+ case "empty":
308
+ default:
309
+ color = "neutral";
310
+ }
311
+ return {
312
+ textColor: `${color}600`,
313
+ backgroundColor: `${color}100`,
314
+ borderColor: `${color}200`
315
+ };
316
+ };
317
+ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
318
+ const { formatMessage } = reactIntl.useIntl();
319
+ if (isError) {
320
+ return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
321
+ }
322
+ if (releases?.length === 0) {
323
+ return /* @__PURE__ */ jsxRuntime.jsx(
324
+ designSystem.EmptyStateLayout,
325
+ {
326
+ content: formatMessage(
327
+ {
328
+ id: "content-releases.page.Releases.tab.emptyEntries",
329
+ defaultMessage: "No releases"
330
+ },
331
+ {
332
+ target: sectionTitle
333
+ }
334
+ ),
335
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
336
+ }
337
+ );
338
+ }
339
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid, { gap: 4, children: releases.map(({ id, name, 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(
340
+ designSystem.Flex,
341
+ {
342
+ direction: "column",
343
+ justifyContent: "space-between",
344
+ padding: 4,
345
+ hasRadius: true,
346
+ background: "neutral0",
347
+ shadow: "tableShadow",
348
+ height: "100%",
349
+ width: "100%",
350
+ alignItems: "start",
351
+ gap: 4,
352
+ children: [
353
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", gap: 1, children: [
354
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
355
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: scheduledAt ? /* @__PURE__ */ jsxRuntime.jsx(RelativeTime, { timestamp: new Date(scheduledAt) }) : formatMessage({
356
+ id: "content-releases.pages.Releases.not-scheduled",
357
+ defaultMessage: "Not scheduled"
358
+ }) })
359
+ ] }),
360
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(status), children: status })
361
+ ]
362
+ }
363
+ ) }) }, id)) });
364
+ };
365
+ const StyledAlert = styled__default.default(designSystem.Alert)`
366
+ button {
367
+ display: none;
368
+ }
369
+ p + div {
370
+ margin-left: auto;
371
+ }
372
+ `;
373
+ const INITIAL_FORM_VALUES = {
374
+ name: "",
375
+ date: null,
376
+ time: "",
377
+ isScheduled: true,
378
+ scheduledAt: null,
379
+ timezone: null
380
+ };
381
+ const ReleasesPage = () => {
382
+ const tabRef = React__namespace.useRef(null);
383
+ const location = reactRouterDom.useLocation();
384
+ const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
385
+ const toggleNotification = helperPlugin.useNotification();
386
+ const { formatMessage } = reactIntl.useIntl();
387
+ const { push, replace } = reactRouterDom.useHistory();
388
+ const { formatAPIError } = helperPlugin.useAPIErrorHandler();
389
+ const [{ query }, setQuery] = helperPlugin.useQueryParams();
390
+ const response = index.useGetReleasesQuery(query);
391
+ const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
392
+ const { getFeature } = strapiAdmin.useLicenseLimits();
393
+ const { maximumReleases = 3 } = getFeature("cms-content-releases");
394
+ const { trackUsage } = helperPlugin.useTracking();
395
+ const { isLoading, isSuccess, isError } = response;
396
+ const activeTab = response?.currentData?.meta?.activeTab || "pending";
397
+ const activeTabIndex = ["pending", "done"].indexOf(activeTab);
398
+ React__namespace.useEffect(() => {
399
+ if (location?.state?.errors) {
400
+ toggleNotification({
401
+ type: "warning",
402
+ title: formatMessage({
403
+ id: "content-releases.pages.Releases.notification.error.title",
404
+ defaultMessage: "Your request could not be processed."
405
+ }),
406
+ message: formatMessage({
407
+ id: "content-releases.pages.Releases.notification.error.message",
408
+ defaultMessage: "Please try again or open another release."
409
+ })
410
+ });
411
+ replace({ state: null });
412
+ }
413
+ }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
414
+ React__namespace.useEffect(() => {
415
+ if (tabRef.current) {
416
+ tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
417
+ }
418
+ }, [activeTabIndex]);
419
+ const toggleAddReleaseModal = () => {
420
+ setReleaseModalShown((prev) => !prev);
421
+ };
422
+ if (isLoading) {
423
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
424
+ }
425
+ const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
426
+ const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
427
+ const handleTabChange = (index2) => {
428
+ setQuery({
429
+ ...query,
430
+ page: 1,
431
+ pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
432
+ filters: {
433
+ releasedAt: {
434
+ $notNull: index2 === 0 ? false : true
435
+ }
436
+ }
437
+ });
438
+ };
439
+ const handleAddRelease = async ({ name, scheduledAt, timezone }) => {
440
+ const response2 = await createRelease({
441
+ name,
442
+ scheduledAt,
443
+ timezone
444
+ });
445
+ if ("data" in response2) {
446
+ toggleNotification({
447
+ type: "success",
448
+ message: formatMessage({
449
+ id: "content-releases.modal.release-created-notification-success",
450
+ defaultMessage: "Release created."
451
+ })
452
+ });
453
+ trackUsage("didCreateRelease");
454
+ push(`/plugins/content-releases/${response2.data.data.id}`);
455
+ } else if (index.isAxiosError(response2.error)) {
456
+ toggleNotification({
457
+ type: "warning",
458
+ message: formatAPIError(response2.error)
459
+ });
460
+ } else {
461
+ toggleNotification({
462
+ type: "warning",
463
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
464
+ });
465
+ }
466
+ };
467
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx(
469
+ designSystem.HeaderLayout,
470
+ {
471
+ title: formatMessage({
472
+ id: "content-releases.pages.Releases.title",
473
+ defaultMessage: "Releases"
474
+ }),
475
+ subtitle: formatMessage({
476
+ id: "content-releases.pages.Releases.header-subtitle",
477
+ defaultMessage: "Create and manage content updates"
478
+ }),
479
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
480
+ designSystem.Button,
481
+ {
482
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
483
+ onClick: toggleAddReleaseModal,
484
+ disabled: hasReachedMaximumPendingReleases,
485
+ children: formatMessage({
486
+ id: "content-releases.header.actions.add-release",
487
+ defaultMessage: "New release"
488
+ })
489
+ }
490
+ ) })
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
494
+ hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
495
+ StyledAlert,
496
+ {
497
+ marginBottom: 6,
498
+ action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
499
+ id: "content-releases.pages.Releases.max-limit-reached.action",
500
+ defaultMessage: "Explore plans"
501
+ }) }),
502
+ title: formatMessage(
503
+ {
504
+ id: "content-releases.pages.Releases.max-limit-reached.title",
505
+ defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
506
+ },
507
+ { number: maximumReleases }
508
+ ),
509
+ onClose: () => {
510
+ },
511
+ closeLabel: "",
512
+ children: formatMessage({
513
+ id: "content-releases.pages.Releases.max-limit-reached.message",
514
+ defaultMessage: "Upgrade to manage an unlimited number of releases."
515
+ })
516
+ }
517
+ ),
518
+ /* @__PURE__ */ jsxRuntime.jsxs(
519
+ designSystem.TabGroup,
520
+ {
521
+ label: formatMessage({
522
+ id: "content-releases.pages.Releases.tab-group.label",
523
+ defaultMessage: "Releases list"
524
+ }),
525
+ variant: "simple",
526
+ initialSelectedTabIndex: activeTabIndex,
527
+ onTabChange: handleTabChange,
528
+ ref: tabRef,
529
+ children: [
530
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
531
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
532
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage(
533
+ {
534
+ id: "content-releases.pages.Releases.tab.pending",
535
+ defaultMessage: "Pending ({count})"
536
+ },
537
+ {
538
+ count: totalPendingReleases
539
+ }
540
+ ) }),
541
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
542
+ id: "content-releases.pages.Releases.tab.done",
543
+ defaultMessage: "Done"
544
+ }) })
545
+ ] }),
546
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
547
+ ] }),
548
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
549
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
550
+ ReleasesGrid,
551
+ {
552
+ sectionTitle: "pending",
553
+ releases: response?.currentData?.data,
554
+ isError
555
+ }
556
+ ) }),
557
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
558
+ ReleasesGrid,
559
+ {
560
+ sectionTitle: "done",
561
+ releases: response?.currentData?.data,
562
+ isError
563
+ }
564
+ ) })
565
+ ] })
566
+ ]
567
+ }
568
+ ),
569
+ response.currentData?.meta?.pagination?.total ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
570
+ /* @__PURE__ */ jsxRuntime.jsx(
571
+ helperPlugin.PageSizeURLQuery,
572
+ {
573
+ options: ["8", "16", "32", "64"],
574
+ defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
575
+ }
576
+ ),
577
+ /* @__PURE__ */ jsxRuntime.jsx(
578
+ helperPlugin.PaginationURLQuery,
579
+ {
580
+ pagination: {
581
+ pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
582
+ }
583
+ }
584
+ )
585
+ ] }) : null
586
+ ] }) }),
587
+ releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
588
+ ReleaseModal,
589
+ {
590
+ handleClose: toggleAddReleaseModal,
591
+ handleSubmit: handleAddRelease,
592
+ isLoading: isSubmittingForm,
593
+ initialValues: INITIAL_FORM_VALUES
594
+ }
595
+ )
596
+ ] });
597
+ };
118
598
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
119
599
  align-self: stretch;
120
600
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
@@ -128,6 +608,10 @@ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
128
608
  span {
129
609
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
130
610
  }
611
+
612
+ &:hover {
613
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
614
+ }
131
615
  `;
132
616
  const PencilIcon = styled__default.default(icons.Pencil)`
133
617
  width: ${({ theme }) => theme.spaces[3]};
@@ -194,7 +678,7 @@ const ReleaseDetailsLayout = ({
194
678
  toggleWarningSubmit,
195
679
  children
196
680
  }) => {
197
- const { formatMessage } = reactIntl.useIntl();
681
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
198
682
  const { releaseId } = reactRouterDom.useParams();
199
683
  const {
200
684
  data,
@@ -240,7 +724,12 @@ const ReleaseDetailsLayout = ({
240
724
  }
241
725
  };
242
726
  const handleRefresh = () => {
243
- dispatch(index.releaseApi.util.invalidateTags([{ type: "ReleaseAction", id: "LIST" }]));
727
+ dispatch(
728
+ index.releaseApi.util.invalidateTags([
729
+ { type: "ReleaseAction", id: "LIST" },
730
+ { type: "Release", id: releaseId }
731
+ ])
732
+ );
244
733
  };
245
734
  const getCreatedByUser = () => {
246
735
  if (!release?.createdBy) {
@@ -276,18 +765,43 @@ const ReleaseDetailsLayout = ({
276
765
  }
277
766
  const totalEntries = release.actions.meta.count || 0;
278
767
  const hasCreatedByUser = Boolean(getCreatedByUser());
768
+ const isScheduled = release.scheduledAt && release.timezone;
769
+ const numberOfEntriesText = formatMessage(
770
+ {
771
+ id: "content-releases.pages.Details.header-subtitle",
772
+ defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
773
+ },
774
+ { number: totalEntries }
775
+ );
776
+ const scheduledText = isScheduled ? formatMessage(
777
+ {
778
+ id: "content-releases.pages.ReleaseDetails.header-subtitle.scheduled",
779
+ defaultMessage: "Scheduled for {date} at {time} ({offset})"
780
+ },
781
+ {
782
+ date: formatDate(new Date(release.scheduledAt), {
783
+ weekday: "long",
784
+ day: "numeric",
785
+ month: "long",
786
+ year: "numeric",
787
+ timeZone: release.timezone
788
+ }),
789
+ time: formatTime(new Date(release.scheduledAt), {
790
+ timeZone: release.timezone,
791
+ hourCycle: "h23"
792
+ }),
793
+ offset: index.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
794
+ }
795
+ ) : "";
279
796
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
280
797
  /* @__PURE__ */ jsxRuntime.jsx(
281
798
  designSystem.HeaderLayout,
282
799
  {
283
800
  title: release.name,
284
- subtitle: formatMessage(
285
- {
286
- id: "content-releases.pages.Details.header-subtitle",
287
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
288
- },
289
- { number: totalEntries }
290
- ),
801
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, lineHeight: 6, children: [
802
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "epsilon", children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : "") }),
803
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...getBadgeProps(release.status), children: release.status })
804
+ ] }),
291
805
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
292
806
  id: "global.back",
293
807
  defaultMessage: "Back"
@@ -318,42 +832,28 @@ const ReleaseDetailsLayout = ({
318
832
  padding: 1,
319
833
  width: "100%",
320
834
  children: [
321
- /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(
322
- designSystem.Flex,
323
- {
324
- paddingTop: 2,
325
- paddingBottom: 2,
326
- alignItems: "center",
327
- gap: 2,
328
- hasRadius: true,
329
- width: "100%",
330
- children: [
331
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
332
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
333
- id: "content-releases.header.actions.edit",
334
- defaultMessage: "Edit"
335
- }) })
336
- ]
337
- }
338
- ) }),
339
- /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canDelete, onSelect: toggleWarningSubmit, children: /* @__PURE__ */ jsxRuntime.jsxs(
340
- designSystem.Flex,
835
+ /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
836
+ /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
837
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
838
+ id: "content-releases.header.actions.edit",
839
+ defaultMessage: "Edit"
840
+ }) })
841
+ ] }) }),
842
+ /* @__PURE__ */ jsxRuntime.jsx(
843
+ StyledMenuItem,
341
844
  {
342
- paddingTop: 2,
343
- paddingBottom: 2,
344
- alignItems: "center",
345
- gap: 2,
346
- hasRadius: true,
347
- width: "100%",
348
- children: [
845
+ disabled: !canDelete,
846
+ onSelect: toggleWarningSubmit,
847
+ variant: "danger",
848
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, hasRadius: true, width: "100%", children: [
349
849
  /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
350
850
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
351
851
  id: "content-releases.header.actions.delete",
352
852
  defaultMessage: "Delete"
353
853
  }) })
354
- ]
854
+ ] })
355
855
  }
356
- ) })
856
+ )
357
857
  ]
358
858
  }
359
859
  ),
@@ -410,6 +910,7 @@ const ReleaseDetailsLayout = ({
410
910
  ] });
411
911
  };
412
912
  const GROUP_BY_OPTIONS = ["contentType", "locale", "action"];
913
+ const GROUP_BY_OPTIONS_NO_LOCALE = ["contentType", "action"];
413
914
  const getGroupByOptionLabel = (value) => {
414
915
  if (value === "locale") {
415
916
  return {
@@ -428,6 +929,21 @@ const getGroupByOptionLabel = (value) => {
428
929
  defaultMessage: "Content-Types"
429
930
  };
430
931
  };
932
+ const DEFAULT_RELEASE_DETAILS_HEADER = [
933
+ {
934
+ key: "__name__",
935
+ fieldSchema: { type: "string" },
936
+ metadatas: {
937
+ label: {
938
+ id: "content-releases.page.ReleaseDetails.table.header.label.name",
939
+ defaultMessage: "name"
940
+ },
941
+ searchable: false,
942
+ sortable: false
943
+ },
944
+ name: "name"
945
+ }
946
+ ];
431
947
  const ReleaseDetailsBody = () => {
432
948
  const { formatMessage } = reactIntl.useIntl();
433
949
  const { releaseId } = reactRouterDom.useParams();
@@ -443,6 +959,17 @@ const ReleaseDetailsBody = () => {
443
959
  const {
444
960
  allowedActions: { canUpdate }
445
961
  } = helperPlugin.useRBAC(index.PERMISSIONS);
962
+ const { runHookWaterfall } = helperPlugin.useStrapiApp();
963
+ const {
964
+ displayedHeaders,
965
+ hasI18nEnabled
966
+ } = runHookWaterfall(
967
+ "ContentReleases/pages/ReleaseDetails/add-locale-in-releases",
968
+ {
969
+ displayedHeaders: DEFAULT_RELEASE_DETAILS_HEADER,
970
+ hasI18nEnabled: false
971
+ }
972
+ );
446
973
  const release = releaseData?.data;
447
974
  const selectedGroupBy = query?.groupBy || "contentType";
448
975
  const {
@@ -544,6 +1071,7 @@ const ReleaseDetailsBody = () => {
544
1071
  }
545
1072
  ) });
546
1073
  }
1074
+ const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
547
1075
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, direction: "column", alignItems: "stretch", children: [
548
1076
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(
549
1077
  designSystem.SingleSelect,
@@ -563,7 +1091,7 @@ const ReleaseDetailsBody = () => {
563
1091
  ),
564
1092
  value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
565
1093
  onChange: (value) => setQuery({ groupBy: value }),
566
- children: GROUP_BY_OPTIONS.map((option) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: option, children: formatMessage(getGroupByOptionLabel(option)) }, option))
1094
+ children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: option, children: formatMessage(getGroupByOptionLabel(option)) }, option))
567
1095
  }
568
1096
  ) }),
569
1097
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
@@ -580,28 +1108,15 @@ const ReleaseDetailsBody = () => {
580
1108
  isFetching,
581
1109
  children: /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Content, { children: [
582
1110
  /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Head, { children: [
583
- /* @__PURE__ */ jsxRuntime.jsx(
584
- helperPlugin.Table.HeaderCell,
585
- {
586
- fieldSchemaType: "string",
587
- label: formatMessage({
588
- id: "content-releases.page.ReleaseDetails.table.header.label.name",
589
- defaultMessage: "name"
590
- }),
591
- name: "name"
592
- }
593
- ),
594
- /* @__PURE__ */ jsxRuntime.jsx(
595
- helperPlugin.Table.HeaderCell,
596
- {
597
- fieldSchemaType: "string",
598
- label: formatMessage({
599
- id: "content-releases.page.ReleaseDetails.table.header.label.locale",
600
- defaultMessage: "locale"
601
- }),
602
- name: "locale"
603
- }
604
- ),
1111
+ displayedHeaders.map(({ key: key2, fieldSchema, metadatas, name }) => /* @__PURE__ */ jsxRuntime.jsx(
1112
+ helperPlugin.Table.HeaderCell,
1113
+ {
1114
+ fieldSchemaType: fieldSchema.type,
1115
+ label: formatMessage(metadatas.label),
1116
+ name
1117
+ },
1118
+ key2
1119
+ )),
605
1120
  /* @__PURE__ */ jsxRuntime.jsx(
606
1121
  helperPlugin.Table.HeaderCell,
607
1122
  {
@@ -640,7 +1155,7 @@ const ReleaseDetailsBody = () => {
640
1155
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
641
1156
  ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
642
1157
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
643
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
1158
+ hasI18nEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: `${locale?.name ? locale.name : "-"}` }) }),
644
1159
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
645
1160
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
646
1161
  {
@@ -712,7 +1227,7 @@ const ReleaseDetailsPage = () => {
712
1227
  const { releaseId } = reactRouterDom.useParams();
713
1228
  const toggleNotification = helperPlugin.useNotification();
714
1229
  const { formatAPIError } = helperPlugin.useAPIErrorHandler();
715
- const { push } = reactRouterDom.useHistory();
1230
+ const { replace } = reactRouterDom.useHistory();
716
1231
  const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
717
1232
  const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
718
1233
  const {
@@ -736,11 +1251,18 @@ const ReleaseDetailsPage = () => {
736
1251
  }
737
1252
  );
738
1253
  }
739
- const title = isSuccessDetails && data?.data?.name || "";
1254
+ const releaseData = isSuccessDetails && data?.data || null;
1255
+ const title = releaseData?.name || "";
1256
+ const timezone = releaseData?.timezone ?? null;
1257
+ const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
1258
+ const date = scheduledAt ? format__default.default(scheduledAt, "yyyy-MM-dd") : null;
1259
+ const time = scheduledAt ? format__default.default(scheduledAt, "HH:mm") : "";
740
1260
  const handleEditRelease = async (values) => {
741
1261
  const response = await updateRelease({
742
1262
  id: releaseId,
743
- name: values.name
1263
+ name: values.name,
1264
+ scheduledAt: values.scheduledAt,
1265
+ timezone: values.timezone
744
1266
  });
745
1267
  if ("data" in response) {
746
1268
  toggleNotification({
@@ -750,6 +1272,7 @@ const ReleaseDetailsPage = () => {
750
1272
  defaultMessage: "Release updated."
751
1273
  })
752
1274
  });
1275
+ toggleEditReleaseModal();
753
1276
  } else if (index.isAxiosError(response.error)) {
754
1277
  toggleNotification({
755
1278
  type: "warning",
@@ -761,14 +1284,13 @@ const ReleaseDetailsPage = () => {
761
1284
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
762
1285
  });
763
1286
  }
764
- toggleEditReleaseModal();
765
1287
  };
766
1288
  const handleDeleteRelease = async () => {
767
1289
  const response = await deleteRelease({
768
1290
  id: releaseId
769
1291
  });
770
1292
  if ("data" in response) {
771
- push("/plugins/content-releases");
1293
+ replace("/plugins/content-releases");
772
1294
  } else if (index.isAxiosError(response.error)) {
773
1295
  toggleNotification({
774
1296
  type: "warning",
@@ -794,7 +1316,14 @@ const ReleaseDetailsPage = () => {
794
1316
  handleClose: toggleEditReleaseModal,
795
1317
  handleSubmit: handleEditRelease,
796
1318
  isLoading: isLoadingDetails || isSubmittingForm,
797
- initialValues: { name: title || "" }
1319
+ initialValues: {
1320
+ name: title || "",
1321
+ scheduledAt,
1322
+ date,
1323
+ time,
1324
+ isScheduled: Boolean(scheduledAt),
1325
+ timezone
1326
+ }
798
1327
  }
799
1328
  ),
800
1329
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -814,281 +1343,6 @@ const ReleaseDetailsPage = () => {
814
1343
  }
815
1344
  );
816
1345
  };
817
- const LinkCard = styled__default.default(v2.Link)`
818
- display: block;
819
- `;
820
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
821
- const { formatMessage } = reactIntl.useIntl();
822
- if (isError) {
823
- return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
824
- }
825
- if (releases?.length === 0) {
826
- return /* @__PURE__ */ jsxRuntime.jsx(
827
- designSystem.EmptyStateLayout,
828
- {
829
- content: formatMessage(
830
- {
831
- id: "content-releases.page.Releases.tab.emptyEntries",
832
- defaultMessage: "No releases"
833
- },
834
- {
835
- target: sectionTitle
836
- }
837
- ),
838
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
839
- }
840
- );
841
- }
842
- 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(
843
- designSystem.Flex,
844
- {
845
- direction: "column",
846
- justifyContent: "space-between",
847
- padding: 4,
848
- hasRadius: true,
849
- background: "neutral0",
850
- shadow: "tableShadow",
851
- height: "100%",
852
- width: "100%",
853
- alignItems: "start",
854
- gap: 2,
855
- children: [
856
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
857
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
858
- {
859
- id: "content-releases.page.Releases.release-item.entries",
860
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
861
- },
862
- { number: actions.meta.count }
863
- ) })
864
- ]
865
- }
866
- ) }) }, id)) });
867
- };
868
- const StyledAlert = styled__default.default(designSystem.Alert)`
869
- button {
870
- display: none;
871
- }
872
- p + div {
873
- margin-left: auto;
874
- }
875
- `;
876
- const INITIAL_FORM_VALUES = {
877
- name: ""
878
- };
879
- const ReleasesPage = () => {
880
- const tabRef = React__namespace.useRef(null);
881
- const location = reactRouterDom.useLocation();
882
- const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
883
- const toggleNotification = helperPlugin.useNotification();
884
- const { formatMessage } = reactIntl.useIntl();
885
- const { push, replace } = reactRouterDom.useHistory();
886
- const { formatAPIError } = helperPlugin.useAPIErrorHandler();
887
- const [{ query }, setQuery] = helperPlugin.useQueryParams();
888
- const response = index.useGetReleasesQuery(query);
889
- const [createRelease, { isLoading: isSubmittingForm }] = index.useCreateReleaseMutation();
890
- const { getFeature } = strapiAdmin.useLicenseLimits();
891
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
892
- const { trackUsage } = helperPlugin.useTracking();
893
- const { isLoading, isSuccess, isError } = response;
894
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
895
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
896
- React__namespace.useEffect(() => {
897
- if (location?.state?.errors) {
898
- toggleNotification({
899
- type: "warning",
900
- title: formatMessage({
901
- id: "content-releases.pages.Releases.notification.error.title",
902
- defaultMessage: "Your request could not be processed."
903
- }),
904
- message: formatMessage({
905
- id: "content-releases.pages.Releases.notification.error.message",
906
- defaultMessage: "Please try again or open another release."
907
- })
908
- });
909
- replace({ state: null });
910
- }
911
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
912
- React__namespace.useEffect(() => {
913
- if (tabRef.current) {
914
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
915
- }
916
- }, [activeTabIndex]);
917
- const toggleAddReleaseModal = () => {
918
- setReleaseModalShown((prev) => !prev);
919
- };
920
- if (isLoading) {
921
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
922
- }
923
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
924
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
925
- const handleTabChange = (index2) => {
926
- setQuery({
927
- ...query,
928
- page: 1,
929
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
930
- filters: {
931
- releasedAt: {
932
- $notNull: index2 === 0 ? false : true
933
- }
934
- }
935
- });
936
- };
937
- const handleAddRelease = async (values) => {
938
- const response2 = await createRelease({
939
- name: values.name
940
- });
941
- if ("data" in response2) {
942
- toggleNotification({
943
- type: "success",
944
- message: formatMessage({
945
- id: "content-releases.modal.release-created-notification-success",
946
- defaultMessage: "Release created."
947
- })
948
- });
949
- trackUsage("didCreateRelease");
950
- push(`/plugins/content-releases/${response2.data.data.id}`);
951
- } else if (index.isAxiosError(response2.error)) {
952
- toggleNotification({
953
- type: "warning",
954
- message: formatAPIError(response2.error)
955
- });
956
- } else {
957
- toggleNotification({
958
- type: "warning",
959
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
960
- });
961
- }
962
- };
963
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
964
- /* @__PURE__ */ jsxRuntime.jsx(
965
- designSystem.HeaderLayout,
966
- {
967
- title: formatMessage({
968
- id: "content-releases.pages.Releases.title",
969
- defaultMessage: "Releases"
970
- }),
971
- subtitle: formatMessage(
972
- {
973
- id: "content-releases.pages.Releases.header-subtitle",
974
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
975
- },
976
- { number: totalReleases }
977
- ),
978
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
979
- designSystem.Button,
980
- {
981
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
982
- onClick: toggleAddReleaseModal,
983
- disabled: hasReachedMaximumPendingReleases,
984
- children: formatMessage({
985
- id: "content-releases.header.actions.add-release",
986
- defaultMessage: "New release"
987
- })
988
- }
989
- ) })
990
- }
991
- ),
992
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
993
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
994
- StyledAlert,
995
- {
996
- marginBottom: 6,
997
- action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
998
- id: "content-releases.pages.Releases.max-limit-reached.action",
999
- defaultMessage: "Explore plans"
1000
- }) }),
1001
- title: formatMessage(
1002
- {
1003
- id: "content-releases.pages.Releases.max-limit-reached.title",
1004
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1005
- },
1006
- { number: maximumReleases }
1007
- ),
1008
- onClose: () => {
1009
- },
1010
- closeLabel: "",
1011
- children: formatMessage({
1012
- id: "content-releases.pages.Releases.max-limit-reached.message",
1013
- defaultMessage: "Upgrade to manage an unlimited number of releases."
1014
- })
1015
- }
1016
- ),
1017
- /* @__PURE__ */ jsxRuntime.jsxs(
1018
- designSystem.TabGroup,
1019
- {
1020
- label: formatMessage({
1021
- id: "content-releases.pages.Releases.tab-group.label",
1022
- defaultMessage: "Releases list"
1023
- }),
1024
- variant: "simple",
1025
- initialSelectedTabIndex: activeTabIndex,
1026
- onTabChange: handleTabChange,
1027
- ref: tabRef,
1028
- children: [
1029
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
1030
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
1031
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
1032
- id: "content-releases.pages.Releases.tab.pending",
1033
- defaultMessage: "Pending"
1034
- }) }),
1035
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
1036
- id: "content-releases.pages.Releases.tab.done",
1037
- defaultMessage: "Done"
1038
- }) })
1039
- ] }),
1040
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
1041
- ] }),
1042
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
1043
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
1044
- ReleasesGrid,
1045
- {
1046
- sectionTitle: "pending",
1047
- releases: response?.currentData?.data,
1048
- isError
1049
- }
1050
- ) }),
1051
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
1052
- ReleasesGrid,
1053
- {
1054
- sectionTitle: "done",
1055
- releases: response?.currentData?.data,
1056
- isError
1057
- }
1058
- ) })
1059
- ] })
1060
- ]
1061
- }
1062
- ),
1063
- totalReleases > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1064
- /* @__PURE__ */ jsxRuntime.jsx(
1065
- helperPlugin.PageSizeURLQuery,
1066
- {
1067
- options: ["8", "16", "32", "64"],
1068
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1069
- }
1070
- ),
1071
- /* @__PURE__ */ jsxRuntime.jsx(
1072
- helperPlugin.PaginationURLQuery,
1073
- {
1074
- pagination: {
1075
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1076
- }
1077
- }
1078
- )
1079
- ] })
1080
- ] }) }),
1081
- releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
1082
- ReleaseModal,
1083
- {
1084
- handleClose: toggleAddReleaseModal,
1085
- handleSubmit: handleAddRelease,
1086
- isLoading: isSubmittingForm,
1087
- initialValues: INITIAL_FORM_VALUES
1088
- }
1089
- )
1090
- ] });
1091
- };
1092
1346
  const App = () => {
1093
1347
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
1094
1348
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
@@ -1096,4 +1350,4 @@ const App = () => {
1096
1350
  ] }) });
1097
1351
  };
1098
1352
  exports.App = App;
1099
- //# sourceMappingURL=App-1hHIqUoZ.js.map
1353
+ //# sourceMappingURL=App-dLXY5ei3.js.map