Skip to content

Commit

Permalink
#183 Fix user form validation
Browse files Browse the repository at this point in the history
When creating a user, the save button is now only enabled when all
required fields are filled out.
Additionally, programmatically all fields have the required attribute
and visually an asterisk was added to indicate users that the field is
required.
  • Loading branch information
lgorzitze committed Jan 6, 2025
1 parent f04660e commit ade49d9
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 28 deletions.
47 changes: 37 additions & 10 deletions app/src/main/ui/src/components/users/UserForm.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import {deprecated_Form as Form, Details} from "@cloudogu/ces-theme-tailwind";
import type {NotifyFunction, UseFormHandlerFunctions} from "@cloudogu/deprecated-ces-theme-tailwind";
import {Button, H2, ListWithSearchbar} from "@cloudogu/deprecated-ces-theme-tailwind";
import {TrashIcon} from "@heroicons/react/24/outline";
import {ChangeEvent, useState} from "react";
import {twMerge} from "tailwind-merge";
import {t} from "../../helpers/i18nHelpers";
import {useConfirmation} from "../../hooks/useConfirmation";
import {Prompt} from "../../hooks/usePrompt";
import useUserFormHandler from "../../hooks/useUserFormHandler";
import type {Group} from "../../services/Groups";
import {GroupsService} from "../../services/Groups";
import {ConfirmationDialog} from "../ConfirmationDialog";
import {useApplicationContext} from "../contexts/ApplicationContext";
import HelpLink from "../helpLink";
import type {Group} from "../../services/Groups";
import type {User} from "../../services/Users";
import type {NotifyFunction, UseFormHandlerFunctions} from "@cloudogu/deprecated-ces-theme-tailwind";

const MAX_SEARCH_RESULTS = 10;

