@strapi/content-releases 0.0.0-next.aa7c7ec6724534e157d8a23fe85ee8318dabbf37 → 0.0.0-next.b11829e6c6aacb45bc1ec2f341609d04d88080d2

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-pspKUC-W.js → App-dLXY5ei3.js} +639 -380
  2. package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
  3. package/dist/_chunks/{App-8FCxPK8-.mjs → App-jrh58sXY.mjs} +650 -392
  4. package/dist/_chunks/App-jrh58sXY.mjs.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  9. package/dist/_chunks/{en-r9YocBH0.js → en-HrREghh3.js} +24 -5
  10. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  11. package/dist/_chunks/{en-m9eTk4UF.mjs → en-ltT1TlKQ.mjs} +24 -5
  12. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  13. package/dist/_chunks/{index-nGaPcY9m.js → index-CVO0Rqdm.js} +398 -19
  14. package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
  15. package/dist/_chunks/{index-8aK7GzI5.mjs → index-PiOGBETy.mjs} +414 -35
  16. package/dist/_chunks/index-PiOGBETy.mjs.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 +953 -443
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +952 -443
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +14 -12
  24. package/dist/_chunks/App-8FCxPK8-.mjs.map +0 -1
  25. package/dist/_chunks/App-pspKUC-W.js.map +0 -1
  26. package/dist/_chunks/en-m9eTk4UF.mjs.map +0 -1
  27. package/dist/_chunks/en-r9YocBH0.js.map +0 -1
  28. package/dist/_chunks/index-8aK7GzI5.mjs.map +0 -1
  29. package/dist/_chunks/index-nGaPcY9m.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-nGaPcY9m.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,12 +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
47
  name: yup__namespace.string().trim().required(),
44
- // scheduledAt is a date, but we always receive strings from the client
45
- scheduledAt: yup__namespace.string().nullable()
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
+ })
46
65
  }).required().noUnknown();
47
66
  const ReleaseModal = ({
48
67
  handleClose,
@@ -53,6 +72,22 @@ const ReleaseModal = ({
53
72
  const { formatMessage } = reactIntl.useIntl();
54
73
  const { pathname } = reactRouterDom.useLocation();
55
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
+ };
56
91
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
57
92
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage(
58
93
  {
@@ -64,45 +99,130 @@ const ReleaseModal = ({
64
99
  /* @__PURE__ */ jsxRuntime.jsx(
65
100
  formik.Formik,
66
101
  {
67
- validateOnChange: false,
68
- onSubmit: handleSubmit,
69
- 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
+ },
70
113
  validationSchema: RELEASE_SCHEMA,
71
- children: ({ values, errors, handleChange }) => /* @__PURE__ */ jsxRuntime.jsxs(formik.Form, { children: [
72
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsx(
73
- designSystem.TextInput,
74
- {
75
- label: formatMessage({
76
- id: "content-releases.modal.form.input.label.release-name",
77
- defaultMessage: "Name"
78
- }),
79
- name: "name",
80
- value: values.name,
81
- error: errors.name,
82
- onChange: handleChange,
83
- required: true
84
- }
85
- ) }),
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
+ ] }) }),
86
215
  /* @__PURE__ */ jsxRuntime.jsx(
87
216
  designSystem.ModalFooter,
88
217
  {
89
218
  startActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({ id: "cancel", defaultMessage: "Cancel" }) }),
90
- endActions: /* @__PURE__ */ jsxRuntime.jsx(
91
- designSystem.Button,
219
+ endActions: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { name: "submit", loading: isLoading, type: "submit", children: formatMessage(
92
220
  {
93
- name: "submit",
94
- loading: isLoading,
95
- disabled: !values.name || values.name === initialValues.name,
96
- type: "submit",
97
- children: formatMessage(
98
- {
99
- id: "content-releases.modal.form.button.submit",
100
- defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
101
- },
102
- { isCreatingRelease }
103
- )
104
- }
105
- )
221
+ id: "content-releases.modal.form.button.submit",
222
+ defaultMessage: "{isCreatingRelease, select, true {Continue} other {Save}}"
223
+ },
224
+ { isCreatingRelease }
225
+ ) })
106
226
  }
107
227
  )
