FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LoginForm.tsx 4.2 KiB

1 jaar geleden
​
1 jaar geleden
1 jaar geleden
​
​
​
​
​
1 jaar geleden
1 jaar geleden
​
1 jaar geleden
1 jaar geleden
​
​
​
​
​
​
​
​
1 jaar geleden
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
1 jaar geleden
​
​
​
1 jaar geleden
1 jaar geleden
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
1 jaar geleden
1 jaar geleden
​
​
​
​
​
​
​
1 jaar geleden
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
1 jaar geleden
1 jaar geleden
​
​
1 jaar geleden
​
​
​
1 jaar geleden
​
​
​
​
​
​
​
1 jaar geleden
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. "use client";
  2. import { FormHelperText, IconButton, InputAdornment } from "@mui/material";
  3. import Button from "@mui/material/Button";
  4. import Stack from "@mui/material/Stack";
  5. import TextField from "@mui/material/TextField";
  6. import Typography from "@mui/material/Typography";
  7. import { TFunction } from "i18next";
  8. import { Session } from "next-auth";
  9. import { getSession, signIn } from "next-auth/react";
  10. import { useRouter } from "next/navigation";
  11. import { useEffect, useState } from "react";
  12. import { SubmitHandler, useForm } from "react-hook-form";
  13. import { useTranslation } from "react-i18next";
  14. import { SetupAxiosInterceptors } from "@/app/(main)/axios/axiosInstance";
  15. import { persistBackendTokens } from "@/app/utils/authToken";
  16. import { Visibility, VisibilityOff } from "@mui/icons-material";
  17. type LoginFields = {
  18. username: string;
  19. password: string;
  20. };
  21. type SessionWithAbilities =
  22. | ({
  23. abilities: string[];
  24. accessToken?: string;
  25. refreshToken?: string;
  26. } & Session)
  27. | null;
  28. // Error codes in https://next-auth.js.org/configuration/pages#sign-in-page
  29. const getHumanFriendlyErrorMessage = (
  30. t: TFunction,
  31. serverError: string,
  32. ): string => {
  33. switch (serverError) {
  34. case "CredentialsSignin":
  35. return t("Invalid username or password.");
  36. case "Default":
  37. default:
  38. return t("Something went wrong. Please try again later.");
  39. }
  40. };
  41. const LoginForm: React.FC = () => {
  42. const { t } = useTranslation("login");
  43. const {
  44. register,
  45. handleSubmit,
  46. formState: { errors, isSubmitting },
  47. } = useForm<LoginFields>();
  48. const [serverError, setServerError] = useState<string>();
  49. const router = useRouter();
  50. const [showPassword, setShowPassword] = useState(false);
  51. const handleClickShowPassword = () => setShowPassword(!showPassword);
  52. const handleMouseDownPassword = () => setShowPassword(!showPassword);
  53. const onSubmit: SubmitHandler<LoginFields> = async (data) => {
  54. console.log(data);
  55. const res = await signIn("credentials", {
  56. redirect: false,
  57. ...data,
  58. });
  59. if (res?.error) {
  60. setServerError(res.error);
  61. return;
  62. }
  63. // Persist access + refresh in localStorage (Login is outside AxiosProvider)
  64. const session = (await getSession()) as SessionWithAbilities;
  65. const access = session?.accessToken ?? "";
  66. if (access) {
  67. persistBackendTokens(access, session?.refreshToken);
  68. SetupAxiosInterceptors(access);
  69. }
  70. window.localStorage.setItem(
  71. "abilities",
  72. JSON.stringify(session?.abilities),
  73. );
  74. const callbackUrl =
  75. new URLSearchParams(window.location.search).get("callbackUrl") || "/";
  76. router.push(callbackUrl);
  77. };
  78. useEffect(() => {
  79. // clean abilities before login
  80. window.localStorage.removeItem("abilities");
  81. }, []);
  82. return (
  83. <Stack
  84. spacing={3}
  85. margin={5}
  86. component="form"
  87. onSubmit={handleSubmit(onSubmit)}
  88. >
  89. <Typography variant="h1">{t("Sign In")}</Typography>
  90. <TextField
  91. label={t("Username")}
  92. {...register("username", { required: t("Please enter a username") })}
  93. error={Boolean(errors.username)}
  94. helperText={errors.username?.message}
  95. />
  96. <TextField
  97. label={t("Password")}
  98. type={showPassword ? "text" : "password"}
  99. {...register("password", { required: t("Please enter a password") })}
  100. error={Boolean(errors.password)}
  101. helperText={errors.password?.message}
  102. InputProps={{
  103. endAdornment: (
  104. <InputAdornment position="end">
  105. <IconButton
  106. aria-label={t("toggle password visibility")}
  107. onClick={handleClickShowPassword}
  108. onMouseDown={handleMouseDownPassword}
  109. >
  110. {showPassword ? <Visibility /> : <VisibilityOff />}
  111. </IconButton>
  112. </InputAdornment>
  113. ),
  114. }}
  115. />
  116. {serverError && (
  117. <FormHelperText error>
  118. {getHumanFriendlyErrorMessage(t, serverError)}
  119. </FormHelperText>
  120. )}
  121. <Button
  122. variant="contained"
  123. size="large"
  124. type="submit"
  125. disabled={isSubmitting}
  126. >
  127. {t("Login")}
  128. </Button>
  129. </Stack>
  130. );
  131. };
  132. export default LoginForm;