Skip to content

Commit 2a88bfa

Browse files
authored
Merge pull request #425 from solid-connection/fix/community-queryclient-modal-crash
커뮤니티 진입 시 QueryClient 컨텍스트 크래시 수정
2 parents a68ef7a + ea983b1 commit 2a88bfa

39 files changed

Lines changed: 490 additions & 508 deletions

File tree

Lines changed: 7 additions & 0 deletions
Loading

apps/web/src/apis/Auth/postAppleAuth.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,36 @@ import { type AppleAuthRequest, type AppleAuthResponse, authApi } from "./api";
1111
* @description 애플 로그인을 위한 useMutation 커스텀 훅
1212
*/
1313
const usePostAppleAuth = () => {
14-
const router = useRouter();
15-
const searchParams = useSearchParams();
14+
const router = useRouter();
15+
const searchParams = useSearchParams();
1616

17-
return useMutation<AppleAuthResponse, AxiosError, AppleAuthRequest>({
18-
mutationFn: (data) => authApi.postAppleAuth(data),
19-
onSuccess: (data) => {
20-
if (data.isRegistered) {
21-
// 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장
22-
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
23-
useAuthStore.getState().setAccessToken(data.accessToken);
17+
return useMutation<AppleAuthResponse, AxiosError, AppleAuthRequest>({
18+
mutationFn: (data) => authApi.postAppleAuth(data),
19+
onSuccess: (data) => {
20+
if (data.isRegistered) {
21+
// 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장
22+
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
23+
useAuthStore.getState().setAccessToken(data.accessToken);
2424

25-
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
26-
const redirectParam = searchParams.get("redirect");
27-
const safeRedirect = validateSafeRedirect(redirectParam);
25+
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
26+
const redirectParam = searchParams.get("redirect");
27+
const safeRedirect = validateSafeRedirect(redirectParam);
2828

29-
toast.success("로그인에 성공했습니다.");
29+
toast.success("로그인에 성공했습니다.");
3030

31-
setTimeout(() => {
32-
router.push(safeRedirect);
33-
}, 100);
34-
} else {
35-
// 새로운 회원일 시 - 회원가입 페이지로 이동
36-
router.push(`/sign-up?token=${data.signUpToken}`);
37-
}
38-
},
39-
onError: () => {
40-
toast.error("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
41-
router.push("/login");
42-
},
43-
});
31+
setTimeout(() => {
32+
router.push(safeRedirect);
33+
}, 100);
34+
} else {
35+
// 새로운 회원일 시 - 회원가입 페이지로 이동
36+
router.push(`/sign-up?token=${data.signUpToken}`);
37+
}
38+
},
39+
onError: () => {
40+
toast.error("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
41+
router.push("/login");
42+
},
43+
});
4444
};
4545

4646
export default usePostAppleAuth;

apps/web/src/apis/Auth/postKakaoAuth.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,37 @@ import { authApi, type KakaoAuthRequest, type KakaoAuthResponse } from "./api";
1111
* @description 카카오 로그인을 위한 useMutation 커스텀 훅
1212
*/
1313
const usePostKakaoAuth = () => {
14-
const { setAccessToken } = useAuthStore();
15-
const router = useRouter();
16-
const searchParams = useSearchParams();
14+
const { setAccessToken } = useAuthStore();
15+
const router = useRouter();
16+
const searchParams = useSearchParams();
1717

18-
return useMutation<KakaoAuthResponse, AxiosError, KakaoAuthRequest>({
19-
mutationFn: (data) => authApi.postKakaoAuth(data),
20-
onSuccess: (data) => {
21-
if (data.isRegistered) {
22-
// 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장
23-
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
24-
setAccessToken(data.accessToken);
18+
return useMutation<KakaoAuthResponse, AxiosError, KakaoAuthRequest>({
19+
mutationFn: (data) => authApi.postKakaoAuth(data),
20+
onSuccess: (data) => {
21+
if (data.isRegistered) {
22+
// 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장
23+
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
24+
setAccessToken(data.accessToken);
2525

26-
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
27-
const redirectParam = searchParams.get("redirect");
28-
const safeRedirect = validateSafeRedirect(redirectParam);
26+
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
27+
const redirectParam = searchParams.get("redirect");
28+
const safeRedirect = validateSafeRedirect(redirectParam);
2929

30-
toast.success("로그인에 성공했습니다.");
30+
toast.success("로그인에 성공했습니다.");
3131

32-
setTimeout(() => {
33-
router.push(safeRedirect);
34-
}, 100);
35-
} else {
36-
// 새로운 회원일 시 - 회원가입 페이지로 이동
37-
router.push(`/sign-up?token=${data.signUpToken}`);
38-
}
39-
},
40-
onError: () => {
41-
toast.error("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
42-
router.push("/login");
43-
},
44-
});
32+
setTimeout(() => {
33+
router.push(safeRedirect);
34+
}, 100);
35+
} else {
36+
// 새로운 회원일 시 - 회원가입 페이지로 이동
37+
router.push(`/sign-up?token=${data.signUpToken}`);
38+
}
39+
},
40+
onError: () => {
41+
toast.error("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
42+
router.push("/login");
43+
},
44+
});
4545
};
4646

4747
export default usePostKakaoAuth;

apps/web/src/apis/universities/server/getRecommendedUniversity.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import serverFetch from "@/utils/serverFetchUtil";
44
type GetRecommendedUniversityResponse = { recommendedUniversities: ListUniversity[] };
55

66
const getRecommendedUniversity = async () => {
7-
const endpoint = "/univ-apply-infos/recommend";
7+
const endpoint = "/univ-apply-infos/recommend";
88

9-
const res = await serverFetch<GetRecommendedUniversityResponse>(endpoint);
9+
const res = await serverFetch<GetRecommendedUniversityResponse>(endpoint);
1010

11-
if (!res.ok) {
12-
console.error(`Failed to fetch recommended universities:`, res.error);
13-
}
11+
if (!res.ok) {
12+
console.error(`Failed to fetch recommended universities:`, res.error);
13+
}
1414

15-
return res;
15+
return res;
1616
};
1717

1818
export default getRecommendedUniversity;

apps/web/src/apis/universities/server/getSearchUniversitiesByFilter.ts

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,58 @@ import type { CountryCode, LanguageTestType, ListUniversity } from "@/types/univ
33
import serverFetch from "@/utils/serverFetchUtil";
44

55
interface UniversitySearchResponse {
6-
univApplyInfoPreviews: ListUniversity[];
6+
univApplyInfoPreviews: ListUniversity[];
77
}
88

99
/**
1010
* 필터 검색에 사용될 파라미터 타입
1111
*/
1212
export interface UniversitySearchFilterParams {
13-
languageTestType?: LanguageTestType;
14-
testScore?: number;
15-
countryCode?: CountryCode[];
13+
languageTestType?: LanguageTestType;
14+
testScore?: number;
15+
countryCode?: CountryCode[];
1616
}
1717

1818
export const getSearchUniversitiesByFilter = async (
19-
filters: UniversitySearchFilterParams,
19+
filters: UniversitySearchFilterParams,
2020
): Promise<ListUniversity[]> => {
21-
const params = new URLSearchParams();
22-
23-
if (filters.languageTestType) {
24-
params.append("languageTestType", filters.languageTestType);
25-
}
26-
if (filters.testScore !== undefined) {
27-
params.append("testScore", String(filters.testScore));
28-
}
29-
// countryCode는 여러 개일 수 있으므로 각각 append 해줍니다.
30-
if (filters.countryCode) {
31-
filters.countryCode.forEach((code) => params.append("countryCode", code));
32-
}
33-
34-
// 필터 값이 하나도 없으면 빈 배열을 반환합니다.
35-
if (params.size === 0) {
36-
return [];
37-
}
38-
39-
const endpoint = `/univ-apply-infos/search/filter?${params.toString()}`;
40-
const response = await serverFetch<UniversitySearchResponse>(endpoint);
41-
42-
if (!response.ok) {
43-
console.error(`Failed to search universities by filter:`, response.error);
44-
return [];
45-
}
46-
47-
return response.data.univApplyInfoPreviews;
21+
const params = new URLSearchParams();
22+
23+
if (filters.languageTestType) {
24+
params.append("languageTestType", filters.languageTestType);
25+
}
26+
if (filters.testScore !== undefined) {
27+
params.append("testScore", String(filters.testScore));
28+
}
29+
// countryCode는 여러 개일 수 있으므로 각각 append 해줍니다.
30+
if (filters.countryCode) {
31+
filters.countryCode.forEach((code) => params.append("countryCode", code));
32+
}
33+
34+
// 필터 값이 하나도 없으면 빈 배열을 반환합니다.
35+
if (params.size === 0) {
36+
return [];
37+
}
38+
39+
const endpoint = `/univ-apply-infos/search/filter?${params.toString()}`;
40+
const response = await serverFetch<UniversitySearchResponse>(endpoint);
41+
42+
if (!response.ok) {
43+
console.error(`Failed to search universities by filter:`, response.error);
44+
return [];
45+
}
46+
47+
return response.data.univApplyInfoPreviews;
4848
};
4949

50-
export const getSearchUniversitiesAllRegions = async (): Promise<
51-
ListUniversity[]
52-
> => {
53-
const endpoint = `/univ-apply-infos/search/filter`;
54-
const response = await serverFetch<UniversitySearchResponse>(endpoint);
50+
export const getSearchUniversitiesAllRegions = async (): Promise<ListUniversity[]> => {
51+
const endpoint = `/univ-apply-infos/search/filter`;
52+
const response = await serverFetch<UniversitySearchResponse>(endpoint);
5553

56-
if (!response.ok) {
57-
console.error(`Failed to fetch all regions universities:`, response.error);
58-
return [];
59-
}
54+
if (!response.ok) {
55+
console.error(`Failed to fetch all regions universities:`, response.error);
56+
return [];
57+
}
6058

61-
return response.data.univApplyInfoPreviews;
59+
return response.data.univApplyInfoPreviews;
6260
};

apps/web/src/apis/universities/server/getSearchUniversitiesByText.ts

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,48 @@ import serverFetch from "@/utils/serverFetchUtil";
33

44
// --- 타입 정의 ---
55
interface UniversitySearchResponse {
6-
univApplyInfoPreviews: ListUniversity[];
6+
univApplyInfoPreviews: ListUniversity[];
77
}
88

9-
export const getUniversitiesByText = async (
10-
value: string,
11-
): Promise<ListUniversity[]> => {
12-
if (value === null || value === undefined) {
13-
return [];
14-
}
15-
const endpoint = `/univ-apply-infos/search/text?value=${encodeURIComponent(value)}`;
16-
const response = await serverFetch<UniversitySearchResponse>(endpoint);
17-
18-
if (!response.ok) {
19-
console.error(
20-
`Failed to search universities by text (value: "${value}"):`,
21-
response.error,
22-
);
23-
return [];
24-
}
25-
26-
return response.data.univApplyInfoPreviews;
9+
export const getUniversitiesByText = async (value: string): Promise<ListUniversity[]> => {
10+
if (value === null || value === undefined) {
11+
return [];
12+
}
13+
const endpoint = `/univ-apply-infos/search/text?value=${encodeURIComponent(value)}`;
14+
const response = await serverFetch<UniversitySearchResponse>(endpoint);
15+
16+
if (!response.ok) {
17+
console.error(`Failed to search universities by text (value: "${value}"):`, response.error);
18+
return [];
19+
}
20+
21+
return response.data.univApplyInfoPreviews;
2722
};
2823

2924
export const getAllUniversities = async (): Promise<ListUniversity[]> => {
30-
return getUniversitiesByText("");
25+
return getUniversitiesByText("");
3126
};
3227

33-
export const getCategorizedUniversities =
34-
async (): Promise<AllRegionsUniversityList> => {
35-
// 1. 단 한 번의 API 호출로 모든 대학 데이터를 가져옵니다.
36-
const allUniversities = await getAllUniversities();
37-
38-
const categorizedList: AllRegionsUniversityList = {
39-
[RegionEnumExtend.ALL]: allUniversities,
40-
[RegionEnumExtend.AMERICAS]: [],
41-
[RegionEnumExtend.EUROPE]: [],
42-
[RegionEnumExtend.ASIA]: [],
43-
[RegionEnumExtend.CHINA]: [],
44-
};
45-
if (!allUniversities) return categorizedList;
46-
47-
for (const university of allUniversities) {
48-
const region = university.region as RegionEnumExtend; // API 응답의 region 타입을 enum으로 간주
49-
50-
if (region && Object.hasOwn(categorizedList, region)) {
51-
categorizedList[region].push(university);
52-
}
53-
}
54-
55-
return categorizedList;
56-
};
28+
export const getCategorizedUniversities = async (): Promise<AllRegionsUniversityList> => {
29+
// 1. 단 한 번의 API 호출로 모든 대학 데이터를 가져옵니다.
30+
const allUniversities = await getAllUniversities();
31+
32+
const categorizedList: AllRegionsUniversityList = {
33+
[RegionEnumExtend.ALL]: allUniversities,
34+
[RegionEnumExtend.AMERICAS]: [],
35+
[RegionEnumExtend.EUROPE]: [],
36+
[RegionEnumExtend.ASIA]: [],
37+
[RegionEnumExtend.CHINA]: [],
38+
};
39+
if (!allUniversities) return categorizedList;
40+
41+
for (const university of allUniversities) {
42+
const region = university.region as RegionEnumExtend; // API 응답의 region 타입을 enum으로 간주
43+
44+
if (region && Object.hasOwn(categorizedList, region)) {
45+
categorizedList[region].push(university);
46+
}
47+
}
48+
49+
return categorizedList;
50+
};

apps/web/src/app/(home)/_ui/NewsSection/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

3-
import Image from "next/image";
43
import Link from "next/link";
4+
import Image from "@/components/ui/FallbackImage";
55
import { IconLoveLetter } from "@/public/svgs/home";
66
import type { News } from "@/types/news";
77
import useSectionHandler from "./_hooks/useSectionHadnler";

apps/web/src/app/(home)/_ui/PopularUniversitySection/_ui/PopularUniversityCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import Image from "next/image";
21
import Link from "next/link";
2+
import Image from "@/components/ui/FallbackImage";
33
import type { ListUniversity } from "@/types/university";
44
import { convertImageUrl } from "@/utils/fileUtils";
55

apps/web/src/app/community/[boardCode]/PostCards.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"use client";
22

33
import { useVirtualizer } from "@tanstack/react-virtual";
4-
import Image from "next/image";
54
import Link from "next/link";
65
import { useRef } from "react";
6+
import Image from "@/components/ui/FallbackImage";
77
import { IconPostLikeOutline } from "@/public/svgs";
88
import { IconCommunication } from "@/public/svgs/community";
99
import { IconSolidConnentionLogo } from "@/public/svgs/mentor";
@@ -102,6 +102,7 @@ export const PostCard = ({ post }: { post: ListPost }) => (
102102
height={82}
103103
width={82}
104104
alt="게시글 사진"
105+
fallbackSrc="/images/article-thumb.png"
105106
/>
106107
) : (
107108
<div className="bg-gray-c-50 flex h-20 w-20 items-center justify-center rounded border border-k-100">

apps/web/src/app/community/[boardCode]/[postId]/CommentSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use client";
22

33
import clsx from "clsx";
4-
import Image from "next/image";
54
import { useState } from "react";
65
import { useDeleteComment } from "@/apis/community";
76
import Dropdown from "@/components/ui/Dropdown";
7+
import Image from "@/components/ui/FallbackImage";
88
import { IconMoreVertFilled, IconSubComment } from "@/public/svgs";
99
import type { Comment as CommentType, CommunityUser } from "@/types/community";
1010
import { convertISODateToDateTime } from "@/utils/datetimeUtils";

0 commit comments

Comments
 (0)