Files
viberoom-web/src/features/auth/hooks/useSocialLogin.ts
corpi cbd9017744 feat(fsd): 허브·스페이스 중심 UI 목업 구조로 재편
맥락:

- 기존 라우트/컴포넌트 구조를 FSD 기준으로 정리하고, /app 허브와 /space 집중 화면 중심의 목업 흐름을 구성하기 위해

변경사항:

- App Router 구조를 /landing, /app, /space, /stats, /settings 중심으로 재배치

- entities/session/room/user 더미 데이터와 타입 정의 추가

- features(커스텀 입장, 룸 선택, 체크인, 리액션, 30초 리스타트 등) 단위로 로직 분리

- widgets(허브, 룸 갤러리, 타이머 HUD, 툴 도크 등) 조합형 UI 추가

- shared 공용 UI(Button/Chip/Modal/Toast 등) 및 유틸(cn/useReducedMotion) 정비

- 로그인 후 이동 경로를 /dashboard 에서 /app 으로 변경

- README를 현재 프로젝트 구조/라우트/구현 범위 기준으로 갱신

검증:

- npx tsc --noEmit

세션-상태: 허브·스페이스 목업이 FSD 레이어로 동작 가능하도록 정리됨

세션-다음: /space 상단 및 도크의 인원 수 카피를 분위기형 카피로 후속 정리

세션-리스크: build는 네트워크 환경에서 Google Fonts fetch 실패 가능
2026-02-27 13:30:55 +09:00

123 lines
3.8 KiB
TypeScript

import { useAuthStore } from "@/store/useAuthStore";
import { useGoogleLogin } from "@react-oauth/google";
import { useRouter } from "next/navigation";
import { useState } from "react";
import appleAuthHelpers from "react-apple-signin-auth";
import { authApi } from "../api/authApi";
export const useSocialLogin = () => {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
/**
* [비즈니스 로직 1] 공통 토큰 교환 로직
* 플랫폼별 SDK에서 획득한 토큰을 백엔드로 보내어 VibeRoom 전용 JWT로 교환합니다.
*/
const handleSocialLogin = async (
provider: "google" | "apple" | "facebook",
socialToken: string,
) => {
setIsLoading(true);
setError(null);
console.log(`[${provider}] token:`, socialToken);
try {
// 1. 백엔드로 소셜 토큰 전송 (토큰 교환)
const response = await authApi.loginWithSocial({
provider,
token: socialToken,
});
console.log(`[${provider}] 백엔드 연동 성공! JWT:`, response.accessToken);
// 2. 응답받은 VibeRoom 전용 토큰과 유저 정보를 전역 상태 및 쿠키에 저장
useAuthStore.getState().setAuth(response);
// 3. 메인 허브 화면으로 이동
router.push("/app");
} catch (err) {
console.error(`[${provider}] 로그인 실패:`, err);
setError("로그인에 실패했습니다. 다시 시도해 주세요.");
} finally {
setIsLoading(false);
}
};
/**
* [비즈니스 로직 2] Google Login
* @react-oauth/google 라이브러리의 훅을 사용하여 구글 로그인 팝업을 호출합니다.
*/
const loginWithGoogle = useGoogleLogin({
onSuccess: (tokenResponse) => {
console.log(tokenResponse);
handleSocialLogin("google", tokenResponse.access_token);
},
onError: () => {
setError("구글 로그인에 실패했습니다. 팝업 차단 여부를 확인해 주세요.");
},
});
/**
* [비즈니스 로직 3] Apple Login
* react-apple-signin-auth 라이브러리를 사용하여 애플 로그인을 팝업으로 호출합니다.
*/
const loginWithApple = () => {
try {
const appleHelperBridge = appleAuthHelpers as unknown as {
signIn: (options: {
authOptions: {
clientId: string;
scope: string;
redirectURI: string;
usePopup: boolean;
};
onSuccess: (response: any) => void;
onError: (err: any) => void;
}) => void;
};
appleHelperBridge.signIn({
authOptions: {
clientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID || "",
scope: "email name",
redirectURI: window.location.origin, // Apple 요구사항: 현재 도메인 입력 필요
usePopup: true,
},
onSuccess: (response: any) => {
if (response.authorization?.id_token) {
handleSocialLogin("apple", response.authorization.id_token);
}
},
onError: (err: any) => {
console.error("Apple SignIn error:", err);
setError("애플 로그인 중 오류가 발생했습니다.");
},
});
} catch (err) {
console.error(err);
setError("애플 로그인 초기화 실패");
}
};
/**
* [비즈니스 로직 4] Facebook Callback
* react-facebook-login 컴포넌트에서 콜백으로 받은 토큰을 처리합니다.
*/
const handleFacebookCallback = (response: any) => {
if (response?.accessToken) {
handleSocialLogin("facebook", response.accessToken);
} else {
setError("페이스북 로그인에 실패했습니다.");
}
};
return {
loginWithGoogle,
loginWithApple,
handleFacebookCallback,
isLoading,
error,
};
};