diff --git a/.github/workflows/notify-pr-author-on-review.yml b/.github/workflows/notify-pr-author-on-review.yml
index d0efcbfb..9f60a07c 100644
--- a/.github/workflows/notify-pr-author-on-review.yml
+++ b/.github/workflows/notify-pr-author-on-review.yml
@@ -49,22 +49,10 @@ jobs:
- name: Send Slack DM to PR Author
if: steps.extract_info.outputs.skip != 'true'
- env:
- SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- AUTHOR_SLACK_ID: ${{ steps.extract_info.outputs.author_slack_id }}
- TEXT: ${{ steps.extract_info.outputs.text }}
- run: |
- RESPONSE=$(curl -s -X POST https://slack.com/api/chat.postMessage \
- -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
- -H "Content-Type: application/json" \
- -d '{
- "channel": "'"$AUTHOR_SLACK_ID"'",
- "text": "'"$TEXT"'"
- }')
-
- echo "Slack DM 전송 응답: $RESPONSE"
-
- if ! echo "$RESPONSE" | jq -e '.ok' | grep -q true; then
- echo "❌ Slack 메시지 전송 실패"
- exit 1
- fi
+ uses: slackapi/slack-github-action@v2.1.0
+ with:
+ method: chat.postMessage
+ token: ${{ secrets.SLACK_BOT_TOKEN }}
+ payload: |
+ channel: ${{ steps.extract_info.outputs.author_slack_id }}
+ text: ${{ steps.extract_info.outputs.text }}
diff --git a/app/redirection/page.tsx b/app/redirection/page.tsx
index 554bf01a..a7caa192 100644
--- a/app/redirection/page.tsx
+++ b/app/redirection/page.tsx
@@ -31,11 +31,6 @@ function RedirectionContent() {
if (isGuest === 'true') {
router.push('/sign-up');
- sendGTMEvent({
- event: 'custom_member_join',
- dl_timestamp: new Date().toISOString(),
- dl_member_id: hashValue(memberId),
- });
} else {
router.push('/');
router.refresh();
diff --git a/next.config.ts b/next.config.ts
index 284e3b98..e1dcedae 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -15,6 +15,11 @@ const nextConfig: NextConfig = {
hostname: 'test-api.zeroone.it.kr',
pathname: '/profile-image/**',
},
+ {
+ protocol: 'https',
+ hostname: 'lh3.googleusercontent.com',
+ pathname: '/**', // 구글 이미지 전체 허용
+ },
],
},
diff --git a/src/features/auth/api/auth.ts b/src/features/auth/api/auth.ts
index 3e9cc8b2..a055640c 100644
--- a/src/features/auth/api/auth.ts
+++ b/src/features/auth/api/auth.ts
@@ -38,14 +38,6 @@ export async function getMemberId() {
return res.data;
}
-// 프로필 조회 API
-export async function getProfile(memberId: number) {
- const res = await axiosInstance.get(`/members/${memberId}/profile`);
- console.log('getProfile res', res);
-
- return res.data;
-}
-
// 로그아웃 API
export const logout = async (): Promise => {
const res = await axiosInstance.post('/auth/logout');
diff --git a/src/features/auth/model/types.ts b/src/features/auth/model/types.ts
index b234f67e..f0881c17 100644
--- a/src/features/auth/model/types.ts
+++ b/src/features/auth/model/types.ts
@@ -1,20 +1,3 @@
-// 사용자 정보 조회 응답 타입
-export interface MemberInfoResponse {
- isLogin: boolean;
- content: {
- memberProfile: {
- memberName: string;
- profileImage: {
- resizedImages: {
- resizedImageUrl: string;
- }[];
- };
- };
- };
- statusCode: number;
- message: string;
-}
-
// 회원가입 응답 타입
export interface SignUpResponse {
content: {
diff --git a/src/features/auth/model/use-auth.ts b/src/features/auth/model/use-auth.ts
index 20e5a92e..efa7f8dc 100644
--- a/src/features/auth/model/use-auth.ts
+++ b/src/features/auth/model/use-auth.ts
@@ -4,9 +4,8 @@
// 한편, prefetchQuery 는 서버컴포넌트에서 사용하는 함수로 데이터를 미리 가져와서 캐시에 저장함
import { useQuery } from '@tanstack/react-query';
-import { getMemberId, getProfile } from '@/features/auth/api/auth';
+import { getMemberId } from '@/features/auth/api/auth';
import { getCookie } from '@/shared/tanstack-query/cookie';
-import { MemberInfoResponse } from './types';
// 회원 Id 조회
export const useMemberId = () => {
@@ -16,35 +15,3 @@ export const useMemberId = () => {
enabled: !!getCookie('accessToken'), // 토큰이 있을 때만 실행
});
};
-
-// 회원 프로필 조회
-export const useProfile = (memberId?: string) => {
- return useQuery({
- queryKey: ['profile', memberId],
- queryFn: () => getProfile(Number(memberId)),
- enabled: !!memberId, // memberId가 있을 때만 실행
- // staleTime으로 캐시 유효 기간 설정
- staleTime: 5 * 60 * 1000, // 5분
- });
-};
-
-// 회원 Id 기반 회원 정보 조회
-export const useMemberInfo = () => {
- const memberId = getCookie('memberId');
-
- return useQuery({
- queryKey: ['memberInfo', memberId],
- queryFn: async () => {
- if (!memberId) return { isLogin: false };
-
- try {
- const profileData = await getProfile(Number(memberId));
-
- return { isLogin: true, ...profileData };
- } catch (error) {
- return { isLogin: false };
- }
- },
- enabled: !!memberId,
- });
-};
diff --git a/src/features/auth/ui/header-user-dropdown.tsx b/src/features/auth/ui/header-user-dropdown.tsx
new file mode 100644
index 00000000..94292a47
--- /dev/null
+++ b/src/features/auth/ui/header-user-dropdown.tsx
@@ -0,0 +1,82 @@
+'use client';
+
+import { sendGTMEvent } from '@next/third-parties/google';
+import { useQueryClient } from '@tanstack/react-query';
+import { useRouter } from 'next/navigation';
+import { hashValue } from '@/shared/lib/hash';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/shared/shadcn/ui/dropdown-menu';
+import { deleteCookie, getCookie } from '@/shared/tanstack-query/cookie';
+import UserAvatar from '@/shared/ui/avatar';
+import { logout } from '../api/auth';
+
+export default function HeaderUserDropdown({ userImg }: { userImg: string }) {
+ const queryClient = useQueryClient();
+ const router = useRouter();
+
+ const handleLogout = async () => {
+ try {
+ // 1. 서버에 로그아웃 요청 (refresh token 삭제)
+ await logout();
+
+ const memberId = getCookie('memberId');
+ sendGTMEvent({
+ event: 'custom_member_logout',
+ dl_timestamp: new Date().toISOString(),
+ dl_member_id: hashValue(memberId),
+ });
+
+ // 2. 클라이언트의 access token 삭제
+ deleteCookie('accessToken');
+ deleteCookie('memberId');
+
+ // 3. React Query 캐시 초기화
+ queryClient.clear();
+
+ // 4. 홈으로 리다이렉트
+ router.push('/');
+ router.refresh(); // 전체 페이지 리프레시
+ } catch (error) {
+ console.error('로그아웃 실패:', error);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {[
+ {
+ label: '내 정보 수정',
+ value: '/my-page',
+ onMenuClick: () => router.push('/my-page'),
+ },
+ {
+ label: '로그아웃',
+ value: 'logout',
+ onMenuClick: handleLogout,
+ },
+ ].map((option) => (
+
+
+ {option.label}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/features/auth/ui/landing.tsx b/src/features/auth/ui/landing.tsx
index 11d15831..df785b52 100644
--- a/src/features/auth/ui/landing.tsx
+++ b/src/features/auth/ui/landing.tsx
@@ -7,7 +7,6 @@ import SignupModal from '@/features/auth/ui/sign-up-modal';
import Button from '@/shared/ui/button';
export default function Landing({ isSignupPage }: { isSignupPage: boolean }) {
- const [loginOpen, setLoginOpen] = useState(false);
const [signupOpen, setSignupOpen] = useState(false);
useEffect(() => {
@@ -27,18 +26,17 @@ export default function Landing({ isSignupPage }: { isSignupPage: boolean }) {
개발자 면접 준비, 이제 ZERO-ONE에서
매주 실전처럼 연습해보세요.
-
- setLoginOpen(false)} />
+
+
+ 시작하기
+
+ }
+ />
setSignupOpen(false)} />
-
+
void;
+ openTrigger: ReactNode;
}) {
const [state, setState] = useState(null);
@@ -20,13 +18,12 @@ export default function LoginModal({
}, []);
if (!state) {
- return 로딩중...
;
+ return <>>;
}
const isLocal = origin.includes('localhost') || origin.includes('127.0.0.1'); // 로컬환경 테스트용
localStorage.setItem('isLocal', JSON.stringify(isLocal));
- console.log("로컬 환경 여부", isLocal);
const API_BASE_URL = isLocal
? 'https://test-api.zeroone.it.kr'
: process.env.NEXT_PUBLIC_API_BASE_URL;
@@ -44,7 +41,8 @@ export default function LoginModal({
const GOOGLE_LOGIN_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=openid%20profile&access_type=offline&prompt=consent&include_granted_scopes=true&response_type=code&redirect_uri=${API_BASE_URL}/api/v1/auth/google/redirect-uri&client_id=${GOOGLE_CLIENT_ID}&state=${state}`;
return (
-
+
+ {openTrigger}
@@ -65,7 +63,7 @@ export default function LoginModal({
{/*
*/}