From 4da90084b5eaf5211754b0f3302a79f13a01aed4 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Sun, 29 Sep 2024 03:41:01 +0900 Subject: [PATCH 01/12] fix: change 'admin' to 'host' --- src/components/SideBar.tsx | 2 +- .../{admin => host}/AuthRequiredRoute.tsx | 2 +- src/components/{admin => host}/OptionalAction.tsx | 0 src/components/{admin => host}/TransferBanner.tsx | 10 +++++----- src/pages/Home.tsx | 6 +++--- src/pages/{admin => host}/SignIn.tsx | 14 +++++++------- src/pages/{admin => host}/SignUp.tsx | 10 +++++----- 7 files changed, 22 insertions(+), 22 deletions(-) rename src/components/{admin => host}/AuthRequiredRoute.tsx (95%) rename src/components/{admin => host}/OptionalAction.tsx (100%) rename src/components/{admin => host}/TransferBanner.tsx (63%) rename src/pages/{admin => host}/SignIn.tsx (94%) rename src/pages/{admin => host}/SignUp.tsx (96%) diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 6a7912c..933aef5 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -44,7 +44,7 @@ function SideBar() { mutationKey: ["logout"], onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["user"] }); - navigate("/admin/signin"); + navigate("/host/signin"); }, }); diff --git a/src/components/admin/AuthRequiredRoute.tsx b/src/components/host/AuthRequiredRoute.tsx similarity index 95% rename from src/components/admin/AuthRequiredRoute.tsx rename to src/components/host/AuthRequiredRoute.tsx index 5a86de3..4fe371a 100644 --- a/src/components/admin/AuthRequiredRoute.tsx +++ b/src/components/host/AuthRequiredRoute.tsx @@ -17,7 +17,7 @@ function AuthRequiredRoute() { } if (!user) { - return ; + return ; } if (classId) { diff --git a/src/components/admin/OptionalAction.tsx b/src/components/host/OptionalAction.tsx similarity index 100% rename from src/components/admin/OptionalAction.tsx rename to src/components/host/OptionalAction.tsx diff --git a/src/components/admin/TransferBanner.tsx b/src/components/host/TransferBanner.tsx similarity index 63% rename from src/components/admin/TransferBanner.tsx rename to src/components/host/TransferBanner.tsx index dbaac1c..f858854 100644 --- a/src/components/admin/TransferBanner.tsx +++ b/src/components/host/TransferBanner.tsx @@ -1,19 +1,19 @@ -// Admin과 Attendees를 전환하기 위한 상단 배너입니다. +// Host와 Attendees를 전환하기 위한 상단 배너입니다. import { Link } from "react-router-dom"; import styled from "styled-components"; interface TransferBannerProps { - from: "admin" | "attendees"; + from: "host" | "attendees"; } function TransferBanner(props: TransferBannerProps) { return ( <> - {props.from === "admin" ? ( - If you are Attendees + {props.from === "host" ? ( + If you are an Attendee, click here! ) : ( - If you are Admin + If you are the Host, click here! )} ); diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index f769784..6afd484 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -5,17 +5,17 @@ import styled from "styled-components"; import { AttendanceErrorCode, postAttend } from "../api/attendance.ts"; import sendIcon from "../assets/send.svg"; -import TransferBanner from "../components/admin/TransferBanner.tsx"; import Alert from "../components/Alert.tsx"; import { PrimaryButton } from "../components/Button.tsx"; import NoteSelectModal from "../components/home/NoteSelectModal.tsx"; +import TransferBanner from "../components/host/TransferBanner.tsx"; import TextField from "../components/TextField"; import useModalState from "../hooks/modal.tsx"; import useSubmitHandler from "../hooks/submitHandler.tsx"; const ErrorMessages: Record = { - [AttendanceErrorCode.InvalidAuthCode]: `Could not find a session corresponding to passcode. Please check your credentials or contact the administrator for help.`, - [AttendanceErrorCode.InvalidName]: `Could not find a session corresponding to your name. Please check your credentials or contact the administrator for help.`, + [AttendanceErrorCode.InvalidAuthCode]: `Could not find a session corresponding to passcode. Please check your credentials or contact the host for help.`, + [AttendanceErrorCode.InvalidName]: `Could not find a session corresponding to your name. Please check your credentials or contact the host for help.`, [AttendanceErrorCode.AlreadyAttended]: `You have already attended the session.`, [AttendanceErrorCode.DifferentName]: `You have already attended the session with a different name.`, [AttendanceErrorCode.FailedToAttend]: `Failed to attend the session. Please try again later.`, diff --git a/src/pages/admin/SignIn.tsx b/src/pages/host/SignIn.tsx similarity index 94% rename from src/pages/admin/SignIn.tsx rename to src/pages/host/SignIn.tsx index 876d35b..ac32fa6 100644 --- a/src/pages/admin/SignIn.tsx +++ b/src/pages/host/SignIn.tsx @@ -4,13 +4,13 @@ import { Link, useNavigate } from "react-router-dom"; import styled from "styled-components"; import { EMAIL_REGEX, signIn, useUser } from "../../api/user"; +import Alert from "../../components/Alert"; +import { PrimaryButton } from "../../components/Button"; import { OptionalActionLabel, OptionalActionLink, -} from "../../components/admin/OptionalAction"; -import TransferBanner from "../../components/admin/TransferBanner"; -import Alert from "../../components/Alert"; -import { PrimaryButton } from "../../components/Button"; +} from "../../components/host/OptionalAction"; +import TransferBanner from "../../components/host/TransferBanner"; import TextField from "../../components/TextField"; function SignIn() { @@ -48,7 +48,7 @@ function SignIn() { return ( - + Simply manage attendance @@ -86,7 +86,7 @@ function SignIn() { onChange={(e) => setPassword(e.target.value)} /> Forgot Password @@ -105,7 +105,7 @@ function SignIn() { Don't have an account?{" "} - + Sign up now diff --git a/src/pages/admin/SignUp.tsx b/src/pages/host/SignUp.tsx similarity index 96% rename from src/pages/admin/SignUp.tsx rename to src/pages/host/SignUp.tsx index 60d0842..2c42a54 100644 --- a/src/pages/admin/SignUp.tsx +++ b/src/pages/host/SignUp.tsx @@ -13,12 +13,12 @@ import { useUser, verifyEmail, } from "../../api/user"; +import { PrimaryButton } from "../../components/Button.tsx"; import { OptionalActionLabel, OptionalActionLink, -} from "../../components/admin/OptionalAction.tsx"; -import TransferBanner from "../../components/admin/TransferBanner.tsx"; -import { PrimaryButton } from "../../components/Button.tsx"; +} from "../../components/host/OptionalAction.tsx"; +import TransferBanner from "../../components/host/TransferBanner.tsx"; import TextField from "../../components/TextField.tsx"; import useSubmitHandler from "../../hooks/submitHandler.tsx"; @@ -95,7 +95,7 @@ function SignUp() { return ( - + Create a free account { @@ -214,7 +214,7 @@ function SignUp() { Already a member?{" "} - Sign in + Sign in From ec626cb15bf09d050b8c793d4b1e838b15d307b9 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Sun, 29 Sep 2024 19:28:59 +0900 Subject: [PATCH 02/12] feat: add pwd recovery in router --- src/router.tsx | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/router.tsx b/src/router.tsx index 81ca06b..5a71a10 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,16 +1,21 @@ import { createBrowserRouter } from "react-router-dom"; import App from "./App"; -import AuthRequiredRoute from "./components/admin/AuthRequiredRoute.tsx"; -import SignIn from "./pages/admin/SignIn"; -import SignUp from "./pages/admin/SignUp"; +import AuthRequiredRoute from "./components/host/AuthRequiredRoute.tsx"; import ClassList from "./pages/class/ClassList.tsx"; import CreateClass from "./pages/class/CreateClass.tsx"; import Home from "./pages/Home"; +import Email from "./pages/host/password/Email.tsx"; +import ForgotPassword from "./pages/host/password/ForgotPassword.tsx"; +import NewPassword from "./pages/host/password/NewPassword.tsx"; +import Success from "./pages/host/password/Success.tsx"; +import Verify from "./pages/host/password/Verify.tsx"; +import SignIn from "./pages/host/SignIn"; +import SignUp from "./pages/host/SignUp"; import Account from "./pages/main/Account.tsx"; -import AddAttendees from "./pages/main/Attendees/AddAttendees.tsx"; -import AttendeeDetail from "./pages/main/Attendees/AttendeeDetail.tsx"; -import Attendees from "./pages/main/Attendees/Attendees.tsx"; +import AddAttendees from "./pages/main/attendees/AddAttendees.tsx"; +import AttendeeDetail from "./pages/main/attendees/AttendeeDetail.tsx"; +import Attendees from "./pages/main/attendees/Attendees.tsx"; import CodePopup from "./pages/main/dashboard/CodePopup.tsx"; import Dashboard from "./pages/main/dashboard/Dashboard.tsx"; import SessionDetail from "./pages/main/session/SessionDetail.tsx"; @@ -27,8 +32,18 @@ const router = createBrowserRouter([ children: [ { path: "/", element: }, { path: "/result", element: }, - { path: "/admin/signin", element: }, - { path: "/admin/signup", element: }, + { path: "/host/signin", element: }, + { path: "/host/signup", element: }, + { + path: "/host/forgot-password", + element: , + children: [ + { path: "email", element: }, + { path: "verify", element: }, + { path: "new-password", element: }, + { path: "success", element: }, + ], + }, { path: "/terms_of_use", element: }, { path: "/", From 4f9ae4283be5418aae2fbef3c7feb9b1537b9cb0 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Sun, 29 Sep 2024 19:29:35 +0900 Subject: [PATCH 03/12] feat: add pwd recovery layout --- src/assets/success.svg | 4 + src/pages/host/password/Email.tsx | 61 ++++++++++++ src/pages/host/password/ForgotPassword.tsx | 53 +++++++++++ src/pages/host/password/NewPassword.tsx | 104 +++++++++++++++++++++ src/pages/host/password/Success.tsx | 27 ++++++ src/pages/host/password/Verify.tsx | 56 +++++++++++ 6 files changed, 305 insertions(+) create mode 100644 src/assets/success.svg create mode 100644 src/pages/host/password/Email.tsx create mode 100644 src/pages/host/password/ForgotPassword.tsx create mode 100644 src/pages/host/password/NewPassword.tsx create mode 100644 src/pages/host/password/Success.tsx create mode 100644 src/pages/host/password/Verify.tsx diff --git a/src/assets/success.svg b/src/assets/success.svg new file mode 100644 index 0000000..236ad7f --- /dev/null +++ b/src/assets/success.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/pages/host/password/Email.tsx b/src/pages/host/password/Email.tsx new file mode 100644 index 0000000..977417b --- /dev/null +++ b/src/pages/host/password/Email.tsx @@ -0,0 +1,61 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import { EMAIL_REGEX } from "../../../api/user.ts"; +import { PrimaryButton, TertiaryButton } from "../../../components/Button.tsx"; +import { + OptionalActionLabel, + OptionalActionLink, +} from "../../../components/host/OptionalAction.tsx"; +import TextField from "../../../components/TextField.tsx"; + +function Email() { + const navigate = useNavigate(); + + const [email, setEmail] = useState(""); + + const emailError = email.match(EMAIL_REGEX) == null; + + const onSubmit = () => { + // TODO : 이메일 전송 api 추가 + navigate("/host/forgot-password/verify"); + }; + + return ( + <> +

Forgot password?

+

+ Please enter the email address you used when you joined and we’ll send + you the verification code to reset password. +

+ setEmail(e.target.value)} + style={{ marginBottom: "3rem" }} + /> + + Send Code + + { + navigate("/host/signin"); + }} + style={{ marginBottom: "2rem" }} + > + Back + + + Don't have an account?{" "} + Sign up now + + + ); +} + +export default Email; diff --git a/src/pages/host/password/ForgotPassword.tsx b/src/pages/host/password/ForgotPassword.tsx new file mode 100644 index 0000000..ad79017 --- /dev/null +++ b/src/pages/host/password/ForgotPassword.tsx @@ -0,0 +1,53 @@ +import { Outlet } from "react-router-dom"; +import styled from "styled-components"; + +import TransferBanner from "../../../components/host/TransferBanner.tsx"; + +function ForgotPassword() { + return ( + + + + + + + ); +} + +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; +`; + +const ContentContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + width: 32rem; + + h1 { + ${({ theme }) => theme.typography.h3}; + margin-top: 8.7rem; + margin-bottom: 1.6rem; + } + + h2 { + ${({ theme }) => theme.typography.b4}; + color: ${({ theme }) => theme.colors.darkGrey}; + margin-bottom: 3rem; + text-align: center; + } + + div, + input, + button { + width: 100%; + } +`; + +export default ForgotPassword; diff --git a/src/pages/host/password/NewPassword.tsx b/src/pages/host/password/NewPassword.tsx new file mode 100644 index 0000000..3a8d4b0 --- /dev/null +++ b/src/pages/host/password/NewPassword.tsx @@ -0,0 +1,104 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import { PASSWORD_REGEX } from "../../../api/user.ts"; +import { PrimaryButton } from "../../../components/Button.tsx"; +import TextField from "../../../components/TextField.tsx"; + +function NewPassword() { + const navigate = useNavigate(); + + const [password, setpassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + + const [isPasswordFocused, setIsPasswordFocused] = useState(false); + const [isConfirmPasswordFocused, setIsConfirmPasswordFocused] = + useState(false); + + const [isPasswordValid, setIsPasswordValid] = useState(true); + const [isConfirmPasswordValid, setIsConfirmPasswordValid] = useState(true); + const [isButtonAble, setIsButtonAble] = useState(false); + + useEffect(() => { + if (password === "") { + setIsPasswordValid(true); + } else { + setIsPasswordValid(PASSWORD_REGEX.test(password)); + } + + if (confirmPassword === "") { + setIsConfirmPasswordValid(true); + } else { + setIsConfirmPasswordValid(password === confirmPassword); + } + }, [isPasswordFocused, isConfirmPasswordFocused]); + + useEffect(() => { + setIsButtonAble( + password !== "" && + confirmPassword !== "" && + PASSWORD_REGEX.test(password) && + password === confirmPassword + ); + }, [password, confirmPassword]); + + const onSubmit = () => { + // TODO : 비밀번호 재설정 api 추가 + navigate("/host/forgot-password/success"); + }; + + return ( + <> +

