diff --git a/packages/core/content-releases/admin/src/components/ReleaseModal.tsx b/packages/core/content-releases/admin/src/components/ReleaseModal.tsx index a7b02499fb..d51890693e 100644 --- a/packages/core/content-releases/admin/src/components/ReleaseModal.tsx +++ b/packages/core/content-releases/admin/src/components/ReleaseModal.tsx @@ -17,7 +17,7 @@ import { import { formatISO } from 'date-fns'; import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz'; import { Formik, Form, useFormikContext } from 'formik'; -import { useIntl } from 'react-intl'; +import { MessageDescriptor, useIntl } from 'react-intl'; import { useLocation } from 'react-router-dom'; import { pluginId } from '../pluginId'; @@ -111,7 +111,14 @@ export const ReleaseModal = ({
- + {formatMessage({ id: 'content-releases.modal.form.input.label.release-name', @@ -158,7 +165,14 @@ export const ReleaseModal = ({ <> - + {formatMessage({ id: 'content-releases.modal.form.input.label.date', @@ -186,7 +200,14 @@ export const ReleaseModal = ({ - + {formatMessage({ id: 'content-releases.modal.form.input.label.time', @@ -269,7 +290,13 @@ const TimezoneComponent = ({ timezoneOptions }: { timezoneOptions: ITimezoneOpti }, [setFieldValue, values.date, values.timezone]); return ( - + {formatMessage({ id: 'content-releases.modal.form.input.label.timezone', diff --git a/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx b/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx index e6a3cde28e..5ca93d1a7d 100644 --- a/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx +++ b/packages/core/content-releases/admin/src/pages/ReleasesPage.tsx @@ -28,6 +28,7 @@ import { } from '@strapi/design-system'; import { Plus } from '@strapi/icons'; import { EmptyDocuments } from '@strapi/icons/symbols'; +import { format } from 'date-fns'; import { useIntl } from 'react-intl'; import { useNavigate, useLocation, NavLink } from 'react-router-dom'; import { styled } from 'styled-components'; @@ -170,7 +171,7 @@ const StyledAlert = styled(Alert)` const INITIAL_FORM_VALUES = { name: '', - date: undefined, + date: format(new Date(), 'yyyy-MM-dd'), time: '', isScheduled: true, scheduledAt: null, diff --git a/packages/core/content-releases/admin/src/translations/en.json b/packages/core/content-releases/admin/src/translations/en.json index ecd795558e..9d79e7e108 100644 --- a/packages/core/content-releases/admin/src/translations/en.json +++ b/packages/core/content-releases/admin/src/translations/en.json @@ -51,6 +51,7 @@ "modal.form.input.label.timezone": "Timezone", "modal.form.input.clearLabel": "Clear", "modal.form.button.submit": "{isCreatingRelease, select, true {Continue} other {Save}}", + "modal.form.time.has-passed": "Selected time has already passed.", "pages.Details.header-subtitle": "{number, plural, =0 {No entries} one {# entry} other {# entries}}", "pages.Releases.tab-group.label": "Releases list", "pages.Releases.tab.pending": "Pending ({count})", diff --git a/packages/core/content-releases/admin/src/validation/schemas.ts b/packages/core/content-releases/admin/src/validation/schemas.ts index d93aadbdd1..4e8973a179 100644 --- a/packages/core/content-releases/admin/src/validation/schemas.ts +++ b/packages/core/content-releases/admin/src/validation/schemas.ts @@ -1,43 +1,51 @@ import { translatedErrors } from '@strapi/admin/strapi-admin'; +import { zonedTimeToUtc } from 'date-fns-tz'; import * as yup from 'yup'; +/** + * FormikErrors type enforce us to always return a string as error. + * We need these errors to be translated, so we need to create a hook to be able to use the formatMessage function. + */ export const RELEASE_SCHEMA = yup .object() .shape({ - name: yup.string().trim().required({ - id: translatedErrors.required.id, - defaultMessage: 'This field is required', - }), + name: yup.string().trim().required(translatedErrors.required.id), scheduledAt: yup.string().nullable(), isScheduled: yup.boolean().optional(), - time: yup.string().when('isScheduled', { - is: true, - then: yup.string().trim().required({ - id: translatedErrors.required.id, - defaultMessage: 'This field is required', - }), - otherwise: yup.string().nullable(), - }), + time: yup + .string() + .when('isScheduled', { + is: true, + then: yup.string().trim().required(translatedErrors.required.id), + otherwise: yup.string().nullable(), + }) + .test( + 'time-in-future-if-today', + 'content-releases.modal.form.time.has-passed', + function (time) { + const { date, timezone } = this.parent; + + if (!date || !timezone || !time) { + return true; + } + + // Timezone is in format "UTC&Europe/Paris", so we get the region part for the dates functions + const region = timezone.split('&')[1]; + + const selectedTime = zonedTimeToUtc(`${date} ${time}`, region); + const now = new Date(); + + return selectedTime > now; + } + ), timezone: yup.string().when('isScheduled', { is: true, - then: yup - .string() - .required({ - id: translatedErrors.required.id, - defaultMessage: 'This field is required', - }) - .nullable(), + then: yup.string().required(translatedErrors.required.id).nullable(), otherwise: yup.string().nullable(), }), date: yup.string().when('isScheduled', { is: true, - then: yup - .string() - .required({ - id: translatedErrors.required.id, - defaultMessage: 'This field is required', - }) - .nullable(), + then: yup.string().required(translatedErrors.required.id).nullable(), otherwise: yup.string().nullable(), }), })