108
228
  ] })
@@ -110,6 +230,371 @@ const ReleaseModal = ({
110
230
  )
111
231
  ] });
112
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
+ };
113
598
  const ReleaseInfoWrapper = styled__default.default(designSystem.Flex)`
114
599
  align-self: stretch;
115
600
  border-bottom-right-radius: ${({ theme }) => theme.borderRadius};
@@ -123,6 +608,10 @@ const StyledMenuItem = styled__default.default(v2.Menu.Item)`
123
608
  span {
124
609
  color: ${({ theme, disabled }) => disabled && theme.colors.neutral500};
125
610
  }
611
+
612
+ &:hover {
613
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
614
+ }
126
615
  `;
127
616
  const PencilIcon = styled__default.default(icons.Pencil)`
128
617
  width: ${({ theme }) => theme.spaces[3]};
@@ -189,7 +678,7 @@ const ReleaseDetailsLayout = ({
189
678
  toggleWarningSubmit,
190
679
  children
191
680
  }) => {
192
- const { formatMessage } = reactIntl.useIntl();
681
+ const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
193
682
  const { releaseId } = reactRouterDom.useParams();
194
683
  const {
195
684
  data,
@@ -235,7 +724,12 @@ const ReleaseDetailsLayout = ({
235
724
  }
236
725
  };
237
726
  const handleRefresh = () => {
238
- 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
+ );
239
733
  };
240
734
  const getCreatedByUser = () => {
241
735
  if (!release?.createdBy) {
@@ -271,18 +765,43 @@ const ReleaseDetailsLayout = ({
271
765
  }
272
766
  const totalEntries = release.actions.meta.count || 0;
273
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
+ ) : "";
274
796
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoadingDetails, children: [
275
797
  /* @__PURE__ */ jsxRuntime.jsx(
276
798
  designSystem.HeaderLayout,
277
799
  {
278
800
  title: release.name,
279
- subtitle: formatMessage(
280
- {
281
- id: "content-releases.pages.Details.header-subtitle",
282
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
283
- },
284
- { number: totalEntries }
285
- ),
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
+ ] }),
286
805
  navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), to: "/plugins/content-releases", children: formatMessage({
287
806
  id: "global.back",
288
807
  defaultMessage: "Back"
@@ -313,42 +832,28 @@ const ReleaseDetailsLayout = ({
313
832
  padding: 1,
314
833
  width: "100%",
315
834
  children: [
316
- /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canUpdate, onSelect: toggleEditReleaseModal, children: /* @__PURE__ */ jsxRuntime.jsxs(
317
- designSystem.Flex,
318
- {
319
- paddingTop: 2,
320
- paddingBottom: 2,
321
- alignItems: "center",
322
- gap: 2,
323
- hasRadius: true,
324
- width: "100%",
325
- children: [
326
- /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, {}),
327
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: formatMessage({
328
- id: "content-releases.header.actions.edit",
329
- defaultMessage: "Edit"
330
- }) })
331
- ]
332
- }
333
- ) }),
334
- /* @__PURE__ */ jsxRuntime.jsx(StyledMenuItem, { disabled: !canDelete, onSelect: toggleWarningSubmit, children: /* @__PURE__ */ jsxRuntime.jsxs(
335
- 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,
336
844
  {
337
- paddingTop: 2,
338
- paddingBottom: 2,
339
- alignItems: "center",
340
- gap: 2,
341
- hasRadius: true,
342
- width: "100%",
343
- 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: [
344
849
  /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}),
345
850
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, textColor: "danger600", children: formatMessage({
346
851
  id: "content-releases.header.actions.delete",
347
852
  defaultMessage: "Delete"
348
853
  }) })
349
- ]
854
+ ] })
350
855
  }
351
- ) })
856
+ )
352
857
  ]
353
858
  }
354
859
  ),
@@ -405,6 +910,7 @@ const ReleaseDetailsLayout = ({
405
910
  ] });
406
911
  };
