Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/hooks/auth/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { useCoreMutation } from "@/hooks/customQuery";
import { login, sendEmail, signUp, verifyEmail } from "@/api/auth/auth";

export const useAuth = () => {
const useLogin = useCoreMutation(login);
const useSignUp = useCoreMutation(signUp);
const useSendCode = useCoreMutation(sendEmail);
const useCheckCode = useCoreMutation(verifyEmail);
const useSignUp = useCoreMutation(signUp);
const useLogin = useCoreMutation(login);

return {
useLogin,
useSignUp,
useSendCode,
useCheckCode,
useSignUp,
useLogin,
};
};
4 changes: 2 additions & 2 deletions src/hooks/auth/useEmailVerification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ export const useEmailVerification = ({
useSendCode.mutate(
{ email },
{
onSuccess: () => {
onSuccess: (data) => {
setSendCode(true);
toast.success(successMessage);
restart();
restart(data.data.expireIn);
},
onError: (error) => {
toast.error(
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/auth/useSocialLogin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type TSocialLoginPlatform } from "@/types/auth/auth";

export const useSocialLogin = () => {
const handleSocialLogin = (platform: TSocialLoginPlatform) => {
const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL || import.meta.env.VITE_API_TARGET_URL;

const baseUrl = `${API_BASE_URL}/oauth2/authorization/${platform}`;
window.location.href = `${baseUrl}`;
};

return { handleSocialLogin };
};
11 changes: 7 additions & 4 deletions src/hooks/common/useTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ export const useTimer = (
setTimeLeft(Math.max(0, initialTime));
}, [initialTime]);

const restart = useCallback(() => {
setTimeLeft(Math.max(0, initialTime));
setIsActive(true);
}, [initialTime]);
const restart = useCallback(
(newTime?: number) => {
setTimeLeft(Math.max(0, newTime ?? initialTime));
setIsActive(true);
},
[initialTime],
);

const formattedTime = useMemo(() => {
const minutes = Math.floor(timeLeft / 60);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ axiosInstance.interceptors.response.use(
originalRequest.url?.includes("/api/auth/reissue") ||
originalRequest._retry
) {
useAuthStore.getState().logout();
localStorage.removeItem("accessToken");
return Promise.reject(error);
}

Expand Down
17 changes: 6 additions & 11 deletions src/pages/auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { z } from "zod";
import { loginSchema } from "@/utils/validation";

import { useAuth } from "@/hooks/auth/useAuth";
import { useSocialLogin } from "@/hooks/auth/useSocialLogin";

import CommonAuthInput from "@/components/auth/common/CommonAuthInput";
import Button from "@/components/common/Button";
Expand All @@ -31,13 +32,13 @@ export default function Login() {
const navigate = useNavigate();
const { useLogin } = useAuth();
const { login: loginAction } = useAuthStore();
const { handleSocialLogin } = useSocialLogin();

const onSubmit: SubmitHandler<TLoginFormValues> = (data) => {
useLogin.mutate(data, {
onSuccess: (response) => {
const { accessToken } = response.data;
localStorage.setItem("accessToken", accessToken);
loginAction(data.email);
loginAction(data.email, accessToken);
navigate("/", { replace: true });
},
onError: (error: any) => {
Expand Down Expand Up @@ -92,9 +93,7 @@ export default function Login() {
type="button"
className="w-14 h-14 rounded-full flex items-center justify-center bg-social-kakao hover:scale-110 transition-transform duration-200 shadow-sm"
aria-label="카카오로 로그인"
onClick={() => {
// 소셜 로그인
}}
onClick={() => handleSocialLogin("kakao")}
>
<KakaoIcon className="w-6 h-auto" />
</button>
Expand All @@ -103,9 +102,7 @@ export default function Login() {
type="button"
className="w-14 h-14 rounded-full flex items-center justify-center bg-social-naver hover:scale-110 transition-transform duration-200 shadow-sm"
aria-label="네이버로 로그인"
onClick={() => {
// 소셜 로그인
}}
onClick={() => handleSocialLogin("naver")}
>
<NaverIcon className="w-5 h-auto text-white" />
</button>
Expand All @@ -114,9 +111,7 @@ export default function Login() {
type="button"
className="w-14 h-14 rounded-full flex items-center justify-center bg-white border border-gray-100 hover:scale-110 transition-transform duration-200 shadow-sm"
aria-label="구글로 로그인"
onClick={() => {
// 소셜 로그인
}}
onClick={() => toast.error("준비중입니다.")}
>
<GoogleIcon className="w-6 h-auto" />
</button>
Expand Down
46 changes: 46 additions & 0 deletions src/pages/auth/RedirectPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";

import useAuthStore from "@/store/useAuthStore";

export default function RedirectPage() {
const navigate = useNavigate();
const { login, setSocialId, setEmail } = useAuthStore();
const processed = useRef(false);

useEffect(() => {
if (processed.current) return;
processed.current = true;

const getCookie = (name: string) => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop()?.split(";").shift();
};

const accessToken = getCookie("access_token");

if (accessToken) {
// TODO: Zustand 로그인 상태 업데이트 - 추후 내 정보 조회 API 연동 시 수정
login("social@user.com", accessToken);

toast.success("소셜 로그인되었습니다.");
navigate("/", { replace: true });
} else {
console.error("No access token found in cookies.");
toast.error("소셜 로그인에 실패했습니다. 다시 시도해주세요.");
navigate("/login", { replace: true });
}
}, [navigate, login, setEmail, setSocialId]);

return (
<div className="relative flex justify-center items-center h-screen w-full bg-white">
<div className="relative flex justify-center h-75 min-w-70 w-112.5 max-w-[96vw]">
<div className="absolute top-80 text-[20px] text-gray-500 font-medium animate-pulse">
로그인 중...
</div>
</div>
</div>
);
}
10 changes: 7 additions & 3 deletions src/pages/auth/Signup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";

import { useSocialLogin } from "@/hooks/auth/useSocialLogin";

import Step01Email from "@/components/auth/signupStep/Step01Email";
import Step02Password from "@/components/auth/signupStep/Step02Password";
import Step03Profile from "@/components/auth/signupStep/Step03Profile";
Expand Down Expand Up @@ -31,6 +33,8 @@ export default function Signup() {
setStep((prev) => prev + 1);
};

const { handleSocialLogin } = useSocialLogin();

if (step === 1) {
return <Step01Email onNext={handleNext} />;
}
Expand Down Expand Up @@ -60,7 +64,7 @@ export default function Signup() {
size="big"
variant="custom"
leftIcon={<GoogleIcon className="w-6 h-6" />}
onClick={() => {}}
onClick={() => handleSocialLogin("google")}
className="bg-white border border-gray-100 text-text-main font-heading3 shadow-sm hover:bg-gray-50"
>
구글 로그인
Expand All @@ -71,7 +75,7 @@ export default function Signup() {
size="big"
variant="custom"
leftIcon={<KakaoIcon className="w-6 h-6" />}
onClick={() => {}}
onClick={() => handleSocialLogin("kakao")}
className="bg-social-kakao text-text-main font-heading3 shadow-sm hover:opacity-90"
>
카카오 로그인
Expand All @@ -82,7 +86,7 @@ export default function Signup() {
size="big"
variant="custom"
leftIcon={<NaverIcon className="w-5 h-5 text-white" />}
onClick={() => {}}
onClick={() => handleSocialLogin("naver")}
className="bg-social-naver text-white font-heading3 shadow-sm hover:opacity-90"
>
네이버 로그인
Expand Down
9 changes: 9 additions & 0 deletions src/routes/AuthRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const Login = loadable(
<LoginSkeleton />,
);

const RedirectPage = loadable(
lazy(() => import("@/pages/auth/RedirectPage")),
<AuthFormSkeleton />,
);

// Signup은 Fallback이 달라짐 -> raw lazy 컴포넌트 사용
const Signup = lazy(() => import("@/pages/auth/Signup"));

Expand Down Expand Up @@ -54,6 +59,10 @@ const AuthRoutes: RouteObject[] = [
path: "find-pw",
element: <FindPw />,
},
{
path: "oauth2/redirect",
element: <RedirectPage />,
},
];

export default AuthRoutes;
9 changes: 7 additions & 2 deletions src/store/useAuthStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ interface IAuthState {
email: string;
password: string;
socialId: number;
login: (email: string) => void;

login: (email: string, accessToken: string) => void;
logout: () => void;
setEmail: (email: string) => void;
setPassword: (password: string) => void;
Expand All @@ -18,11 +19,15 @@ const useAuthStore = create<IAuthState>((set) => ({
email: "",
password: "",
socialId: -1,
login: (email) => set({ isLoggedIn: true, email }),
login: (email, accessToken) => {
localStorage.setItem("accessToken", accessToken);
set({ isLoggedIn: true, email });
},
logout: () => {
localStorage.removeItem("accessToken");
set({ isLoggedIn: false, email: "", password: "", socialId: -1 });
},

setEmail: (email) => set({ email }),
setPassword: (password) => set({ password }),
setSocialId: (socialId) => set({ socialId }),
Expand Down
22 changes: 13 additions & 9 deletions src/types/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
// 이메일 인증 코드 전송 요청 타입
// 이메일 인증 전송 요청
export interface IEmailSendRequest {
email: string;
}

// 이메일 인증 코드 전송 응답 타입
// 이메일 인증 전송 응답
export interface IEmailSendResponse {
message: string;
email: string;
expireIn: number;
}

// 이메일 인증 코드 확인 요청 타입
// 이메일 인증 확인 요청
export interface IEmailVerifyRequest {
email: string;
authCode: string;
}

// 회원가입 요청 타입
// 회원가입 요청
export interface ISignUpRequest {
email: string;
password: string;
name: string;
phone_number: string;
}

// 회원가입 응답 타입
// 회원가입 응답
export interface ISignUpResponse {
userId: number;
createdAt: string;
}

// 로그인 요청 타입
// 로그인 요청
export interface ILoginRequest {
email: string;
password: string;
}
export type TLoginFormValues = ILoginRequest;

// 로그인 응답 타입
// 로그인 응답
export interface ILoginResponse {
grantType: string;
accessToken: string;
accessTokenExpiresIn: number;
}

// 토큰 재발급 요청 타입
// 토큰 재발급 요청
export interface ITokenRefreshRequest {
refreshToken: string;
}

// 토큰 재발급 응답 타입
// 토큰 재발급 응답
export interface ITokenRefreshResponse {
grantType: string;
accessToken: string;
accessTokenExpiresIn: number;
}

// 소셜 로그인 플랫폼
export type TSocialLoginPlatform = "kakao" | "naver" | "google";