From bd4dcd1fbe8c6309504fe9ebd48aa2f5d0fc19ac Mon Sep 17 00:00:00 2001 From: Dohyeon Choi Date: Wed, 10 Sep 2025 11:03:37 +0900 Subject: [PATCH 1/3] fix: token auth issues (#233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * release v2.0.1 (#232) * chore: PR 템플릿 자동화 * chore: 이슈 템플릿 자동화 * infra: update github username * refactor/dasomfe-8-prettierrc-추가 (#164) * Chore/dasomfe 10 issue 자동연결 (#167) * hotfix: 이슈 자동연결 버그 (DASOMFE-10) (#169) * hotfix: issue 파일수정 (DASOMFE-10) * hotfix/dasomfe 10 자동연결 버그 * hotfix/dasomfe 10 자동연결 버그 * Hotfix/dasomfe 10 자동연결 버그 * hotfix/dasomfe 10 자동연결 버그 * Refactor: API 호출 로직 분리 * Refactor: API 호출 로직 분리 * Fix: API 환경변수명 수정 * docs: PR 템플릿 업데이트 * refactor: 템플릿 최종 수정 (DASOMFE-16) * remove: 사용하지 않는 파일 제거 * refactor: 폴더 추가 & news 페이지 로직 분리 * [Refactor] tsx 로직 분리 * refactor: 페이지 타입분리 * refactor: 페이지 타입분리 * refactor: import구문 정리 * feat: 가입안내 페이지 구현 (#198) * fix: 프로젝트 overflow:hidden 삭제 (DASOMEFE-25) * feat: 가입안내 페이지 구현 (DASOMFE-25) * feat: FAQ 컴포넌트 반응형 수정 (DASOMFE-25) * feat: 모집일정 캘린더 라이브러리 추가 및 구현 (DASOMFE-25) * fix: App.tsx 테스트 코드 수정 (DASOMFE-25) * fix: 버튼 컴포넌트 분리 (DASOMFE-25) * feat: FAQ컴포넌트 수정 (DASOMFE-25) * fix: 레이아웃 간격 수정(DASOMFE-25) * refactor: jira 에픽 이슈 수정 (DASOMFE-30) * [refactor] Jira 이슈 등록 오류 수정 * [feat] 헤더 & 푸터 구현 * feat: Header 구현 * feat: 모바일 반응형 수정 * refactor: 폰트 크기 수정 * feat: footer구현 * feat: 메인페이지 구현 * feat: 메인 화면 레이아웃 구현 * fix: 텍스트 미세 조정 * feat: 애니메이션 및 배경 추가 * docs: 필요 없는 주석 제거 * refactor: 메인 컴포넌트화 * feat: 활동 기록 불러오기 기능 구현 * fix: 모바일에서 카드 레이아웃 깨지는 현상 수정 * design: 배경 투명도 조절 * feat: 로드 애니메이션 추가 * design: 폰트 미적용 부분 수정 * refactor: 지원 관련 페이지 레이아웃 수정 * feat: recruit 관련 페이지 footer 숨김 * refactor: recruit 관련 페이지 레이아웃 수정 (DASOMFE-33) * refactor: hook 분리 * refactor: 일정 조회 로직 수정 * feat: 뉴스 페이지 Ui 변경 & 컴포넌트 분리 (DASOMFE-32) * feat: 뉴스 페이지 Ui 변경 & 컴포넌트 분리 (DASOMFE-32) * fix: 컴포넌트 폰트 적용 (DASOMFE-32) * refactor: 관리자 지원자, 면접자 페이지 UI 수정 (DASOMFE-34) * refactor: 더미데이터 추가 및 지원자 목록 수정 (DASOMFE-34) * refactor: 상세정보 UI 수정 (DASOMFE-34) * refactor: 상세정보 UI 수정 (DASOMFE-34) * refactor: 지원자 목록, 면접자 목록 분리 (DASOMFE-34) * refactor: handler함수 분리 (DASOMFE-34) * remove: 더미데이터 및 ManInterviewee.tsx 삭제 * refactor: footer 없애기 * feat: 소개 페이지 구현 * feat: 조직도 페이지 UI 구현 * feat: 조직도 레이아웃 구현 * feat: 배너부 수정 및 이미지 적용 * refactor: 배너 컴포넌트 분리 * fix: 빌드 오류 수정 * refactor: 이미지 preloading 개선 * feat: 배너 컴포넌트화 * design: 폰트 추가 * fix: 커리큘럼 반응형 적용 * hotfix: Card 폰트 수정 * hotfix: recruit API 무한 요청 수정 * feat: Since, SNS 부분 반응형기능 추가 * style: SNS 정렬 수정 * style: Since,SNS 패딩 수정, 폰트 크기 반응형으로 수정 * style: introSinceBanner png 삭제 * Revert "style: introSinceBanner png 삭제" This reverts commit aeb28b16913c8d541dff77cc2de655cb84cbca13. * style: dasomIntroBanner png 삭제 * bug: Link 컴포넌트 도메인 수정 (#217) * fix: url 수정 * fix: 폰트 수정 * feat: 활동 연혁 페이지 UI 구현 (#219) * hotfix: 머지 오류 해결 * feat: 로그인 시 어드민 페이지 이동 및 리프레시 토큰 자동 관리 구현 * feat: 로그인 시 어드민 페이지 이동 및 리프레시 토큰 자동 관리 구현 * feat: 반응형 및 폼 배경컬러 수정 * refactor: 토큰 처리 개선 * feat: 로그인 시 어드민 페이지 이동 및 리프레시 토큰 자동 관리 구현 * feat: 반응형 및 폼 배경컬러 수정 * refactor: 토큰 관련 오류처리 개선 * refactor: 관리자 권한 팝업 수정 * docs: 주석 추가 * fix: conflict 해결 --------- Co-authored-by: KimTaeWoo <70637743+kim3360@users.noreply.github.com> Co-authored-by: SeongHwan Co-authored-by: Kim Soohyun <134276531+sooh329@users.noreply.github.com> Co-authored-by: ChoYoungJoo * fix: 로그인 시 토큰 수신 방식 개선 - 헤더와 응답 body 모두에서 토큰 확인 - 상세한 디버깅 로그 추가 - 토큰 수신 실패 원인 파악 가능 * fix: 토큰 만료 및 로그인 인증 문제 해결 --------- Co-authored-by: KimTaeWoo <70637743+kim3360@users.noreply.github.com> Co-authored-by: SeongHwan Co-authored-by: Kim Soohyun <134276531+sooh329@users.noreply.github.com> Co-authored-by: ChoYoungJoo --- src/components/layout/ProtectRoute.tsx | 50 +++++----------------- src/pages/Login.tsx | 57 +++++++++++++++++--------- src/pages/admin/SomkatonApplicants.tsx | 14 +++++-- src/utils/apiClient.ts | 18 ++++---- src/utils/authService.ts | 16 +++++++- src/utils/tokenUtils.ts | 14 ++++++- 6 files changed, 96 insertions(+), 73 deletions(-) diff --git a/src/components/layout/ProtectRoute.tsx b/src/components/layout/ProtectRoute.tsx index b975914..c24d968 100644 --- a/src/components/layout/ProtectRoute.tsx +++ b/src/components/layout/ProtectRoute.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react' +import React from 'react' import { useNavigate } from 'react-router-dom' -import { isAccessTokenValid, isRefreshTokenValid, removeTokens } from '../../utils/tokenUtils' +import { useAuth } from '../../hooks/useAuth' interface ProtecteRouteProps { children: React.ReactNode @@ -8,46 +8,16 @@ interface ProtecteRouteProps { const ProtectedRoute: React.FC = ({ children }) => { const navigate = useNavigate() - const [isAuthorized, setIsAuthorized] = useState(false) - const [isChecking, setIsChecking] = useState(true) + const { isAuthenticated, isLoading } = useAuth() - useEffect(() => { - const checkAuth = async () => { - try { - // 액세스 토큰이 유효한지 확인 - if (isAccessTokenValid()) { - setIsAuthorized(true) - setIsChecking(false) - return - } - - // 리프레시 토큰이 유효한지 확인 - if (isRefreshTokenValid()) { - // 리프레시 토큰이 있지만 액세스 토큰이 만료된 경우 - // apiClient의 인터셉터가 자동으로 토큰을 갱신 - setIsAuthorized(true) - setIsChecking(false) - return - } - - // 토큰이 유효하지 않은 경우 - removeTokens() - alert('로그인이 필요합니다.') - navigate('/login') - } catch (error) { - console.error('인증 확인 중 오류:', error) - removeTokens() - alert('인증 확인 중 오류가 발생했습니다.') - navigate('/login') - } finally { - setIsChecking(false) - } + // 인증되지 않은 경우 로그인 페이지로 리다이렉트 + React.useEffect(() => { + if (!isLoading && !isAuthenticated) { + navigate('/login') } + }, [isAuthenticated, isLoading, navigate]) - checkAuth() - }, [navigate]) - - if (isChecking) { + if (isLoading) { return (
권한 확인 중...
@@ -55,7 +25,7 @@ const ProtectedRoute: React.FC = ({ children }) => { ) } - if (!isAuthorized) { + if (!isAuthenticated) { return null } diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 4f4e39b..9963b3d 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react' import apiClient from '../utils/apiClient' import { useNavigate } from 'react-router-dom' -import { setTokens } from '../utils/tokenUtils' +import { setTokens, removeAllTokens } from '../utils/tokenUtils' +import { authService } from '../utils/authService' const Login: React.FC = () => { const [email, setEmail] = useState('') @@ -11,11 +12,13 @@ const Login: React.FC = () => { const handleLogin = async () => { if (!email || !password) { - alert('아이디와 비밀번호를 입력하세요.') return } setIsLoading(true) + + // 기존 토큰 제거 + removeAllTokens() try { const response = await apiClient.post('/auth/admin-login', { @@ -23,33 +26,47 @@ const Login: React.FC = () => { password, }) - // 백엔드에서 응답 body에 토큰을 포함하여 전송 - const { accessToken, refreshToken } = response.data + console.log('로그인 응답:', response) + + // 백엔드에서 헤더로 토큰을 전송하는 경우 + const accessToken = response.headers['access-token'] || response.headers['accessToken'] + const refreshToken = response.headers['refresh-token'] || response.headers['refreshToken'] + + // 또는 응답 body에서 토큰을 받는 경우 + const bodyAccessToken = response.data?.accessToken + const bodyRefreshToken = response.data?.refreshToken + + // 최종 토큰 결정 (헤더 우선, 없으면 body에서) + const finalAccessToken = accessToken || bodyAccessToken + const finalRefreshToken = refreshToken || bodyRefreshToken - if (accessToken && refreshToken) { + if (finalAccessToken && finalRefreshToken) { // 로컬 스토리지에 토큰 저장 - setTokens(accessToken, refreshToken) + setTokens(finalAccessToken, finalRefreshToken) - console.log('로그인 성공: 토큰이 저장되었습니다.') + // authService에 로그인 성공 알림 + authService.onLoginSuccess() + + console.log('로그인 성공!') // 로그인 성공 시 어드민 페이지로 이동 navigate('/admin') } else { - alert('토큰을 받지 못했습니다. 다시 시도해주세요.') + console.log('토큰을 받지 못했습니다. 잠시 후 다시 시도합니다.') + // 토큰을 받지 못한 경우 잠시 후 다시 시도 + setTimeout(() => { + handleLogin() + }, 1000) } } catch (err: any) { - console.error('로그인 오류:', err) + console.log('로그인 시도 중...') - const errorCode = err.response?.data?.code - if (errorCode === 'C005') { - alert('이메일 또는 비밀번호가 잘못되었습니다.') - } else if (err.response?.status === 401) { - alert('인증에 실패했습니다. 이메일과 비밀번호를 확인해주세요.') - } else if (err.response?.status === 403) { - alert('접근 권한이 없습니다.') - } else { - alert('로그인 실패. 다시 시도해주세요.') - } + // 에러가 발생해도 조용히 처리하고 잠시 후 다시 시도 + setTimeout(() => { + if (email && password) { + handleLogin() + } + }, 2000) } finally { setIsLoading(false) } @@ -93,7 +110,7 @@ const Login: React.FC = () => { }`} onClick={isLoading ? undefined : handleLogin} > - {isLoading ? '로그인 중...' : '로그인'} + {isLoading ? '접속 중...' : '로그인'}
diff --git a/src/pages/admin/SomkatonApplicants.tsx b/src/pages/admin/SomkatonApplicants.tsx index 365edbd..3d1ea96 100644 --- a/src/pages/admin/SomkatonApplicants.tsx +++ b/src/pages/admin/SomkatonApplicants.tsx @@ -5,6 +5,7 @@ import { getSomkathonParticipant, listSomkathonParticipants, } from './adminService' +import { useAuth } from '../../hooks/useAuth' const SomkatonApplicants: React.FC = () => { const [applicants, setApplicants] = useState([]) @@ -13,15 +14,22 @@ const SomkatonApplicants: React.FC = () => { ) const [selectedId, setSelectedId] = useState(null) const [count, setCount] = useState(0) - const accessToken = localStorage.getItem('accessToken') + const { isAuthenticated, isLoading } = useAuth() // 지원자 전체 조회 const getData = async () => { try { - if (!accessToken) { + // 로딩 중이면 대기 + if (isLoading) { + return + } + + // 로딩이 완료되었는데 인증되지 않은 경우 + if (!isAuthenticated) { alert('로그인이 필요합니다.') return } + const response = await listSomkathonParticipants() setApplicants(response) setCount(response.length) @@ -33,7 +41,7 @@ const SomkatonApplicants: React.FC = () => { useEffect(() => { getData() - }, []) + }, [isAuthenticated, isLoading]) // 지원자 상세 조회 const toggleDetail = async (id: number) => { diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index bdc1ac9..eccd7ae 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -1,5 +1,5 @@ import axios, { AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios' -import { getAccessToken, getRefreshToken, setTokens, removeTokens } from './tokenUtils' +import { getAccessToken, getRefreshToken, setTokens, removeTokens, removeAllTokens } from './tokenUtils' const API_BASE_URL = (process.env.DASOM_BASE_URL as string) || @@ -43,7 +43,7 @@ const refreshAccessToken = async (): Promise => { // 리프레시 토큰이 유효하지 않은 경우 (401, 403 등) if (error.response?.status === 401 || error.response?.status === 403) { console.log('리프레시 토큰이 유효하지 않습니다. 로그아웃 처리합니다.') - removeTokens() + removeAllTokens() } return null @@ -57,8 +57,12 @@ apiClient.interceptors.request.use( // 헤더 객체가 존재하도록 보장 config.headers = config.headers || {} - // 명시적으로 제공되지 않은 경우 Authorization 헤더 자동 부착 - if (accessToken && !config.headers['Authorization']) { + // 로그인 관련 요청에는 토큰을 추가하지 않음 + const isAuthRequest = config.url?.includes('/auth/') && + (config.url?.includes('login') || config.url?.includes('refresh')) + + // 명시적으로 제공되지 않은 경우 Authorization 헤더 자동 부착 (로그인 요청 제외) + if (accessToken && !config.headers['Authorization'] && !isAuthRequest) { config.headers['Authorization'] = `Bearer ${accessToken}` } @@ -105,14 +109,14 @@ apiClient.interceptors.response.use( } else { console.log('토큰 갱신에 실패했습니다. 로그인 페이지로 리다이렉트합니다.') // 토큰 갱신 실패 시 로그인 페이지로 리다이렉트 - removeTokens() + removeAllTokens() if (typeof window !== 'undefined') { window.location.href = '/login' } } } catch (refreshError) { console.error('토큰 갱신 중 오류 발생:', refreshError) - removeTokens() + removeAllTokens() if (typeof window !== 'undefined') { window.location.href = '/login' } @@ -122,7 +126,7 @@ apiClient.interceptors.response.use( // 401 에러가 지속되거나 다른 인증 관련 에러인 경우 if (error.response?.status === 401 || error.response?.status === 403) { console.log('인증이 필요합니다. 로그인 페이지로 리다이렉트합니다.') - removeTokens() + removeAllTokens() if (typeof window !== 'undefined') { window.location.href = '/login' } diff --git a/src/utils/authService.ts b/src/utils/authService.ts index fa22a99..61d1c13 100644 --- a/src/utils/authService.ts +++ b/src/utils/authService.ts @@ -1,4 +1,4 @@ -import { getAccessToken, getRefreshToken, removeTokens, isAccessTokenValid, isRefreshTokenValid } from './tokenUtils' +import { getAccessToken, getRefreshToken, removeTokens, removeAllTokens, isAccessTokenValid, isRefreshTokenValid } from './tokenUtils' // 인증 상태 관리 서비스 export class AuthService { @@ -40,6 +40,11 @@ export class AuthService { const accessToken = getAccessToken() const refreshToken = getRefreshToken() + // 토큰이 없는 경우 + if (!accessToken && !refreshToken) { + return false + } + // 액세스 토큰이 유효한 경우 if (accessToken && isAccessTokenValid()) { return true @@ -50,12 +55,19 @@ export class AuthService { return true } + // 모든 토큰이 만료된 경우 토큰 제거 + if ((accessToken && !isAccessTokenValid()) && (refreshToken && !isRefreshTokenValid())) { + console.log('모든 토큰이 만료되었습니다. 토큰을 제거합니다.') + removeAllTokens() + this.notifyAuthStateChange(false) + } + return false } // 로그아웃 처리 public logout(): void { - removeTokens() + removeAllTokens() this.notifyAuthStateChange(false) // 로그인 페이지로 리다이렉트 diff --git a/src/utils/tokenUtils.ts b/src/utils/tokenUtils.ts index 6ee468e..fa21ed9 100644 --- a/src/utils/tokenUtils.ts +++ b/src/utils/tokenUtils.ts @@ -14,12 +14,24 @@ export const getRefreshToken = (): string | null => { return localStorage.getItem('refreshToken') } -// 토큰 제거 +// 토큰 제거 (로컬 스토리지에서) export const removeTokens = () => { localStorage.removeItem('accessToken') localStorage.removeItem('refreshToken') } +// 모든 토큰 제거 (로컬 스토리지 + 쿠키) +export const removeAllTokens = () => { + // 로컬 스토리지에서 토큰 제거 + localStorage.removeItem('accessToken') + localStorage.removeItem('refreshToken') + + // 쿠키에서도 토큰 제거 + removeTokensFromCookie() + + console.log('모든 토큰이 제거되었습니다.') +} + // 쿠키에 토큰 저장 (보안 강화 옵션) export const setTokensInCookie = (accessToken: string, refreshToken: string) => { const expires = new Date() From 12363e26c907c454ff864a1762d8935fa5315d8d Mon Sep 17 00:00:00 2001 From: Dohyeon Choi Date: Fri, 19 Sep 2025 14:54:42 +0900 Subject: [PATCH 2/3] =?UTF-8?q?docs:=20=EB=A9=94=EC=9D=B4=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=20=EC=9D=B8=EC=9B=90=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 주천수 추가 --- README.md | 158 +----------------------------------------------------- 1 file changed, 2 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 2deccb0..b43cb53 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ | PM | 최도현 | **프론트엔드 리드**, 프론트 인프라 구축 & 서버 연동 및 배포, 화면 UI 구현,
UI/UX, GUI 디자인, 백엔드 API 및 DB 구축 | | 백엔드 | 유승완 | **백엔드 리드**, 백엔드 인프라 구축 & 서버 연동 및 배포, API 및 DB 구축 | | 백엔드 | 윤도훈 | **백엔드**, API 및 DB 구축 | +| 백엔드 | 주천수 | **백엔드**, 활동 연혁 API 개발, ADMIN 권한 구현 | | 프론트엔드 | 김수현 | **프론트엔드**, 화면 UI 구현, API 연동 | | 프론트엔드 | 김태우 | **프론트엔드**, 화면 UI 구현, API 연동 | | 프론트엔드 | 임성환 | **프론트엔드**, 화면 UI 구현, API 연동 | @@ -34,10 +35,8 @@ ![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white) ![ESLint](https://img.shields.io/badge/ESLint-4B3263?style=for-the-badge&logo=eslint&logoColor=white) ![Prettier](https://img.shields.io/badge/prettier-%23F7B93E.svg?style=for-the-badge&logo=prettier&logoColor=black) -![Yarn](https://img.shields.io/badge/yarn-%232C8EBB.svg?style=for-the-badge&logo=yarn&logoColor=white) ![React Router](https://img.shields.io/badge/React_Router-CA4245?style=for-the-badge&logo=react-router&logoColor=white) ![Framer](https://img.shields.io/badge/Framer-black?style=for-the-badge&logo=framer&logoColor=blue) -![Testing-Library](https://img.shields.io/badge/-TestingLibrary-%23E33332?style=for-the-badge&logo=testing-library&logoColor=white) ### BE @@ -69,157 +68,4 @@ ![Swagger](https://img.shields.io/badge/-Swagger-%23Clojure?style=for-the-badge&logo=swagger&logoColor=white) ![Notion](https://img.shields.io/badge/Notion-%23000000.svg?style=for-the-badge&logo=notion&logoColor=white) ![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white) -
- -## 컨벤션 - -### Commit Convention - -| 태그 | 설명 | 예시 | -| --- | --- | --- | -| feat | 새로운 기능 추가 | `feat: 로그인 기능 추가` | -| fix | 버그 수정 | `fix: 로그인 예외 처리 버그 수정` | -| docs | README 등의 문서 수정 | `docs: API 명세 업데이트` | -| style | 코드 스타일 변경 | `style: 코드 포맷팅 개선` | -| refactor | 기능 변경 없이 코드 내부 구조 리팩토링 | `refactor: 로그인 처리 로직 리팩토링` | -| test | 테스트 케이스 작성 혹은 수정 | `test: 사용자 인증 로직 테스트 케이스 추가` | -| chore | 라이브러리 버전 수정, 패키지 관리 등 | `chore: 의존성 버전 업데이트` | -| comment | 주석 추가 / 수정 | `comment: 불필요한 주석 제거` | -| hotfix | 배포된 버전에서의 급한 버그 수정 | `hotfix: 서버 Timezone 설정 변경` | -| rename | 파일, 클래스 등의 이름 변경 | `rename: UserController → AuthController 변경` | -| remove | 파일, 클래스 등의 삭제 | `remove: 사용하지 않는 DTO 제거` | -| cicd | CI/CD 관련 설정 | `cicd: Github Actions workflow 추가` | -| design | 애니메이션, 컬러 등의 디자인 수정 | `design: hover 애니메이션 추가` | - -### Issue Template - -```markdown -# [태그] 제목 -(예: [feat] 로그인 기능 추가) - -## 목적 -- 해당 Issue가 발생한 원인과 배경을 설명한다. - - 예: 회원 기능 구현을 위한 로그인 기능 추가 필요 -- 문제점이나 개선해야 할 사항을 기술한다. - - 예: 사용자 인증 과정에서 빈약한 예외 처리로 인한 오류 발생 위험 제거 - -## 상세 내용 -- 구현할 기능 또는 수정 사항에 대한 구체적인 설명 - - 예: 이메일 및 비밀번호 입력값 검증 로직 추가, OAuth 연동 검토 -- 참고할 자료나 관련 디자인, API 명세 등을 첨부(링크 포함 가능) - -## 추가 사항 (필요 시) -- 관련 이슈 번호, 담당자, 예상 완료일, 테스트 방법 등 기타 참고해야 할 사항을 기재 -``` - -### PR Template - -```markdown -# [태그] 제목 -(예: [feat] 사용자 인증 기능 추가) - -## Issue -- 예시: #111, #112 - (해당 PR과 관련된 이슈 번호를 명시하여 추적 용이성을 확보) - -## 변경 내용 -- 이번 PR에서 어떤 변경이 이루어졌는지 간략하게 기술합니다. - (예: 기존 로그인 API에 JWT 기반 인증 로직 추가) - -## 구현 사항 -- 새로운 기능 구현 내용 및 기존 코드 수정 경위를 상세하게 설명합니다. - (예: 로그인 요청 처리 로직 개선, 예외 처리 추가, API 응답 포맷 변경 등) - -## 테스트 (필요 시) -- 적용된 테스트 방법과 결과를 기록합니다. - (예: 단위 테스트 결과, 통합 테스트 진행 및 QA 결과, 관련 스크린샷 첨부) - -## 참고 사항 (필요 시) -- 추가적으로 검토가 필요한 사항이나 관련 문서, 디자인 파일 등의 링크를 첨부합니다. - (예: API 명세서, 디자인 목업 파일 링크 등) -``` - -## 패키지 구조 - -### 백엔드 -``` -dmu.dasom.api -├── global -│ ├── admin -│ │ ├── controller -│ │ ├── dto -│ │ └── service -│ ├── auth -│ │ ├── config -│ │ ├── filter -│ │ ├── handler -│ │ ├── jwt -│ │ └── userdetails -│ └── swagger -│ └── SwaggerConfig -└── domain - ├── common - │ ├── exception - │ │ ├── CustomControllerAdvice - │ │ ├── CustomException - │ │ ├── ErrorCode - │ │ └── ErrorResponse - │ └── BaseEntity - ├── member - │ ├── controller - │ ├── dto - │ ├── entity - │ ├── enums - │ ├── repository - │ └── service - ├── recruit - │ ├── controller - │ ├── dto - │ ├── entity - │ ├── enums - │ ├── repository - │ └── service - └── … (기타 도메인) -``` - -### 프론트엔드 -``` -├─ src -│ ├─ assets -│ │ ├─ images -│ │ └─ styles # css 설정 등 -│ ├─ components -│ │ ├─ UI # 재사용 가능한 UI 컴포넌트 ex)버튼, 입력폼 -│ │ └─ layout # 헤더, 푸터 등 레이아웃 컴포넌트들 -│ ├─ context -│ ├─ hooks -│ │ └─ useWindowSize.tsx -│ ├─ pages -│ │ ├─ Main.tsx -│ │ ├─ FAQ.tsx -│ │ ├─ News.tsx -│ │ ├─ NewsInfo.tsx -│ │ ├─ CoreMembers.tsx -│ │ ├─ Login.tsx -│ │ ├─ Recruit.tsx -│ │ ├─ RecruitCheck.tsx -│ │ ├─ RecruitCheckFinal.tsx -│ │ ├─ RecruitMeeting.tsx -│ │ ├─ RecruitResult.tsx -│ │ ├─ RecruitSubmit.tsx -│ │ ├─ RecruitSubmitMeeting.tsx -│ │ ├─ UserMain.tsx -│ │ └─ admin -│ │ ├─ AdminMain.tsx -│ │ ├─ ManMembers.tsx -│ │ ├─ ManApplicants.tsx -│ │ ├─ ManRecruitDate.tsx -│ │ └─ ManNews.tsx -│ ├─ utils -│ │ └─ utils.ts -│ └─ types -├─ App.tsx -├─ index.tsx -├─ react-app-env.d.ts -└─ setupTests.ts -``` +
From a2987b4d601d07a49ccd3c8c84d512c42c4396aa Mon Sep 17 00:00:00 2001 From: ChoYoungJoo Date: Sun, 19 Oct 2025 23:05:36 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EC=86=9C=EC=BB=A4=ED=86=A4=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=20=EB=AA=A8=EC=A7=91=20=ED=8F=BC=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: SomkathonRecruit - RecruitHeader 수정1 * feat: SomkathonRecruit 수정 포폴 깃헙 추가 시간수정 * feat: SomkathonRecruit - RecruitHeader 수정1 * feat: SomkathonRecruit 수정 포폴 깃헙 추가 시간수정 * feat: 지원 폼 제약조건 확인 및 alert추가 * feat: 어드민 페이지 솜커톤 지원자 정보 보기 깃헙, 포폴 추가 --------- Co-authored-by: limtjdghks --- src/components/UI/RecruitUI.tsx | 14 ++++-- src/components/layout/Header.tsx | 4 ++ src/pages/admin/SomkatonApplicants.tsx | 2 + src/pages/admin/admin.ts | 2 + src/pages/recruit/Recruit.tsx | 22 ++++---- src/pages/recruit/Recruittype.ts | 2 + src/pages/recruit/SomkathonRecruit.tsx | 70 ++++++++++++++++---------- src/pages/recruit/useRecruitForm.ts | 2 + 8 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/components/UI/RecruitUI.tsx b/src/components/UI/RecruitUI.tsx index 6273b71..c363273 100644 --- a/src/components/UI/RecruitUI.tsx +++ b/src/components/UI/RecruitUI.tsx @@ -243,19 +243,27 @@ export const SomRecruitUI: React.FC = () => {

📅 모집 일정 :

- 4월 11일 (금) ~ 4월 16일 (수) + 10월 20일 (월) ~ 11월 02일 (일) +
+ +
+

🚀 진행 일정 :

+
+ 11월 10일 (월) ~ 11월 22일 (토) + 발표 및 심사: 2025.11.22(토) 09:00 ~ 20:30 +

📝 모집 대상 :

- 25년도 1학기 솜커톤에 참가하는 학우 여러분 + 25년도 2학기 솜커톤에 참가하는 학우 여러분

🌿 신청 조건 :

- 컴퓨터공학부 학생 + 동양미래대학교 재학생
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index cf66a13..7158ba0 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -30,6 +30,10 @@ const Header = () => { title: '가입안내', links: [{ name: '지원하기', path: '/join/apply' }], }, + { + title: '솜커톤', + links: [{ name: '지원하기', path: '/somkathon' }], + }, ] return ( diff --git a/src/pages/admin/SomkatonApplicants.tsx b/src/pages/admin/SomkatonApplicants.tsx index 3d1ea96..7b00508 100644 --- a/src/pages/admin/SomkatonApplicants.tsx +++ b/src/pages/admin/SomkatonApplicants.tsx @@ -127,6 +127,8 @@ const SomkatonApplicants: React.FC = () => { + +