feat: 대시보드에 유저의 정보를 불러오기
This commit is contained in:
@@ -92,10 +92,6 @@ const SocialLoginButtons = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 로그인 화면 UI (View)
|
||||
* GoogleOAuthProvider로 감싸서 내부에서 useGoogleLogin 훅을 사용할 수 있게 합니다.
|
||||
*/
|
||||
export const SocialLoginGroup = () => {
|
||||
return (
|
||||
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const useSocialLogin = () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
console.log("token:" + socialToken);
|
||||
console.log(`[${provider}] token:`, socialToken);
|
||||
try {
|
||||
// 1. 백엔드로 소셜 토큰 전송 (토큰 교환)
|
||||
const response = await authApi.loginWithSocial({
|
||||
@@ -50,6 +50,7 @@ export const useSocialLogin = () => {
|
||||
*/
|
||||
const loginWithGoogle = useGoogleLogin({
|
||||
onSuccess: (tokenResponse) => {
|
||||
console.log(tokenResponse);
|
||||
handleSocialLogin("google", tokenResponse.access_token);
|
||||
},
|
||||
onError: () => {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
export interface SocialLoginRequest {
|
||||
provider: "GOOGLE" | "apple" | "FACEBOOK";
|
||||
provider: "google" | "apple" | "facebook";
|
||||
token: string; // 소셜 프로바이더로부터 발급받은 id_token 또는 access_token
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
accessToken: string; // VibeRoom 전용 JWT (API 요청 시 사용)
|
||||
refreshToken: string; // 토큰 갱신용
|
||||
user?: UserMeResponse; // 선택적으로 유저 정보를 포함할 수 있음
|
||||
}
|
||||
|
||||
export interface UserMeResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
grade: string;
|
||||
}
|
||||
|
||||
11
src/features/user/api/userApi.ts
Normal file
11
src/features/user/api/userApi.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { apiClient } from "@/shared/lib/apiClient";
|
||||
import { UserProfile } from "../types";
|
||||
|
||||
export const userApi = {
|
||||
/**
|
||||
* 내 정보 조회
|
||||
*/
|
||||
getMe: async (): Promise<UserProfile> => {
|
||||
return apiClient<UserProfile>("api/v1/user/me");
|
||||
},
|
||||
};
|
||||
60
src/features/user/components/UserProfileCard.tsx
Normal file
60
src/features/user/components/UserProfileCard.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { UserProfile } from "../types";
|
||||
|
||||
interface UserProfileCardProps {
|
||||
user: UserProfile | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export const UserProfileCard = ({
|
||||
user,
|
||||
isLoading,
|
||||
error,
|
||||
}: UserProfileCardProps) => {
|
||||
if (isLoading) {
|
||||
return <div className="animate-pulse h-48 bg-slate-100 rounded-2xl" />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="p-8 text-center text-red-500 bg-red-50 rounded-2xl border border-red-100">
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<section className="bg-white p-8 rounded-2xl shadow-sm border border-slate-100">
|
||||
<div className="flex items-center gap-6 mb-8">
|
||||
<div className="w-20 h-20 bg-brand-dark/10 rounded-full flex items-center justify-center text-3xl font-bold text-brand-dark">
|
||||
{user.name[0]}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-slate-900">{user.name}</h2>
|
||||
<p className="text-slate-500">{user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 border-t border-slate-50 pt-8">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-slate-400 uppercase tracking-wider">
|
||||
User ID
|
||||
</p>
|
||||
<p className="text-lg font-semibold text-slate-700">{user.id}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-slate-400 uppercase tracking-wider">
|
||||
Grade
|
||||
</p>
|
||||
<p className="text-lg font-semibold text-brand-dark">
|
||||
<span className="bg-brand-dark/5 px-3 py-1 rounded-full text-sm">
|
||||
{user.grade}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
32
src/features/user/hooks/useUserProfile.ts
Normal file
32
src/features/user/hooks/useUserProfile.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useAuthStore } from "@/store/useAuthStore";
|
||||
import { useEffect, useState } from "react";
|
||||
import { userApi } from "../api/userApi";
|
||||
import { UserProfile } from "../types";
|
||||
|
||||
export const useUserProfile = () => {
|
||||
const [user, setUser] = useState<UserProfile | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { isAuthenticated } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) return;
|
||||
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data = await userApi.getMe();
|
||||
console.log("data: " + data);
|
||||
setUser(data);
|
||||
} catch (err) {
|
||||
setError("프로필을 불러오는 데 실패했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadProfile();
|
||||
}, [isAuthenticated]);
|
||||
|
||||
return { user, isLoading, error };
|
||||
};
|
||||
4
src/features/user/index.ts
Normal file
4
src/features/user/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./types";
|
||||
export * from "./hooks/useUserProfile";
|
||||
export * from "./components/UserProfileCard";
|
||||
export * from "./api/userApi";
|
||||
6
src/features/user/types/index.ts
Normal file
6
src/features/user/types/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface UserProfile {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
grade: string;
|
||||
}
|
||||
Reference in New Issue
Block a user