PublishReleaseTime widget improvements (#1740)

* PublishReleaseDate: improve calendar error handling

Ticket: 1738

- Report invalid `minute` and `day`. The 3rd-party widget auto-corrects the other fields. Don't think there is a way to make it autocorrect for all.
- Report invalid range (cannot set to future date).

* Block form on releaseDate error instead of silently sending last valid value

which does not tally with what's on screen.
This commit is contained in:
infinite-persistence 2022-06-23 19:27:08 +08:00 committed by GitHub
parent b6f9b0e6d4
commit 053e214c86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 94 additions and 11 deletions

View file

@ -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,

View file

@ -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",

View file

@ -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);

View file

@ -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) => ({

View file

@ -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 (
<div className="form-field-date-picker">
<label>{__('Release date')}</label>
<div className="controls">
<div className="form-field-date-picker__controls">
{showDatePicker && (
<DateTimePicker
className="date-picker-input"
calendarClassName="form-field-calendar"
onBlur={handleBlur}
onChange={onDateTimePickerChanged}
value={date}
maxDate={maxDate}
format="y-MM-dd h:mm a"
disableClock
clearIcon={null}
@ -142,6 +192,12 @@ const PublishReleaseDate = (props: Props) => {
onClick={() => newDate(DEFAULT)}
/>
)}
{error.length > 0 && (
<span className="form-field-date-picker__error">
{error.includes(FUTURE_DATE_ERROR) && <span>{__(FUTURE_DATE_ERROR)}</span>}
{(!error.includes(FUTURE_DATE_ERROR) || error.length > 1) && <span>{__('Invalid date/time.')}</span>}
</span>
)}
</div>
</div>
);

View file

@ -78,6 +78,7 @@ const defaultState: PublishState = {
language: '',
releaseTime: undefined,
releaseTimeEdited: undefined,
releaseTimeError: false,
releaseAnytime: false,
nsfw: false,
channel: CHANNEL_ANONYMOUS,

View file

@ -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);