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
21 changes: 10 additions & 11 deletions src/api/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
IEmailVerifyRequest,
ILoginRequest,
ILoginResponse,
IMyPageInfoResponse,
IPasswordResetRequest,
ISignUpRequest,
ISignUpResponse,
Expand All @@ -15,17 +16,15 @@ import type {
} from "@/types/auth/auth";
import type { ICommonResponse } from "@/types/common/common";

import { axiosInstance } from "@/lib/axiosInstance";
import { authInstance, axiosInstance } from "@/lib/axiosInstance";

// 이메일 인증 코드 전송
export const sendEmail = async ({
email,
}: IEmailSendRequest): Promise<ICommonResponse<IEmailSendResponse>> => {
const { data } = await axiosInstance.post("/api/users/email-send", { email });
return data;
};

// 이메일 인증 코드 검증
export const verifyEmail = async ({
email,
authCode,
Expand All @@ -37,7 +36,6 @@ export const verifyEmail = async ({
return data;
};

// 단순 회원가입
export const signUp = async ({
email,
password,
Expand All @@ -53,27 +51,24 @@ export const signUp = async ({
return data;
};

// 토큰 재발급
export const reissueToken = async (): Promise<
ICommonResponse<ITokenRefreshResponse>
> => {
const { data } = await axiosInstance.post("/api/auth/reissue");
return data;
};

// 로그인
export const login = async ({
email,
password,
}: ILoginRequest): Promise<ICommonResponse<ILoginResponse>> => {
const { data } = await axiosInstance.post("/api/auth/login", {
const { data } = await authInstance.post("/api/auth/login", {
email,
password,
});
return data;
};

// SMS 인증 코드 전송
export const sendSMS = async ({
phoneNumber,
}: ISmsSendRequest): Promise<ICommonResponse<ISmsSendResponse>> => {
Expand All @@ -83,7 +78,6 @@ export const sendSMS = async ({
return data;
};

// SMS 인증 코드 검증
export const verifySMS = async ({
phoneNumber,
verificationCode,
Expand All @@ -95,7 +89,6 @@ export const verifySMS = async ({
return data;
};

// 비밀번호 재설정 요청
export const requestPasswordReset = async ({
email,
}: IEmailSendRequest): Promise<ICommonResponse<IEmailSendResponse>> => {
Expand All @@ -108,7 +101,6 @@ export const requestPasswordReset = async ({
return data;
};

// 비밀번호 재설정
export const resetPassword = async ({
email,
password,
Expand All @@ -122,3 +114,10 @@ export const resetPassword = async ({
);
return data;
};

export const getMyInfo = async (): Promise<
ICommonResponse<IMyPageInfoResponse>
> => {
const { data } = await axiosInstance.get("/api/users/my");
return data;
};
4 changes: 1 addition & 3 deletions src/components/auth/common/InputActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ export default function InputActions({

return (
<div className="flex items-center gap-2">
{timer && (
<span className="text-status-red font-body2 mr-3">{timer}</span>
)}
{timer && <span className="text-status-red font-body2">{timer}</span>}
{button && (
<Button
size="small"
Expand Down
13 changes: 2 additions & 11 deletions src/components/auth/flows/signup/PasswordSetupStep.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import PasswordForm from "@/components/auth/common/PasswordForm";

import useAuthStore from "@/store/useAuthStore";

interface IPasswordSetupStepProps {
onNext: () => void;
onNext: (password: string) => void;
}

export default function PasswordSetupStep({ onNext }: IPasswordSetupStepProps) {
const { setPassword } = useAuthStore();

const handleSubmit = (password: string) => {
setPassword(password);
onNext();
};

return (
<PasswordForm
title={
Expand All @@ -24,7 +15,7 @@ export default function PasswordSetupStep({ onNext }: IPasswordSetupStepProps) {
</>
}
buttonText="다음으로"
onSubmit={handleSubmit}
onSubmit={onNext}
/>
);
}
8 changes: 6 additions & 2 deletions src/components/auth/flows/signup/ProfileSetupStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import useModalStore, { MODAL_TYPES } from "@/store/useModalStore";

type TSignupProfileFormValues = z.infer<typeof signupProfileSchema>;

export default function ProfileSetupStep() {
interface IProfileSetupStepProps {
password: string;
}

export default function ProfileSetupStep({ password }: IProfileSetupStepProps) {
const navigate = useNavigate();
const { email, password, resetAuth } = useAuthStore();
const { email, resetAuth } = useAuthStore();
const { openModal } = useModalStore();
const { useSignUp } = useAuth();

Expand Down
18 changes: 11 additions & 7 deletions src/components/common/input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const Input = forwardRef<HTMLInputElement, IInputProps>(
containerClassName,
inputClassName,
id,
disabled,
...props
},
ref,
Expand All @@ -52,25 +53,28 @@ const Input = forwardRef<HTMLInputElement, IInputProps>(
<div
className={twMerge(
"flex items-center w-full h-input bg-white ring-1 ring-logo-1/30 rounded-component-md transition-colors duration-200 ease-out overflow-hidden",
error
? "ring-2 ring-status-red bg-status-red/5"
: success
? "rubg-2 ring-status-green bg-status-green/5"
: "hover:bg-gray-100 hover:ring-logo-1/40 focus-within:bg-white focus-within:ring-2 focus-within:ring-logo-1/50",
disabled
? "bg-gray-100 cursor-not-allowed"
: error
? "ring-2 ring-status-red bg-status-red/5"
: success
? "ring-2 ring-status-green bg-status-green/5"
: "hover:bg-gray-100 hover:ring-logo-1/40 focus-within:bg-white focus-within:ring-2 focus-within:ring-logo-1/50",
containerClassName,
)}
>
<input
ref={ref}
className={twMerge(
"flex-1 h-full w-full bg-transparent border-none outline-none text-body1 text-text-main placeholder:text-text-placeholder px-5",
"disabled:bg-gray-100 disabled:cursor-not-allowed disabled:text-text-disabled",
"disabled:cursor-not-allowed disabled:text-text-disabled",
rightElement ? "pr-2" : "",
inputClassName,
)}
id={inputId}
aria-invalid={error ? "true" : "false"}
aria-describedby={helperTextId}
disabled={disabled}
{...props}
/>
{rightElement && (
Expand All @@ -84,7 +88,7 @@ const Input = forwardRef<HTMLInputElement, IInputProps>(
id={helperTextId}
aria-live="polite"
className={twMerge(
"font-caption pl-1",
"font-caption pl-1 mt-1.5",
error ? "text-status-red" : "text-text-sub",
)}
>
Expand Down
11 changes: 8 additions & 3 deletions src/hooks/auth/useTokenRefresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import { reissueToken } from "@/api/auth/auth";
import useAuthStore from "@/store/useAuthStore";

export const useTokenRefresh = () => {
const { setAccessToken, logout } = useAuthStore();
const { setAccessToken, logout, setTokenInitialized } = useAuthStore();
const initialized = useRef(false);

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

if (!localStorage.getItem("hasSession")) return;
if (!localStorage.getItem("hasSession")) {
setTokenInitialized();
return;
}

const initAuth = async () => {
try {
Expand All @@ -21,9 +24,11 @@ export const useTokenRefresh = () => {
}
} catch {
logout();
} finally {
setTokenInitialized();
}
};

initAuth();
}, [setAccessToken, logout]);
}, [setAccessToken, logout, setTokenInitialized]);
};
7 changes: 6 additions & 1 deletion src/layout/main/MainLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Outlet } from "react-router-dom";

import { useCoreQuery } from "@/hooks/customQuery";

import Sidebar from "@/components/Sidebar/Sidebar";

import { getMyInfo } from "@/api/auth/auth";

export default function MainLayout() {
useCoreQuery(["myInfo"], getMyInfo);
return (
<div className="flex h-screen p-3 bg-gray-50 sm:p-5">
<Sidebar />
<main className="flex-1 overflow-auto">
<div className="mx-auto w-full max-w-[1600px] py-6 px-4 sm:px-6 lg:px-20">
<div className="mx-auto w-full max-w-400 py-6 px-4 sm:px-6 lg:px-20">
<Outlet />
</div>
</main>
Expand Down
14 changes: 11 additions & 3 deletions src/pages/auth/Signup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";

import { useSocialLogin } from "@/hooks/auth/useSocialLogin";
Expand All @@ -21,6 +21,7 @@ export default function Signup() {
const { step, setStep, handleNext } = useStepNavigation(
location.state?.step || 0,
);
const [password, setPassword] = useState("");

useEffect(() => {
return () => {
Expand All @@ -34,10 +35,17 @@ export default function Signup() {
return <Step01Email onNext={handleNext} />;
}
if (step === 2) {
return <Step02Password onNext={handleNext} />;
return (
<Step02Password
onNext={(pw) => {
setPassword(pw);
handleNext();
}}
/>
);
}
if (step === 3) {
return <Step03Profile />;
return <Step03Profile password={password} />;
}

return (
Expand Down
13 changes: 9 additions & 4 deletions src/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import AuthLayout from "@/layout/auth/AuthLayout";
import GlobalLayout from "@/layout/GlobalLayout";
import MainLayout from "@/layout/main/MainLayout";
import Error from "@/pages/common/Error";
import useAuthStore from "@/store/useAuthStore";

function AuthGuard({ children }: { children: React.ReactNode }) {
// 실제 인증 상태 확인 로직으로 대체 예정
const isAuthenticated = true;
const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
const isTokenInitialized = useAuthStore((state) => state.isTokenInitialized);

if (!isAuthenticated) {
return <Navigate to="/signup" replace />;
if (!isTokenInitialized) {
return null;
}

if (!isLoggedIn) {
return <Navigate to="/login" replace />;
}

return <>{children}</>;
Expand Down
21 changes: 9 additions & 12 deletions src/store/useAuthStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,49 @@ import { create } from "zustand";

interface IAuthState {
isLoggedIn: boolean;
isTokenInitialized: boolean;
accessToken: string | null;
email: string;
password: string;
socialId: number;
socialId: number | null;

login: (email: string, accessToken: string) => void;
logout: () => void;
setAccessToken: (token: string) => void;
setTokenInitialized: () => void;
setEmail: (email: string) => void;
setPassword: (password: string) => void;
setSocialId: (socialId: number) => void;
setSocialId: (socialId: number | null) => void;
resetAuth: () => void;
}

const useAuthStore = create<IAuthState>((set) => ({
isLoggedIn: false,
isTokenInitialized: false,
accessToken: null,
email: "",
password: "",
socialId: -1,
socialId: null,

login: (email, accessToken) => {
localStorage.setItem("hasSession", "true");
set({ isLoggedIn: true, email, accessToken });
},
logout: () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
localStorage.removeItem("hasSession");
set({
isLoggedIn: false,
accessToken: null,
email: "",
password: "",
socialId: -1,
socialId: null,
});
},

setAccessToken: (token) => {
localStorage.setItem("hasSession", "true");
set({ accessToken: token, isLoggedIn: true });
},
setTokenInitialized: () => set({ isTokenInitialized: true }),
setEmail: (email) => set({ email }),
setPassword: (password) => set({ password }),
setSocialId: (socialId) => set({ socialId }),
resetAuth: () => set({ email: "", password: "", accessToken: null }),
resetAuth: () => set({ email: "", socialId: null }),
}));

export default useAuthStore;
Loading