Merge pull request #376 from ONLYOFFICE/bugfix/profile-form-validation

Bugfix/profile form validation
This commit is contained in:
Ilya Oleshko 2021-10-06 13:58:03 +03:00 committed by GitHub
commit 596ff52ed4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 114 additions and 27 deletions

View File

@ -92,6 +92,9 @@ class SettingsStore {
}; };
debugInfo = false; debugInfo = false;
userFormValidation = /^[\p{L}\p{M}'\-]+$/gu;
folderFormValidation = new RegExp('[*+:"<>?|\\\\/]', "gim");
constructor() { constructor() {
makeAutoObservable(this); makeAutoObservable(this);
} }

View File

@ -182,15 +182,15 @@ export default function withContent(WrappedContent) {
}; };
renameTitle = (e) => { renameTitle = (e) => {
const { t } = this.props; const { t, folderFormValidation } = this.props;
let title = e.target.value; let title = e.target.value;
//const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate //const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate
const regexp = new RegExp('[*+:"<>?|\\\\/]', "gim");
if (title.match(regexp)) { if (title.match(folderFormValidation)) {
toastr.warning(t("ContainsSpecCharacter")); toastr.warning(t("ContainsSpecCharacter"));
} }
title = title.replace(regexp, "_"); title = title.replace(folderFormValidation, "_");
return this.setState({ itemTitle: title }); return this.setState({ itemTitle: title });
}; };
@ -324,7 +324,11 @@ export default function withContent(WrappedContent) {
id: fileActionId, id: fileActionId,
} = filesStore.fileActionStore; } = filesStore.fileActionStore;
const { replaceFileStream, setEncryptionAccess } = auth; const { replaceFileStream, setEncryptionAccess } = auth;
const { culture, isDesktopClient } = auth.settingsStore; const {
culture,
isDesktopClient,
folderFormValidation,
} = auth.settingsStore;
return { return {
setIsLoading, setIsLoading,
@ -346,6 +350,7 @@ export default function withContent(WrappedContent) {
homepage: config.homepage, homepage: config.homepage,
viewer: auth.userStore.user, viewer: auth.userStore.user,
viewAs, viewAs,
folderFormValidation,
}; };
} }
)(observer(WithContent)); )(observer(WithContent));

View File

@ -29,6 +29,7 @@ const PureConnectDialogContainer = (props) => {
setConnectDialogVisible, setConnectDialogVisible,
personal, personal,
getSubfolders, getSubfolders,
folderFormValidation,
} = props; } = props;
const { const {
corporate, corporate,
@ -80,12 +81,11 @@ const PureConnectDialogContainer = (props) => {
setIsTitleValid(true); setIsTitleValid(true);
let title = e.target.value; let title = e.target.value;
//const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate //const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate
const regexp = new RegExp('[*+:"<>?|\\\\/]', "gim");
if (title.match(regexp)) { if (title.match(folderFormValidation)) {
toastr.warning(t("Home:ContainsSpecCharacter")); toastr.warning(t("Home:ContainsSpecCharacter"));
} }
title = title.replace(regexp, "_"); title = title.replace(folderFormValidation, "_");
setCustomerTitleValue(title); setCustomerTitleValue(title);
}; };
@ -340,7 +340,11 @@ export default inject(
openConnectWindow, openConnectWindow,
fetchThirdPartyProviders, fetchThirdPartyProviders,
} = settingsStore.thirdPartyStore; } = settingsStore.thirdPartyStore;
const { getOAuthToken, personal } = auth.settingsStore; const {
getOAuthToken,
personal,
folderFormValidation,
} = auth.settingsStore;
const { const {
treeFolders, treeFolders,
@ -364,6 +368,7 @@ export default inject(
providers, providers,
visible, visible,
item, item,
folderFormValidation,
getOAuthToken, getOAuthToken,
getSubfolders, getSubfolders,

View File

@ -19,5 +19,7 @@
"TemporaryPassword": "Vorübergehendes Kennwort", "TemporaryPassword": "Vorübergehendes Kennwort",
"TermsOfUsePopupHelperLink": "Erfahren Sie mehr über die Nutzungsbedingungen", "TermsOfUsePopupHelperLink": "Erfahren Sie mehr über die Nutzungsbedingungen",
"UpdatingProcess": "Wird aktualisiert...", "UpdatingProcess": "Wird aktualisiert...",
"WriteComment": "Kommentar hinzufügen" "WriteComment": "Kommentar hinzufügen",
"ErrorInvalidUserLastName": "Ungültiger Nachname",
"ErrorInvalidUserFirstName": "Ungültiger Vorname"
} }

View File

@ -20,5 +20,7 @@
"TemporaryPassword": "Temporary password", "TemporaryPassword": "Temporary password",
"TermsOfUsePopupHelperLink": "Read more about terms of use", "TermsOfUsePopupHelperLink": "Read more about terms of use",
"UpdatingProcess": "Updating...", "UpdatingProcess": "Updating...",
"WriteComment": "Add comment" "WriteComment": "Add comment",
"ErrorInvalidUserLastName": "Invalid last name",
"ErrorInvalidUserFirstName": "Invalid first name"
} }

