Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"@next/bundle-analyzer": "^16.1.6",
"@svgr/webpack": "^8.1.0",
"@types/node": "^20.11.19",
"@types/react": "18.3.27",
"@types/react-dom": "18.3.7",
"@types/react": "19.2.10",
"@types/react-dom": "19.2.3",
"autoprefixer": "^10.4.20",
"critters": "^0.0.23",
"postcss": "^8.4.45",
Expand Down
52 changes: 26 additions & 26 deletions apps/web/src/apis/Auth/postAppleAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,36 @@ import { type AppleAuthRequest, type AppleAuthResponse, authApi } from "./api";
* @description ์• ํ”Œ ๋กœ๊ทธ์ธ์„ ์œ„ํ•œ useMutation ์ปค์Šคํ…€ ํ›…
*/
const usePostAppleAuth = () => {
const router = useRouter();
const searchParams = useSearchParams();
const router = useRouter();
const searchParams = useSearchParams();

return useMutation<AppleAuthResponse, AxiosError, AppleAuthRequest>({
mutationFn: (data) => authApi.postAppleAuth(data),
onSuccess: (data) => {
if (data.isRegistered) {
// ๊ธฐ์กด ํšŒ์›์ผ ์‹œ - Zustand persist๊ฐ€ ์ž๋™์œผ๋กœ localStorage์— ์ €์žฅ
// refreshToken์€ ์„œ๋ฒ„์—์„œ HTTP-only ์ฟ ํ‚ค๋กœ ์ž๋™ ์„ค์ •๋จ
useAuthStore.getState().setAccessToken(data.accessToken);
return useMutation<AppleAuthResponse, AxiosError, AppleAuthRequest>({
mutationFn: (data) => authApi.postAppleAuth(data),
onSuccess: (data) => {
if (data.isRegistered) {
// ๊ธฐ์กด ํšŒ์›์ผ ์‹œ - Zustand persist๊ฐ€ ์ž๋™์œผ๋กœ localStorage์— ์ €์žฅ
// refreshToken์€ ์„œ๋ฒ„์—์„œ HTTP-only ์ฟ ํ‚ค๋กœ ์ž๋™ ์„ค์ •๋จ
useAuthStore.getState().setAccessToken(data.accessToken);

// ์•ˆ์ „ํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ - ์˜คํ”ˆ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฐฉ์ง€
const redirectParam = searchParams.get("redirect");
const safeRedirect = validateSafeRedirect(redirectParam);
// ์•ˆ์ „ํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ - ์˜คํ”ˆ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฐฉ์ง€
const redirectParam = searchParams.get("redirect");
const safeRedirect = validateSafeRedirect(redirectParam);

toast.success("๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.");
toast.success("๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.");

setTimeout(() => {
router.push(safeRedirect);
}, 100);
} else {
// ์ƒˆ๋กœ์šด ํšŒ์›์ผ ์‹œ - ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๋กœ ์ด๋™
router.push(`/sign-up?token=${data.signUpToken}`);
}
},
onError: () => {
toast.error("์• ํ”Œ ๋กœ๊ทธ์ธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.");
router.push("/login");
},
});
setTimeout(() => {
router.push(safeRedirect);
}, 100);
} else {
// ์ƒˆ๋กœ์šด ํšŒ์›์ผ ์‹œ - ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๋กœ ์ด๋™
router.push(`/sign-up?token=${data.signUpToken}`);
}
},
onError: () => {
toast.error("์• ํ”Œ ๋กœ๊ทธ์ธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.");
router.push("/login");
},
});
};

