diff --git a/apps/web/src/app/(home)/_ui/HomeActionLinks/index.tsx b/apps/web/src/app/(home)/_ui/HomeActionLinks/index.tsx new file mode 100644 index 00000000..38b65bf6 --- /dev/null +++ b/apps/web/src/app/(home)/_ui/HomeActionLinks/index.tsx @@ -0,0 +1,58 @@ +import Link from "next/link"; + +import { IconIdCard, IconMagnifyingGlass, IconMuseum, IconPaper } from "@/public/svgs/home"; + +const HomeActionLinks = () => { + return ( +
+
+ +
+ 학교 검색하기 + 모든 학교 목록을 확인해보세요 +
+
+ +
+ + +
+ 성적 입력하기 + 성적을 입력해보세요 +
+
+ +
+ +
+
+ +
+ 학교 지원하기 + 학교를 지원해주세요 +
+
+ +
+ + +
+ 지원자 현황 확인 + 경쟁률을 바로 분석해드려요 +
+
+ +
+ +
+
+ ); +}; + +export default HomeActionLinks; diff --git a/apps/web/src/app/(home)/_ui/HomeEntrySection/index.tsx b/apps/web/src/app/(home)/_ui/HomeEntrySection/index.tsx new file mode 100644 index 00000000..b4fc2072 --- /dev/null +++ b/apps/web/src/app/(home)/_ui/HomeEntrySection/index.tsx @@ -0,0 +1,18 @@ +"use client"; + +import useAuthStore from "@/lib/zustand/useAuthStore"; +import HomeActionLinks from "../HomeActionLinks"; +import HomeUniversitySearchSection from "../HomeUniversitySearchSection"; +import HomeEntrySectionSkeleton from "./skeleton"; + +const HomeEntrySection = () => { + const { isAuthenticated, isInitialized } = useAuthStore(); + + if (!isInitialized) { + return ; + } + + return isAuthenticated ? : ; +}; + +export default HomeEntrySection; diff --git a/apps/web/src/app/(home)/_ui/HomeEntrySection/skeleton/index.tsx b/apps/web/src/app/(home)/_ui/HomeEntrySection/skeleton/index.tsx new file mode 100644 index 00000000..f5c16732 --- /dev/null +++ b/apps/web/src/app/(home)/_ui/HomeEntrySection/skeleton/index.tsx @@ -0,0 +1,20 @@ +const HomeEntrySectionSkeleton = () => ( +
+
+ +
+ {[...Array(3)].map((_, index) => ( +
+ ))} +
+ +
+
+
+
+ +
+
+); + +export default HomeEntrySectionSkeleton; diff --git a/apps/web/src/app/(home)/_ui/HomeUniversitySearchSection/_hooks/useHomeUniversitySearch.ts b/apps/web/src/app/(home)/_ui/HomeUniversitySearchSection/_hooks/useHomeUniversitySearch.ts new file mode 100644 index 00000000..67c6bf56 --- /dev/null +++ b/apps/web/src/app/(home)/_ui/HomeUniversitySearchSection/_hooks/useHomeUniversitySearch.ts @@ -0,0 +1,69 @@ +import { useRouter } from "next/navigation"; +import { useMemo, useState } from "react"; + +import { HOME_UNIVERSITY_LIST } from "@/constants/university"; +import type { HomeUniversitySlug } from "@/types/university"; +import { + buildUniversitySearchQuery, + getCountryOptionsByIndex, + getLanguageTestOptions, + getVisibleCountrySelectIndexes, +} from "@/utils/universitySearchQuery"; + +const MAX_COUNTRY_SELECT_COUNT = 3; + +const createEmptyCountries = () => Array(MAX_COUNTRY_SELECT_COUNT).fill(""); + +const useHomeUniversitySearch = () => { + const router = useRouter(); + const [selectedHomeUniversitySlug, setSelectedHomeUniversitySlug] = useState( + HOME_UNIVERSITY_LIST[0].slug, + ); + const [languageTestType, setLanguageTestType] = useState(""); + const [countries, setCountries] = useState(createEmptyCountries); + + const languageOptions = useMemo(() => getLanguageTestOptions(), []); + + const visibleCountryCount = useMemo(() => { + return getVisibleCountrySelectIndexes(countries, MAX_COUNTRY_SELECT_COUNT).length; + }, [countries]); + + const countryOptionsByIndex = useMemo( + () => getCountryOptionsByIndex({ countries, visibleCount: visibleCountryCount }), + [countries, visibleCountryCount], + ); + + const handleCountryChange = (index: number, value: string) => { + setCountries((prevCountries) => + prevCountries.map((country, countryIndex) => { + if (countryIndex < index) return country; + if (countryIndex === index) return value; + return value ? country : ""; + }), + ); + }; + + const submitSearch = () => { + const queryString = buildUniversitySearchQuery({ + languageTestType, + countryCodes: countries, + }).toString(); + + router.push(`/university/${selectedHomeUniversitySlug}${queryString ? `?${queryString}` : ""}`); + }; + + return { + homeUniversities: HOME_UNIVERSITY_LIST, + selectedHomeUniversitySlug, + setSelectedHomeUniversitySlug, + languageTestType, + setLanguageTestType, + countries, + languageOptions, + countryOptionsByIndex, + handleCountryChange, + submitSearch, + }; +}; + +export default useHomeUniversitySearch; diff --git a/apps/web/src/app/(home)/_ui/HomeUniversitySearchSection/index.tsx b/apps/web/src/app/(home)/_ui/HomeUniversitySearchSection/index.tsx new file mode 100644 index 00000000..b84ae01c --- /dev/null +++ b/apps/web/src/app/(home)/_ui/HomeUniversitySearchSection/index.tsx @@ -0,0 +1,88 @@ +"use client"; + +import clsx from "clsx"; + +import CustomDropdown from "@/components/search/CustomDropdown"; +import { IconHatColor, IconHatGray, IconLocationColor, IconLocationGray } from "@/public/svgs/search"; +import useHomeUniversitySearch from "./_hooks/useHomeUniversitySearch"; + +const HomeUniversitySearchSection = () => { + const { + homeUniversities, + selectedHomeUniversitySlug, + setSelectedHomeUniversitySlug, + languageTestType, + setLanguageTestType, + countries, + languageOptions, + countryOptionsByIndex, + handleCountryChange, + submitSearch, + } = useHomeUniversitySearch(); + + return ( +
+

파견 학교 찾기

+ +
+ {homeUniversities.map((university) => { + const isSelected = university.slug === selectedHomeUniversitySlug; + + return ( + + ); + })} +
+ +
+ } + icon={} + options={languageOptions} + /> + + {countryOptionsByIndex.map((countryOptions, index) => { + return ( + handleCountryChange(index, value)} + placeholder="관심있는 나라" + placeholderSelect="나라" + placeholderIcon={} + icon={} + options={countryOptions} + /> + ); + })} +
+ + +
+ ); +}; + +export default HomeUniversitySearchSection; diff --git a/apps/web/src/app/(home)/_ui/UniversityList/_hooks/useHomeUniversityList.ts b/apps/web/src/app/(home)/_ui/UniversityList/_hooks/useHomeUniversityList.ts new file mode 100644 index 00000000..d28f2385 --- /dev/null +++ b/apps/web/src/app/(home)/_ui/UniversityList/_hooks/useHomeUniversityList.ts @@ -0,0 +1,50 @@ +import { type Dispatch, type SetStateAction, useMemo, useState } from "react"; + +import { HOME_UNIVERSITY_LIST, isMatchedHomeUniversityName } from "@/constants/university"; +import { type AllRegionsUniversityList, type ListUniversity, RegionEnumExtend } from "@/types/university"; + +const ALL_HOME_UNIVERSITY_CHOICE = "전체"; +const PREVIEW_UNIVERSITY_COUNT = 3; + +const useHomeUniversityList = (allRegionsUniversityList: AllRegionsUniversityList) => { + const [selectedHomeUniversity, setSelectedHomeUniversity] = useState(ALL_HOME_UNIVERSITY_CHOICE); + const handleHomeUniversityChange: Dispatch> = (nextHomeUniversity) => { + setSelectedHomeUniversity((prevHomeUniversity) => { + const resolvedHomeUniversity = + typeof nextHomeUniversity === "function" ? nextHomeUniversity(prevHomeUniversity) : nextHomeUniversity; + + return resolvedHomeUniversity ?? ALL_HOME_UNIVERSITY_CHOICE; + }); + }; + const homeUniversityChoices = useMemo( + () => [ALL_HOME_UNIVERSITY_CHOICE, ...HOME_UNIVERSITY_LIST.map((university) => university.shortName)], + [], + ); + + const allUniversities = allRegionsUniversityList[RegionEnumExtend.ALL] ?? []; + const selectedUniversityInfo = useMemo( + () => HOME_UNIVERSITY_LIST.find((university) => university.shortName === selectedHomeUniversity), + [selectedHomeUniversity], + ); + + const universities: ListUniversity[] = useMemo(() => { + if (!selectedUniversityInfo) return allUniversities; + + return allUniversities.filter((university) => + isMatchedHomeUniversityName(university.homeUniversityName, selectedUniversityInfo.name), + ); + }, [allUniversities, selectedUniversityInfo]); + + const previewUniversities = useMemo(() => universities.slice(0, PREVIEW_UNIVERSITY_COUNT), [universities]); + const moreHref = selectedUniversityInfo ? `/university/${selectedUniversityInfo.slug}` : "/university"; + + return { + selectedHomeUniversity, + setSelectedHomeUniversity: handleHomeUniversityChange, + homeUniversityChoices, + previewUniversities, + moreHref, + }; +}; + +export default useHomeUniversityList; diff --git a/apps/web/src/app/(home)/_ui/UniversityList/_hooks/useRegionHandler.ts b/apps/web/src/app/(home)/_ui/UniversityList/_hooks/useRegionHandler.ts deleted file mode 100644 index 12b467cf..00000000 --- a/apps/web/src/app/(home)/_ui/UniversityList/_hooks/useRegionHandler.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useEffect, useState } from "react"; - -import { RegionEnumExtend } from "@/types/university"; - -const useRegionHandler = () => { - const [region, setRegion] = useState(RegionEnumExtend.ALL); - - const handleRegionChange = (newRegion: RegionEnumExtend | null) => { - setRegion(newRegion); - }; - useEffect(() => { - if (region === null) { - setRegion(RegionEnumExtend.ALL); - } - }, [region]); - - return { - region, - handleRegionChange, - }; -}; - -export default useRegionHandler; diff --git a/apps/web/src/app/(home)/_ui/UniversityList/index.tsx b/apps/web/src/app/(home)/_ui/UniversityList/index.tsx index 5722b8ab..529e55b2 100644 --- a/apps/web/src/app/(home)/_ui/UniversityList/index.tsx +++ b/apps/web/src/app/(home)/_ui/UniversityList/index.tsx @@ -1,34 +1,27 @@ "use client"; import Link from "next/link"; -import { useMemo } from "react"; import ButtonTab from "@/components/ui/ButtonTab"; import UniversityCards from "@/components/university/UniversityCards"; import { IconDirectionRight } from "@/public/svgs/mentor"; -import { type AllRegionsUniversityList, type ListUniversity, RegionEnumExtend } from "@/types/university"; -import useRegionHandler from "./_hooks/useRegionHandler"; +import type { AllRegionsUniversityList } from "@/types/university"; +import useHomeUniversityList from "./_hooks/useHomeUniversityList"; interface UniversityListProps { allRegionsUniversityList: AllRegionsUniversityList; } const UniversityList = ({ allRegionsUniversityList }: UniversityListProps) => { - const { region, handleRegionChange } = useRegionHandler(); - const choices = Object.values(RegionEnumExtend); + const { selectedHomeUniversity, setSelectedHomeUniversity, homeUniversityChoices, previewUniversities, moreHref } = + useHomeUniversityList(allRegionsUniversityList); - const universities: ListUniversity[] = useMemo( - () => allRegionsUniversityList[region || RegionEnumExtend.ALL] ?? [], - [allRegionsUniversityList, region], - ); - // 홈 카드 영역에는 최대 3개만 노출 - const previewUniversities: ListUniversity[] = useMemo(() => universities.slice(0, 3), [universities]); return (
전체 학교 리스트 - + 더보기 @@ -36,15 +29,15 @@ const UniversityList = ({ allRegionsUniversityList }: UniversityListProps) => {
diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx index f1947f50..38f02a8c 100644 --- a/apps/web/src/app/(home)/page.tsx +++ b/apps/web/src/app/(home)/page.tsx @@ -1,16 +1,15 @@ import type { Metadata } from "next"; -import dynamic from "next/dynamic"; -import Link from "next/link"; +import nextDynamic from "next/dynamic"; import { getHomeNewsList } from "@/apis/news/server/getNewsList"; import { getCategorizedUniversities, getRecommendedUniversity } from "@/apis/universities/server"; -import { IconIdCard, IconMagnifyingGlass, IconMuseum, IconPaper } from "@/public/svgs/home"; -import { RegionEnumExtend } from "@/types/university"; +import { type ListUniversity, RegionEnumExtend } from "@/types/university"; import FindLastYearScoreBar from "./_ui/FindLastYearScoreBar"; +import HomeEntrySection from "./_ui/HomeEntrySection"; import NewsSectionSkeleton from "./_ui/NewsSection/skeleton"; import PopularUniversitySection from "./_ui/PopularUniversitySection"; import UniversityList from "./_ui/UniversityList"; -const NewsSectionDynamic = dynamic(() => import("./_ui/NewsSection"), { +const NewsSectionDynamic = nextDynamic(() => import("./_ui/NewsSection"), { ssr: false, loading: () => , }); @@ -65,6 +64,20 @@ const structuredData = { }, }; +const resolveRecommendedUniversitiesHomeUniversityName = ( + recommendedUniversities: ListUniversity[], + allUniversities: ListUniversity[], +) => { + const homeUniversityNameById = new Map( + allUniversities.map((university) => [university.id, university.homeUniversityName]), + ); + + return recommendedUniversities.map((university) => ({ + ...university, + homeUniversityName: university.homeUniversityName ?? homeUniversityNameById.get(university.id), + })); +}; + const HomePage = async () => { const newsList = await getHomeNewsList(); const { data } = await getRecommendedUniversity(); @@ -72,73 +85,17 @@ const HomePage = async () => { // 권역별 전체 대학 리스트를 미리 가져와 빌드합니다 const allRegionsUniversityList = await getCategorizedUniversities(); const allUniversities = allRegionsUniversityList[RegionEnumExtend.ALL] || []; - const homeUniversityNameById = new Map( - allUniversities.map((university) => [university.id, university.homeUniversityName]), + const resolvedRecommendedUniversities = resolveRecommendedUniversitiesHomeUniversityName( + recommendedUniversities, + allUniversities, ); - const resolvedRecommendedUniversities = recommendedUniversities.map((university) => ({ - ...university, - homeUniversityName: university.homeUniversityName ?? homeUniversityNameById.get(university.id), - })); return ( <>