Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2b2d99a
fix: Slack 메시지 전송 시 JSON 데이터 포맷팅 개선
aken-you Jul 8, 2025
2802f43
refactor: im 채널 id 조회하는 로직 추가
aken-you Jul 11, 2025
b0ec139
fix : QNRR-356 회원가입 시 memberId가 null 문제 해결
Jul 12, 2025
a254b6e
fix : QNRR-327 구글로그인시 외부도메인 이미지 등록 및 GA 회원가입 이벤트 위치 수정
Jul 12, 2025
520a8d3
refactor : 헤더의 회원가입/로그인 버튼시 로그인 모달팝업되도록함
Jul 12, 2025
527fb33
fix : QNRR-335 accessToken 만료토큰 삭제 및 Refresh 로직 추가
Jul 12, 2025
3041544
chore : build 에러 회피
Jul 12, 2025
a43551e
refactor: slackapi/slack-github-action 사용
aken-you Jul 13, 2025
8fe44ae
chore : 주석제거, 로그인 아이콘 사이 gap 설정
Jul 13, 2025
9da22c5
Merge pull request #83 from code-zero-to-one/QNRR-354-유수아-코드-리뷰하면-pr-…
aken-you Jul 14, 2025
97d0cd2
Merge pull request #87 from code-zero-to-one/QNRR-356-박경도-조성진-회원가입-시-…
seong-jin-jo Jul 14, 2025
62eb15b
test : Next 이미지 컴포넌트가 문제인가
seong-jin-jo Jul 14, 2025
2e57140
refactor: 사용하지 않는 useQuery 삭제
aken-you Jul 17, 2025
1c2b19a
fix: 스터디 조회 api 변경된 것 적용
Mimiminz Jul 17, 2025
706bc11
fix: 캘린더 변수 추가 밎 적용
Mimiminz Jul 17, 2025
170ada7
delete: 사용하지 않는 함수 제거
Mimiminz Jul 17, 2025
5521fe2
style: login modal에 "로딩 중..." text 삭제
aken-you Jul 17, 2025
e6da198
style: landing 컴포넌트에서 불필요한 배경 이미지 URL 제거
aken-you Jul 17, 2025
0ec798c
refactor: header 컴포넌트를 서버 컴포넌트로 변경
aken-you Jul 17, 2025
1dc40c8
delete: 불필요한 변수 삭제
Mimiminz Jul 17, 2025
89842d7
fix: 피면접자 면접 작성 수정
Mimiminz Jul 17, 2025
b4d4c7e
fix: 면접자 피드백 작성 api 수정
Mimiminz Jul 17, 2025
622148d
fix: status 변경
Mimiminz Jul 17, 2025
ec0717b
refactor: HeaderDropdown 컴포넌트 삭제
aken-you Jul 18, 2025
079cbca
refactor: 로그인 모달을 여는 버튼 컴포넌트 삭제
aken-you Jul 18, 2025
7b76fd5
refactor: 소셜 로고 이미지 style 설정
aken-you Jul 18, 2025
4891729
style: 스터디 상태 값 변경
Mimiminz Jul 18, 2025
6d4e2cf
fix: 변수 네이밍 변경
Mimiminz Jul 18, 2025
61ed83e
Merge pull request #88 from code-zero-to-one/QNRR-371-유수아-header-ui-개선
aken-you Jul 18, 2025
49a297a
style: 시작 전 뱃지 네임 변경
Mimiminz Jul 18, 2025
aa518f3
Merge pull request #89 from code-zero-to-one/QNRR-373-조민주-스터디-면접-준비-완…
Mimiminz Jul 18, 2025
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
26 changes: 7 additions & 19 deletions .github/workflows/notify-pr-author-on-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
5 changes: 0 additions & 5 deletions app/redirection/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ const nextConfig: NextConfig = {
hostname: 'test-api.zeroone.it.kr',
pathname: '/profile-image/**',
},
{
protocol: 'https',
hostname: 'lh3.googleusercontent.com',
pathname: '/**', // 구글 이미지 전체 허용
},
],
},

Expand Down
8 changes: 0 additions & 8 deletions src/features/auth/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number> => {
const res = await axiosInstance.post('/auth/logout');
Expand Down
17 changes: 0 additions & 17 deletions src/features/auth/model/types.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down
35 changes: 1 addition & 34 deletions src/features/auth/model/use-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -16,35 +15,3 @@ export const useMemberId = () => {
enabled: !!getCookie('accessToken'), // 토큰이 있을 때만 실행
});
};