407
912
  const GROUP_BY_OPTIONS = ["contentType", "locale", "action"];
913
+ const GROUP_BY_OPTIONS_NO_LOCALE = ["contentType", "action"];
408
914
  const getGroupByOptionLabel = (value) => {
409
915
  if (value === "locale") {
410
916
  return {
@@ -423,6 +929,21 @@ const getGroupByOptionLabel = (value) => {
423
929
  defaultMessage: "Content-Types"
424
930
  };
425
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
+ ];
426
947
  const ReleaseDetailsBody = () => {
427
948
  const { formatMessage } = reactIntl.useIntl();
428
949
  const { releaseId } = reactRouterDom.useParams();
@@ -438,6 +959,17 @@ const ReleaseDetailsBody = () => {
438
959
  const {
439
960
  allowedActions: { canUpdate }
440
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
+ );
441
973
  const release = releaseData?.data;
442
974
  const selectedGroupBy = query?.groupBy || "contentType";
443
975
  const {
@@ -539,6 +1071,7 @@ const ReleaseDetailsBody = () => {
539
1071
  }
540
1072
  ) });
541
1073
  }
1074
+ const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
542
1075
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, direction: "column", alignItems: "stretch", children: [
543
1076
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(
544
1077
  designSystem.SingleSelect,
@@ -558,7 +1091,7 @@ const ReleaseDetailsBody = () => {
558
1091
  ),
559
1092
  value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
560
1093
  onChange: (value) => setQuery({ groupBy: value }),
561
- 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))
562
1095
  }
563
1096
  ) }),