View File

@ -19,5 +19,7 @@
"TemporaryPassword": "Mot de passe temporaire", "TemporaryPassword": "Mot de passe temporaire",
"TermsOfUsePopupHelperLink": "Savoir plus à propos de termes d'utilisation ", "TermsOfUsePopupHelperLink": "Savoir plus à propos de termes d'utilisation ",
"UpdatingProcess": "Mis à jour...", "UpdatingProcess": "Mis à jour...",
"WriteComment": "Ajouter un commentaire" "WriteComment": "Ajouter un commentaire",
"ErrorInvalidUserLastName": "Prénom invalide",
"ErrorInvalidUserFirstName": "Nom invalide"
} }

View File

@ -19,5 +19,7 @@
"TemporaryPassword": "Password temporanea", "TemporaryPassword": "Password temporanea",
"TermsOfUsePopupHelperLink": "Maggiori dettagli sui termini di utilizzo", "TermsOfUsePopupHelperLink": "Maggiori dettagli sui termini di utilizzo",
"UpdatingProcess": "Caricamento in corso...", "UpdatingProcess": "Caricamento in corso...",
"WriteComment": "Aggiungi commento" "WriteComment": "Aggiungi commento",
"ErrorInvalidUserLastName": "Cognome non valido",
"ErrorInvalidUserFirstName": "Nome non valido"
} }

View File

@ -19,5 +19,7 @@
"TemporaryPassword": "Senha temporária", "TemporaryPassword": "Senha temporária",
"TermsOfUsePopupHelperLink": "Leia mais sobre os termos de uso", "TermsOfUsePopupHelperLink": "Leia mais sobre os termos de uso",
"UpdatingProcess": "Atualizando...", "UpdatingProcess": "Atualizando...",
"WriteComment": "Adicionar comentário" "WriteComment": "Adicionar comentário",
"ErrorInvalidUserLastName": "Sobrenome inválido",
"ErrorInvalidUserFirstName": "Primeiro nome inválido"
} }

View File

@ -20,5 +20,7 @@
"TemporaryPassword": "Временный пароль", "TemporaryPassword": "Временный пароль",
"TermsOfUsePopupHelperLink": "Подробнее об условиях использования", "TermsOfUsePopupHelperLink": "Подробнее об условиях использования",
"UpdatingProcess": "Обновление...", "UpdatingProcess": "Обновление...",
"WriteComment": "Добавить комментарий" "WriteComment": "Добавить комментарий",
"ErrorInvalidUserLastName": "Недопустимая фамилия",
"ErrorInvalidUserFirstName": "Недопустимое имя"
} }

View File