Set New Password

+

+ Password must be 8-20 characters long, including at least one letter, + one number, and one special character. +

+ setpassword(e.target.value)} + onFocus={() => { + setIsPasswordFocused(true); + }} + onBlur={() => { + setIsPasswordFocused(false); + }} + supportingText={ + !isPasswordValid + ? "Password must be 8-20 characters long, including at least one letter, one number, and one special character." + : undefined + } + hasError={!isPasswordValid} + style={{ marginBottom: "1rem" }} + /> + setConfirmPassword(e.target.value)} + onFocus={() => { + setIsConfirmPasswordFocused(true); + }} + onBlur={() => { + setIsConfirmPasswordFocused(false); + }} + supportingText={ + !isConfirmPasswordValid ? "Passwords do not match." : undefined + } + hasError={!isConfirmPasswordValid} + style={{ marginBottom: "3rem" }} + /> + + Set New Password + + + ); +} + +export default NewPassword; diff --git a/src/pages/host/password/Success.tsx b/src/pages/host/password/Success.tsx new file mode 100644 index 0000000..d6647dc --- /dev/null +++ b/src/pages/host/password/Success.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; + +import successIcon from "../../../assets/success.svg"; +import { PrimaryButton } from "../../../components/Button.tsx"; + +function Success() { + const navigate = useNavigate(); + + return ( + <> + success +

Reset Successfully

+

Sign in into your account with your new password.

+ { + navigate("/host/signin"); + }} + style={{ marginBottom: "1.5rem" }} + > + Sign In + + + ); +} + +export default Success; diff --git a/src/pages/host/password/Verify.tsx b/src/pages/host/password/Verify.tsx new file mode 100644 index 0000000..d9341a2 --- /dev/null +++ b/src/pages/host/password/Verify.tsx @@ -0,0 +1,56 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import { PrimaryButton, TertiaryButton } from "../../../components/Button.tsx"; +import { + OptionalActionLabel, + OptionalActionLink, +} from "../../../components/host/OptionalAction.tsx"; +import TextField from "../../../components/TextField.tsx"; + +function Verify() { + const navigate = useNavigate(); + const [verificationCode, setVerificationCode] = useState(""); + + const onSubmit = () => { + // TODO : 코드 검증 api 추가 + navigate("/host/forgot-password/new-password"); + }; + + return ( + <> +

Enter your code

+

Please enter the code that we sent to your email address.

+ setVerificationCode(e.target.value)} + style={{ marginBottom: "3rem" }} + /> + + Continue + + { + navigate("/host/signin"); + }} + style={{ marginBottom: "2rem" }} + > + Back to Sign In + + + Don’t receive the code?{" "} + + Click to Resend + + + + ); +} + +export default Verify; From 98e7008bdf894c7d712ebea73b980dc835f6f36e Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Sun, 29 Sep 2024 19:30:21 +0900 Subject: [PATCH 04/12] refactor: camel case to pascal case --- src/pages/main/{Attendees => attendees}/AddAttendees.tsx | 0 src/pages/main/{Attendees => attendees}/AttendeeDetail.tsx | 0 src/pages/main/{Attendees => attendees}/Attendees.tsx | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/pages/main/{Attendees => attendees}/AddAttendees.tsx (100%) rename src/pages/main/{Attendees => attendees}/AttendeeDetail.tsx (100%) rename src/pages/main/{Attendees => attendees}/Attendees.tsx (100%) diff --git a/src/pages/main/Attendees/AddAttendees.tsx b/src/pages/main/attendees/AddAttendees.tsx similarity index 100% rename from src/pages/main/Attendees/AddAttendees.tsx rename to src/pages/main/attendees/AddAttendees.tsx diff --git a/src/pages/main/Attendees/AttendeeDetail.tsx b/src/pages/main/attendees/AttendeeDetail.tsx similarity index 100% rename from src/pages/main/Attendees/AttendeeDetail.tsx rename to src/pages/main/attendees/AttendeeDetail.tsx diff --git a/src/pages/main/Attendees/Attendees.tsx b/src/pages/main/attendees/Attendees.tsx similarity index 100% rename from src/pages/main/Attendees/Attendees.tsx rename to src/pages/main/attendees/Attendees.tsx From ee8100c42a9e30feea064c76046e737bb10d7452 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Sun, 29 Sep 2024 19:30:50 +0900 Subject: [PATCH 05/12] fix: change word admin to host --- src/components/host/TransferBanner.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/host/TransferBanner.tsx b/src/components/host/TransferBanner.tsx index f858854..63e7122 100644 --- a/src/components/host/TransferBanner.tsx +++ b/src/components/host/TransferBanner.tsx @@ -3,17 +3,23 @@ import { Link } from "react-router-dom"; import styled from "styled-components"; +import { useUser } from "../../api/user.ts"; + interface TransferBannerProps { from: "host" | "attendees"; } function TransferBanner(props: TransferBannerProps) { + const { data: user } = useUser(); + return ( <> {props.from === "host" ? ( If you are an Attendee, click here! ) : ( - If you are the Host, click here! + + If you are the Host, click here! + )} ); From 800c57d7592fc15a91ed2f9d559db429a713380d Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Thu, 3 Oct 2024 04:23:29 +0900 Subject: [PATCH 06/12] feat: add password recovery --- src/api/user.ts | 20 ++++- src/hooks/password.tsx | 53 ++++++++++++ src/pages/host/password/Email.tsx | 55 ++++++++++--- src/pages/host/password/ForgotPassword.tsx | 17 +++- src/pages/host/password/NewPassword.tsx | 93 ++++++++++++---------- src/pages/host/password/Success.tsx | 17 +++- src/pages/host/password/Verify.tsx | 65 ++++++++++++--- src/router.tsx | 2 +- 8 files changed, 247 insertions(+), 75 deletions(-) create mode 100644 src/hooks/password.tsx diff --git a/src/api/user.ts b/src/api/user.ts index 18c9086..e60fb13 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -28,9 +28,9 @@ export type VerifyEmailRequest = { }; export const EMAIL_REGEX = - "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$"; -export const PASSWORD_REGEX = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[\\W_]).{8,20}$"; -export const NAME_REGEX = "^(?=.*[a-zA-Z0-9가-힣])[a-zA-Z0-9가-힣]{2,16}$"; + /^[\w!#$%&'*+/=?`{|}~^.-]+(?:\.[\w!#$%&'*+/=?`{|}~^.-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$/; +export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[\W_]).{8,20}$/; +export const NAME_REGEX = /^(?=.*[a-zA-Z0-9가-힣])[a-zA-Z0-9가-힣]{2,16}$/; export const getUser = async (): Promise => { const res = await axios.get("/api/auth/me", { @@ -98,17 +98,29 @@ export const isEmailConflict = async ( }; // GET /api/auth/email?email=”String” -export const sendVerificationEmail = async (email: string): Promise => { +export const sendSignInEmail = async (email: string): Promise => { return await axios.get("/api/auth/email", { params: { email }, }); }; +// GET /api/auth/password?email=”String” +export const sendForgotPasswordEmail = async (email: string): Promise => { + return await axios.get("/api/auth/password", { + params: { email }, + }); +}; + // POST /api/auth/verification export const verifyEmail = async (req: VerifyEmailRequest): Promise => { return await axios.post("/api/auth/verification", req); }; +// POST /api/auth/password +export const setNewPassword = async (req: SignInRequest): Promise => { + return await axios.post("/api/auth/password", req); +}; + export const useUser = () => { return useQuery({ queryKey: ["user"], diff --git a/src/hooks/password.tsx b/src/hooks/password.tsx new file mode 100644 index 0000000..f2a228d --- /dev/null +++ b/src/hooks/password.tsx @@ -0,0 +1,53 @@ +import { useState, useEffect } from "react"; + +import { PASSWORD_REGEX } from "../api/user.ts"; + +export function usePasswordValidation() { + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + + const [isPasswordFocused, setIsPasswordFocused] = useState(false); + const [isConfirmPasswordFocused, setIsConfirmPasswordFocused] = + useState(false); + + const [isPasswordError, setIsPasswordError] = useState(false); + const [isConfirmPasswordError, setIsConfirmPasswordError] = useState(false); + const [isButtonDisabled, setIsButtonDisabled] = useState(true); + + useEffect(() => { + if (password === "") { + setIsPasswordError(false); + } else { + setIsPasswordError(!PASSWORD_REGEX.test(password)); + } + + if (confirmPassword === "") { + setIsConfirmPasswordError(false); + } else { + setIsConfirmPasswordError(password !== confirmPassword); + } + }, [isPasswordFocused, isConfirmPasswordFocused]); + + useEffect(() => { + setIsButtonDisabled( + password === "" || + confirmPassword === "" || + !PASSWORD_REGEX.test(password) || + password !== confirmPassword + ); + }, [password, confirmPassword]); + + return { + password, + setPassword, + confirmPassword, + setConfirmPassword, + isPasswordFocused, + setIsPasswordFocused, + isConfirmPasswordFocused, + setIsConfirmPasswordFocused, + isPasswordError, + isConfirmPasswordError, + isButtonDisabled, + }; +} diff --git a/src/pages/host/password/Email.tsx b/src/pages/host/password/Email.tsx index 977417b..a16697b 100644 --- a/src/pages/host/password/Email.tsx +++ b/src/pages/host/password/Email.tsx @@ -1,29 +1,57 @@ +import { useMutation } from "@tanstack/react-query"; +import { AxiosError } from "axios"; import React, { useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useOutletContext } from "react-router-dom"; -import { EMAIL_REGEX } from "../../../api/user.ts"; +import { EMAIL_REGEX, sendForgotPasswordEmail } from "../../../api/user.ts"; import { PrimaryButton, TertiaryButton } from "../../../components/Button.tsx"; import { OptionalActionLabel, OptionalActionLink, } from "../../../components/host/OptionalAction.tsx"; import TextField from "../../../components/TextField.tsx"; +import useSubmitHandler from "../../../hooks/submitHandler.tsx"; function Email() { const navigate = useNavigate(); - const [email, setEmail] = useState(""); + const { email, setEmail } = useOutletContext<{ + email: string; + setEmail: React.Dispatch>; + }>(); - const emailError = email.match(EMAIL_REGEX) == null; + const isEmailError = !EMAIL_REGEX.test(email); + const [isEmailExist, setIsEmailExist] = useState(true); - const onSubmit = () => { - // TODO : 이메일 전송 api 추가 - navigate("/host/forgot-password/verify"); + const { mutate: sendForgotPasswordEmailMutate } = useMutation({ + mutationFn: sendForgotPasswordEmail, + mutationKey: ["sendForgotPasswordEmail"], + onSuccess: () => { + setIsEmailExist(true); + navigate("/host/forgot-password/verify"); + }, + onError: (error) => { + const axiosError = error as AxiosError; + if (axiosError.response?.status === 403) { + setIsEmailExist(false); + } + }, + }); + + const submit = () => { + sendForgotPasswordEmailMutate(email); }; + const { isSubmitting, handleSubmit } = useSubmitHandler(); + return ( - <> -

