Skip to content

Commit

Permalink
Merge pull request #283 from boostcampwm-2024/Feature/#279_회원가입_유효성_검…
Browse files Browse the repository at this point in the history
…사_구현

Feature/#279 회원가입 유효성 검사 구현
  • Loading branch information
github-actions[bot] authored Dec 3, 2024
2 parents 9a6cbd5 + 5e3dc0d commit 3065b75
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 14 deletions.
15 changes: 12 additions & 3 deletions client/src/apis/auth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { useUserActions } from "@stores/useUserStore";
import { unAuthorizationFetch, fetch } from "./axios";

const authKey = {
all: ["auth"] as const,
refresh: () => [...authKey.all, "refresh"] as const,
};
export interface ApiErrorResponse {
message: string;
code?: string;
}

export const useSignupMutation = (onSuccess: () => void) => {
interface MutationOptions {
onError?: (error: AxiosError<ApiErrorResponse>) => void;
}
export const useSignupMutation = (onSuccess: () => void, options?: MutationOptions) => {
const fetcher = ({ name, email, password }: { name: string; email: string; password: string }) =>
unAuthorizationFetch.post("/auth/register", { name, email, password });

Expand All @@ -16,10 +24,10 @@ export const useSignupMutation = (onSuccess: () => void) => {
onSuccess: () => {
onSuccess();
},
onError: options?.onError,
});
};

export const useLoginMutation = (onSuccess: () => void) => {
export const useLoginMutation = (onSuccess: () => void, options?: MutationOptions) => {
const { setUserInfo } = useUserActions();

const fetcher = ({ email, password }: { email: string; password: string }) =>
Expand All @@ -33,6 +41,7 @@ export const useLoginMutation = (onSuccess: () => void) => {
setUserInfo(id, name, accessToken);
onSuccess();
},
onError: options?.onError,
});
};