564
1097
  Object.keys(releaseActions).map((key) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, direction: "column", alignItems: "stretch", children: [
@@ -575,28 +1108,15 @@ const ReleaseDetailsBody = () => {
575
1108
  isFetching,
576
1109
  children: /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Content, { children: [
577
1110
  /* @__PURE__ */ jsxRuntime.jsxs(helperPlugin.Table.Head, { children: [
578
- /* @__PURE__ */ jsxRuntime.jsx(
579
- helperPlugin.Table.HeaderCell,
580
- {
581
- fieldSchemaType: "string",
582
- label: formatMessage({
583
- id: "content-releases.page.ReleaseDetails.table.header.label.name",
584
- defaultMessage: "name"
585
- }),
586
- name: "name"
587
- }
588
- ),
589
- /* @__PURE__ */ jsxRuntime.jsx(
590
- helperPlugin.Table.HeaderCell,
591
- {
592
- fieldSchemaType: "string",
593
- label: formatMessage({
594
- id: "content-releases.page.ReleaseDetails.table.header.label.locale",
595
- defaultMessage: "locale"
596
- }),
597
- name: "locale"
598
- }
599
- ),
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
+ )),
600
1120
  /* @__PURE__ */ jsxRuntime.jsx(
601
1121
  helperPlugin.Table.HeaderCell,
602
1122
  {
@@ -635,7 +1155,7 @@ const ReleaseDetailsBody = () => {
635
1155
  /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.Table.Body, { children: releaseActions[key].map(
636
1156
  ({ id, contentType, locale, type, entry }, actionIndex) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
637
1157
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "25%", maxWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, children: `${contentType.mainFieldValue || entry.id}` }) }),
638
- /* @__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 : "-"}` }) }),
639
1159
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "10%", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: contentType.displayName || "" }) }),
640
1160
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { width: "20%", children: release.releasedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
641
1161
  {
@@ -707,7 +1227,7 @@ const ReleaseDetailsPage = () => {
707
1227
  const { releaseId } = reactRouterDom.useParams();
708
1228
  const toggleNotification = helperPlugin.useNotification();
709
1229
  const { formatAPIError } = helperPlugin.useAPIErrorHandler();
710
- const { push } = reactRouterDom.useHistory();
1230
+ const { replace } = reactRouterDom.useHistory();
711
1231
  const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
712
1232
  const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
713
1233
  const {
@@ -731,11 +1251,18 @@ const ReleaseDetailsPage = () => {
731
1251
  }
732
1252
  );
733
1253
  }
734
- 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") : "";
735
1260
  const handleEditRelease = async (values) => {
736
1261
  const response = await updateRelease({
737
1262
  id: releaseId,
738
- name: values.name
1263
+ name: values.name,
1264
+ scheduledAt: values.scheduledAt,
1265
+ timezone: values.timezone
739
1266
  });
740
1267
  if ("data" in response) {
741
1268
  toggleNotification({
@@ -745,6 +1272,7 @@ const ReleaseDetailsPage = () => {
745
1272
  defaultMessage: "Release updated."
746
1273
  })
747
1274
  });
1275
+ toggleEditReleaseModal();
748
1276
  } else if (index.isAxiosError(response.error)) {
749
1277
  toggleNotification({
750
1278
  type: "warning",
@@ -756,14 +1284,13 @@ const ReleaseDetailsPage = () => {
756
1284
  message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
757
1285
  });
758
1286
  }
759
- toggleEditReleaseModal();
760
1287
  };
761
1288
  const handleDeleteRelease = async () => {
762
1289
  const response = await deleteRelease({
763
1290
  id: releaseId
764
1291
  });
765
1292
  if ("data" in response) {
766
- push("/plugins/content-releases");
1293
+ replace("/plugins/content-releases");
767
1294
  } else if (index.isAxiosError(response.error)) {
768
1295
  toggleNotification({
769
1296
  type: "warning",
@@ -789,7 +1316,14 @@ const ReleaseDetailsPage = () => {
789
1316
  handleClose: toggleEditReleaseModal,
790
1317
  handleSubmit: handleEditRelease,
791
1318
  isLoading: isLoadingDetails || isSubmittingForm,
792
- initialValues: { name: title || "" }
1319
+ initialValues: {
1320
+ name: title || "",
1321
+ scheduledAt,
1322
+ date,
1323
+ time,
1324
+ isScheduled: Boolean(scheduledAt),
1325
+ timezone
1326
+ }
793
1327
  }
794
1328
  ),
795
1329
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -809,281 +1343,6 @@ const ReleaseDetailsPage = () => {
809
1343
  }
810
1344
  );
811
1345
  };
812
- const LinkCard = styled__default.default(v2.Link)`
813
- display: block;
814
- `;
815
- const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }) => {
816
- const { formatMessage } = reactIntl.useIntl();
817
- if (isError) {
818
- return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.AnErrorOccurred, {});
819
- }
820
- if (releases?.length === 0) {
821
- return /* @__PURE__ */ jsxRuntime.jsx(
822
- designSystem.EmptyStateLayout,
823
- {
824
- content: formatMessage(
825
- {
826
- id: "content-releases.page.Releases.tab.emptyEntries",
827
- defaultMessage: "No releases"
828
- },
829
- {
830
- target: sectionTitle
831
- }
832
- ),
833
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EmptyDocuments, { width: "10rem" })
834
- }
835
- );
836
- }
837
- 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(
838
- designSystem.Flex,
839
- {
840
- direction: "column",
841
- justifyContent: "space-between",
842
- padding: 4,
843
- hasRadius: true,
844
- background: "neutral0",
845
- shadow: "tableShadow",
846
- height: "100%",
847
- width: "100%",
848
- alignItems: "start",
849
- gap: 2,
850
- children: [
851
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h3", variant: "delta", fontWeight: "bold", children: name }),
852
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: formatMessage(
853
- {
854
- id: "content-releases.page.Releases.release-item.entries",
855
- defaultMessage: "{number, plural, =0 {No entries} one {# entry} other {# entries}}"
856
- },
857
- { number: actions.meta.count }
858
- ) })
859
- ]
860
- }
861
- ) }) }, id)) });
862
- };
863
- const StyledAlert = styled__default.default(designSystem.Alert)`
864
- button {
865
- display: none;
866
- }
867
- p + div {
868
- margin-left: auto;
869
- }
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 { getFeature } = strapiAdmin.useLicenseLimits();
886
- const { maximumReleases = 3 } = getFeature("cms-content-releases");
887
- const { trackUsage } = helperPlugin.useTracking();
888
- const { isLoading, isSuccess, isError } = response;
889
- const activeTab = response?.currentData?.meta?.activeTab || "pending";
890
- const activeTabIndex = ["pending", "done"].indexOf(activeTab);
891
- React__namespace.useEffect(() => {
892
- if (location?.state?.errors) {
893
- toggleNotification({
894
- type: "warning",
895
- title: formatMessage({
896
- id: "content-releases.pages.Releases.notification.error.title",
897
- defaultMessage: "Your request could not be processed."
898
- }),
899
- message: formatMessage({
900
- id: "content-releases.pages.Releases.notification.error.message",
901
- defaultMessage: "Please try again or open another release."
902
- })
903
- });
904
- replace({ state: null });
905
- }
906
- }, [formatMessage, location?.state?.errors, replace, toggleNotification]);
907
- React__namespace.useEffect(() => {
908
- if (tabRef.current) {
909
- tabRef.current._handlers.setSelectedTabIndex(activeTabIndex);
910
- }
911
- }, [activeTabIndex]);
912
- const toggleAddReleaseModal = () => {
913
- setReleaseModalShown((prev) => !prev);
914
- };
915
- if (isLoading) {
916
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-busy": isLoading, children: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.LoadingIndicatorPage, {}) });
917
- }
918
- const totalReleases = isSuccess && response.currentData?.meta?.pagination?.total || 0;
919
- const hasReachedMaximumPendingReleases = totalReleases >= maximumReleases;
920
- const handleTabChange = (index2) => {
921
- setQuery({
922
- ...query,
923
- page: 1,
924
- pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
925
- filters: {
926
- releasedAt: {
927
- $notNull: index2 === 0 ? false : true
928
- }
929
- }
930
- });
931
- };
932
- const handleAddRelease = async (values) => {
933
- const response2 = await createRelease({
934
- name: values.name
935
- });
936
- if ("data" in response2) {
937
- toggleNotification({
938
- type: "success",
939
- message: formatMessage({
940
- id: "content-releases.modal.release-created-notification-success",
941
- defaultMessage: "Release created."
942
- })
943
- });
944
- trackUsage("didCreateRelease");
945
- push(`/plugins/content-releases/${response2.data.data.id}`);
946
- } else if (index.isAxiosError(response2.error)) {
947
- toggleNotification({
948
- type: "warning",
949
- message: formatAPIError(response2.error)
950
- });
951
- } else {
952
- toggleNotification({
953
- type: "warning",
954
- message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
955
- });
956
- }
957
- };
958
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { "aria-busy": isLoading, children: [
959
- /* @__PURE__ */ jsxRuntime.jsx(
960
- designSystem.HeaderLayout,
961
- {
962
- title: formatMessage({
963
- id: "content-releases.pages.Releases.title",
964
- defaultMessage: "Releases"
965
- }),
966
- subtitle: formatMessage(
967
- {
968
- id: "content-releases.pages.Releases.header-subtitle",
969
- defaultMessage: "{number, plural, =0 {No releases} one {# release} other {# releases}}"
970
- },
971
- { number: totalReleases }
972
- ),
973
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPermissions, { permissions: index.PERMISSIONS.create, children: /* @__PURE__ */ jsxRuntime.jsx(
974
- designSystem.Button,
975
- {
976
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
977
- onClick: toggleAddReleaseModal,
978
- disabled: hasReachedMaximumPendingReleases,
979
- children: formatMessage({
980
- id: "content-releases.header.actions.add-release",
981
- defaultMessage: "New release"
982
- })
983
- }
984
- ) })
985
- }
986
- ),
987
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.ContentLayout, { children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
988
- activeTab === "pending" && hasReachedMaximumPendingReleases && /* @__PURE__ */ jsxRuntime.jsx(
989
- StyledAlert,
990
- {
991
- marginBottom: 6,
992
- action: /* @__PURE__ */ jsxRuntime.jsx(v2.Link, { href: "https://strapi.io/pricing-cloud", isExternal: true, children: formatMessage({
993
- id: "content-releases.pages.Releases.max-limit-reached.action",
994
- defaultMessage: "Explore plans"
995
- }) }),
996
- title: formatMessage(
997
- {
998
- id: "content-releases.pages.Releases.max-limit-reached.title",
999
- defaultMessage: "You have reached the {number} pending {number, plural, one {release} other {releases}} limit."
1000
- },
1001
- { number: maximumReleases }
1002
- ),
1003
- onClose: () => {
1004
- },
1005
- closeLabel: "",
1006
- children: formatMessage({
1007
- id: "content-releases.pages.Releases.max-limit-reached.message",
1008
- defaultMessage: "Upgrade to manage an unlimited number of releases."
1009
- })
1010
- }
1011
- ),
1012
- /* @__PURE__ */ jsxRuntime.jsxs(
1013
- designSystem.TabGroup,
1014
- {
1015
- label: formatMessage({
1016
- id: "content-releases.pages.Releases.tab-group.label",
1017
- defaultMessage: "Releases list"
1018
- }),
1019
- variant: "simple",
1020
- initialSelectedTabIndex: activeTabIndex,
1021
- onTabChange: handleTabChange,
1022
- ref: tabRef,
1023
- children: [
1024
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 8, children: [
1025
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs, { children: [
1026
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
1027
- id: "content-releases.pages.Releases.tab.pending",
1028
- defaultMessage: "Pending"
1029
- }) }),
1030
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tab, { children: formatMessage({
1031
- id: "content-releases.pages.Releases.tab.done",
1032
- defaultMessage: "Done"
1033
- }) })
1034
- ] }),
1035
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
1036
- ] }),
1037
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.TabPanels, { children: [
1038
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
1039
- ReleasesGrid,
1040
- {
1041
- sectionTitle: "pending",
1042
- releases: response?.currentData?.data,
1043
- isError
1044
- }
1045
- ) }),
1046
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.TabPanel, { children: /* @__PURE__ */ jsxRuntime.jsx(
1047
- ReleasesGrid,
1048
- {
1049
- sectionTitle: "done",
1050
- releases: response?.currentData?.data,
1051
- isError
1052
- }
1053
- ) })
1054
- ] })
1055
- ]
1056
- }
1057
- ),
1058
- totalReleases > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { paddingTop: 4, alignItems: "flex-end", justifyContent: "space-between", children: [
1059
- /* @__PURE__ */ jsxRuntime.jsx(
1060
- helperPlugin.PageSizeURLQuery,
1061
- {
1062
- options: ["8", "16", "32", "64"],
1063
- defaultValue: response?.currentData?.meta?.pagination?.pageSize.toString()
1064
- }
1065
- ),
1066
- /* @__PURE__ */ jsxRuntime.jsx(
1067
- helperPlugin.PaginationURLQuery,
1068
- {
1069
- pagination: {
1070
- pageCount: response?.currentData?.meta?.pagination?.pageCount || 0
1071
- }
1072
- }
1073
- )
1074
- ] })
1075
- ] }) }),
1076
- releaseModalShown && /* @__PURE__ */ jsxRuntime.jsx(
1077
- ReleaseModal,
1078
- {
1079
- handleClose: toggleAddReleaseModal,
1080
- handleSubmit: handleAddRelease,
1081
- isLoading: isSubmittingForm,
1082
- initialValues: INITIAL_FORM_VALUES
1083
- }
1084
- )
1085
- ] });
1086
- };
1087
1346
  const App = () => {
1088
1347
  return /* @__PURE__ */ jsxRuntime.jsx(helperPlugin.CheckPagePermissions, { permissions: index.PERMISSIONS.main, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Switch, { children: [
1089
1348
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { exact: true, path: `/plugins/${index.pluginId}`, component: ReleasesPage }),
@@ -1091,4 +1350,4 @@ const App = () => {
1091
1350
  ] }) });
1092
1351
  };
1093
1352
  exports.App = App;
1094
- //# sourceMappingURL=App-pspKUC-W.js.map
1353
+ //# sourceMappingURL=App-dLXY5ei3.js.map