Skip to content

Commit c19b228

Browse files
authoredFeb 25, 2025··
feat: adds validation on date from/to inputs in the schedule modal (#18437)
1 parent 913205e commit c19b228

File tree

1 file changed

+135
-59
lines changed

1 file changed

+135
-59
lines changed
 

‎src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.element.ts

+135-59
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import type {
66
UmbDocumentScheduleModalValue,
77
UmbDocumentScheduleSelectionModel,
88
} from './document-schedule-modal.token.js';
9-
import { css, customElement, html, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
9+
import { css, customElement, html, ref, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
1010
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
1111
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
1212
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
13+
import { umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation';
1314
import type { UmbInputDateElement } from '@umbraco-cms/backoffice/components';
14-
import type { UUIBooleanInputElement } from '@umbraco-cms/backoffice/external/uui';
15+
import type { UUIBooleanInputElement, UUIButtonState } from '@umbraco-cms/backoffice/external/uui';
1516

1617
@customElement('umb-document-schedule-modal')
1718
export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
@@ -35,6 +36,11 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
3536
@state()
3637
_internalValues: Array<UmbDocumentScheduleSelectionModel> = [];
3738

39+
@state()
40+
private _submitButtonState?: UUIButtonState;
41+
42+
#validation = new UmbValidationContext(this);
43+
3844
#pickableFilter = (option: UmbDocumentVariantOptionModel) => {
3945
if (isNotPublishedMandatory(option)) {
4046
return true;
@@ -91,11 +97,20 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
9197
this.#selectionManager.setSelection(selected);
9298
}
9399

94-
#submit() {
95-
this.value = {
96-
selection: this._internalValues,
97-
};
98-
this.modalContext?.submit();
100+
async #submit() {
101+
this._submitButtonState = 'waiting';
102+
try {
103+
await this.#validation.validate();
104+
this._submitButtonState = 'success';
105+
this.value = {
106+
selection: this._internalValues,
107+
};
108+
this.modalContext?.submit();
109+
} catch {
110+
this._submitButtonState = 'failed';
111+
} finally {
112+
this._submitButtonState = undefined;
113+
}
99114
}
100115

101116
#close() {
@@ -146,6 +161,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
146161
<div slot="actions">
147162
<uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button>
148163
<uui-button
164+
.state=${this._submitButtonState}
149165
label="${this.localize.term('buttons_schedulePublish')}"
150166
look="primary"
151167
color="positive"
@@ -203,59 +219,115 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
203219
`;
204220
}
205221

222+
#attachValidatorsToPublish(element: UmbInputDateElement | null) {
223+
if (!element) return;
224+
225+
element.addValidator(
226+
'badInput',
227+
() => this.localize.term('speechBubbles_scheduleErrReleaseDate1'),
228+
() => {
229+
const value = element.value.toString();
230+
if (!value) return false;
231+
const date = new Date(value);
232+
return date < new Date();
233+
},
234+
);
235+
}
236+
237+
#attachValidatorsToUnpublish(element: UmbInputDateElement | null, unique: string) {
238+
if (!element) return;
239+
240+
element.addValidator(
241+
'badInput',
242+
() => this.localize.term('speechBubbles_scheduleErrExpireDate1'),
243+
() => {
244+
const value = element.value.toString();
245+
if (!value) return false;
246+
const date = new Date(value);
247+
return date < new Date();
248+
},
249+
);
250+
251+
element.addValidator(
252+
'customError',
253+
() => this.localize.term('speechBubbles_scheduleErrExpireDate2'),
254+
() => {
255+
const value = element.value.toString();
256+
if (!value) return false;
257+
258+
// Check if the unpublish date is before the publish date
259+
const variant = this._internalValues.find((s) => s.unique === unique);
260+
if (!variant) return false;
261+
const publishTime = variant.schedule?.publishTime;
262+
if (!publishTime) return false;
263+
264+
const date = new Date(value);
265+
const publishDate = new Date(publishTime);
266+
return date < publishDate;
267+
},
268+
);
269+
}
270+
206271
#renderPublishDateInput(option: UmbDocumentVariantOptionModel, fromDate: string | null, toDate: string | null) {
207-
return html`<div class="publish-date">
208-
<uui-form-layout-item>
209-
<uui-label slot="label"><umb-localize key="content_releaseDate">Publish at</umb-localize></uui-label>
210-
<div>
211-
<umb-input-date
212-
type="datetime-local"
213-
.value=${this.#formatDate(fromDate)}
214-
@change=${(e: Event) => this.#onFromDateChange(e, option.unique)}
215-
label=${this.localize.term('general_publishDate')}>
216-
<div slot="append">
217-
${when(
218-
fromDate,
219-
() => html`
220-
<uui-button
221-
compact
222-
label=${this.localize.term('general_clear')}
223-
title=${this.localize.term('general_clear')}
224-
@click=${() => this.#removeFromDate(option.unique)}>
225-
<uui-icon name="remove"></uui-icon>
226-
</uui-button>
227-
`,
228-
)}
229-
</div>
230-
</umb-input-date>
231-
</div>
232-
</uui-form-layout-item>
233-
<uui-form-layout-item>
234-
<uui-label slot="label"><umb-localize key="content_unpublishDate">Unpublish at</umb-localize></uui-label>
235-
<div>
236-
<umb-input-date
237-
type="datetime-local"
238-
.value=${this.#formatDate(toDate)}
239-
@change=${(e: Event) => this.#onToDateChange(e, option.unique)}
240-
label=${this.localize.term('general_publishDate')}>
241-
<div slot="append">
242-
${when(
243-
toDate,
244-
() => html`
245-
<uui-button
246-
compact
247-
label=${this.localize.term('general_clear')}
248-
title=${this.localize.term('general_clear')}
249-
@click=${() => this.#removeToDate(option.unique)}>
250-
<uui-icon name="remove"></uui-icon>
251-
</uui-button>
252-
`,
253-
)}
254-
</div>
255-
</umb-input-date>
256-
</div>
257-
</uui-form-layout-item>
258-
</div>`;
272+
return html`
273+
<div class="publish-date">
274+
<uui-form-layout-item>
275+
<uui-label slot="label"><umb-localize key="content_releaseDate">Publish at</umb-localize></uui-label>
276+
<div>
277+
<umb-input-date
278+
${ref((e) => this.#attachValidatorsToPublish(e as UmbInputDateElement))}
279+
${umbBindToValidation(this)}
280+
type="datetime-local"
281+
.value=${this.#formatDate(fromDate)}
282+
@change=${(e: Event) => this.#onFromDateChange(e, option.unique)}
283+
label=${this.localize.term('general_publishDate')}>
284+
<div slot="append">
285+
${when(
286+
fromDate,
287+
() => html`
288+
<uui-button
289+
compact
290+
label=${this.localize.term('general_clear')}
291+
title=${this.localize.term('general_clear')}
292+
@click=${() => this.#removeFromDate(option.unique)}>
293+
<uui-icon name="remove"></uui-icon>
294+
</uui-button>
295+
`,
296+
)}
297+
</div>
298+
</umb-input-date>
299+
</div>
300+
</uui-form-layout-item>
301+
302+
<uui-form-layout-item>
303+
<uui-label slot="label"><umb-localize key="content_unpublishDate">Unpublish at</umb-localize></uui-label>
304+
<div>
305+
<umb-input-date
306+
${ref((e) => this.#attachValidatorsToUnpublish(e as UmbInputDateElement, option.unique))}
307+
${umbBindToValidation(this)}
308+
type="datetime-local"
309+
.value=${this.#formatDate(toDate)}
310+
@change=${(e: Event) => this.#onToDateChange(e, option.unique)}
311+
label=${this.localize.term('general_publishDate')}>
312+
<div slot="append">
313+
${when(
314+
toDate,
315+
() => html`
316+
<uui-button
317+
compact
318+
label=${this.localize.term('general_clear')}
319+
title=${this.localize.term('general_clear')}
320+
@click=${() => this.#removeToDate(option.unique)}>
321+
<uui-icon name="remove"></uui-icon>
322+
</uui-button>
323+
`,
324+
)}
325+
</div>
326+
</umb-input-date>
327+
</div>
328+
</uui-form-layout-item>
329+
</div>
330+
`;
259331
}
260332

261333
#fromDate(unique: string): string | null {
@@ -275,6 +347,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
275347
...variant.schedule,
276348
publishTime: null,
277349
};
350+
this.#validation.validate();
278351
this.requestUpdate('_internalValues');
279352
}
280353

@@ -285,6 +358,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
285358
...variant.schedule,
286359
unpublishTime: null,
287360
};
361+
this.#validation.validate();
288362
this.requestUpdate('_internalValues');
289363
}
290364

@@ -325,6 +399,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
325399
...variant.schedule,
326400
publishTime: this.#getDateValue(e),
327401
};
402+
this.#validation.validate();
328403
this.requestUpdate('_internalValues');
329404
}
330405

@@ -335,6 +410,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
335410
...variant.schedule,
336411
unpublishTime: this.#getDateValue(e),
337412
};
413+
this.#validation.validate();
338414
this.requestUpdate('_internalValues');
339415
}
340416

0 commit comments

Comments
 (0)
Please sign in to comment.