mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
## For #24486 - Check invite validity before rendering form, error if invalid - Use data returned from validity check to pre-populate form - Remove dependence of flow on URL params other than token - Remove other URL params from link generated in invite confirmation email - Refactor form from JS to TS - Refactor form from class to functional components - Cleanup unused logic - Improve error handling **Invalid invite**  **Valid invite**  - [x] Changes file added for user-visible changes in `changes/` - [x] Updated tests - [ ] A detailed QA plan exists on the associated ticket (if it isn't there, work with the product group's QA engineer to add it) - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
149 lines
4 KiB
TypeScript
149 lines
4 KiB
TypeScript
import React, { useCallback, useState } from "react";
|
|
|
|
import validateEquality from "components/forms/validators/validate_equality";
|
|
|
|
import Button from "components/buttons/Button";
|
|
// @ts-ignore
|
|
import InputField from "components/forms/fields/InputField";
|
|
import { IFormField } from "interfaces/form_field";
|
|
|
|
const baseClass = "confirm-invite-page__form";
|
|
export interface IConfirmInviteFormData {
|
|
name: string;
|
|
password: string;
|
|
password_confirmation: string;
|
|
}
|
|
interface IConfirmInviteFormProps {
|
|
defaultFormData?: Partial<IConfirmInviteFormData>;
|
|
handleSubmit: (data: IConfirmInviteFormData) => void;
|
|
ancestorError?: string;
|
|
}
|
|
interface IConfirmInviteFormErrors {
|
|
name?: string | null;
|
|
password?: string | null;
|
|
password_confirmation?: string | null;
|
|
}
|
|
|
|
const validate = (formData: IConfirmInviteFormData) => {
|
|
const errors: IConfirmInviteFormErrors = {};
|
|
const {
|
|
name,
|
|
password,
|
|
password_confirmation: passwordConfirmation,
|
|
} = formData;
|
|
|
|
if (!name) {
|
|
errors.name = "Full name must be present";
|
|
}
|
|
|
|
if (
|
|
password &&
|
|
passwordConfirmation &&
|
|
!validateEquality(password, passwordConfirmation)
|
|
) {
|
|
errors.password_confirmation =
|
|
"Password confirmation does not match password";
|
|
}
|
|
|
|
if (!password) {
|
|
errors.password = "Password must be present";
|
|
}
|
|
|
|
if (!passwordConfirmation) {
|
|
errors.password_confirmation = "Password confirmation must be present";
|
|
}
|
|
|
|
return errors;
|
|
};
|
|
const ConfirmInviteForm = ({
|
|
defaultFormData,
|
|
handleSubmit,
|
|
ancestorError,
|
|
}: IConfirmInviteFormProps) => {
|
|
const [formData, setFormData] = useState<IConfirmInviteFormData>({
|
|
name: defaultFormData?.name || "",
|
|
password: defaultFormData?.password || "",
|
|
password_confirmation: defaultFormData?.password || "",
|
|
});
|
|
const [formErrors, setFormErrors] = useState<IConfirmInviteFormErrors>({});
|
|
|
|
const { name, password, password_confirmation } = formData;
|
|
|
|
const onInputChange = ({ name: n, value }: IFormField) => {
|
|
const newFormData = { ...formData, [n]: value };
|
|
setFormData(newFormData);
|
|
const newErrs = validate(newFormData);
|
|
// only set errors that are updates of existing errors
|
|
// new errors are only set on submit
|
|
const errsToSet: Record<string, string> = {};
|
|
Object.keys(formErrors).forEach((k) => {
|
|
// @ts-ignore
|
|
if (newErrs[k]) {
|
|
// @ts-ignore
|
|
errsToSet[k] = newErrs[k];
|
|
}
|
|
});
|
|
setFormErrors(errsToSet);
|
|
};
|
|
|
|
const onSubmit = useCallback(
|
|
(evt: React.FormEvent<HTMLFormElement>) => {
|
|
evt.preventDefault();
|
|
|
|
const errs = validate(formData);
|
|
if (Object.keys(errs).length > 0) {
|
|
setFormErrors(errs);
|
|
return;
|
|
}
|
|
handleSubmit(formData);
|
|
},
|
|
[formData, handleSubmit]
|
|
);
|
|
|
|
return (
|
|
<form onSubmit={onSubmit} className={baseClass} autoComplete="off">
|
|
{ancestorError && <div className="form__base-error">{ancestorError}</div>}
|
|
<InputField
|
|
label="Full name"
|
|
autofocus
|
|
onChange={onInputChange}
|
|
name="name"
|
|
value={name}
|
|
error={formErrors.name}
|
|
parseTarget
|
|
maxLength={80}
|
|
/>
|
|
<InputField
|
|
label="Password"
|
|
type="password"
|
|
placeholder="Password"
|
|
helpText="Must include 12 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)"
|
|
onChange={onInputChange}
|
|
name="password"
|
|
value={password}
|
|
error={formErrors.password}
|
|
parseTarget
|
|
/>
|
|
<InputField
|
|
label="Confirm password"
|
|
type="password"
|
|
placeholder="Confirm password"
|
|
onChange={onInputChange}
|
|
name="password_confirmation"
|
|
value={password_confirmation}
|
|
error={formErrors.password_confirmation}
|
|
parseTarget
|
|
/>
|
|
<Button
|
|
type="submit"
|
|
disabled={Object.keys(formErrors).length > 0}
|
|
className="confirm-invite-button"
|
|
variant="brand"
|
|
>
|
|
Submit
|
|
</Button>
|
|
</form>
|
|
);
|
|
};
|
|
|
|
export default ConfirmInviteForm;
|