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
78 changes: 45 additions & 33 deletions src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,45 @@ import type {
SignUpRequest,
SocialLoginRequest,
} from '@/types/auth';
import { useCallback } from 'react';
import { useNavigate } from 'react-router';

export default function useAuth() {
const navigate = useNavigate();

const { user, isLoggedIn, login, logout, updateUser } = useAuthStore(
(state) => state
);
const user = useAuthStore((state) => state.user);
const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
const login = useAuthStore((state) => state.login);
const logout = useAuthStore((state) => state.logout);
const updateUser = useAuthStore((state) => state.updateUser);

// 1. ์ด๋ฉ”์ผ ๋กœ๊ทธ์ธ ๋กœ์ง
const handleLogin = async (data: LoginRequest) => {
// 5. ๋กœ๊ทธ์•„์›ƒ ๋กœ์ง (handleLogout์„ ๋จผ์ € ์ •์˜ํ•จ)
const handleLogout = useCallback(async () => {
try {
const { token } = await loginApi(data);
const user = await getMeApi(token);
login(user, token); // Zustand ์Šคํ† ์–ด ์—…๋ฐ์ดํŠธ
navigate('/'); // ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
} catch (error) {
console.error('Login failed:', error);
await logoutApi();
} finally {
logout(); // Zustand ์ƒํƒœ ์ดˆ๊ธฐํ™”
navigate('/login');
}
};
}, [logout, navigate]);

// 1. ์ด๋ฉ”์ผ ๋กœ๊ทธ์ธ ๋กœ์ง
const handleLogin = useCallback(
async (data: LoginRequest) => {
try {
const { token } = await loginApi(data);
const user = await getMeApi(token);
login(user, token); // Zustand ์Šคํ† ์–ด ์—…๋ฐ์ดํŠธ
navigate('/'); // ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
} catch (error) {
console.error('Login failed:', error);
}
},
[login, navigate]
);

// 2. ์ด๋ฉ”์ผ ํšŒ์›๊ฐ€์ž… ๋กœ์ง
const handleSignUp = async (data: SignUpRequest) => {
const handleSignUp = useCallback(async (data: SignUpRequest) => {
try {
const response = await signupApi(data);

Expand All @@ -44,22 +60,25 @@ export default function useAuth() {
console.error('Signup failed:', error);
}
return false;
};
}, []);

// 3. ์†Œ์…œ ๋กœ๊ทธ์ธ ๋กœ์ง
const handleSocialLogin = async (data: SocialLoginRequest) => {
try {
const { token } = await socialApi(data);
const user = await getMeApi(token);
login(user, token);
navigate('/');
} catch (error) {
console.error('Social login failed:', error);
}
};
const handleSocialLogin = useCallback(
async (data: SocialLoginRequest) => {
try {
const { token } = await socialApi(data);
const user = await getMeApi(token);
login(user, token);
navigate('/');
} catch (error) {
console.error('Social login failed:', error);
}
},
[login, navigate]
);

// 4. ๋‚ด ์ •๋ณด ๋™๊ธฐํ™”
const refreshUser = async () => {
const refreshUser = useCallback(async () => {
if (!isLoggedIn) return;
try {
const userData = await getMeApi();
Expand All @@ -69,14 +88,7 @@ export default function useAuth() {
console.error('Refresh user failed:', error);
handleLogout(); // ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
}
};

// 5. ๋กœ๊ทธ์•„์›ƒ ๋กœ์ง
const handleLogout = async () => {
await logoutApi();
logout(); // Zustand ์ƒํƒœ ์ดˆ๊ธฐํ™”
navigate('/login');
};
}, [isLoggedIn, updateUser, handleLogout]);

return {
user,
Expand Down
40 changes: 40 additions & 0 deletions src/mocks/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type {
LogoutResponse,
SignUpRequest,
SignUpResponse,
SocialLoginRequest,
SocialLoginResponse,
} from '@/types/auth';
import { http, HttpResponse, delay } from 'msw';
import { userDB } from '../db/user.db';
Expand Down Expand Up @@ -80,4 +82,42 @@ export const authHandlers = [
});
}
),

// 5. ์†Œ์…œ ๋กœ๊ทธ์ธ
http.post<never, SocialLoginRequest, SocialLoginResponse>(
path('/auth/social'),
async ({ request }) => {
const { provider, code } = await request.json();

// ์‹ค์ œ ์„œ๋ฒ„์ฒ˜๋Ÿผ ์ด๋ฏธ ์‚ฌ์šฉ๋œ ์ฝ”๋“œ๋Š” 401์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜
if (usedSocialCodes.has(code)) {
await delay(500);
return new HttpResponse(null, {
status: 401,
statusText: 'Unauthorized',
});
}

// ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด provider์™€ code๊ฐ€ ์žˆ์œผ๋ฉด ๋ฌด์กฐ๊ฑด ์„ฑ๊ณตํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค (์ค€์—ฝ ์œ ์ €๋กœ ๋กœ๊ทธ์ธ)
if (provider && code) {
const user = userDB.find((u) => u.id === 2);

if (user) {
usedSocialCodes.add(code); // ์ฝ”๋“œ ์‚ฌ์šฉ ์ฒ˜๋ฆฌ
await delay(500);
return HttpResponse.json({
token: user.token,
});
}
}

return new HttpResponse(null, {
status: 401,
statusText: 'Unauthorized',
});
}
),
];

// ์ฝ”๋“œ ์‚ฌ์šฉ ์—ฌ๋ถ€๋ฅผ ์ถ”์ ํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ
const usedSocialCodes = new Set<string>();
15 changes: 11 additions & 4 deletions src/routes/SocialCallback.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AuthProvider } from '@/types/auth';
import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router';
import useAuth from '../hooks/useAuth';

Expand All @@ -13,23 +13,30 @@ export default function SocialCallback() {
? SOCIAL_PROVIDER_MAP[providerParam.toLowerCase()]
: undefined;
const [searchParams] = useSearchParams();
const { handleSocialLogin } = useAuth();
const { handleSocialLogin, isLoggedIn } = useAuth();
const navigate = useNavigate();
const loginAttempted = useRef(false);

useEffect(() => {
// 1. ์ด๋ฏธ ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๊ฑฐ๋‚˜ ์ด๋ฏธ ์š”์ฒญ์„ ๋ณด๋ƒˆ๋‹ค๋ฉด ์ค‘๋‹จ
if (isLoggedIn || loginAttempted.current) {
return;
}

const code = searchParams.get('code');

if (code && provider) {
loginAttempted.current = true; // ์š”์ฒญ ์‹œ์ž‘์„ ํ‘œ์‹œ
handleSocialLogin({ provider, code });
} else {
alert('๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
navigate('/login');
}
}, [provider, searchParams, handleSocialLogin, navigate]);
}, [provider, searchParams, handleSocialLogin, navigate, isLoggedIn]);

return (
<div className="flex flex-col items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
<p className="mt-4 text-gray-600">์†Œ์…œ ๊ณ„์ • ์ •๋ณด๋ฅผ ํ™•์ธ ์ค‘์ž…๋‹ˆ๋‹ค...</p>
</div>
);
Expand Down