From c147a95563ab36eefe2bd587df55d462ef4ad4d5 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:40:56 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20Sign=20Up=20and=20make=20`OPE?= =?UTF-8?q?N=5FUSER=5FREGISTRATION=3DTrue`=20by=20default=20(#1265)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- frontend/src/hooks/useAuth.ts | 29 +++++- frontend/src/routeTree.gen.ts | 11 +++ frontend/src/routes/login.tsx | 16 ++-- frontend/src/routes/signup.tsx | 163 +++++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 frontend/src/routes/signup.tsx diff --git a/.env b/.env index cd90c54b90..84e1bab670 100644 --- a/.env +++ b/.env @@ -13,7 +13,7 @@ BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,h SECRET_KEY=changethis FIRST_SUPERUSER=admin@example.com FIRST_SUPERUSER_PASSWORD=changethis -USERS_OPEN_REGISTRATION=False +USERS_OPEN_REGISTRATION=True # Emails SMTP_HOST= diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 397e408f8d..6b2499fee8 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from "@tanstack/react-query" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useNavigate } from "@tanstack/react-router" import { useState } from "react" @@ -8,8 +8,10 @@ import { type ApiError, LoginService, type UserPublic, + type UserRegister, UsersService, } from "../client" +import useCustomToast from "./useCustomToast" const isLoggedIn = () => { return localStorage.getItem("access_token") !== null @@ -18,12 +20,36 @@ const isLoggedIn = () => { const useAuth = () => { const [error, setError] = useState(null) const navigate = useNavigate() + const showToast = useCustomToast() + const queryClient = useQueryClient() const { data: user, isLoading } = useQuery({ queryKey: ["currentUser"], queryFn: UsersService.readUserMe, enabled: isLoggedIn(), }) + const signUpMutation = useMutation({ + mutationFn: (data: UserRegister) => + UsersService.registerUser({ requestBody: data }), + + onSuccess: () => { + navigate({ to: "/login" }) + showToast("Success!", "User created successfully.", "success") + }, + onError: (err: ApiError) => { + let errDetail = (err.body as any)?.detail + + if (err instanceof AxiosError) { + errDetail = err.message + } + + showToast("Something went wrong.", `${errDetail}`, "error") + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["users"] }) + }, + }) + const login = async (data: AccessToken) => { const response = await LoginService.loginAccessToken({ formData: data, @@ -57,6 +83,7 @@ const useAuth = () => { } return { + signUpMutation, loginMutation, logout, user, diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 395866a44b..0e78c9ba20 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -11,6 +11,7 @@ // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as SignupImport } from './routes/signup' import { Route as ResetPasswordImport } from './routes/reset-password' import { Route as RecoverPasswordImport } from './routes/recover-password' import { Route as LoginImport } from './routes/login' @@ -22,6 +23,11 @@ import { Route as LayoutAdminImport } from './routes/_layout/admin' // Create/Update Routes +const SignupRoute = SignupImport.update({ + path: '/signup', + getParentRoute: () => rootRoute, +} as any) + const ResetPasswordRoute = ResetPasswordImport.update({ path: '/reset-password', getParentRoute: () => rootRoute, @@ -82,6 +88,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ResetPasswordImport parentRoute: typeof rootRoute } + '/signup': { + preLoaderRoute: typeof SignupImport + parentRoute: typeof rootRoute + } '/_layout/admin': { preLoaderRoute: typeof LayoutAdminImport parentRoute: typeof LayoutImport @@ -113,6 +123,7 @@ export const routeTree = rootRoute.addChildren([ LoginRoute, RecoverPasswordRoute, ResetPasswordRoute, + SignupRoute, ]) /* prettier-ignore-end */ diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 2c35208c71..20a9be6564 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -1,7 +1,6 @@ import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons" import { Button, - Center, Container, FormControl, FormErrorMessage, @@ -11,6 +10,7 @@ import { InputGroup, InputRightElement, Link, + Text, useBoolean, } from "@chakra-ui/react" import { @@ -126,14 +126,18 @@ function Login() { {error && {error}} -
- - Forgot password? - -
+ + Forgot password? + + + Don't have an account?{" "} + + Sign up + + ) diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx new file mode 100644 index 0000000000..1a27e3b870 --- /dev/null +++ b/frontend/src/routes/signup.tsx @@ -0,0 +1,163 @@ +import { + Button, + Container, + Flex, + FormControl, + FormErrorMessage, + FormLabel, + Image, + Input, + Link, + Text, +} from "@chakra-ui/react" +import { + Link as RouterLink, + createFileRoute, + redirect, +} from "@tanstack/react-router" +import { type SubmitHandler, useForm } from "react-hook-form" + +import Logo from "/assets/images/fastapi-logo.svg" +import type { UserRegister } from "../client" +import useAuth, { isLoggedIn } from "../hooks/useAuth" +import { confirmPasswordRules, emailPattern, passwordRules } from "../utils" + +export const Route = createFileRoute("/signup")({ + component: SignUp, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: "/", + }) + } + }, +}) + +interface UserRegisterForm extends UserRegister { + confirm_password: string +} + +function SignUp() { + const { signUpMutation } = useAuth() + const { + register, + handleSubmit, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + email: "", + full_name: "", + password: "", + confirm_password: "", + }, + }) + + const onSubmit: SubmitHandler = (data) => { + signUpMutation.mutate(data) + } + + return ( + <> + + + FastAPI logo + + + Full Name + + + {errors.full_name && ( + {errors.full_name.message} + )} + + + + Email + + + {errors.email && ( + {errors.email.message} + )} + + + + Password + + + {errors.password && ( + {errors.password.message} + )} + + + + Confirm Password + + + + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + Already have an account?{" "} + + Log In + + + + + + ) +} + +export default SignUp