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
7 changes: 7 additions & 0 deletions apps/web/public/svgs/placeholders/image-placeholder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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;
};
2 changes: 1 addition & 1 deletion apps/web/src/app/(home)/_ui/NewsSection/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import Image from "next/image";
import Link from "next/link";
import Image from "@/components/ui/FallbackImage";
import { IconLoveLetter } from "@/public/svgs/home";
import type { News } from "@/types/news";
import useSectionHandler from "./_hooks/useSectionHadnler";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Image from "next/image";
import Link from "next/link";
import Image from "@/components/ui/FallbackImage";
import type { ListUniversity } from "@/types/university";
import { convertImageUrl } from "@/utils/fileUtils";

Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/community/[boardCode]/PostCards.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";

import { useVirtualizer } from "@tanstack/react-virtual";
import Image from "next/image";
import Link from "next/link";
import { useRef } from "react";
import Image from "@/components/ui/FallbackImage";
import { IconPostLikeOutline } from "@/public/svgs";
import { IconCommunication } from "@/public/svgs/community";
import { IconSolidConnentionLogo } from "@/public/svgs/mentor";
Expand Down Expand Up @@ -102,6 +102,7 @@ export const PostCard = ({ post }: { post: ListPost }) => (
height={82}
width={82}
alt="게시글 사진"
fallbackSrc="/images/article-thumb.png"
/>
) : (
<div className="bg-gray-c-50 flex h-20 w-20 items-center justify-center rounded border border-k-100">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import clsx from "clsx";
import Image from "next/image";
import { useState } from "react";
import { useDeleteComment } from "@/apis/community";
import Dropdown from "@/components/ui/Dropdown";
import Image from "@/components/ui/FallbackImage";
import { IconMoreVertFilled, IconSubComment } from "@/public/svgs";
import type { Comment as CommentType, CommunityUser } from "@/types/community";
import { convertISODateToDateTime } from "@/utils/datetimeUtils";
Expand Down
Loading
Loading