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
158 changes: 2 additions & 156 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
| PM | <a href="https://github.com/dohy-eon">최도현</a> | **프론트엔드 리드**, 프론트 인프라 구축 & 서버 연동 및 배포, 화면 UI 구현, <br> UI/UX, GUI 디자인, 백엔드 API 및 DB 구축 |
| 백엔드 | <a href="https://github.com/ysw789">유승완</a> | **백엔드 리드**, 백엔드 인프라 구축 & 서버 연동 및 배포, API 및 DB 구축 |
| 백엔드 | <a href="https://github.com/hodoon">윤도훈</a> | **백엔드**, API 및 DB 구축 |
| 백엔드 | <a href="https://github.com/jucheonsu">주천수</a> | **백엔드**, 활동 연혁 API 개발, ADMIN 권한 구현 |
| 프론트엔드 | <a href="https://github.com/sooh329">김수현</a> | **프론트엔드**, 화면 UI 구현, API 연동 |
| 프론트엔드 | <a href="https://github.com/kim3360">김태우</a> | **프론트엔드**, 화면 UI 구현, API 연동 |
| 프론트엔드 | <a href="https://github.com/limtjdghks">임성환</a> | **프론트엔드**, 화면 UI 구현, API 연동 |
Expand All @@ -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

Expand Down Expand Up @@ -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)
<br>

## 컨벤션

### 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
```
<br/>
14 changes: 11 additions & 3 deletions src/components/UI/RecruitUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,27 @@ export const SomRecruitUI: React.FC = () => {
</p>
<div className='mt-2 pl-2 flex'>
<p className='text-mainColor font-pretendardSemiBold'>📅 모집 일정 :</p>
<span className='text-white pl-1'>4월 11일 (금) ~ 4월 16일 (수)</span>
<span className='text-white pl-1'>10월 20일 (월) ~ 11월 02일 (일)</span>
</div>

<div className='mt-2 pl-2 flex'>
<p className='text-mainColor font-pretendardSemiBold'>🚀 진행 일정 :</p>
<div className='flex flex-col'>
<span className='text-white pl-1'>11월 10일 (월) ~ 11월 22일 (토)</span>
<span className='text-white pl-1'>발표 및 심사: 2025.11.22(토) 09:00 ~ 20:30</span>
</div>
</div>

<div className='mt-2 pl-2 flex items-center'>
<p className='text-mainColor font-pretendardSemiBold'>📝 모집 대상 :</p>
<span className='text-white pl-1'>
25년도 1학기 솜커톤에 참가하는 학우 여러분
25년도 2학기 솜커톤에 참가하는 학우 여러분
</span>
</div>

<div className='mt-2 pl-2 flex items-center'>
<p className='text-mainColor font-pretendardSemiBold'>🌿 신청 조건 :</p>
<span className='text-white pl-1 '>컴퓨터공학부 학생</span>
<span className='text-white pl-1 '>동양미래대학교 재학생</span>
</div>

<div className='mt-2 pl-2'>
Expand Down
4 changes: 4 additions & 0 deletions src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const Header = () => {
title: '가입안내',
links: [{ name: '지원하기', path: '/join/apply' }],
},
{
title: '솜커톤',
links: [{ name: '지원하기', path: '/somkathon' }],
},
]

return (
Expand Down
47 changes: 22 additions & 25 deletions src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -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('')
Expand All @@ -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', {
Expand All @@ -24,8 +27,6 @@ const Login: React.FC = () => {
})

console.log('로그인 응답:', response)
console.log('응답 헤더:', response.headers)
console.log('응답 데이터:', response.data)

// 백엔드에서 헤더로 토큰을 전송하는 경우
const accessToken = response.headers['access-token'] || response.headers['accessToken']
Expand All @@ -43,33 +44,29 @@ const Login: React.FC = () => {
// 로컬 스토리지에 토큰 저장
setTokens(finalAccessToken, finalRefreshToken)

console.log('로그인 성공: 토큰이 저장되었습니다.')
console.log('저장된 액세스 토큰:', finalAccessToken)
console.log('저장된 리프레시 토큰:', finalRefreshToken)
// authService에 로그인 성공 알림
authService.onLoginSuccess()

console.log('로그인 성공!')

// 로그인 성공 시 어드민 페이지로 이동
navigate('/admin')
} else {
console.error('토큰을 찾을 수 없습니다.')
console.error('헤더 액세스 토큰:', accessToken)
console.error('헤더 리프레시 토큰:', refreshToken)
console.error('바디 액세스 토큰:', bodyAccessToken)
console.error('바디 리프레시 토큰:', bodyRefreshToken)
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)
}
Expand Down Expand Up @@ -113,7 +110,7 @@ const Login: React.FC = () => {
}`}
onClick={isLoading ? undefined : handleLogin}
>
{isLoading ? '로그인 중...' : '로그인'}
{isLoading ? '접속 중...' : '로그인'}
</div>
</div>
</div>
Expand Down
16 changes: 13 additions & 3 deletions src/pages/admin/SomkatonApplicants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getSomkathonParticipant,
listSomkathonParticipants,
} from './adminService'
import { useAuth } from '../../hooks/useAuth'

const SomkatonApplicants: React.FC = () => {
const [applicants, setApplicants] = useState<SomkathonApplicantListItem[]>([])
Expand All @@ -13,15 +14,22 @@ const SomkatonApplicants: React.FC = () => {
)
const [selectedId, setSelectedId] = useState<number | null>(null)
const [count, setCount] = useState<number>(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)
Expand All @@ -33,7 +41,7 @@ const SomkatonApplicants: React.FC = () => {

useEffect(() => {
getData()
}, [])
}, [isAuthenticated, isLoading])

// 지원자 상세 조회
const toggleDetail = async (id: number) => {
Expand Down Expand Up @@ -119,6 +127,8 @@ const SomkatonApplicants: React.FC = () => {
<DetailItem label='이메일' value={applicant.email} />
<DetailItem label='학년' value={applicant.grade} />
<DetailItem label='학과' value={applicant.department} />
<DetailItem label='깃헙' value={applicant.gitHubLink} />
<DetailItem label='포트폴리오' value={applicant.portfolioLink} />
<div className='flex gap-[10px]'>
<button className='bg-gray-700 text-white px-2 py-1 rounded w-20'>
수정
Expand Down
2 changes: 2 additions & 0 deletions src/pages/admin/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,6 @@ export interface SomkathonApplicantDetail extends SomkathonApplicantListItem {
email: string
grade: string
department: string
gitHubLink: string
portfolioLink: string
}
Loading
Loading