Skip to content

Commit 4ac896a

Browse files
authored
Refactor: 제출 버튼 클릭 시, 추가적인 요청 금지
Refactor: 제출 버튼 클릭 시, 추가적인 요청 금지
2 parents f950c7f + 418ee41 commit 4ac896a

File tree

8 files changed

+134
-97
lines changed

8 files changed

+134
-97
lines changed

components/HeaderMenu.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,14 @@ import Link from "next/link";
55
import SubmitButton from "./SubMitButton";
66
import { useRouter } from "next/router";
77
import useAuthStore from "@/store/useAuthStore";
8-
import { useEffect, useState } from "react";
8+
import { useState } from "react";
99
import Dropdown from "./Dropdown";
1010

1111
const HeaderMenu = () => {
12-
const { user, checkLogin, isLoggedIn, logout } = useAuthStore();
12+
const { user, logout } = useAuthStore();
1313
const [isOpen, setIsOpen] = useState(false);
1414
const router = useRouter();
1515

16-
useEffect(() => {
17-
checkLogin();
18-
}, [checkLogin]);
19-
2016
const dropdownItems = [
2117
{
2218
label: "마이링크",
@@ -31,7 +27,7 @@ const HeaderMenu = () => {
3127

3228
return (
3329
<>
34-
{!isLoggedIn ? (
30+
{!user ? (
3531
<SubmitButton
3632
onClick={() => {
3733
router.push("/login");

components/LoadingSpinner.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { FadeLoader } from "react-spinners";
1+
import { ClipLoader } from "react-spinners";
22

3-
const LoadingSpinner = ({ text }: { text?: string }) => {
3+
const LoadingSpinner = ({
4+
text,
5+
size = 35,
6+
}: {
7+
text?: string;
8+
size?: number;
9+
}) => {
410
return (
5-
<div className="flex flex-col items-center justify-center h-full">
11+
<div className="flex flex-col items-center justify-center">
612
<p className="md:text-2xl mb-4 sm:text-lg">{text}</p>
7-
<FadeLoader color="#6d6afe" />
13+
<ClipLoader color="#6d6afe" size={size} />
814
</div>
915
);
1016
};

components/SubMitButton.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React, { ReactNode, ButtonHTMLAttributes } from "react";
1+
import React, { ReactNode, ButtonHTMLAttributes, useState } from "react";
2+
import LoadingSpinner from "./LoadingSpinner";
23

34
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
45
children: ReactNode;
@@ -17,8 +18,25 @@ const SubmitButton = ({
1718
color = "positive",
1819
size = "18px",
1920
className = "",
21+
onClick,
22+
disabled,
2023
...props
2124
}: ButtonProps) => {
25+
const [isLoading, setIsLoading] = useState(false);
26+
27+
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
28+
if (isLoading || disabled) return;
29+
30+
setIsLoading(true);
31+
try {
32+
if (onClick) await onClick(e);
33+
} catch (error) {
34+
console.error("버튼 클릭 중 에러 발생:", error);
35+
} finally {
36+
setIsLoading(false);
37+
}
38+
};
39+
2240
const backgroundStyle =
2341
color === "positive"
2442
? "linear-gradient(90.99deg, #6D6AFE 0.12%, #6AE3FE 101.84%)"
@@ -30,10 +48,20 @@ const SubmitButton = ({
3048
borderRadius: radius,
3149
background: backgroundStyle,
3250
}}
33-
className={`flex justify-center ${width} ${height} ${size} ${className} items-center text-white font-[600] whitespace-nowrap hover:opacity-90`}
51+
className={`flex justify-center items-center ${width} ${height} ${size} ${className} text-white font-[600] whitespace-nowrap hover:opacity-90 ${
52+
isLoading ? "opacity-50 cursor-not-allowed" : ""
53+
}`}
54+
onClick={handleClick}
55+
disabled={isLoading || disabled}
3456
{...props}
3557
>
36-
{children}
58+
{isLoading ? (
59+
<div className="h-full m-4">
60+
<LoadingSpinner size={25} />
61+
</div>
62+
) : (
63+
children
64+
)}
3765
</button>
3866
);
3967
};

hooks/useForm.tsx

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { useState, ChangeEvent, FormEvent } from "react";
22
import { useRouter } from "next/router";
3-
import { postSignIn, postSignUp } from "@/lib/api/auth";
3+
import { postSignUp } from "@/lib/api/auth";
44
import useAuthStore from "@/store/useAuthStore";
5-
import { TbWashDryP } from "react-icons/tb";
65
import toast from "react-hot-toast";
76
import toastMessages from "@/lib/toastMessage";
87

@@ -23,6 +22,7 @@ const INITIAL_VALUES: FormValues = {
2322
const useForm = (isSignUp = false) => {
2423
const [values, setValues] = useState<FormValues>(INITIAL_VALUES);
2524
const [errors, setErrors] = useState<FormValues>(INITIAL_VALUES);
25+
const [isLoading, setIsLoading] = useState<boolean>(false);
2626
const router = useRouter();
2727
const { login } = useAuthStore();
2828

@@ -84,30 +84,42 @@ const useForm = (isSignUp = false) => {
8484

8585
const handleSubmit = async (e: FormEvent) => {
8686
e.preventDefault();
87-
if (isFormInvalid()) return;
87+
if (isFormInvalid() || isLoading) return;
88+
89+
setIsLoading(true);
90+
8891
const { email, password, nickname } = values;
8992

90-
if (isSignUp) {
91-
const data = await postSignUp({ email, password, name: nickname || "" });
93+
try {
94+
if (isSignUp) {
95+
const data = await postSignUp({
96+
email,
97+
password,
98+
name: nickname || "",
99+
});
92100

93-
if (data) {
94-
router.push("/login");
95-
toast.success(toastMessages.success.signup);
101+
if (data) {
102+
router.push("/login");
103+
toast.success(toastMessages.success.signup);
104+
} else {
105+
toast.error(toastMessages.error.signup);
106+
}
96107
} else {
97-
toast.error(toastMessages.error.signup);
98-
}
99-
} else {
100-
const data = await login({ email, password });
108+
const data = await login({ email, password });
101109

102-
if (data) {
103-
router.push("/");
104-
toast.success(toastMessages.success.login);
105-
} else {
106-
toast.error(toastMessages.error.login);
110+
if (data) {
111+
router.push("/");
112+
toast.success(toastMessages.success.login);
113+
} else {
114+
toast.error(toastMessages.error.login);
115+
}
107116
}
117+
} catch (error) {
118+
toast.error("요청 중 오류가 발생했습니다.");
119+
} finally {
120+
setIsLoading(false);
121+
setValues(INITIAL_VALUES);
108122
}
109-
110-
setValues(INITIAL_VALUES);
111123
};
112124

113125
const validateEmail = (email: string) => {
@@ -133,6 +145,7 @@ const useForm = (isSignUp = false) => {
133145
handleBlur,
134146
handleSubmit,
135147
isFormInvalid,
148+
isLoading,
136149
};
137150
};
138151

pages/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ const sectionImageStyle =
2020

2121
const HomePage = () => {
2222
const router = useRouter();
23-
const { isLoggedIn } = useAuthStore();
23+
const { user } = useAuthStore();
2424

2525
const handleClick = () => {
26-
if (isLoggedIn) {
26+
if (user) {
2727
router.push("/link");
2828
} else {
2929
router.push("/login");

pages/login/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const LoginPage = () => {
1313
handleBlur,
1414
handleSubmit,
1515
isFormInvalid,
16+
isLoading,
1617
} = useForm(false);
1718

1819
return (
@@ -57,7 +58,6 @@ const LoginPage = () => {
5758
width="w-full"
5859
height="h-[53px]"
5960
className="mt-[30px]"
60-
disabled={isFormInvalid()}
6161
>
6262
로그인
6363
</SubmitButton>

pages/signup/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ const SignupPage = () => {
6666
onBlur={handleBlur}
6767
error={errors.passwordConfirm}
6868
/>
69-
<SubmitButton width="w-full" height="h-[53px]" className="mt-[30px]">
69+
<SubmitButton
70+
type="submit"
71+
width="w-full"
72+
height="h-[53px]"
73+
className="mt-[30px]"
74+
>
7075
회원가입
7176
</SubmitButton>
7277
<SnsPassword />

store/useAuthStore.tsx

Lines changed: 49 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { create } from "zustand";
2-
import { persist } from "zustand/middleware";
32
import { User } from "@/types/AuthTypes";
43
import {
54
postSignIn,
@@ -9,11 +8,10 @@ import {
98
} from "@/lib/api/auth";
109
import { getUserInfo } from "@/lib/api/user";
1110
import { proxy } from "@/lib/api/axiosInstanceApi";
11+
import { persist } from "zustand/middleware";
1212

1313
interface AuthStore {
1414
user: User | null;
15-
isLoggedIn: boolean;
16-
checkLogin: () => Promise<void>;
1715
login: (body: signInProps) => Promise<boolean>;
1816
SNSLogin: (
1917
provider: "google" | "kakao",
@@ -22,68 +20,59 @@ interface AuthStore {
2220
logout: () => Promise<void>;
2321
}
2422

25-
const useAuthStore = create<AuthStore>()((set) => ({
26-
user: null,
27-
isLoggedIn: false,
28-
29-
checkLogin: async () => {
30-
try {
31-
const response = await proxy.get("/api/auth/sign-check");
32-
if (response.data.isLoggedIn) {
33-
const userInfo = await getUserInfo();
34-
if (userInfo) {
35-
set({ isLoggedIn: true, user: userInfo });
36-
}
37-
} else {
38-
set({ isLoggedIn: false, user: null });
39-
}
40-
} catch (error) {
41-
console.error("로그인 상태 확인 중 오류 발생", error);
42-
set({ isLoggedIn: false, user: null });
23+
const fetchUserInfo = async (set: any) => {
24+
try {
25+
const userInfo = await getUserInfo();
26+
if (userInfo) {
27+
set({ user: userInfo });
28+
return true;
4329
}
44-
},
30+
} catch (error) {
31+
console.error("사용자 정보 가져오기 에러", error);
32+
}
33+
return false;
34+
};
35+
36+
const useAuthStore = create<AuthStore>()(
37+
persist(
38+
(set) => ({
39+
user: null,
4540

46-
login: async (body) => {
47-
try {
48-
const { email, password } = body;
49-
const response = await postSignIn({ email, password });
50-
if (response) {
51-
const userInfo = await getUserInfo();
52-
if (userInfo) {
53-
set({ isLoggedIn: true, user: userInfo });
54-
return true;
41+
login: async (body) => {
42+
try {
43+
const response = await postSignIn(body);
44+
if (response) {
45+
return await fetchUserInfo(set);
46+
}
47+
} catch (error) {
48+
console.error("로그인 중 에러가 발생했습니다", error);
5549
}
56-
}
57-
} catch (error) {
58-
console.error("로그인 중 에러가 발생했습니다", error);
59-
}
60-
return false;
61-
},
50+
return false;
51+
},
6252

63-
SNSLogin: async (provider, body) => {
64-
try {
65-
const response = await postEasySignIn(provider, body);
66-
if (response) {
67-
const userInfo = await getUserInfo();
68-
if (userInfo) {
69-
set({ isLoggedIn: true, user: userInfo });
70-
return true;
53+
SNSLogin: async (provider, body) => {
54+
try {
55+
const response = await postEasySignIn(provider, body);
56+
if (response) {
57+
return await fetchUserInfo(set);
58+
}
59+
} catch (error) {
60+
console.error("소셜 로그인 중 에러가 발생했습니다.", error);
7161
}
72-
}
73-
} catch (error) {
74-
console.error("소셜 로그인 중 에러가 발생했습니다.", error);
75-
}
76-
return false;
77-
},
62+
return false;
63+
},
7864

79-
logout: async () => {
80-
try {
81-
await proxy.post("/api/auth/sign-out");
82-
set({ user: null, isLoggedIn: false });
83-
} catch (error) {
84-
console.error("로그아웃 중 에러가 발생했습니다.", error);
85-
}
86-
},
87-
}));
65+
logout: async () => {
66+
try {
67+
await proxy.post("/api/auth/sign-out");
68+
localStorage.removeItem("auth-storage");
69+
} catch (error) {
70+
console.error("로그아웃 중 에러가 발생했습니다.", error);
71+
}
72+
},
73+
}),
74+
{ name: "auth-storage" }
75+
)
76+
);
8877

8978
export default useAuthStore;

0 commit comments

Comments
 (0)