Files
hexabot/frontend/src/app-components/auth/Register.tsx
2024-10-14 15:19:50 +01:00

266 lines
8.6 KiB
TypeScript

/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import AbcIcon from "@mui/icons-material/Abc";
import KeyIcon from "@mui/icons-material/Key";
import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight";
import MarkunreadIcon from "@mui/icons-material/Markunread";
import PersonIcon from "@mui/icons-material/Person";
import {
Button,
FormControlLabel,
Grid,
Paper,
Switch,
Typography,
} from "@mui/material";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useAcceptInvite } from "@/hooks/entities/auth-hooks";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { useValidationRules } from "@/hooks/useValidationRules";
import { IRegisterAttributes } from "@/types/auth/register.types";
import { JWT } from "@/utils/Jwt";
import { PublicContentWrapper } from "../../components/anonymous/PublicContentWrapper";
import { ContentContainer } from "../dialogs/layouts/ContentContainer";
import { ContentItem } from "../dialogs/layouts/ContentItem";
import { Adornment } from "../inputs/Adornment";
import { Input } from "../inputs/Input";
import { PasswordInput } from "../inputs/PasswordInput";
const DEFAULT_VALUES: IRegisterAttributes = {
email: "",
first_name: "",
last_name: "",
password: "",
username: "",
roles: [],
token: "",
};
type TRegisterExtendedPayload = IRegisterAttributes & { password2: string };
export const Register = () => {
const { t } = useTranslate();
const router = useRouter();
const { toast } = useToast();
const { mutateAsync: acceptInvite, isLoading } = useAcceptInvite({
onError: () => {
toast.error(t("message.internal_server_error"));
},
onSuccess: () => {
toast.success(t("message.success_invitation_sent"));
router.push("/login");
},
});
const {
watch,
trigger,
register,
setValue,
formState: { errors },
handleSubmit,
} = useForm<TRegisterExtendedPayload>({
defaultValues: DEFAULT_VALUES,
});
const [isTermsAccepted, setIsTermsAccepted] = useState<boolean>(false);
const [readonlyEmail, setReadonlyEmail] = useState<boolean>(false);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setIsTermsAccepted(event.target.checked);
};
const rules = useValidationRules();
const validationRules = {
first_name: {
required: t("message.first_name_is_required"),
},
last_name: {
required: t("message.last_name_is_required"),
},
username: {
required: t("message.username_is_required"),
},
email: {
...rules.email,
required: t("message.email_is_required"),
},
roles: {},
token: {},
password: {
...rules.password,
required: t("message.password_is_required"),
},
password2: {
validate: (val?: string) => {
if (val !== watch("password")) {
trigger("password");
return t("message.password_match");
}
},
},
};
const onSubmitForm = async ({
password2: _password2,
...rest
}: TRegisterExtendedPayload) => {
await acceptInvite(rest);
};
useEffect(() => {
const queryToken = router.query.token;
if (queryToken) {
const jwt = new JWT();
const decodedToken = jwt.decode(String(queryToken));
if (jwt.isExpired(decodedToken)) {
toast.error("Invalid Token");
} else {
setValue("token", String(queryToken));
// (decodedToken);
setValue("email", decodedToken.email);
if (decodedToken.roles.length) setValue("roles", decodedToken.roles);
setReadonlyEmail(!!decodedToken?.email);
}
}
}, [router.query.token]);
return (
<PublicContentWrapper>
<Paper
sx={{
width: { xs: "100%", md: "33%" },
p: 2,
}}
>
<form onSubmit={handleSubmit(onSubmitForm)}>
<ContentContainer>
<ContentItem>
<Typography variant="h1" fontSize="19px" fontWeight={700}>
{t("title.register")}
</Typography>
</ContentItem>
<ContentItem>
<Input
label={t("placeholder.first_name")}
error={!!errors.first_name}
required
autoFocus
{...register("first_name", validationRules.first_name)}
InputProps={{
startAdornment: <Adornment Icon={AbcIcon} />,
}}
helperText={
errors.first_name ? errors.first_name.message : null
}
/>
</ContentItem>
<ContentItem>
<Input
label={t("placeholder.last_name")}
error={!!errors.last_name}
required
{...register("last_name", validationRules.last_name)}
InputProps={{
startAdornment: <Adornment Icon={AbcIcon} />,
}}
helperText={errors.last_name ? errors.last_name.message : null}
/>
</ContentItem>
<ContentItem>
<Input
label={t("placeholder.username")}
error={!!errors.username}
required
{...register("username", validationRules.username)}
InputProps={{
startAdornment: <Adornment Icon={PersonIcon} />,
}}
helperText={errors.username ? errors.username.message : null}
/>
</ContentItem>
<ContentItem>
<Input
label={t("placeholder.email")}
error={!!errors.email}
required
{...register("email", validationRules.email)}
helperText={errors.email ? errors.email.message : null}
InputProps={{
disabled: readonlyEmail,
readOnly: readonlyEmail,
startAdornment: <Adornment Icon={MarkunreadIcon} />,
}}
/>
</ContentItem>
<ContentItem>
<PasswordInput
label={t("label.auth_pass")}
error={!!errors.password}
required
{...register("password", validationRules.password)}
helperText={errors.password ? errors.password.message : null}
InputProps={{
startAdornment: <Adornment Icon={KeyIcon} />,
}}
/>
</ContentItem>
<ContentItem>
<PasswordInput
label={t("placeholder.password2")}
error={!!errors.password2}
required
{...register("password2", validationRules.password2)}
helperText={errors.password2 ? errors.password2.message : null}
InputProps={{
startAdornment: <Adornment Icon={KeyIcon} />,
}}
/>
</ContentItem>
<ContentItem>
<FormControlLabel
control={
<Switch checked={isTermsAccepted} onChange={handleChange} />
}
label={
<Typography
color={isTermsAccepted ? "primary.main" : "text.primary"}
fontSize="14px"
>
{t("label.terms")}
</Typography>
}
/>
</ContentItem>
<ContentItem>
<Grid container gap={1} justifyContent="end">
<Grid>
<Button
type="submit"
endIcon={<KeyboardArrowRightIcon />}
onClick={handleSubmit(onSubmitForm)}
disabled={isLoading || !isTermsAccepted}
>
{t("button.register")}
</Button>
</Grid>
</Grid>
</ContentItem>
</ContentContainer>
</form>
</Paper>
</PublicContentWrapper>
);
};