// 회원 프로필 조회
export const useProfile = (memberId?: string) => {
return useQuery<MemberInfoResponse>({
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<MemberInfoResponse>({
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,
});
};
82 changes: 82 additions & 0 deletions src/features/auth/ui/header-user-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<DropdownMenu>
<DropdownMenuTrigger className="w-full focus:outline-none">
<div>
<UserAvatar image={userImg} />
</div>
</DropdownMenuTrigger>

<DropdownMenuContent className="rounded-100 border-border-default bg-background-default shadow-2 flex w-full flex-col gap-50 border p-50">
{[
{
label: '내 정보 수정',
value: '/my-page',
onMenuClick: () => router.push('/my-page'),
},
{
label: '로그아웃',
value: 'logout',
onMenuClick: handleLogout,
},
].map((option) => (
<DropdownMenuItem
key={option.value}
onClick={option.onMenuClick}
className="active:bg-fill-neutral-subtle-pressed rounded-100 h-[48px] w-full cursor-pointer p-150"
>
<span className="font-designer-14m text-text-subtle">
{option.label}
</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
20 changes: 9 additions & 11 deletions src/features/auth/ui/landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -27,18 +26,17 @@ export default function Landing({ isSignupPage }: { isSignupPage: boolean }) {
개발자 면접 준비, 이제 ZERO-ONE에서 <br />
매주 실전처럼 연습해보세요.
</p>
<Button
color="primary"
size="large"
className="w-[234px]"
onClick={() => setLoginOpen(true)}
>
시작하기
</Button>
<LoginModal open={loginOpen} onClose={() => setLoginOpen(false)} />

<LoginModal
openTrigger={
<Button color="primary" size="large" className="w-[234px]">
시작하기
</Button>
}
/>
<SignupModal open={signupOpen} onClose={() => setSignupOpen(false)} />
</section>
<section className="aspect-[349.44/524.16] h-[524.16px] w-[349.44px] flex-shrink-0 bg-[url('/your-image.jpg')] bg-cover bg-center bg-no-repeat">
<section className="aspect-[349.44/524.16] h-[524.16px] w-[349.44px] flex-shrink-0 bg-cover bg-center bg-no-repeat">
<Image
src="graphic-area.svg"
alt="Graphic Area"
Expand Down
28 changes: 17 additions & 11 deletions src/features/auth/ui/login-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

import { XIcon } from 'lucide-react';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { ReactNode, useEffect, useState } from 'react';
import { Modal } from '../../../shared/ui/modal';

export default function LoginModal({
open,
onClose,
openTrigger,
}: {
open: boolean;
onClose: () => void;
openTrigger: ReactNode;
}) {
const [state, setState] = useState<string | null>(null);

Expand All @@ -20,13 +18,12 @@ export default function LoginModal({
}, []);

if (!state) {
return <div>로딩중...</div>;
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;
Expand All @@ -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 (
<Modal.Root open={open} onOpenChange={onClose}>
<Modal.Root>
<Modal.Trigger asChild>{openTrigger}</Modal.Trigger>
<Modal.Portal>
<Modal.Overlay />
<Modal.Content>
Expand All @@ -65,7 +63,7 @@ export default function LoginModal({
</div>
<div className="flex flex-col gap-150 py-150">
{/* <button
className="flex h-[52px] px-5 justify-center items-center gap-3 rounded bg-[#03C75A]"
className="flex h-[52px] px-5 justify-center items-center gap-150 rounded bg-[#03C75A]"
onClick={() => { window.location.href = NAVER_LOGIN_URL }}
>
<Image src="/naver-icon.svg" alt="Naver" width={20} height={20} />
Expand All @@ -74,7 +72,7 @@ export default function LoginModal({
</span>
</button> */}
<button
className="rounded-50 flex h-[52px] items-center justify-center gap-3 border border-[#FEE500] bg-[#FFE812] px-5 text-black"
className="rounded-50 flex h-[52px] items-center justify-center gap-150 border border-[#FEE500] bg-[#FFE812] px-5 text-black"
onClick={() => {
window.location.href = KAKAO_LOGIN_URL;
}}
Expand All @@ -84,13 +82,17 @@ export default function LoginModal({
alt="Naver"
width={20}
height={20}
style={{
width: '20px',
height: '20px',
}}
/>
<span className="text-center font-['Pretendard'] text-[15px] leading-[23px] font-bold text-[#181D27]">
카카오 계정 로그인
</span>
</button>
<button
className="rounded-50 flex h-[52px] items-center justify-center gap-3 border border-[#3D4148] bg-white px-5 text-black"
className="rounded-50 flex h-[52px] items-center justify-center gap-150 border border-[#3D4148] bg-white px-5 text-black"
onClick={() => {
window.location.href = GOOGLE_LOGIN_URL;
}}
Expand All @@ -100,6 +102,10 @@ export default function LoginModal({
alt="Naver"
width={20}
height={20}
style={{
width: '20px',
height: '20px',
}}
/>
<span className="text-center font-['Pretendard'] text-[15px] leading-[23px] font-bold text-[#181D27]">
Google 계정 로그인
Expand Down
1 change: 1 addition & 0 deletions src/features/auth/ui/sign-up-image-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function SignupImageSelector({
<div className="relative">
<div className="relative h-[112px] w-[112px] overflow-hidden rounded-full">
<Image src={image} alt="프로필" fill className="object-cover" />
<img src={image} alt="next최적화안쓴프로필" className='object-covoer' />
</div>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
Expand Down
19 changes: 17 additions & 2 deletions src/features/auth/ui/sign-up-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
} from '@/features/auth/model/use-auth-mutation';
import SignupImageSelector from '@/features/auth/ui/sign-up-image-selector';
import SignupNameInput from '@/features/auth/ui/sign-up-name-input';
import { getCookie } from '@/shared/tanstack-query/cookie';
import { getCookie, setCookie } from '@/shared/tanstack-query/cookie';
import Button from '@/shared/ui/button';
import { Modal } from '@/shared/ui/modal';
import { sendGTMEvent } from '@next/third-parties/google';
import { hashValue } from '@/shared/lib/hash';

export default function SignupModal({
open,
Expand Down Expand Up @@ -58,7 +60,19 @@ export default function SignupModal({
{
// 회원가입 성공 시 프로필 이미지 업로드
onSuccess: (data) => {
if (data && data.content.generatedMemberId) {

const memberId = data.content.generatedMemberId;

if (data && memberId) {
setCookie('memberId', memberId)

// 회원가입 GA 이벤트 전송
sendGTMEvent({
event: 'custom_member_join',
dl_timestamp: new Date().toISOString(),
dl_member_id: hashValue(memberId),
});

const formData = new FormData();

if (fileInputRef.current?.files?.[0]) {
Expand All @@ -71,6 +85,7 @@ export default function SignupModal({
file: formData,
});
}


// 성공 후 홈페이지로 이동
window.location.href = '/';
Expand Down
Loading