Forgot password?

+
{ + e.preventDefault(); + handleSubmit(submit); + }} + > +

Forgot Password?

Please enter the email address you used when you joined and we’ll send you the verification code to reset password. @@ -34,10 +62,11 @@ function Email() { label="Email Address" onChange={(e) => setEmail(e.target.value)} style={{ marginBottom: "3rem" }} + supportingText={!isEmailExist ? "Email does not exist." : undefined} + hasError={!isEmailExist} /> Send Code @@ -52,9 +81,9 @@ function Email() { Don't have an account?{" "} - Sign up now + Sign Up Now - + ); } diff --git a/src/pages/host/password/ForgotPassword.tsx b/src/pages/host/password/ForgotPassword.tsx index ad79017..1dc2a1a 100644 --- a/src/pages/host/password/ForgotPassword.tsx +++ b/src/pages/host/password/ForgotPassword.tsx @@ -1,14 +1,28 @@ +import { useState } from "react"; import { Outlet } from "react-router-dom"; import styled from "styled-components"; import TransferBanner from "../../../components/host/TransferBanner.tsx"; function ForgotPassword() { + const [email, setEmail] = useState(""); + const [isVerified, setIsVerified] = useState(false); + const [isPasswordSet, setIsPasswordSet] = useState(false); + return ( - + ); @@ -34,6 +48,7 @@ const ContentContainer = styled.div` ${({ theme }) => theme.typography.h3}; margin-top: 8.7rem; margin-bottom: 1.6rem; + text-align: center; } h2 { diff --git a/src/pages/host/password/NewPassword.tsx b/src/pages/host/password/NewPassword.tsx index 3a8d4b0..d549628 100644 --- a/src/pages/host/password/NewPassword.tsx +++ b/src/pages/host/password/NewPassword.tsx @@ -1,54 +1,62 @@ -import React, { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useMutation } from "@tanstack/react-query"; +import React, { useEffect } from "react"; +import { useNavigate, useOutletContext } from "react-router-dom"; -import { PASSWORD_REGEX } from "../../../api/user.ts"; +import { setNewPassword } from "../../../api/user.ts"; import { PrimaryButton } from "../../../components/Button.tsx"; import TextField from "../../../components/TextField.tsx"; +import { usePasswordValidation } from "../../../hooks/password.tsx"; +import useSubmitHandler from "../../../hooks/submitHandler.tsx"; function NewPassword() { const navigate = useNavigate(); - const [password, setpassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); - - const [isPasswordFocused, setIsPasswordFocused] = useState(false); - const [isConfirmPasswordFocused, setIsConfirmPasswordFocused] = - useState(false); - - const [isPasswordValid, setIsPasswordValid] = useState(true); - const [isConfirmPasswordValid, setIsConfirmPasswordValid] = useState(true); - const [isButtonAble, setIsButtonAble] = useState(false); + const { email, isVerified, setIsPasswordSet } = useOutletContext<{ + email: string; + isVerified: boolean; + setIsPasswordSet: React.Dispatch>; + }>(); + // 직접 접근 차단 useEffect(() => { - if (password === "") { - setIsPasswordValid(true); - } else { - setIsPasswordValid(PASSWORD_REGEX.test(password)); + if (email == "" || !isVerified) { + navigate("/host/forgot-password/email"); } + }, []); - if (confirmPassword === "") { - setIsConfirmPasswordValid(true); - } else { - setIsConfirmPasswordValid(password === confirmPassword); - } - }, [isPasswordFocused, isConfirmPasswordFocused]); + const { + password, + setPassword, + setConfirmPassword, + setIsPasswordFocused, + setIsConfirmPasswordFocused, + isPasswordError, + isConfirmPasswordError, + isButtonDisabled, + } = usePasswordValidation(); - useEffect(() => { - setIsButtonAble( - password !== "" && - confirmPassword !== "" && - PASSWORD_REGEX.test(password) && - password === confirmPassword - ); - }, [password, confirmPassword]); + const { mutate: setNewPasswordMutate } = useMutation({ + mutationFn: setNewPassword, + mutationKey: ["setNewPassword"], + onSuccess: () => { + setIsPasswordSet(true); + navigate("/host/forgot-password/success"); + }, + }); - const onSubmit = () => { - // TODO : 비밀번호 재설정 api 추가 - navigate("/host/forgot-password/success"); + const submit = () => { + setNewPasswordMutate({ email, password }); }; + const { isSubmitting, handleSubmit } = useSubmitHandler(); + return ( - <> +
{ + e.preventDefault(); + handleSubmit(submit); + }} + >

Set New Password

Password must be 8-20 characters long, including at least one letter, @@ -58,7 +66,7 @@ function NewPassword() { type="password" name="password" label="Password" - onChange={(e) => setpassword(e.target.value)} + onChange={(e) => setPassword(e.target.value)} onFocus={() => { setIsPasswordFocused(true); }} @@ -66,11 +74,11 @@ function NewPassword() { setIsPasswordFocused(false); }} supportingText={ - !isPasswordValid + isPasswordError ? "Password must be 8-20 characters long, including at least one letter, one number, and one special character." : undefined } - hasError={!isPasswordValid} + hasError={isPasswordError} style={{ marginBottom: "1rem" }} /> Set New Password - + ); } diff --git a/src/pages/host/password/Success.tsx b/src/pages/host/password/Success.tsx index d6647dc..1a6a739 100644 --- a/src/pages/host/password/Success.tsx +++ b/src/pages/host/password/Success.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { useNavigate } from "react-router-dom"; +import React, { useEffect } from "react"; +import { useNavigate, useOutletContext } from "react-router-dom"; import successIcon from "../../../assets/success.svg"; import { PrimaryButton } from "../../../components/Button.tsx"; @@ -7,6 +7,19 @@ import { PrimaryButton } from "../../../components/Button.tsx"; function Success() { const navigate = useNavigate(); + const { email, isVerified, isPasswordSet } = useOutletContext<{ + email: string; + isVerified: boolean; + isPasswordSet: boolean; + }>(); + + // 직접 접근 차단 + useEffect(() => { + if (email === "" || !isVerified || !isPasswordSet) { + navigate("/host/forgot-password/email"); + } + }, []); + return ( <> success diff --git a/src/pages/host/password/Verify.tsx b/src/pages/host/password/Verify.tsx index d9341a2..812bc87 100644 --- a/src/pages/host/password/Verify.tsx +++ b/src/pages/host/password/Verify.tsx @@ -1,36 +1,76 @@ -import React, { useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useMutation } from "@tanstack/react-query"; +import React, { useEffect, useState } from "react"; +import { useNavigate, useOutletContext } from "react-router-dom"; +import { verifyEmail } from "../../../api/user.ts"; import { PrimaryButton, TertiaryButton } from "../../../components/Button.tsx"; import { OptionalActionLabel, OptionalActionLink, } from "../../../components/host/OptionalAction.tsx"; import TextField from "../../../components/TextField.tsx"; +import useSubmitHandler from "../../../hooks/submitHandler.tsx"; function Verify() { const navigate = useNavigate(); + + const { email, setEmail, setIsVerified } = useOutletContext<{ + email: string; + setEmail: React.Dispatch>; + setIsVerified: React.Dispatch>; + }>(); + + // 직접 접근 차단 + useEffect(() => { + if (email === "") { + navigate("/host/forgot-password/email"); + } + }, []); + const [verificationCode, setVerificationCode] = useState(""); + const [verificationCodeError, setVerificationCodeError] = useState(false); - const onSubmit = () => { - // TODO : 코드 검증 api 추가 - navigate("/host/forgot-password/new-password"); + const { mutate: verifyEmailMutate } = useMutation({ + mutationFn: verifyEmail, + mutationKey: ["verifyEmail"], + onSuccess: () => { + setVerificationCodeError(false); + setIsVerified(true); + navigate("/host/forgot-password/new-password"); + }, + onError: () => { + setVerificationCodeError(true); + }, + }); + + const submit = () => { + verifyEmailMutate({ email, code: verificationCode }); }; + const { isSubmitting, handleSubmit } = useSubmitHandler(); + return ( - <> -

Enter your code

+
{ + e.preventDefault(); + handleSubmit(submit); + }} + > +