Expand Down
1 change: 1 addition & 0 deletions client/src/components/inputField/InputField.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const formGroup = css({

export const inputContainer = css({
position: "relative",
border: "1px solid white",
borderRadius: "md",
padding: "1",
background: "white/30",
Expand Down
20 changes: 17 additions & 3 deletions client/src/components/inputField/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,25 @@ interface InputFieldProps {
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
Icon?: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
isError?: boolean;
}

export const InputField = ({ type, name, value, onChange, placeholder, Icon }: InputFieldProps) => (
export const InputField = ({
type,
name,
value,
onChange,
placeholder,
Icon,
isError,
}: InputFieldProps) => (
<div className={formGroup}>
<div className={inputContainer}>
<div
className={inputContainer}
style={{
border: isError ? "1px solid #EF4444" : "none", // Using Tailwind's red-500 color
}}
>
<input
type={type}
name={name}
Expand All @@ -22,6 +36,6 @@ export const InputField = ({ type, name, value, onChange, placeholder, Icon }: I
required
/>
</div>
{Icon && <Icon className={iconBox} />}
{Icon && <Icon className={`${iconBox} ${isError ? "c_red" : ""}`} />}
</div>
);
2 changes: 1 addition & 1 deletion client/src/features/auth/AuthButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { TextButton } from "@components/button/textButton";
import { Modal } from "@components/modal/modal";
import { useModal } from "@components/modal/useModal";
import { useCheckLogin } from "@stores/useUserStore";
import { AuthModal } from "./AuthModal";
import { container } from "./AuthButton.style";
import { AuthModal } from "./AuthModal";

export const AuthButton = () => {
const isLogin = useCheckLogin();
Expand Down
15 changes: 14 additions & 1 deletion client/src/features/auth/AuthModal.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export const title = css({
textShadow: "0 2px 4px rgba(0,0,0,0.1)",
});

export const errorWrapper = css({
display: "flex",
justifyContent: "center",
width: "100%",
height: "20px",
paddingBottom: "40px",
});
export const toggleButton = css({
marginBottom: "md",
color: "white",
Expand All @@ -24,7 +31,13 @@ export const toggleButton = css({
textDecoration: "underline",
},
});

export const errorContainer = css({
display: "flex",
position: "relative",
alignContent: "center",
alignItems: "center",
color: "red",
});
export const formContainer = css({
display: "flex",
gap: "md",
Expand Down
106 changes: 100 additions & 6 deletions client/src/features/auth/AuthModal.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useLoginMutation, useSignupMutation } from "@apis/auth";
import { useState } from "react";
import { AxiosError } from "axios";
import { useState, useEffect } from "react";
import { useLoginMutation, useSignupMutation, ApiErrorResponse } from "@apis/auth";
import Lock from "@assets/icons/lock.svg?react";
import Mail from "@assets/icons/mail.svg?react";
import User from "@assets/icons/user.svg?react";
import { InputField } from "@components/inputField/InputField";
import { Modal } from "@components/modal/modal";
import { container, formContainer, title, toggleButton } from "./AuthModal.style";
import {
container,
formContainer,
title,
toggleButton,
errorContainer,
errorWrapper,
} from "./AuthModal.style";

interface AuthModalProps {
isOpen: boolean;
Expand All @@ -20,13 +28,67 @@ export const AuthModal = ({ isOpen, onClose }: AuthModalProps) => {
email: "",
password: "",
});
const [error, setError] = useState<string>("");

const { mutate: login } = useLoginMutation(onClose);
const { mutate: signUp } = useSignupMutation(() => login(formData));
const getErrorMessage = (error: AxiosError<ApiErrorResponse>) => {
switch (error.response?.status) {
case 400:
return "입력하신 정보가 올바르지 않습니다.";
case 401:
return "이메일 또는 비밀번호가 올바르지 않습니다.";
case 409:
return "이미 사용 중인 이메일입니다.";
default:
return "오류가 발생했습니다. 다시 시도해주세요.";
}
};

const { mutate: login } = useLoginMutation(onClose, {
onError: (error: AxiosError<ApiErrorResponse>) => {
setError(getErrorMessage(error));
},
});

const { mutate: signUp } = useSignupMutation(
() => {
// 회원가입 성공 시 자동으로 로그인 시도
const { email, password } = formData;
login({ email, password });
},
{
onError: (error: AxiosError<ApiErrorResponse>) => {
setError(getErrorMessage(error));
},
},
);

const validateForm = (): boolean => {
// 이메일 유효성 검사
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(formData.email)) {
setError("올바른 이메일 형식이 아닙니다.");
return false;
}

// 비밀번호 유효성 검사 (최소 8자)
if (formData.password.length < 1) {
setError("비밀번호를 입력해주세요.");
return false;
}

// 회원가입 시 이름 필드 검사
if (mode === "register" && !formData.name.trim()) {
setError("이름을 입력해주세요.");
return false;
}

return true;
};

const toggleMode = () => {
setMode(mode === "login" ? "register" : "login");
setFormData({ email: "", password: "", name: "" });
setError("");
};

const closeModal = () => {
Expand All @@ -41,13 +103,42 @@ export const AuthModal = ({ isOpen, onClose }: AuthModalProps) => {
};

const handleSubmitButtonClick = () => {
if (!validateForm()) {
return;
}
if (mode === "register") {
signUp(formData);
} else {
login(formData);
}
};

const getFieldError = (fieldName: string): boolean => {
if (!error) return false;
if (error === "올바른 이메일 형식이 아닙니다." && fieldName === "email") {
return true;
}
if (error === "비밀번호를 입력해주세요." && fieldName === "password") {
return true;
}
if (error === "이름을 입력해주세요." && fieldName === "name") {
return true;
}
return false;
};

useEffect(() => {
if (isOpen) {
// 모달이 열릴 때마다 초기화
setFormData({
name: "",
email: "",
password: "",
});
setError("");
setMode("login");
}
}, [isOpen]); // isOpen이 변경될 때마다 실행
return (
<Modal
isOpen={isOpen}
Expand All @@ -67,6 +158,7 @@ export const AuthModal = ({ isOpen, onClose }: AuthModalProps) => {
onChange={handleInputChange}
placeholder="이름"
Icon={User}
isError={getFieldError("name")}
/>
)}
<InputField
Expand All @@ -76,6 +168,7 @@ export const AuthModal = ({ isOpen, onClose }: AuthModalProps) => {
onChange={handleInputChange}
placeholder="이메일"
Icon={Mail}
isError={getFieldError("email")}
/>
<InputField
type="password"
Expand All @@ -84,9 +177,10 @@ export const AuthModal = ({ isOpen, onClose }: AuthModalProps) => {
onChange={handleInputChange}
placeholder="비밀번호"
Icon={Lock}
isError={getFieldError("password")}
/>
</div>

<div className={errorWrapper}>{error && <p className={errorContainer}>{error}</p>}</div>
<button onClick={toggleMode} className={toggleButton}>
{mode === "login"
? "계정이 없으신가요? 회원가입하기"
Expand Down

0 comments on commit 3065b75

Please sign in to comment.