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
14 changes: 14 additions & 0 deletions src/apis/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import axiosHelper from '@/utils/network/axiosHelper';
import { LoginFormData, LoginResponse } from './types';
import { isAxiosError } from 'axios';
import { isError } from 'es-toolkit/compat';

export const login = async (loginFormData: LoginFormData): LoginResponse => {
try {
const response = await axiosHelper.post('/auth/login', loginFormData);
return response.data;
} catch (error) {
if (isAxiosError(error)) return error.response?.data;
return { message: isError(error) ? error.message : String(error) };
}
};
21 changes: 21 additions & 0 deletions src/apis/auth/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from 'zod';
import { LOGIN_FORM_VALID_LENGTH, LOGIN_FORM_ERROR_MESSAGE } from '@/constants/auth';
import { User } from '@/apis/users/types';

export const loginSchema = z.object({
email: z.string().min(LOGIN_FORM_VALID_LENGTH.EMAIL.MIN, LOGIN_FORM_ERROR_MESSAGE.EMAIL.MIN).email(LOGIN_FORM_ERROR_MESSAGE.EMAIL.NOT_FORM),
password: z.string().min(LOGIN_FORM_VALID_LENGTH.PASSWORD.MIN, LOGIN_FORM_ERROR_MESSAGE.PASSWORD.MIN),
});

export type LoginFormData = z.infer<typeof loginSchema>;

export interface LoginSuccessResponse {
user: User;
accessToken: string;
}

export interface LoginFailResponse {
message: string;
}

export type LoginResponse = Promise<LoginSuccessResponse | LoginFailResponse>;
4 changes: 1 addition & 3 deletions src/apis/users/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import axiosHelper from '@/utils/network/axiosHelper';
import { SignupFormData } from './types';
import { SignupFormData, SignupResponse } from './types';
import { isAxiosError } from 'axios';
import { SignupResponse } from './types';
import { isError } from 'es-toolkit/compat';

export const signup = async (signupFormData: SignupFormData): SignupResponse => {
Expand All @@ -10,7 +9,6 @@ export const signup = async (signupFormData: SignupFormData): SignupResponse =>
return response.data;
} catch (error) {
if (isAxiosError(error)) return error.response?.data;

return {
message: isError(error) ? error.message : String(error),
};
Expand Down
4 changes: 3 additions & 1 deletion src/apis/users/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const signupSchema = z

export type SignupFormData = z.infer<typeof signupSchema>;

export interface SignupSuccessResponse {
export interface User {
id: number;
email: string;
nickname: string;
Expand All @@ -29,6 +29,8 @@ export interface SignupSuccessResponse {
updatedAt: string | Date;
}

export type SignupSuccessResponse = User;

export interface SignupFailResponse {
message: string;
}
Expand Down
17 changes: 15 additions & 2 deletions src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
'use client';

import useAuthStore from '@/stores/authStore';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import Link from 'next/link';
import LoginForm from './_components/LoginForm';
import Header from '../_components/Header';
import LoginForm from '@/components/auth/LoginForm';
import Header from '@/components/auth/Header';

export default function Login() {
const router = useRouter();
const { accessToken } = useAuthStore();

useEffect(() => {
if (accessToken) router.push('/mydashboard');
}, [router, accessToken]);

return (
<>
<Header>오늘도 만나서 반가워요!</Header>
Expand Down
17 changes: 15 additions & 2 deletions src/app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
'use client';

import useAuthStore from '@/stores/authStore';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import Link from 'next/link';
import SignupForm from './_components/SignupForm';
import Header from '../_components/Header';
import SignupForm from '@/components/auth/SignupForm';
import Header from '@/components/auth/Header';

export default function Signup() {
const router = useRouter();
const { accessToken } = useAuthStore();

useEffect(() => {
if (accessToken) router.push('/mydashboard');
}, [router, accessToken]);

return (
<>
<Header>첫 방문을 환영합니다!</Header>
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
'use client';

import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import Field from '@/components/auth/Field';
import SubmitButton from '@/components/auth/SubmitButton';
import { LOGIN_FORM_ERROR_MESSAGE, LOGIN_FORM_PLACEHOLDER, LOGIN_FORM_VALID_LENGTH } from '@/constants/auth';

//TODO: API 함수 구현 후 스키마와 타입 정의 옮길 예정
const loginSchema = z.object({
email: z.string().min(LOGIN_FORM_VALID_LENGTH.EMAIL.MIN, LOGIN_FORM_ERROR_MESSAGE.EMAIL.MIN).email(LOGIN_FORM_ERROR_MESSAGE.EMAIL.NOT_FORM),
password: z.string().min(LOGIN_FORM_VALID_LENGTH.PASSWORD.MIN, LOGIN_FORM_ERROR_MESSAGE.PASSWORD.MIN),
});

type LoginFormData = z.infer<typeof loginSchema>;
import { LOGIN_FORM_PLACEHOLDER } from '@/constants/auth';
import { loginSchema, LoginFormData } from '@/apis/auth/types';
import { login } from '@/apis/auth';
import { useRouter } from 'next/navigation';
import useAuthStore from '@/hooks/useAuthStore';

export default function LoginForm() {
const {
Expand All @@ -28,9 +23,20 @@ export default function LoginForm() {
password: '',
},
});
const onSubmit = (loginFormData: LoginFormData) => {
// TODO : 디버깅 용으로 남겼습니다. API 함수 구현이 완료되면 로직 수정 예정입니다.
console.log(loginFormData);

const router = useRouter();
const { setAccessToken } = useAuthStore();

const onSubmit = async (loginFormData: LoginFormData) => {
const response = await login(loginFormData);
// TODO: 디버깅 용으로 alert로 구현했습니다. 모달 기능 구현 후 로직 수정 예정입니다.
if ('message' in response) {
alert(response.message);
} else {
alert('로그인이 완료되었습니다!');
setAccessToken(response.accessToken);
router.replace('/mydashboard');
}
};

return (
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/useAuthStore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import authStore from '@/stores/authStore';
import { useEffect, useState } from 'react';

export default function useAuthStore() {
const store = authStore();
const [isHydrated, setIsHydrated] = useState(false);

useEffect(() => {
setIsHydrated(true);
}, []);

return isHydrated ? store : { accessToken: null, setAccessToken: () => {}, clearAccessToken: () => {} };
}
24 changes: 24 additions & 0 deletions src/stores/authStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

interface AuthState {
accessToken: string | null;
setAccessToken: (accessToken: string) => void;
clearAccessToken: () => void;
}

const authStore = create<AuthState>()(
persist(
(set) => ({
accessToken: null,
setAccessToken: (accessToken) => set({ accessToken }),
clearAccessToken: () => set({ accessToken: null }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => localStorage),
},
),
);

export default authStore;
18 changes: 18 additions & 0 deletions src/utils/network/axiosHelper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import axios from 'axios';
import authStore from '@/stores/authStore';

const axiosHelper = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});

const getAccessToken = () => {
if (!window) return null;
const accessToken = authStore.getState().accessToken;
return accessToken;
};

axiosHelper.interceptors.request.use(
(config) => {
const token = getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error),
);

export default axiosHelper;