Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ https://velog.io/@oneook/tailwindcss-4.0-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%AC%E
--color-background-accent-yellow-subtle: var(--color-yellow-50);
--color-background-accent-yellow-default: var(--color-yellow-100);
--color-background-accent-yellow-strong: var(--color-yellow-600);
--color-background-neutral-strong: var(--color-gray-900);
--color-background-success-default: var(--color-green-500);
--color-background-danger-default: var(--color-red-500);

--color-fill-brand-default-default: var(--color-rose-500);
--color-fill-brand-default-hover: var(--color-rose-600);
Expand Down
2 changes: 0 additions & 2 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,5 @@ export const metadata: Metadata = {

// 랜딩페이지에서 로그인 모달이 뜨는 것으로 파악
export default function LoginPage() {
console.log("NEXT_PUBLIC_API_BASE_URL", process.env.NEXT_PUBLIC_API_BASE_URL)

return <Landing isSignupPage={false} />;
}
4 changes: 2 additions & 2 deletions app/redirection/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ function RedirectionContent() {
handleRedirection().catch(console.error);
}, [searchParams, router, queryClient]); // 의존성 추가

return <div>처리중...</div>;
return <></>;
}

// useSearchParams() should be wrapped in a suspense boundary
export default function RedirectionPage() {
return (
<Suspense fallback={<div>로딩중...</div>}>
<Suspense fallback={<></>}>
<RedirectionContent />
</Suspense>
);
Expand Down
1 change: 0 additions & 1 deletion app/sign-up/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Landing from '@/features/auth/ui/landing';
// 랜딩페이지에서 회원가입 모달이 뜨는 것으로 파악
export default function SignupPage() {
// TODO : URL 에서 토큰 파싱후
console.log('회원가입 페이지');

return <Landing isSignupPage={true} />;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"react": "^19.0.0",
"react-day-picker": "9.4.3",
"react-dom": "^19.0.0",
"sonner": "^2.0.6",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.0.6",
"tailwindcss-animate": "^1.0.7",
Expand Down
25 changes: 25 additions & 0 deletions src/app/provider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { Toaster } from 'sonner';
import QueryProvider from '@/app/provider/query-provider';

interface ProviderProps {
Expand All @@ -8,6 +9,30 @@ interface ProviderProps {
function MainProvider({ children }: ProviderProps) {
return (
<QueryProvider>
<Toaster
position="top-center"
icons={{
success: null,
error: null,
info: null,
warning: null,
}}
toastOptions={{
unstyled: true,
classNames: {
toast:
'flex flex-row-reverse items-center gap-150 font-designer-14m p-200 rounded-50 text-text-inverse shadow-[2px_2px_5px_#00000014]',
info: 'bg-background-neutral-strong',
success: 'bg-background-success-default',
warning: 'bg-background-danger-default',
closeButton: '!font-thin',
},
style: {
width: 'fit-content',
},
}}
closeButton
/>
{children}
{process.env.NODE_ENV === 'development' && (
<ReactQueryDevtools initialIsOpen={false} />
Expand Down
14 changes: 9 additions & 5 deletions src/entities/user/api/get-user-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import type {
GetUserProfileResponse,
PatchAutoMatchingParams,
} from '@/entities/user/api/types';
import { axiosInstance } from '@/shared/tanstack-query/axios';
import { axiosClientInstance } from '@/shared/tanstack-query/axios.client';

export const getUserProfile = async (
memberId: number,
): Promise<GetUserProfileResponse> => {
const res = await axiosInstance.get(`/members/${memberId}/profile`);
const res = await axiosClientInstance.get(`/members/${memberId}/profile`);

return res.data.content;
};
Expand All @@ -16,7 +16,11 @@ export const patchAutoMatching = async ({
memberId,
autoMatching,
}: PatchAutoMatchingParams): Promise<void> => {
await axiosInstance.patch(`/members/${memberId}/auto-matching`, undefined, {
params: { 'auto-matching': autoMatching },
});
await axiosClientInstance.patch(
`/members/${memberId}/auto-matching`,
undefined,
{
params: { 'auto-matching': autoMatching },
},
);
};
21 changes: 10 additions & 11 deletions src/features/auth/api/auth.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// API 통신만 담당하는 순수 함수들

import {
axiosInstance,
axiosInstanceForMultipart,
} from '@/shared/tanstack-query/axios';
axiosClientInstance,
axiosClientInstanceForMultipart,
} from '@/shared/tanstack-query/axios.client';

// 회원가입 요청 API
export async function signUp(data: any) {
const res = await axiosInstance.post('/members', data);
console.log('signUp res', res);
const res = await axiosClientInstance.post('/members', data);

return res.data;
}
Expand All @@ -19,7 +18,7 @@ export async function uploadProfileImage(
filename: string,
file: FormData,
) {
const res = await axiosInstanceForMultipart.put(
const res = await axiosClientInstanceForMultipart.put(
`/files/members/${memberId}/profile/image/${filename}`,
file,
);
Expand All @@ -29,15 +28,15 @@ export async function uploadProfileImage(

// 멤버 ID 조회 API
export async function getMemberId() {
const res = await axiosInstance.get(`/auth/me`);
console.log('getMemberId res', res);
const res = await axiosClientInstance.get(`/auth/me`);

return res.data;
}

// 로그아웃 API
export const logout = async (): Promise<number> => {
const res = await axiosInstance.post('/auth/logout');
// 성공하면, content는 빈배열로 응답
export const logout = async (): Promise<Record<string, never>> => {
const res = await axiosClientInstance.post('/auth/logout');

return res.data.statusCode;
return res.data.content;
};
29 changes: 27 additions & 2 deletions src/features/auth/model/use-auth-mutation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// 데이터 변경(Mutation) 을 담당하는 커스텀 훅

import { useMutation } from '@tanstack/react-query';
import { sendGTMEvent } from '@next/third-parties/google';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { logout, signUp, uploadProfileImage } from '@/features/auth/api/auth';
import { hashValue } from '@/shared/lib/hash';
import { deleteCookie, getCookie } from '@/shared/tanstack-query/cookie';
import { SignUpResponse } from './types';

// 회원가입 요청 커스텀 훅
Expand All @@ -27,7 +31,28 @@ export function useUploadProfileImageMutation() {
}

export const useLogoutMutation = () => {
return useMutation<number, unknown, void>({
const queryClient = useQueryClient();
const router = useRouter();

return useMutation({
mutationFn: logout,
onSuccess: () => {
const memberId = getCookie('memberId');

if (memberId)
sendGTMEvent({
event: 'custom_member_logout',
dl_timestamp: new Date().toISOString(),
dl_member_id: hashValue(memberId),
});

deleteCookie('accessToken');
deleteCookie('memberId');

queryClient.clear();

router.push('/login');
router.refresh();
},
});
};
33 changes: 3 additions & 30 deletions src/features/auth/ui/header-user-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,21 @@
'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';
import { useLogoutMutation } from '../model/use-auth-mutation';

export default function HeaderUserDropdown({ userImg }: { userImg: string }) {
const queryClient = useQueryClient();
const router = useRouter();
const { mutateAsync: logout } = useLogoutMutation();

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);
}
await logout();
};

return (
Expand Down
17 changes: 10 additions & 7 deletions src/features/my-page/api/update-user-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import type {
UpdateUserProfileRequest,
UpdateUserProfileResponse,
} from '@/features/my-page/api/types';
import { axiosInstance } from '@/shared/tanstack-query/axios';
import { axiosClientInstance } from '@/shared/tanstack-query/axios.client';

export const updateUserProfile = async (
memberId: number,
body: UpdateUserProfileRequest,
): Promise<UpdateUserProfileResponse> => {
const res = await axiosInstance.patch(`/members/${memberId}/profile`, body);
const res = await axiosClientInstance.patch(
`/members/${memberId}/profile`,
body,
);

return res.data.content;
};
Expand All @@ -23,7 +26,7 @@ export const updateUserProfileInfo = async (
memberId: number,
body: UpdateUserProfileInfoRequest,
): Promise<UpdateUserProfileInfoResponse> => {
const res = await axiosInstance.patch(
const res = await axiosClientInstance.patch(
`/members/${memberId}/profile/info`,
body,
);
Expand All @@ -34,25 +37,25 @@ export const updateUserProfileInfo = async (
export const getAvailableStudyTimes = async (): Promise<
AvailableStudyTimeResponse[]
> => {
const res = await axiosInstance.get('/available-study-times');
const res = await axiosClientInstance.get('/available-study-times');

return res.data.content;
};

export const getStudySubjects = async (): Promise<StudySubjectResponse[]> => {
const res = await axiosInstance.get('/study-subjects');
const res = await axiosClientInstance.get('/study-subjects');

return res.data.content;
};

export const getTechStacks = async (): Promise<TechStackResponse[]> => {
const res = await axiosInstance.get('/tech-stacks');
const res = await axiosClientInstance.get('/tech-stacks');

return res.data.content;
};

export const getStudyDashboard = async (): Promise<StudyDashboardResponse> => {
const res = await axiosInstance.get('/study/dashboard');
const res = await axiosClientInstance.get('/study/dashboard');

return res.data.content;
};
21 changes: 12 additions & 9 deletions src/features/study/api/get-study-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import type {
PrepareStudyRequest,
WeeklyParticipationResponse,
} from '@/features/study/api/types';
import { axiosInstance } from '@/shared/tanstack-query/axios';
import { axiosClientInstance } from '@/shared/tanstack-query/axios.client';

// 스터디 상세 조회
export const getDailyStudyDetail = async (
params: string,
): Promise<DailyStudyDetail> => {
const res = await axiosInstance.get(`/study/daily/mine/${params}`);
const res = await axiosClientInstance.get(`/study/daily/mine/${params}`);

return res.data.content;
};
Expand All @@ -25,7 +25,7 @@ export const getDailyStudyDetail = async (
export const getDailyStudies = async (
params?: GetDailyStudiesParams,
): Promise<GetDailyStudiesResponse> => {
const res = await axiosInstance.get('/study/daily', { params });
const res = await axiosClientInstance.get('/study/daily', { params });

return res.data.content;
};
Expand All @@ -34,13 +34,13 @@ export const getDailyStudies = async (
export const getMonthlyStudyCalendar = async (
params: GetMonthlyCalendarParams,
): Promise<MonthlyCalendarResponse> => {
const res = await axiosInstance.get('/study/daily/month', { params });
const res = await axiosClientInstance.get('/study/daily/month', { params });

return res.data.content;
};

export const postDailyRetrospect = async (body: PostDailyRetrospectRequest) => {
const res = await axiosInstance.post('/study/daily/retrospect', body);
const res = await axiosClientInstance.post('/study/daily/retrospect', body);

return res.data;
};
Expand All @@ -50,7 +50,10 @@ export const putStudyDaily = async (
dailyId: number,
body: PrepareStudyRequest,
) => {
const res = await axiosInstance.put(`/study/daily/${dailyId}/prepare`, body);
const res = await axiosClientInstance.put(
`/study/daily/${dailyId}/prepare`,
body,
);

return res.data;
};
Expand All @@ -60,7 +63,7 @@ export const completeStudy = async (
dailyStudyId: number,
body: CompleteStudyRequest,
) => {
const res = await axiosInstance.post(
const res = await axiosClientInstance.post(
`/study/daily/${dailyStudyId}/complete`,
body,
);
Expand All @@ -79,7 +82,7 @@ export const postJoinStudy = async (payload: JoinStudyRequest) => {
),
);

const res = await axiosInstance.post('/matching/apply', cleanPayload);
const res = await axiosClientInstance.post('/matching/apply', cleanPayload);

return res.data;
};
Expand All @@ -93,4 +96,4 @@ export const getWeeklyParticipation = async (
});

return res.data.content;
};
};
Loading