Expand All @@ -31,9 +32,17 @@ export interface UserFormProps<T extends User> {
export default function UserForm<T extends User>(props: UserFormProps<T>) {
const {handler, notification, notify} = useUserFormHandler<T>(props.initialUser, (values: T) => props.onSubmit(values, notify, handler));
const {open, setOpen: toggleModal, targetName: groupName, setTargetName: setGroupName} = useConfirmation();
const [formDisabled, setFormDisabled] = useState(true);

const {admin} = useApplicationContext().casUser;

const originalChangeFunction = handler.handleChange;

handler.handleChange = (e:ChangeEvent) => {
originalChangeFunction(e);
hasEmptyRequiredFields();
};

const addGroup = (groupName: string): void => {
if (handler.values.memberOf.indexOf(groupName) < 0) {
const newGroups = [...handler.values.memberOf, groupName];
Expand Down Expand Up @@ -83,6 +92,24 @@ export default function UserForm<T extends User>(props: UserFormProps<T>) {
/>
);

const hasEmptyRequiredFields = (): void => {
const form = document.forms.item(0);
console.log("Check for null values");
if (form) {
const inputs: NodeListOf<HTMLInputElement> = form.querySelectorAll("input:required");
for (const input of inputs) {
if (!input.value) {
setFormDisabled(true);
return;
}
}
setFormDisabled(false);
return;
}
setFormDisabled(true);
return;
};

return (
<>
<ConfirmationDialog
Expand All @@ -103,27 +130,27 @@ export default function UserForm<T extends User>(props: UserFormProps<T>) {
{t("users.externalUserWarning")}
</span>
)}
<Form.ValidatedTextInput type={"text"} name={"username"} disabled={props.disableUsernameField ?? true} data-testid="username" placeholder={t("users.placeholder.username")} hint={t("users.hint.username")}>
<Form.ValidatedTextInput required type={"text"} name={"username"} disabled={props.disableUsernameField ?? true} data-testid="username" placeholder={t("users.placeholder.username")} hint={t("users.hint.username")} >
{t("editUser.labels.username")}
</Form.ValidatedTextInput>
<Form.ValidatedTextInput disabled={props.initialUser.external} type={"text"} name={"givenname"} data-testid="givenname" placeholder={t("users.placeholder.givenname")}>
<Form.ValidatedTextInput required disabled={props.initialUser.external} type={"text"} name={"givenname"} data-testid="givenname" placeholder={t("users.placeholder.givenname")} >
{t("editUser.labels.givenName")}
</Form.ValidatedTextInput>
<Form.ValidatedTextInput disabled={props.initialUser.external} type={"text"} name={"surname"} data-testid="surname" placeholder={t("users.placeholder.surname")}>
<Form.ValidatedTextInput required disabled={props.initialUser.external} type={"text"} name={"surname"} data-testid="surname" placeholder={t("users.placeholder.surname")} >
{t("editUser.labels.surname")}
</Form.ValidatedTextInput>
<Form.ValidatedTextInput disabled={props.initialUser.external} type={"text"} name={"displayName"} data-testid="displayName" placeholder={t("users.placeholder.displayName")} hint={t("users.hint.displayName")}>
<Form.ValidatedTextInput required disabled={props.initialUser.external} type={"text"} name={"displayName"} data-testid="displayName" placeholder={t("users.placeholder.displayName")} hint={t("users.hint.displayName")} >
{t("editUser.labels.displayName")}
</Form.ValidatedTextInput>
<Form.ValidatedTextInput disabled={props.initialUser.external} type={"text"} name={"mail"} data-testid="mail" placeholder={t("users.placeholder.mail")}>
<Form.ValidatedTextInput required disabled={props.initialUser.external} type={"text"} name={"mail"} data-testid="mail" placeholder={t("users.placeholder.mail")} >
{t("editUser.labels.email")}
</Form.ValidatedTextInput>
{!props.initialUser.external &&
<>
<Form.ValidatedTextInput disabled={props.initialUser.external} type={"password"} name={"password"} data-testid="password" placeholder={t("users.placeholder.password")}>
<Form.ValidatedTextInput required disabled={props.initialUser.external} type={"password"} name={"password"} data-testid="password" placeholder={t("users.placeholder.password")} >
{t("editUser.labels.password")}
</Form.ValidatedTextInput>
<Form.ValidatedTextInput disabled={props.initialUser.external} type={"password"} name={"confirmPassword"} data-testid="confirmPassword" placeholder={t("users.placeholder.confirmPassword")}>
<Form.ValidatedTextInput required disabled={props.initialUser.external} type={"password"} name={"confirmPassword"} data-testid="confirmPassword" placeholder={t("users.placeholder.confirmPassword")} >
{t("editUser.labels.confirmPassword")}
</Form.ValidatedTextInput>
</>
Expand Down Expand Up @@ -156,7 +183,7 @@ export default function UserForm<T extends User>(props: UserFormProps<T>) {
)}

<div className={"my-4"}>
<Button variant={"primary"} type={"submit"} disabled={!handler.dirty} data-testid="save-button">
<Button variant={"primary"} type={"submit"} disabled={formDisabled} data-testid="save-button">
{t("editUser.buttons.save")}
</Button>
{props.additionalButtons as JSX.Element}
Expand Down
14 changes: 7 additions & 7 deletions app/src/main/ui/src/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
"editUser.errors.username.maxlength": "Darf höchstens 128 zeichen enthalten",
"editUser.errors.username.minlength": "Muss mindestens 2 Zeichen enthalten",
"editUser.errors.username.required": "Nutzername muss ausgefüllt sein.",
"editUser.labels.confirmPassword": "Passwort bestätigen",
"editUser.labels.displayName": "Anzeigename",
"editUser.labels.email": "E-Mail",
"editUser.labels.confirmPassword": "Passwort bestätigen*",
"editUser.labels.displayName": "Anzeigename*",
"editUser.labels.email": "E-Mail*",
"editUser.labels.external": "Externer Account",
"editUser.labels.givenName": "Vorname",
"editUser.labels.givenName": "Vorname*",
"editUser.labels.mustChangePassword": "Nutzer muss sein Passwort beim nächsten Login ändern",
"editUser.labels.password": "Passwort",
"editUser.labels.surname": "Nachname",
"editUser.labels.username": "Nutzername",
"editUser.labels.password": "Passwort*",
"editUser.labels.surname": "Nachname*",
"editUser.labels.username": "Nutzername*",
"editUser.notification.error": "Die Account Informationen konnten nicht gespeichert werden. Bitte versuchen Sie es später erneut.",
"editUser.notification.success": "Die Account Informationen wurden erfolgreich gespeichert.",
"general.applicationName": "User Management",
Expand Down
14 changes: 7 additions & 7 deletions app/src/main/ui/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
"editUser.errors.username.maxlength": "May contain a maximum of 128 characters",
"editUser.errors.username.minlength": "Must contain at least 2 characters",
"editUser.errors.username.required": "Username is required.",
"editUser.labels.confirmPassword": "Confirm Password",
"editUser.labels.displayName": "Display Name",
"editUser.labels.email": "E-Mail",
"editUser.labels.confirmPassword": "Confirm Password*",
"editUser.labels.displayName": "Display Name*",
"editUser.labels.email": "E-Mail*",
"editUser.labels.external": "External account",
"editUser.labels.givenName": "Given Name",
"editUser.labels.givenName": "Given Name*",
"editUser.labels.mustChangePassword": "The user must change the password at the next login",
"editUser.labels.password": "Password",
"editUser.labels.surname": "Surname",
"editUser.labels.username": "Username",
"editUser.labels.password": "Password*",
"editUser.labels.surname": "Surname*",
"editUser.labels.username": "Username*",
"editUser.notification.error": "Account information could not be saved. Please try again later.",
"editUser.notification.success": "Account information saved successfully.",
"general.applicationName": "User Management",
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ asn1@~0.2.3:
dependencies:
safer-buffer "~2.1.0"

assert-plus@^1.0.0, [email protected]:
[email protected], assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
Expand Down Expand Up @@ -401,7 +401,7 @@ end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"

enquirer@^2.3.6, "enquirer@>= 2.3.0 < 3":
enquirer@^2.3.6:
version "2.4.1"
resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz"
integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==
Expand Down Expand Up @@ -469,7 +469,7 @@ [email protected]:
optionalDependencies:
"@types/yauzl" "^2.9.1"

extsprintf@^1.2.0, extsprintf@1.3.0:
extsprintf@1.3.0, extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz"
integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
Expand Down Expand Up @@ -784,7 +784,7 @@ minimist@^1.2.8:
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==

ms@^2.1.1, [email protected].2:
[email protected].2, ms@^2.1.1:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
Expand Down

0 comments on commit ade49d9

Please sign in to comment.