Enter Your Code

Please enter the code that we sent to your email address.

setVerificationCode(e.target.value)} + supportingText={ + verificationCodeError ? "Invalid verification code." : undefined + } + hasError={verificationCodeError} style={{ marginBottom: "3rem" }} /> Continue @@ -45,11 +85,14 @@ function Verify() { Don’t receive the code?{" "} - + setEmail("")} + > Click to Resend - + ); } diff --git a/src/router.tsx b/src/router.tsx index 5a71a10..c67f973 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -44,7 +44,7 @@ const router = createBrowserRouter([ { path: "success", element: }, ], }, - { path: "/terms_of_use", element: }, + { path: "/terms-of-use", element: }, { path: "/", element: , From b7a6b5ea9cfc8f15e7610db30809d26b8f753ae5 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Thu, 3 Oct 2024 04:24:07 +0900 Subject: [PATCH 07/12] feat: add description --- src/components/TextField.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TextField.tsx b/src/components/TextField.tsx index 052195e..314469d 100644 --- a/src/components/TextField.tsx +++ b/src/components/TextField.tsx @@ -98,6 +98,7 @@ function TextField({ } // create class, setting, account setting에서만 쓰임 +// label이 왼쪽에 있음 function SingleLineTextField({ label, style, From c0935ecb27e47df99ac851d626b5469bc0fd98a4 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Thu, 3 Oct 2024 04:25:07 +0900 Subject: [PATCH 08/12] feat: change error message action --- src/pages/main/Account.tsx | 102 +++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/src/pages/main/Account.tsx b/src/pages/main/Account.tsx index c5befd2..4e84e91 100644 --- a/src/pages/main/Account.tsx +++ b/src/pages/main/Account.tsx @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; @@ -7,7 +7,6 @@ import { deleteUser, editProfile, NAME_REGEX, - PASSWORD_REGEX, useUser, } from "../../api/user.ts"; import AlertModal from "../../components/AlertModal.tsx"; @@ -16,6 +15,7 @@ import SnackBar from "../../components/SnackBar.tsx"; import { SingleLineTextField } from "../../components/TextField.tsx"; import TitleBar from "../../components/TitleBar.tsx"; import useModalState from "../../hooks/modal.tsx"; +import { usePasswordValidation } from "../../hooks/password.tsx"; import useSnackbar from "../../hooks/snackbar.tsx"; import useSubmitHandler from "../../hooks/submitHandler.tsx"; @@ -25,17 +25,6 @@ function Account() { const { data: user } = useUser(); - const [name, setName] = useState(user?.name ?? ""); - const [password, setPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); - - const nameError = name.match(NAME_REGEX) == null; - const passwordError = password.match(PASSWORD_REGEX) == null; - const confirmPasswordError = password !== confirmPassword; - - const { showSnackbar, show } = useSnackbar(); - const [showError, setShowError] = useState(false); - const { mutate: editUserMutate } = useMutation({ mutationFn: editProfile, mutationKey: ["editProfile"], @@ -58,6 +47,38 @@ function Account() { const [deleteModalState, openDeleteModal, closeDeleteModal] = useModalState(); + const [name, setName] = useState(user?.name ?? ""); + const [isNameFocused, setIsNameFocused] = useState(false); + const [isNameError, setIsNameError] = useState(false); + + useEffect(() => { + if (name === "") { + setIsNameError(false); + } else { + setIsNameError(!NAME_REGEX.test(name)); + } + }, [isNameFocused]); + + const { + password, + setPassword, + confirmPassword, + setConfirmPassword, + setIsPasswordFocused, + setIsConfirmPasswordFocused, + isPasswordError, + isConfirmPasswordError, + isButtonDisabled, + } = usePasswordValidation(); + + const { showSnackbar, show } = useSnackbar(); + + const submit = () => { + if (!isNameError && !isPasswordError && !isConfirmPasswordError) { + editUserMutate({ name, password }); + } + }; + const { isSubmitting, handleSubmit } = useSubmitHandler(); return ( @@ -66,13 +87,7 @@ function Account() { { e.preventDefault(); - - if (nameError || passwordError || confirmPasswordError) { - setShowError(true); - return; - } - - editUserMutate({ name, password }); + handleSubmit(submit); }} > { + setIsNameFocused(true); + }} + onBlur={() => { + setIsNameFocused(false); + }} onChange={(e) => setName(e.target.value)} supportingText={ - showError && nameError - ? "name must be 2-16 characters long" - : undefined + isNameError ? "Name must be 2-16 characters long." : undefined } - hasError={showError && nameError} + hasError={isNameError} value={name} /> setPassword(e.target.value)} + onFocus={() => { + setIsPasswordFocused(true); + }} + onBlur={() => { + setIsPasswordFocused(false); + }} supportingText={ - "Password must be 8-20 characters long, including at least one letter, one number, and one special character." + isPasswordError + ? "Password must be 8-20 characters long, including at least one letter, one number, and one special character." + : undefined } - hasError={showError && passwordError} + hasError={isPasswordError} /> setConfirmPassword(e.target.value)} + onFocus={() => { + setIsConfirmPasswordFocused(true); + }} + onBlur={() => { + setIsConfirmPasswordFocused(false); + }} supportingText={ - showError && confirmPasswordError - ? "Passwords do not match" - : undefined + isConfirmPasswordError ? "Passwords do not match." : undefined } - hasError={showError && confirmPasswordError} + hasError={isConfirmPasswordError} />
Save Changes From fd12b5bd7f9cea2a87f82c5a95bce25a05fdf789 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Thu, 3 Oct 2024 04:26:14 +0900 Subject: [PATCH 09/12] refactor: delete console message --- src/pages/Home.tsx | 1 - src/pages/main/attendees/AttendeeDetail.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 6afd484..0f966ea 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -49,7 +49,6 @@ function Home() { } }, onError: (error) => { - console.error(error); setErrorMessage(ErrorMessages[error.message as AttendanceErrorCode]); }, }); diff --git a/src/pages/main/attendees/AttendeeDetail.tsx b/src/pages/main/attendees/AttendeeDetail.tsx index 3bdedfc..c17c1ed 100644 --- a/src/pages/main/attendees/AttendeeDetail.tsx +++ b/src/pages/main/attendees/AttendeeDetail.tsx @@ -63,7 +63,6 @@ function AttendeeDetail() { onError: (error) => { // TODO: How to handle error? // what if one request success and the other fails? Can we rollback? - console.error(error); setHasNamesakeError(true); }, }); From 1f5d5a4aec7ddf945c5ae5d5dfc4bc83396699b6 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Thu, 3 Oct 2024 04:27:00 +0900 Subject: [PATCH 10/12] fix: unify to upper case --- src/pages/host/SignIn.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/host/SignIn.tsx b/src/pages/host/SignIn.tsx index ac32fa6..6de6208 100644 --- a/src/pages/host/SignIn.tsx +++ b/src/pages/host/SignIn.tsx @@ -20,7 +20,7 @@ function SignIn() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const emailError = email.match(EMAIL_REGEX) == null; + const emailError = !EMAIL_REGEX.test(email); const [showError, setShowError] = useState(false); const [resultError, setResultError] = useState(); @@ -71,7 +71,7 @@ function SignIn() { type="text" name="email" autoComplete="username" - label="Email address" + label="Email Address" onChange={(e) => setEmail(e.target.value)} supportingText={ showError && emailError ? "Invalid email address" : undefined @@ -86,7 +86,7 @@ function SignIn() { onChange={(e) => setPassword(e.target.value)} /> Forgot Password @@ -106,7 +106,7 @@ function SignIn() { Don't have an account?{" "} - Sign up now + Sign Up Now From 87b4a7b4229cd5474dff06641546fcc7fc0d9c45 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Thu, 3 Oct 2024 04:27:28 +0900 Subject: [PATCH 11/12] fix: unify text style --- src/components/FloatingSupportButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FloatingSupportButton.tsx b/src/components/FloatingSupportButton.tsx index dd679d0..00ed651 100644 --- a/src/components/FloatingSupportButton.tsx +++ b/src/components/FloatingSupportButton.tsx @@ -30,7 +30,7 @@ const FloatingSupportButton = () => { Contact & Feedback - + Terms of Use Terms of Use From 1d9bf5e761183fe453f72c4586c361ab99e6b746 Mon Sep 17 00:00:00 2001 From: Jaewoong Choi Date: Thu, 3 Oct 2024 04:29:44 +0900 Subject: [PATCH 12/12] feat: change error message action --- src/pages/host/SignUp.tsx | 146 ++++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 61 deletions(-) diff --git a/src/pages/host/SignUp.tsx b/src/pages/host/SignUp.tsx index 2c42a54..98f30e8 100644 --- a/src/pages/host/SignUp.tsx +++ b/src/pages/host/SignUp.tsx @@ -6,8 +6,7 @@ import styled from "styled-components"; import { EMAIL_REGEX, NAME_REGEX, - PASSWORD_REGEX, - sendVerificationEmail, + sendSignInEmail, signUp, useEmailConflict, useUser, @@ -20,29 +19,49 @@ import { } from "../../components/host/OptionalAction.tsx"; import TransferBanner from "../../components/host/TransferBanner.tsx"; import TextField from "../../components/TextField.tsx"; +import { usePasswordValidation } from "../../hooks/password.tsx"; import useSubmitHandler from "../../hooks/submitHandler.tsx"; function SignUp() { const navigate = useNavigate(); const queryClient = useQueryClient(); + const { data: user } = useUser(); + + // Name const [name, setName] = useState(""); + const [isNameFocused, setIsNameFocused] = useState(false); + const [isNameError, setIsNameError] = useState(false); + + useEffect(() => { + if (name === "") { + setIsNameError(false); + } else { + setIsNameError(!NAME_REGEX.test(name)); + } + }, [isNameFocused]); + + // Email const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); const [sentEmail, setSentEmail] = useState(false); + const isEmailError = !EMAIL_REGEX.test(email); + const [verificationCode, setVerificationCode] = useState(""); const [verified, setVerified] = useState(false); - - const nameError = name.match(NAME_REGEX) == null; - const emailError = email.match(EMAIL_REGEX) == null; - const passwordError = password.match(PASSWORD_REGEX) == null; - const confirmPasswordError = password !== confirmPassword; const [verificationCodeError, setVerificationCodeError] = useState(false); - const [showError, setShowError] = useState(false); - - const { data: user } = useUser(); + // Password + const { + password, + setPassword, + confirmPassword, + setConfirmPassword, + setIsPasswordFocused, + setIsConfirmPasswordFocused, + isPasswordError, + isConfirmPasswordError, + isButtonDisabled, + } = usePasswordValidation(); // TODO: handle failure cases const { mutate: signUpMutate } = useMutation({ @@ -54,9 +73,9 @@ function SignUp() { }, }); - const { mutate: sendVerificationCodeMutate } = useMutation({ - mutationFn: sendVerificationEmail, - mutationKey: ["sendVerificationCode"], + const { mutate: sendSignInEmailMutate } = useMutation({ + mutationFn: sendSignInEmail, + mutationKey: ["sendSignInEmail"], onSuccess: () => { setSentEmail(true); }, @@ -74,7 +93,7 @@ function SignUp() { }, }); - const { data: isEmailConflict } = useEmailConflict(email, !emailError); + const { data: isEmailConflict } = useEmailConflict(email, !isEmailError); useEffect(() => { if (user != null) { @@ -83,12 +102,9 @@ function SignUp() { }, [navigate, user]); const submit = () => { - if (nameError || passwordError || confirmPasswordError) { - setShowError(true); - return; + if (!isNameError && !isPasswordError && !isConfirmPasswordError) { + signUpMutate({ name, email, password }); } - - signUpMutate({ name, email, password }); }; const { isSubmitting, handleSubmit } = useSubmitHandler(); @@ -96,7 +112,7 @@ function SignUp() { return ( - Create a free account + Create a Free Account { e.preventDefault(); @@ -104,41 +120,43 @@ function SignUp() { }} > { + setIsNameFocused(true); + }} + onBlur={() => { + setIsNameFocused(false); + }} onChange={(e) => setName(e.target.value)} supportingText={ - showError && nameError - ? "Name must be 2-16 characters long" - : undefined + isNameError ? "Name must be 2-16 characters long." : undefined } - hasError={showError && nameError} + hasError={isNameError} + value={name} /> setEmail(e.target.value)} supportingText={ - isEmailConflict ? "Email already exists" : undefined + isEmailConflict ? "Email already exists." : undefined } hasError={isEmailConflict} /> { e.preventDefault(); - sendVerificationCodeMutate(email); + sendSignInEmailMutate(email); }} > {sentEmail ? "Resend" : "Send Code"} @@ -149,10 +167,10 @@ function SignUp() { setVerificationCode(e.target.value)} supportingText={ - verificationCodeError ? "Invalid verification code" : undefined + verificationCodeError ? "Invalid verification code." : undefined } hasError={verificationCodeError} /> @@ -161,7 +179,7 @@ function SignUp() { height: "4.2rem", marginBottom: verificationCodeError ? "1.8rem" : "0.0rem", }} - disabled={verified} + disabled={verificationCode == "" || verified} onClick={(e) => { e.preventDefault(); verifyEmailMutate({ email, code: verificationCode }); @@ -172,49 +190,55 @@ function SignUp() { )} setPassword(e.target.value)} + onFocus={() => { + setIsPasswordFocused(true); + }} + onBlur={() => { + setIsPasswordFocused(false); + }} supportingText={ - - Password must be 8-20 characters long, including at least one - letter, one number, and one special character. - + "Password must be 8-20 characters long, including at least one letter, one number, and one special character." } - hasError={showError && passwordError} + hasError={isPasswordError} + style={{ marginTop: "2.5rem" }} /> setConfirmPassword(e.target.value)} + onFocus={() => { + setIsConfirmPasswordFocused(true); + }} + onBlur={() => { + setIsConfirmPasswordFocused(false); + }} supportingText={ - showError && confirmPasswordError - ? "Passwords do not match" - : undefined + isConfirmPasswordError ? "Passwords do not match." : undefined } - hasError={showError && confirmPasswordError} + hasError={isConfirmPasswordError} + style={{ marginTop: "2.5rem" }} /> - Sign up + Sign Up Already a member?{" "} - Sign in + Sign In