From c7c96950d10a7cf9cf4c96be1cdce11e78266d2b Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 27 Dec 2025 18:48:08 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=B8=ED=92=8B=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - border 색상을 border-k-100으로 명시 - 고정 높이(h-[41px]) 제거하여 padding과 line-height로 자동 계산 - 피그마 디자인과 일치하도록 스타일 수정 --- src/app/login/LoginContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/login/LoginContent.tsx b/src/app/login/LoginContent.tsx index 729c2694..7cacede7 100644 --- a/src/app/login/LoginContent.tsx +++ b/src/app/login/LoginContent.tsx @@ -84,7 +84,7 @@ const LoginContent = () => { { From df043e629bddd027e95ab98eab5976c56097f0e8 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 27 Dec 2025 19:27:21 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20CI=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 빌드, 린트, 타입 체크를 포함한 기본 CI 워크플로우 추가 - main 및 develop 브랜치에 push/PR 시 자동 실행 --- .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e9ee7e78 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint + + - name: Type check + run: npx tsc --noEmit + + - name: Build + run: npm run build + env: + NODE_ENV: production + From 38d5d41f35e200a016da44f024e9c948d8781c51 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 27 Dec 2025 19:48:44 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=A6=B0=ED=8A=B8=20=EA=B2=BD?= =?UTF-8?q?=EA=B3=A0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - designUtils, scoreUtils, universityUtils를 default export로 변경 - jwtUtils의 any 타입을 JwtPayload로 명시 - useInfinityScroll의 사용하지 않는 변수 제거 - 관련 import 문 수정 --- .../score/submit/language-test/_lib/schema.ts | 2 +- src/components/button/BlockBtn.tsx | 2 +- src/components/button/RoundBtn.tsx | 2 +- src/components/ui/UniverSityCard/index.tsx | 2 +- src/utils/designUtils.ts | 4 +++- src/utils/jwtUtils.ts | 18 +++++++++--------- src/utils/scoreUtils.ts | 4 +++- src/utils/universityUtils.ts | 7 +++++-- src/utils/useInfinityScroll.ts | 3 ++- 9 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/app/university/score/submit/language-test/_lib/schema.ts b/src/app/university/score/submit/language-test/_lib/schema.ts index 2a9b21b5..1bb25974 100644 --- a/src/app/university/score/submit/language-test/_lib/schema.ts +++ b/src/app/university/score/submit/language-test/_lib/schema.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { validateLanguageScore } from "@/utils/scoreUtils"; +import validateLanguageScore from "@/utils/scoreUtils"; import { LanguageTestEnum } from "@/types/score"; diff --git a/src/components/button/BlockBtn.tsx b/src/components/button/BlockBtn.tsx index 13d9563d..5b6fbdcf 100644 --- a/src/components/button/BlockBtn.tsx +++ b/src/components/button/BlockBtn.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { type VariantProps, cva } from "class-variance-authority"; -import { cn } from "@/utils/designUtils"; +import cn from "@/utils/designUtils"; const blockBtnVariants = cva("h-13 w-full min-w-80 max-w-screen-sm rounded-lg flex items-center justify-center", { variants: { diff --git a/src/components/button/RoundBtn.tsx b/src/components/button/RoundBtn.tsx index 6885662b..a2f8027c 100644 --- a/src/components/button/RoundBtn.tsx +++ b/src/components/button/RoundBtn.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { type VariantProps, cva } from "class-variance-authority"; -import { cn } from "@/utils/designUtils"; +import cn from "@/utils/designUtils"; const roundBtnVariants = cva("h-[2.375rem] w-[6.375rem] rounded-3xl px-4 py-2.5 ", { variants: { diff --git a/src/components/ui/UniverSityCard/index.tsx b/src/components/ui/UniverSityCard/index.tsx index e93d2512..1d7ec1cc 100644 --- a/src/components/ui/UniverSityCard/index.tsx +++ b/src/components/ui/UniverSityCard/index.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import Link from "next/link"; import { convertImageUrl } from "@/utils/fileUtils"; -import { shortenLanguageTestName } from "@/utils/universityUtils"; +import shortenLanguageTestName from "@/utils/universityUtils"; import CheveronRightFilled from "@/components/ui/icon/ChevronRightFilled"; diff --git a/src/utils/designUtils.ts b/src/utils/designUtils.ts index e167c34a..27692d34 100644 --- a/src/utils/designUtils.ts +++ b/src/utils/designUtils.ts @@ -1,4 +1,6 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); +const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); + +export default cn; diff --git a/src/utils/jwtUtils.ts b/src/utils/jwtUtils.ts index a732c1e4..43cfbae1 100644 --- a/src/utils/jwtUtils.ts +++ b/src/utils/jwtUtils.ts @@ -1,7 +1,14 @@ +interface JwtPayload { + sub: number; + role: string; + iat: number; + exp: number; +} + export const isTokenExpired = (token: string | null): boolean => { if (!token) return true; try { - const payload = JSON.parse(atob(token.split(".")[1])); + const payload = JSON.parse(atob(token.split(".")[1])) as JwtPayload; const currentTime = Math.floor(Date.now() / 1000); return payload.exp < currentTime; } catch (error) { @@ -10,20 +17,13 @@ export const isTokenExpired = (token: string | null): boolean => { } }; -interface JwtPayload { - sub: number; - role: string; - iat: number; - exp: number; -} - export const tokenParse = (token: string | null): JwtPayload | null => { if (typeof window === "undefined") return null; if (!token) return null; try { - const payload: JwtPayload = JSON.parse(atob(token.split(".")[1])); + const payload = JSON.parse(atob(token.split(".")[1])) as JwtPayload; return payload; } catch (error) { console.error("토큰 파싱 오류:", error); diff --git a/src/utils/scoreUtils.ts b/src/utils/scoreUtils.ts index 020109ec..7848e0eb 100644 --- a/src/utils/scoreUtils.ts +++ b/src/utils/scoreUtils.ts @@ -1,6 +1,6 @@ import { LanguageTestEnum } from "@/types/score"; -export const validateLanguageScore = (testType: string, score: string) => { +const validateLanguageScore = (testType: string, score: string) => { const numScore = Number(score); if (testType === LanguageTestEnum.TOEIC) { @@ -31,3 +31,5 @@ export const validateLanguageScore = (testType: string, score: string) => { } } }; + +export default validateLanguageScore; diff --git a/src/utils/universityUtils.ts b/src/utils/universityUtils.ts index fa04e361..ca288534 100644 --- a/src/utils/universityUtils.ts +++ b/src/utils/universityUtils.ts @@ -1,7 +1,10 @@ import { SHORT_LANGUAGE_TEST } from "@/constants/application"; -export const shortenLanguageTestName = (name: string) => { +const shortenLanguageTestName = (name: string): string | undefined => { if (Object.prototype.hasOwnProperty.call(SHORT_LANGUAGE_TEST, name)) { - return SHORT_LANGUAGE_TEST[name]; + return SHORT_LANGUAGE_TEST[name] as string; } + return undefined; }; + +export default shortenLanguageTestName; diff --git a/src/utils/useInfinityScroll.ts b/src/utils/useInfinityScroll.ts index 79fa49bc..7a3281d0 100644 --- a/src/utils/useInfinityScroll.ts +++ b/src/utils/useInfinityScroll.ts @@ -25,7 +25,8 @@ const useInfinityScroll = ({ if (!node) return; observerRef.current = new IntersectionObserver( - ([entry]) => { + (entries) => { + const [entry] = entries; if (entry.isIntersecting) { fetchNextPage(); } From 544cf62e6c705f271036e357ea33f620f7a25de2 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sat, 27 Dec 2025 19:56:01 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=EB=A6=B0=ED=8A=B8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmailSignUpForm: any 타입을 unknown으로 변경 - SignupPolicyScreen: 중복된 className prop 제거 - SignupSurvey: any 타입들을 unknown으로 변경 - UniversityFilterSection, UniversityRegionTabs: import 경로에 확장자 추가 - useInfinityScroll: 타입 정의 수정 --- src/app/sign-up/email/EmailSignUpForm.tsx | 5 ++-- .../login/signup/SignupPolicyScreen.tsx | 3 +- src/components/login/signup/SignupSurvey.tsx | 28 +++++++++++-------- .../search/UniversityFilterSection.tsx | 2 +- .../search/UniversityRegionTabs.tsx | 2 +- src/utils/useInfinityScroll.ts | 2 +- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/app/sign-up/email/EmailSignUpForm.tsx b/src/app/sign-up/email/EmailSignUpForm.tsx index 69831bd0..05674585 100644 --- a/src/app/sign-up/email/EmailSignUpForm.tsx +++ b/src/app/sign-up/email/EmailSignUpForm.tsx @@ -73,8 +73,9 @@ const EmailSignUpForm = () => { onSuccess: (data) => { router.push(`/sign-up?token=${data.signUpToken}`); }, - onError: (error: any) => { - toast.error(error.response?.data?.message || "회원가입에 실패했습니다."); + onError: (error: unknown) => { + const axiosError = error as { response?: { data?: { message?: string } } }; + toast.error(axiosError.response?.data?.message || "회원가입에 실패했습니다."); }, }, ); diff --git a/src/components/login/signup/SignupPolicyScreen.tsx b/src/components/login/signup/SignupPolicyScreen.tsx index 3154bc4c..07b83a7d 100644 --- a/src/components/login/signup/SignupPolicyScreen.tsx +++ b/src/components/login/signup/SignupPolicyScreen.tsx @@ -58,10 +58,9 @@ const SignupPolicyScreen = ({ toNextStage }: SignupPolicyScreenProps) => {
다음 diff --git a/src/components/login/signup/SignupSurvey.tsx b/src/components/login/signup/SignupSurvey.tsx index 59fe1587..e3265da0 100644 --- a/src/components/login/signup/SignupSurvey.tsx +++ b/src/components/login/signup/SignupSurvey.tsx @@ -64,8 +64,9 @@ const SignupSurvey = ({ baseNickname, baseEmail, baseProfileImageUrl }: SignupSu try { const result = await uploadImageMutation.mutateAsync(profileImageFile); imageUrl = result.fileUrl; - } catch (err: any) { - console.error("Error", err.message); + } catch (err: unknown) { + const error = err as { message?: string }; + console.error("Error", error.message); // toast.error는 hook의 onError에서 이미 처리되므로 중복 호출 제거 } } @@ -89,19 +90,24 @@ const SignupSurvey = ({ baseNickname, baseEmail, baseProfileImageUrl }: SignupSu toast.success("회원가입이 완료되었습니다."); router.push("/"); }, - onError: (error: any) => { - if (error.response) { - console.error("Axios response error", error.response); - toast.error(error.response.data?.message || "회원가입에 실패했습니다."); + onError: (error: unknown) => { + const axiosError = error as { + response?: { data?: { message?: string } }; + message?: string; + }; + if (axiosError.response) { + console.error("Axios response error", axiosError.response); + toast.error(axiosError.response.data?.message || "회원가입에 실패했습니다."); } else { - console.error("Error", error.message); - toast.error(error.message || "회원가입에 실패했습니다."); + console.error("Error", axiosError.message); + toast.error(axiosError.message || "회원가입에 실패했습니다."); } }, }); - } catch (err: any) { - console.error("Error", err.message); - toast.error(err.message || "회원가입에 실패했습니다."); + } catch (err: unknown) { + const error = err as { message?: string }; + console.error("Error", error.message); + toast.error(error.message || "회원가입에 실패했습니다."); } }; diff --git a/src/components/search/UniversityFilterSection.tsx b/src/components/search/UniversityFilterSection.tsx index 46d76b0f..c9996c13 100644 --- a/src/components/search/UniversityFilterSection.tsx +++ b/src/components/search/UniversityFilterSection.tsx @@ -9,7 +9,7 @@ import UniversitySearchInput from "@/components/search/UniversitySearchInput"; import { RegionKo } from "@/types/university"; -import { RegionOption } from "@/app/search/SearchContent"; +import { RegionOption } from "@/app/search/SearchContent.tsx"; import { IconDownArrow, IconHatColor, IconHatGray, IconLocationColor, IconLocationGray } from "@/public/svgs/search"; interface UniversityFilterSectionProps { diff --git a/src/components/search/UniversityRegionTabs.tsx b/src/components/search/UniversityRegionTabs.tsx index 41a00841..71dc50d5 100644 --- a/src/components/search/UniversityRegionTabs.tsx +++ b/src/components/search/UniversityRegionTabs.tsx @@ -4,7 +4,7 @@ import React from "react"; import { RegionKo } from "@/types/university"; -import { RegionOption } from "@/app/search/SearchContent"; +import { RegionOption } from "@/app/search/SearchContent.tsx"; interface UniversityRegionTabsProps { regions: RegionOption[]; diff --git a/src/utils/useInfinityScroll.ts b/src/utils/useInfinityScroll.ts index 7a3281d0..78c0af18 100644 --- a/src/utils/useInfinityScroll.ts +++ b/src/utils/useInfinityScroll.ts @@ -7,7 +7,7 @@ type UseInfinityScrollProps = { }; type UseInfinityScrollReturn = { - lastElementRef: (node: HTMLDivElement | null) => void; + lastElementRef: (node: Element | null) => void; }; const useInfinityScroll = ({