Skip to content

Commit 47ccade

Browse files
authored
hotfix: 면접 일정 및 공개 API 토큰 수정
1 parent 345a612 commit 47ccade

2 files changed

Lines changed: 58 additions & 22 deletions

File tree

src/pages/recruit/RecruitInfo.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ const RecruitInfo: React.FC = () => {
1414

1515
const navigate = useNavigate()
1616
const { loadSchedule } = useRecruitSchedule()
17-
const [periodData, setPeriodData] = useState({
18-
recruitmentPeriodStart: '',
19-
recruitmentPeriodEnd: '',
20-
documentPassAnnouncement: '',
21-
interviewPeriodStart: '',
22-
interviewPeriodEnd: '',
23-
interviewPassAnnouncement: '',
24-
})
17+
18+
// 기본 모집 일정 (API 실패 시 사용)
19+
const defaultPeriodData = {
20+
recruitmentPeriodStart: '2026-02-23', // 02월 23일
21+
recruitmentPeriodEnd: '2026-03-10', // 03월 10일
22+
documentPassAnnouncement: '2026-03-11', // 03월 11일
23+
interviewPeriodStart: '2026-03-11', // 03월 11일
24+
interviewPeriodEnd: '2026-03-13', // 03월 13일
25+
interviewPassAnnouncement: '2026-03-14', // 03월 14일
26+
}
27+
28+
const [periodData, setPeriodData] = useState(defaultPeriodData)
2529
const [buttonState, setButtonState] = useState({
2630
text: '',
2731
disabled: false,
@@ -47,10 +51,11 @@ const RecruitInfo: React.FC = () => {
4751
} = periodData
4852

4953
if (!recruitmentPeriodStart || !recruitmentPeriodEnd) {
54+
// API 호출 실패 시 기본값으로 지원하기 버튼 표시
5055
return {
51-
text: '모집 일정 준비 중',
52-
disabled: true,
53-
onClick: () => {},
56+
text: `${currentGeneration}기 지원하기`,
57+
disabled: false,
58+
onClick: () => navigate('/recruit'),
5459
}
5560
}
5661

@@ -133,16 +138,18 @@ const RecruitInfo: React.FC = () => {
133138
const fetchData = async () => {
134139
try {
135140
const { scheduleData } = await loadSchedule()
141+
// API 성공 시 서버 데이터로 업데이트
136142
setPeriodData({
137-
recruitmentPeriodStart: scheduleData.recruitmentPeriodStart,
138-
recruitmentPeriodEnd: scheduleData.recruitmentPeriodEnd,
139-
documentPassAnnouncement: scheduleData.documentPassAnnouncement,
140-
interviewPeriodStart: scheduleData.interviewPeriodStart,
141-
interviewPeriodEnd: scheduleData.interviewPeriodEnd,
142-
interviewPassAnnouncement: scheduleData.interviewPassAnnouncement,
143+
recruitmentPeriodStart: scheduleData.recruitmentPeriodStart || defaultPeriodData.recruitmentPeriodStart,
144+
recruitmentPeriodEnd: scheduleData.recruitmentPeriodEnd || defaultPeriodData.recruitmentPeriodEnd,
145+
documentPassAnnouncement: scheduleData.documentPassAnnouncement || defaultPeriodData.documentPassAnnouncement,
146+
interviewPeriodStart: scheduleData.interviewPeriodStart || defaultPeriodData.interviewPeriodStart,
147+
interviewPeriodEnd: scheduleData.interviewPeriodEnd || defaultPeriodData.interviewPeriodEnd,
148+
interviewPassAnnouncement: scheduleData.interviewPassAnnouncement || defaultPeriodData.interviewPassAnnouncement,
143149
})
144150
} catch (error) {
145-
console.error('Error fetching recruit configs:', error)
151+
console.warn('API를 통한 모집 일정 조회 실패, 기본 일정을 사용합니다:', error)
152+
// API 실패 시 기본 일정 데이터 유지 (이미 useState로 설정됨)
146153
}
147154
}
148155

src/utils/apiClient.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,27 @@ apiClient.interceptors.request.use(
6161
const isAuthRequest = config.url?.includes('/auth/') &&
6262
(config.url?.includes('login') || config.url?.includes('refresh'))
6363

64-
// 명시적으로 제공되지 않은 경우 Authorization 헤더 자동 부착 (로그인 요청 제외)
65-
if (accessToken && !config.headers['Authorization'] && !isAuthRequest) {
64+
// 공개 API 엔드포인트들 (토큰 없이 접근 가능)
65+
const publicEndpoints = ['/news', '/recruit', '/health', '/activities', '/executives']
66+
const isPublicEndpoint = publicEndpoints.some(endpoint => config.url?.includes(endpoint))
67+
68+
// 공개 엔드포인트는 토큰 없이도 작동하도록 처리
69+
if (isPublicEndpoint) {
70+
// 토큰이 있고 유효한 경우에만 추가 (선택적)
71+
if (accessToken) {
72+
try {
73+
const payload = JSON.parse(atob(accessToken.split('.')[1]))
74+
const currentTime = Date.now() / 1000
75+
if (payload.exp > currentTime) {
76+
config.headers['Authorization'] = `Bearer ${accessToken}`
77+
}
78+
} catch {
79+
// 토큰 파싱 실패 시 토큰 없이 진행
80+
}
81+
}
82+
// 토큰이 없거나 유효하지 않아도 공개 API는 그대로 진행
83+
} else if (accessToken && !isAuthRequest && !config.headers['Authorization']) {
84+
// 비공개 API는 항상 토큰 추가
6685
config.headers['Authorization'] = `Bearer ${accessToken}`
6786
}
6887

@@ -92,6 +111,16 @@ apiClient.interceptors.response.use(
92111
async error => {
93112
const originalRequest = error.config
94113

114+
// 공개 API 엔드포인트 확인
115+
const publicEndpoints = ['/news', '/recruit', '/health', '/activities', '/executives']
116+
const isPublicEndpoint = publicEndpoints.some(endpoint => originalRequest?.url?.includes(endpoint))
117+
118+
// 공개 API에서 401 에러가 발생한 경우, 로그인 리다이렉트 없이 에러만 반환
119+
if (isPublicEndpoint && error.response?.status === 401) {
120+
console.log('공개 API에서 401 에러 발생 - 로그인 리다이렉트하지 않음')
121+
return Promise.reject(error)
122+
}
123+
95124
// 401 Unauthorized 에러이고 토큰 갱신을 시도하지 않은 경우
96125
if (error.response?.status === 401 && !originalRequest._retry) {
97126
originalRequest._retry = true
@@ -123,8 +152,8 @@ apiClient.interceptors.response.use(
123152
}
124153
}
125154

126-
// 401 에러가 지속되거나 다른 인증 관련 에러인 경우
127-
if (error.response?.status === 401 || error.response?.status === 403) {
155+
// 401 에러가 지속되거나 다른 인증 관련 에러인 경우 (공개 API가 아닐 때만)
156+
if (!isPublicEndpoint && (error.response?.status === 401 || error.response?.status === 403)) {
128157
console.log('인증이 필요합니다. 로그인 페이지로 리다이렉트합니다.')
129158
removeAllTokens()
130159
if (typeof window !== 'undefined') {

0 commit comments

Comments
 (0)