export default usePostAppleAuth;
54 changes: 27 additions & 27 deletions apps/web/src/apis/Auth/postKakaoAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,37 @@ import { authApi, type KakaoAuthRequest, type KakaoAuthResponse } from "./api";
* @description ์นด์นด์˜ค ๋กœ๊ทธ์ธ์„ ์œ„ํ•œ useMutation ์ปค์Šคํ…€ ํ›…
*/
const usePostKakaoAuth = () => {
const { setAccessToken } = useAuthStore();
const router = useRouter();
const searchParams = useSearchParams();
const { setAccessToken } = useAuthStore();
const router = useRouter();
const searchParams = useSearchParams();

return useMutation<KakaoAuthResponse, AxiosError, KakaoAuthRequest>({
mutationFn: (data) => authApi.postKakaoAuth(data),
onSuccess: (data) => {
if (data.isRegistered) {
// ๊ธฐ์กด ํšŒ์›์ผ ์‹œ - Zustand persist๊ฐ€ ์ž๋™์œผ๋กœ localStorage์— ์ €์žฅ
// refreshToken์€ ์„œ๋ฒ„์—์„œ HTTP-only ์ฟ ํ‚ค๋กœ ์ž๋™ ์„ค์ •๋จ
setAccessToken(data.accessToken);
return useMutation<KakaoAuthResponse, AxiosError, KakaoAuthRequest>({
mutationFn: (data) => authApi.postKakaoAuth(data),
onSuccess: (data) => {
if (data.isRegistered) {
// ๊ธฐ์กด ํšŒ์›์ผ ์‹œ - Zustand persist๊ฐ€ ์ž๋™์œผ๋กœ localStorage์— ์ €์žฅ
// refreshToken์€ ์„œ๋ฒ„์—์„œ HTTP-only ์ฟ ํ‚ค๋กœ ์ž๋™ ์„ค์ •๋จ
setAccessToken(data.accessToken);

// ์•ˆ์ „ํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ - ์˜คํ”ˆ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฐฉ์ง€
const redirectParam = searchParams.get("redirect");
const safeRedirect = validateSafeRedirect(redirectParam);
// ์•ˆ์ „ํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ - ์˜คํ”ˆ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฐฉ์ง€
const redirectParam = searchParams.get("redirect");
const safeRedirect = validateSafeRedirect(redirectParam);

toast.success("๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.");
toast.success("๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.");

setTimeout(() => {
router.push(safeRedirect);
}, 100);
} else {
// ์ƒˆ๋กœ์šด ํšŒ์›์ผ ์‹œ - ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๋กœ ์ด๋™
router.push(`/sign-up?token=${data.signUpToken}`);
}
},
onError: () => {
toast.error("์นด์นด์˜ค ๋กœ๊ทธ์ธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.");
router.push("/login");
},
});
setTimeout(() => {
router.push(safeRedirect);
}, 100);
} else {
// ์ƒˆ๋กœ์šด ํšŒ์›์ผ ์‹œ - ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๋กœ ์ด๋™
router.push(`/sign-up?token=${data.signUpToken}`);
}
},
onError: () => {
toast.error("์นด์นด์˜ค ๋กœ๊ทธ์ธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.");
router.push("/login");
},
});
};

export default usePostKakaoAuth;
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import serverFetch from "@/utils/serverFetchUtil";
type GetRecommendedUniversityResponse = { recommendedUniversities: ListUniversity[] };

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

const res = await serverFetch<GetRecommendedUniversityResponse>(endpoint);
const res = await serverFetch<GetRecommendedUniversityResponse>(endpoint);

if (!res.ok) {
console.error(`Failed to fetch recommended universities:`, res.error);
}
if (!res.ok) {
console.error(`Failed to fetch recommended universities:`, res.error);
}

return res;
return res;
};

export default getRecommendedUniversity;
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,58 @@ import type { CountryCode, LanguageTestType, ListUniversity } from "@/types/univ
import serverFetch from "@/utils/serverFetchUtil";

interface UniversitySearchResponse {
univApplyInfoPreviews: ListUniversity[];
univApplyInfoPreviews: ListUniversity[];
}

/**
* ํ•„ํ„ฐ ๊ฒ€์ƒ‰์— ์‚ฌ์šฉ๋  ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…
*/
export interface UniversitySearchFilterParams {
languageTestType?: LanguageTestType;
testScore?: number;
countryCode?: CountryCode[];
languageTestType?: LanguageTestType;
testScore?: number;
countryCode?: CountryCode[];
}

export const getSearchUniversitiesByFilter = async (
filters: UniversitySearchFilterParams,
filters: UniversitySearchFilterParams,
): Promise<ListUniversity[]> => {
const params = new URLSearchParams();

if (filters.languageTestType) {
params.append("languageTestType", filters.languageTestType);
}
if (filters.testScore !== undefined) {
params.append("testScore", String(filters.testScore));
}
// countryCode๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ฐ๊ฐ append ํ•ด์ค๋‹ˆ๋‹ค.
if (filters.countryCode) {
filters.countryCode.forEach((code) => params.append("countryCode", code));
}

// ํ•„ํ„ฐ ๊ฐ’์ด ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
if (params.size === 0) {
return [];
}

const endpoint = `/univ-apply-infos/search/filter?${params.toString()}`;
const response = await serverFetch<UniversitySearchResponse>(endpoint);

if (!response.ok) {
console.error(`Failed to search universities by filter:`, response.error);
return [];
}

return response.data.univApplyInfoPreviews;
const params = new URLSearchParams();

if (filters.languageTestType) {
params.append("languageTestType", filters.languageTestType);
}
if (filters.testScore !== undefined) {
params.append("testScore", String(filters.testScore));
}
// countryCode๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ฐ๊ฐ append ํ•ด์ค๋‹ˆ๋‹ค.
if (filters.countryCode) {
filters.countryCode.forEach((code) => params.append("countryCode", code));
}

// ํ•„ํ„ฐ ๊ฐ’์ด ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
if (params.size === 0) {
return [];
}

const endpoint = `/univ-apply-infos/search/filter?${params.toString()}`;
const response = await serverFetch<UniversitySearchResponse>(endpoint);

if (!response.ok) {
console.error(`Failed to search universities by filter:`, response.error);
return [];
}

return response.data.univApplyInfoPreviews;
};

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

if (!response.ok) {
console.error(`Failed to fetch all regions universities:`, response.error);
return [];
}
if (!response.ok) {
console.error(`Failed to fetch all regions universities:`, response.error);
return [];
}

