diff --git a/flow-typed/publish.js b/flow-typed/publish.js index 237380016..5f9daa4f1 100644 --- a/flow-typed/publish.js +++ b/flow-typed/publish.js @@ -14,6 +14,9 @@ declare type UpdatePublishFormData = { thumbnailError?: boolean, description?: string, language?: string, + releaseTime?: number, + releaseTimeEdited?: number, + releaseTimeError?: string, channel?: string, channelId?: string, name?: string, diff --git a/static/app-strings.json b/static/app-strings.json index b0e70cf25..1e3b467ef 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -1939,6 +1939,8 @@ "Release date": "Release date", "Scheduled for": "Scheduled for", "Set custom release date": "Set custom release date", + "Cannot set to a future date.": "Cannot set to a future date.", + "Invalid date/time.": "Invalid date/time.", "Now": "Now", "Set to current date and time": "Set to current date and time", "Remove custom release date": "Remove custom release date", diff --git a/ui/component/publishForm/view.jsx b/ui/component/publishForm/view.jsx index 4fb02c77d..cffc255ab 100644 --- a/ui/component/publishForm/view.jsx +++ b/ui/component/publishForm/view.jsx @@ -54,6 +54,7 @@ type Props = { thumbnailPath: ?string, description: ?string, language: string, + releaseTimeError: ?string, nsfw: boolean, contentIsFree: boolean, fee: { @@ -109,6 +110,7 @@ function PublishForm(props: Props) { title, bid, bidError, + releaseTimeError, uploadThumbnailStatus, resetThumbnailStatus, updatePublishForm, @@ -236,6 +238,7 @@ function PublishForm(props: Props) { bid && thumbnail && !bidError && + !releaseTimeError && !emptyPostError && !(thumbnailError && !thumbnailUploaded) && !(uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS); diff --git a/ui/component/publishReleaseDate/index.js b/ui/component/publishReleaseDate/index.js index 962afce19..e7a1e4fee 100644 --- a/ui/component/publishReleaseDate/index.js +++ b/ui/component/publishReleaseDate/index.js @@ -6,6 +6,7 @@ import PublishReleaseDate from './view'; const select = (state) => ({ releaseTime: makeSelectPublishFormValue('releaseTime')(state), releaseTimeEdited: makeSelectPublishFormValue('releaseTimeEdited')(state), + releaseTimeError: makeSelectPublishFormValue('releaseTimeError')(state), }); const perform = (dispatch) => ({ diff --git a/ui/component/publishReleaseDate/view.jsx b/ui/component/publishReleaseDate/view.jsx index 1abf0f84b..8639bcd13 100644 --- a/ui/component/publishReleaseDate/view.jsx +++ b/ui/component/publishReleaseDate/view.jsx @@ -14,6 +14,7 @@ function dateToLinuxTimestamp(date: Date) { const NOW = 'now'; const DEFAULT = 'default'; const RESET_TO_ORIGINAL = 'reset-to-original'; +const FUTURE_DATE_ERROR = 'Cannot set to a future date.'; type Props = { releaseTime: ?number, @@ -35,6 +36,7 @@ const PublishReleaseDate = (props: Props) => { } = props; const maxDate = useMaxDate ? new Date() : undefined; const [date, setDate] = React.useState(releaseTime ? linuxTimestampToDate(releaseTime) : new Date()); + const [error, setError] = React.useState([]); const isNew = releaseTime === undefined; const isEdit = !isNew || allowDefault === false; @@ -45,7 +47,38 @@ const PublishReleaseDate = (props: Props) => { // const showDatePicker = isEdit || releaseTimeEdited !== undefined; const showDatePicker = true; + const updateError = (action, error) => { + switch (action) { + case 'remove': + setError((prev) => prev.filter((x) => x !== error)); + break; + + case 'clear': + setError([]); + break; + + case 'add': + setError((prev) => { + const nextError = prev.slice(); + if (!nextError.includes(error)) { + nextError.push(error); + return nextError; + } + return prev; + }); + break; + } + }; + const onDateTimePickerChanged = (value) => { + const isValueInFuture = maxDate && value && value.getTime() > maxDate.getTime(); + if (isValueInFuture) { + updateError('add', FUTURE_DATE_ERROR); + return; + } + + updateError('remove', FUTURE_DATE_ERROR); + if (value) { newDate(value); } else { @@ -60,6 +93,8 @@ const PublishReleaseDate = (props: Props) => { }; function newDate(value: string | Date) { + updateError('clear', FUTURE_DATE_ERROR); + switch (value) { case NOW: const newDate = new Date(); @@ -88,23 +123,38 @@ const PublishReleaseDate = (props: Props) => { } } + function handleBlur(event) { + if (event.target.name === 'minute' || event.target.name === 'day') { + const validity = event?.target?.validity; + if (validity.rangeOverflow || validity.rangeUnderflow) { + updateError('add', event.target.name); + } else if (error.includes(event.target.name)) { + updateError('remove', event.target.name); + } + } + } + useEffect(() => { return () => { updatePublishForm({ releaseTimeEdited: undefined }); }; }, []); + useEffect(() => { + updatePublishForm({ releaseTimeError: error.join(';') }); + }, [error]); + return (
-
+
{showDatePicker && ( { onClick={() => newDate(DEFAULT)} /> )} + {error.length > 0 && ( + + {error.includes(FUTURE_DATE_ERROR) && {__(FUTURE_DATE_ERROR)}} + {(!error.includes(FUTURE_DATE_ERROR) || error.length > 1) && {__('Invalid date/time.')}} + + )}
); diff --git a/ui/redux/reducers/publish.js b/ui/redux/reducers/publish.js index f9a99fde9..23d481cb7 100644 --- a/ui/redux/reducers/publish.js +++ b/ui/redux/reducers/publish.js @@ -78,6 +78,7 @@ const defaultState: PublishState = { language: '', releaseTime: undefined, releaseTimeEdited: undefined, + releaseTimeError: false, releaseAnytime: false, nsfw: false, channel: CHANNEL_ANONYMOUS, diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 40492d99c..f8c9c22d5 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -596,15 +596,6 @@ fieldset-section { display: block; } - .controls { - display: flex; - - .date-picker-input, - .button--link { - margin-right: var(--spacing-m); - } - } - .react-datetime-picker__button { svg { stroke: var(--color-text); @@ -802,6 +793,32 @@ fieldset-section { } } +.form-field-date-picker__header { + display: flex; + + label { + margin-right: var(--spacing-m); + } +} + +.form-field-date-picker__error { + color: var(--color-error); + align-self: center; + + span { + margin-right: var(--spacing-xs); + } +} + +.form-field-date-picker__controls { + display: flex; + + .date-picker-input, + .button--link { + margin-right: var(--spacing-m); + } +} + .form-field-calendar { border-radius: var(--border-radius); border: 1px solid var(--color-border);