@ -15,6 +15,7 @@ class TextField extends React.Component {
isRequired, isRequired,
hasError, hasError,
labelText, labelText,
errorMessage,
inputName, inputName,
inputValue, inputValue,
@ -32,6 +33,7 @@ class TextField extends React.Component {
<FieldContainer <FieldContainer
isRequired={isRequired} isRequired={isRequired}
hasError={hasError} hasError={hasError}
errorMessage={errorMessage}
labelText={labelText} labelText={labelText}
tooltipContent={tooltipContent} tooltipContent={tooltipContent}
helpButtonHeaderContent={helpButtonHeaderContent} helpButtonHeaderContent={helpButtonHeaderContent}

View File

@ -242,8 +242,19 @@ class CreateUserForm extends React.Component {
} }
onInputChange = (event) => { onInputChange = (event) => {
const { userFormValidation } = this.props;
var stateCopy = Object.assign({}, this.state); var stateCopy = Object.assign({}, this.state);
stateCopy.profile[event.target.name] = event.target.value; const value = event.target.value;
const title = event.target.name;
if (!value.match(userFormValidation)) {
stateCopy.errors[title] = true;
} else {
if (this.state.errors[title]) stateCopy.errors[title] = false;
}
stateCopy.profile[title] = value;
this.setState(stateCopy); this.setState(stateCopy);
this.setIsEdit(); this.setIsEdit();
}; };
@ -262,8 +273,20 @@ class CreateUserForm extends React.Component {
this.setIsEdit(); this.setIsEdit();
}; };
scrollToErrorForm = () => {
const element = this.mainFieldsContainerRef.current;
const parent = element.closest(".scroll-body");
(parent || window).scrollTo(0, element.offsetTop);
};
validate = () => { validate = () => {
const { profile, errors: stateErrors } = this.state; const { profile, errors: stateErrors } = this.state;
if (stateErrors.firstName || stateErrors.lastName) {
this.scrollToErrorForm();
return;
}
const errors = { const errors = {
firstName: !profile.firstName.trim(), firstName: !profile.firstName.trim(),
lastName: !profile.lastName.trim(), lastName: !profile.lastName.trim(),
@ -274,9 +297,7 @@ class CreateUserForm extends React.Component {
errors.firstName || errors.lastName || errors.email || errors.password; errors.firstName || errors.lastName || errors.email || errors.password;
if (hasError) { if (hasError) {
const element = this.mainFieldsContainerRef.current; this.scrollToErrorForm();
const parent = element.closest(".scroll-body");
(parent || window).scrollTo(0, element.offsetTop);
} }
this.setState({ errors: errors }); this.setState({ errors: errors });
@ -436,6 +457,9 @@ class CreateUserForm extends React.Component {
const pattern = getUserContactsPattern(); const pattern = getUserContactsPattern();
const contacts = getUserContacts(profile.contacts); const contacts = getUserContacts(profile.contacts);
const notEmptyFirstName = Boolean(profile.firstName.trim());
const notEmptyLastName = Boolean(profile.lastName.trim());
return ( return (
<> <>
<MainContainer> <MainContainer>
@ -470,6 +494,9 @@ class CreateUserForm extends React.Component {
<TextField <TextField
isRequired={true} isRequired={true}
hasError={errors.firstName} hasError={errors.firstName}
{...(notEmptyFirstName && {
errorMessage: t("ErrorInvalidUserFirstName"),
})}
labelText={`${t("FirstName")}:`} labelText={`${t("FirstName")}:`}
inputName="firstName" inputName="firstName"
inputValue={profile.firstName} inputValue={profile.firstName}
@ -481,6 +508,9 @@ class CreateUserForm extends React.Component {
<TextField <TextField
isRequired={true} isRequired={true}
hasError={errors.lastName} hasError={errors.lastName}
{...(notEmptyLastName && {
errorMessage: t("ErrorInvalidUserLastName"),
})}
labelText={`${t("Common:LastName")}:`} labelText={`${t("Common:LastName")}:`}
inputName="lastName" inputName="lastName"
inputValue={profile.lastName} inputValue={profile.lastName}
@ -687,6 +717,7 @@ export default withRouter(
setCroppedAvatar: peopleStore.avatarEditorStore.setCroppedAvatar, setCroppedAvatar: peopleStore.avatarEditorStore.setCroppedAvatar,
updateProfileInUsers: peopleStore.usersStore.updateProfileInUsers, updateProfileInUsers: peopleStore.usersStore.updateProfileInUsers,
updateCreatedAvatar: peopleStore.targetUserStore.updateCreatedAvatar, updateCreatedAvatar: peopleStore.targetUserStore.updateCreatedAvatar,
userFormValidation: auth.settingsStore.userFormValidation,
}))( }))(
observer( observer(
withTranslation(["ProfileAction", "Common", "Translations"])( withTranslation(["ProfileAction", "Common", "Translations"])(

View File

@ -238,8 +238,19 @@ class UpdateUserForm extends React.Component {
} }
onInputChange(event) { onInputChange(event) {
const { userFormValidation } = this.props;
var stateCopy = Object.assign({}, this.state); var stateCopy = Object.assign({}, this.state);
stateCopy.profile[event.target.name] = event.target.value; const value = event.target.value;
const title = event.target.name;
if (!value.match(userFormValidation)) {
stateCopy.errors[title] = true;
} else {
if (this.state.errors[title]) stateCopy.errors[title] = false;
}
stateCopy.profile[title] = value;
this.setState(stateCopy); this.setState(stateCopy);
this.setIsEdit(); this.setIsEdit();
} }
@ -278,21 +289,30 @@ class UpdateUserForm extends React.Component {
this.setIsEdit(); this.setIsEdit();
} }
scrollToErrorForm = () => {
const element = this.mainFieldsContainerRef.current;
const parent = element.closest(".scroll-body");
(parent || window).scrollTo(0, element.offsetTop);
};
validate() { validate() {
const { profile } = this.state; const { profile, errors } = this.state;
const errors = {
if (errors.firstName || errors.lastName) {
this.scrollToErrorForm();
return;
}
const errorsObj = {
firstName: !profile.firstName.trim(), firstName: !profile.firstName.trim(),
lastName: !profile.lastName.trim(), lastName: !profile.lastName.trim(),
}; };
const hasError = errors.firstName || errors.lastName; const hasError = errorsObj.firstName || errorsObj.lastName;
if (hasError) { if (hasError) {
const element = this.mainFieldsContainerRef.current; this.scrollToErrorForm();
const parent = element.closest(".scroll-body");
(parent || window).scrollTo(0, element.offsetTop);
} }
this.setState({ errors: errors }); this.setState({ errors: errorsObj });
return !hasError; return !hasError;
} }
@ -615,6 +635,8 @@ class UpdateUserForm extends React.Component {
const pattern = getUserContactsPattern(); const pattern = getUserContactsPattern();
const contacts = getUserContacts(profile.contacts); const contacts = getUserContacts(profile.contacts);
const notEmptyFirstName = Boolean(profile.firstName.trim());
const notEmptyLastName = Boolean(profile.lastName.trim());
//TODO: inject guestsCaption in 'ProfileTypePopupHelper' key instead of hardcoded 'Guests' //TODO: inject guestsCaption in 'ProfileTypePopupHelper' key instead of hardcoded 'Guests'
const tooltipTypeContent = ( const tooltipTypeContent = (
<> <>
@ -790,6 +812,9 @@ class UpdateUserForm extends React.Component {
isRequired={true} isRequired={true}
hasError={errors.firstName} hasError={errors.firstName}
labelText={`${t("FirstName")}:`} labelText={`${t("FirstName")}:`}
{...(notEmptyFirstName && {
errorMessage: t("ErrorInvalidUserFirstName"),
})}
inputName="firstName" inputName="firstName"
inputValue={profile.firstName} inputValue={profile.firstName}
inputIsDisabled={isLoading} inputIsDisabled={isLoading}
@ -802,6 +827,9 @@ class UpdateUserForm extends React.Component {
<TextField <TextField
isRequired={true} isRequired={true}
hasError={errors.lastName} hasError={errors.lastName}
{...(notEmptyLastName && {
errorMessage: t("ErrorInvalidUserLastName"),
})}
labelText={`${t("Common:LastName")}:`} labelText={`${t("Common:LastName")}:`}
inputName="lastName" inputName="lastName"
inputValue={profile.lastName} inputValue={profile.lastName}
@ -1020,6 +1048,7 @@ export default withRouter(
isEditTargetUser: peopleStore.targetUserStore.isEditTargetUser, isEditTargetUser: peopleStore.targetUserStore.isEditTargetUser,
personal: auth.settingsStore.personal, personal: auth.settingsStore.personal,
setUserIsUpdate: auth.userStore.setUserIsUpdate, setUserIsUpdate: auth.userStore.setUserIsUpdate,
userFormValidation: auth.settingsStore.userFormValidation,
}))( }))(
observer( observer(
withTranslation(["ProfileAction", "Common", "Translations"])( withTranslation(["ProfileAction", "Common", "Translations"])(