return response.data.univApplyInfoPreviews;
return response.data.univApplyInfoPreviews;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,48 @@ import serverFetch from "@/utils/serverFetchUtil";

// --- ํƒ€์ž… ์ •์˜ ---
interface UniversitySearchResponse {
univApplyInfoPreviews: ListUniversity[];
univApplyInfoPreviews: ListUniversity[];
}

export const getUniversitiesByText = async (
value: string,
): Promise<ListUniversity[]> => {
if (value === null || value === undefined) {
return [];
}
const endpoint = `/univ-apply-infos/search/text?value=${encodeURIComponent(value)}`;
const response = await serverFetch<UniversitySearchResponse>(endpoint);

if (!response.ok) {
console.error(
`Failed to search universities by text (value: "${value}"):`,
response.error,
);
return [];
}

return response.data.univApplyInfoPreviews;
export const getUniversitiesByText = async (value: string): Promise<ListUniversity[]> => {
if (value === null || value === undefined) {
return [];
}
const endpoint = `/univ-apply-infos/search/text?value=${encodeURIComponent(value)}`;
const response = await serverFetch<UniversitySearchResponse>(endpoint);

if (!response.ok) {
console.error(`Failed to search universities by text (value: "${value}"):`, response.error);
return [];
}

return response.data.univApplyInfoPreviews;
};

export const getAllUniversities = async (): Promise<ListUniversity[]> => {
return getUniversitiesByText("");
return getUniversitiesByText("");
};

export const getCategorizedUniversities =
async (): Promise<AllRegionsUniversityList> => {
// 1. ๋‹จ ํ•œ ๋ฒˆ์˜ API ํ˜ธ์ถœ๋กœ ๋ชจ๋“  ๋Œ€ํ•™ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
const allUniversities = await getAllUniversities();

const categorizedList: AllRegionsUniversityList = {
[RegionEnumExtend.ALL]: allUniversities,
[RegionEnumExtend.AMERICAS]: [],
[RegionEnumExtend.EUROPE]: [],
[RegionEnumExtend.ASIA]: [],
[RegionEnumExtend.CHINA]: [],
};
if (!allUniversities) return categorizedList;

for (const university of allUniversities) {
const region = university.region as RegionEnumExtend; // API ์‘๋‹ต์˜ region ํƒ€์ž…์„ enum์œผ๋กœ ๊ฐ„์ฃผ

if (region && Object.hasOwn(categorizedList, region)) {
categorizedList[region].push(university);
}
}

return categorizedList;
};
export const getCategorizedUniversities = async (): Promise<AllRegionsUniversityList> => {
// 1. ๋‹จ ํ•œ ๋ฒˆ์˜ API ํ˜ธ์ถœ๋กœ ๋ชจ๋“  ๋Œ€ํ•™ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
const allUniversities = await getAllUniversities();

const categorizedList: AllRegionsUniversityList = {
[RegionEnumExtend.ALL]: allUniversities,
[RegionEnumExtend.AMERICAS]: [],
[RegionEnumExtend.EUROPE]: [],
[RegionEnumExtend.ASIA]: [],
[RegionEnumExtend.CHINA]: [],
};
if (!allUniversities) return categorizedList;

for (const university of allUniversities) {
const region = university.region as RegionEnumExtend; // API ์‘๋‹ต์˜ region ํƒ€์ž…์„ enum์œผ๋กœ ๊ฐ„์ฃผ

if (region && Object.hasOwn(categorizedList, region)) {
categorizedList[region].push(university);
}
}

return categorizedList;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type RefObject, useEffect, useRef, useState } from "react";

interface UseSectionHandlerReturn {
sectionRef: RefObject<HTMLDivElement>;
sectionRef: RefObject<HTMLDivElement | null>;
visible: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type RefObject, useRef, useState } from "react";
import { FilterTab } from "@/types/mentor";

interface UseSelectedTabReturn {
listRef: RefObject<HTMLDivElement>;
listRef: RefObject<HTMLDivElement | null>;
selectedTab: FilterTab;
handleSelectTab: (tab: FilterTab) => void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ interface ChatNavBarProps {
const ChatNavBar = ({ chatId }: ChatNavBarProps) => {
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const [isAnimating, setIsAnimating] = useState<boolean>(false);
const result = tokenParse(useAuthStore.getState().accessToken);
const accessToken = useAuthStore((state) => state.accessToken);
const result = tokenParse(accessToken);
const isMentor = result?.role === UserRole.MENTOR || result?.role === UserRole.ADMIN;

// ํŒŒํŠธ๋„ˆ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { convertUploadedImageUrl } from "@/utils/fileUtils";
interface ImageInputHandlerReturn {
selectedImage: File | undefined;
imagePreviewUrl: string | null;
fileInputRef: RefObject<HTMLInputElement>;
fileInputRef: RefObject<HTMLInputElement | null>;
handleImageSelect: () => void;
handleFileChange: (event: ChangeEvent<HTMLInputElement>) => void;
}
Expand Down
Loading