diff --git a/src/apps/dashboard/routes/users/add.tsx b/src/apps/dashboard/routes/users/add.tsx index ffb8d3dc04..d61b2f502b 100644 --- a/src/apps/dashboard/routes/users/add.tsx +++ b/src/apps/dashboard/routes/users/add.tsx @@ -149,9 +149,17 @@ const UserNew = () => { }).catch(err => { console.error('[usernew] failed to update user policy', err); }); - }, function () { - toast(globalize.translate('ErrorDefault')); - loading.hide(); + }, function (error) { + try { + console.error('[usernew] failed to create new user', error); + error.text().then((errorMessage: string) => { + toast(errorMessage); + loading.hide(); + }); + } catch { + toast(globalize.translate('ErrorDefault')); + loading.hide(); + } }); }; @@ -198,6 +206,7 @@ const UserNew = () => { type='text' id='txtUsername' label='LabelName' + validator={{ pattern: '^([\\w \\-\'._@+]*)([\\w\\-\'._@+])([\\w \\-\'._@+]*)$', errMessage: globalize.translate('MessageInvalidUsernameFormat') }} options={'required'} /> diff --git a/src/apps/dashboard/routes/users/profile.tsx b/src/apps/dashboard/routes/users/profile.tsx index 36afb9ca9f..baf8c93132 100644 --- a/src/apps/dashboard/routes/users/profile.tsx +++ b/src/apps/dashboard/routes/users/profile.tsx @@ -230,7 +230,16 @@ const UserEdit = () => { )).then(() => { onSaveComplete(); }).catch(err => { - console.error('[useredit] failed to update user', err); + try { + console.error('[useredit] failed to update user', err); + err.text().then((errorMessage: string) => { + toast(errorMessage); + loading.hide(); + }); + } catch { + toast(globalize.translate('ErrorDefault')); + loading.hide(); + } }); }; @@ -318,6 +327,7 @@ const UserEdit = () => { type='text' id='txtUserName' label='LabelName' + validator={{ pattern: '^([\\w \\-\'._@+]*)([\\w\\-\'._@+])([\\w \\-\'._@+]*)$', errMessage: globalize.translate('MessageInvalidUsernameFormat') }} options={'required'} /> diff --git a/src/elements/InputElement.tsx b/src/elements/InputElement.tsx index ae7e757b69..c181b53057 100644 --- a/src/elements/InputElement.tsx +++ b/src/elements/InputElement.tsx @@ -2,23 +2,30 @@ import React, { type FC, useCallback, useEffect, useMemo, useRef } from 'react'; import globalize from 'lib/globalize'; +import './InputElementInvalidMessage.scss'; + interface CreateInputElementParams { type?: string id?: string label?: string initialValue?: string + validator?: { pattern: string, errMessage: string } options?: string } -const createInputElement = ({ type, id, label, initialValue, options }: CreateInputElementParams) => ({ +const createInputElement = ({ type, id, label, initialValue, validator, options }: CreateInputElementParams) => ({ __html: `` + /> +
+ ${validator?.errMessage || ''} +
` }); type InputElementProps = { @@ -33,7 +40,9 @@ const InputElement: FC = ({ type, id, label, + validator, options = '' + }) => { const container = useRef(null); @@ -44,14 +53,16 @@ const InputElement: FC = ({ id, label: globalize.translate(label), initialValue, + validator, options }) // eslint-disable-next-line react-hooks/exhaustive-deps ), []); const onInput = useCallback((e: Event) => { + if (validator) (e.target as HTMLElement).parentElement?.querySelector('.inputElementInvalidMessage')?.classList.add('inputReadyForValidation'); onChange((e.target as HTMLInputElement).value); - }, [ onChange ]); + }, [ onChange, validator ]); useEffect(() => { const inputElement = container?.current?.querySelector('input'); diff --git a/src/elements/InputElementInvalidMessage.scss b/src/elements/InputElementInvalidMessage.scss new file mode 100644 index 0000000000..89cba5ea8d --- /dev/null +++ b/src/elements/InputElementInvalidMessage.scss @@ -0,0 +1,8 @@ +.inputElementInvalidMessage { + color: red; + display: none; +} + +.emby-input:invalid + .inputElementInvalidMessage.inputReadyForValidation { + display: block; +} diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 2d6678c325..ee76a327fb 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1124,6 +1124,7 @@ "MessageImageTypeNotSelected": "Please select an image type from the drop-down menu.", "MessageInvalidForgotPasswordPin": "An invalid or expired PIN code was entered. Please try again.", "MessageInvalidUser": "Invalid username or password. Please try again.", + "MessageInvalidUsernameFormat": "Username must not be empty and contain only numbers, letters, spaces, or the following symbols -'._@+", "MessageItemsAdded": "Items added.", "MessageItemSaved": "Item saved.", "MessageLeaveEmptyToInherit": "Leave empty to inherit settings from a parent item or the global default value.",