From c61ef3fa2d41c9eecb3a9a1c89a036970af2eb8a Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:03:29 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix=20:=20=EB=AA=A8=EC=A7=91=20=EB=A7=88?= =?UTF-8?q?=EA=B0=90=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/summary/study-info-summary.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/summary/study-info-summary.tsx b/src/components/summary/study-info-summary.tsx index 32038c08..72a5c321 100644 --- a/src/components/summary/study-info-summary.tsx +++ b/src/components/summary/study-info-summary.tsx @@ -9,6 +9,7 @@ import Button from '@/components/ui/button'; import { GroupStudyFullResponse } from '@/features/study/group/api/group-study-types'; import ApplyGroupStudyModal from '@/features/study/group/ui/apply-group-study-modal'; import { useGetGroupStudyMyStatus } from '@/hooks/queries/group-study-member-api'; +import { useToastStore } from '@/stores/use-toast-store'; import { useUserStore } from '@/stores/useUserStore'; import { EXPERIENCE_LEVEL_LABELS, @@ -28,6 +29,7 @@ export default function SummaryStudyInfo({ data }: Props) { const queryClient = useQueryClient(); const [isExpanded, setIsExpanded] = useState(false); const memberId = useUserStore((state) => state.memberId); + const showToast = useToastStore((state) => state.showToast); const { basicInfo, detailInfo, interviewPost } = data; const { @@ -119,8 +121,12 @@ export default function SummaryStudyInfo({ data }: Props) { const visibleItems = isExpanded ? infoItems : infoItems.slice(0, 4); const handleCopyURL = async () => { - await navigator.clipboard.writeText(window.location.href); - alert('스터디 링크가 복사되었습니다!'); + try { + await navigator.clipboard.writeText(window.location.href); + showToast('스터디 링크가 복사되었습니다!'); + } catch { + showToast('클립보드 복사에 실패했습니다. 다시 시도해주세요.', 'error'); + } }; const handleApplySuccess = async () => { @@ -132,11 +138,16 @@ export default function SummaryStudyInfo({ data }: Props) { } }; + // 신청 마감 여부 체크 (스터디 시작일이 오늘 이전이거나 같은 경우) + const isDeadlinePassed = + !!startDate && !dayjs(startDate).isAfter(dayjs(), 'day'); + const isApplyDisabled = isLeader || myApplicationStatus?.status !== 'NONE' || groupStudyStatus !== 'RECRUITING' || - approvedCount >= maxMembersCount; + approvedCount >= maxMembersCount || + isDeadlinePassed; const getButtonText = () => { if (myApplicationStatus?.status === 'APPROVED') { @@ -148,6 +159,9 @@ export default function SummaryStudyInfo({ data }: Props) { if (myApplicationStatus?.status === 'REJECTED') { return '신청 거절됨'; } + if (isDeadlinePassed) { + return '모집 마감'; + } return '신청하기'; }; @@ -243,4 +257,4 @@ export default function SummaryStudyInfo({ data }: Props) { ); -} +} \ No newline at end of file From 2a5085b66b2f10898056ef5e1d80d1a1310faaa6 Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:13:52 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix=20:=20prettier=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/summary/study-info-summary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/summary/study-info-summary.tsx b/src/components/summary/study-info-summary.tsx index 72a5c321..711509cd 100644 --- a/src/components/summary/study-info-summary.tsx +++ b/src/components/summary/study-info-summary.tsx @@ -257,4 +257,4 @@ export default function SummaryStudyInfo({ data }: Props) { ); -} \ No newline at end of file +} From 58942959fbb5b1d0950ab63eaf7431178bc72756 Mon Sep 17 00:00:00 2001 From: Hyeonjun0527 Date: Fri, 13 Feb 2026 16:28:27 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83?= =?UTF-8?q?=20alert=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/client/axios.ts | 27 +++++++++++++++++++++++++-- src/api/client/axiosV2.ts | 27 +++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/api/client/axios.ts b/src/api/client/axios.ts index d20b8ad5..d40beff0 100644 --- a/src/api/client/axios.ts +++ b/src/api/client/axios.ts @@ -59,8 +59,7 @@ const refreshAccessToken = async (): Promise => { } return null; - } catch (error) { - alert('토큰 갱신에 실패했습니다. 다시 로그인해주세요'); + } catch { window.location.href = '/login'; return null; @@ -88,6 +87,20 @@ const processFailedQueue = (error: unknown, token: string | null = null) => { failedQueue = []; }; +const hasAuthToken = (requestConfig?: InternalAxiosRequestConfig) => { + const accessToken = getCookie('accessToken'); + if (accessToken) { + return true; + } + + const authorizationHeader = requestConfig?.headers?.Authorization; + if (Array.isArray(authorizationHeader)) { + return authorizationHeader.length > 0; + } + + return Boolean(authorizationHeader); +}; + axiosInstance.interceptors.response.use( (config) => config, async (error) => { @@ -102,6 +115,11 @@ axiosInstance.interceptors.response.use( // 유효하지 않은 accessToken인 경우, 재발급 if (errorResponseBody.errorCode === 'AUTH001') { + // 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음 + if (!hasAuthToken(originalRequest)) { + return Promise.reject(new ApiError(errorResponseBody)); + } + if (isRefreshing) { // 이미 토큰 갱신 중이면 대기열에 추가 return new Promise((resolve, reject) => { @@ -171,6 +189,11 @@ axiosInstanceForMultipart.interceptors.response.use( // 유효하지 않은 accessToken인 경우, 재발급 if (errorResponseBody.errorCode === 'AUTH001') { + // 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음 + if (!hasAuthToken(originalRequest)) { + return Promise.reject(new ApiError(errorResponseBody)); + } + if (isRefreshing) { // 이미 토큰 갱신 중이면 대기열에 추가 return new Promise((resolve, reject) => { diff --git a/src/api/client/axiosV2.ts b/src/api/client/axiosV2.ts index 9b227e08..585c7329 100644 --- a/src/api/client/axiosV2.ts +++ b/src/api/client/axiosV2.ts @@ -59,8 +59,7 @@ const refreshAccessToken = async (): Promise => { } return null; - } catch (error) { - alert('토큰 갱신에 실패했습니다. 다시 로그인해주세요'); + } catch { window.location.href = '/login'; return null; @@ -88,6 +87,20 @@ const processFailedQueue = (error: unknown, token: string | null = null) => { failedQueue = []; }; +const hasAuthToken = (requestConfig?: InternalAxiosRequestConfig) => { + const accessToken = getCookie('accessToken'); + if (accessToken) { + return true; + } + + const authorizationHeader = requestConfig?.headers?.Authorization; + if (Array.isArray(authorizationHeader)) { + return authorizationHeader.length > 0; + } + + return Boolean(authorizationHeader); +}; + axiosInstanceV2.interceptors.response.use( (config) => config, async (error) => { @@ -102,6 +115,11 @@ axiosInstanceV2.interceptors.response.use( // 유효하지 않은 accessToken인 경우, 재발급 if (errorResponseBody.errorCode === 'AUTH001') { + // 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음 + if (!hasAuthToken(originalRequest)) { + return Promise.reject(new ApiError(errorResponseBody)); + } + if (isRefreshing) { // 이미 토큰 갱신 중이면 대기열에 추가 return new Promise((resolve, reject) => { @@ -171,6 +189,11 @@ axiosInstanceForMultipartV2.interceptors.response.use( // 유효하지 않은 accessToken인 경우, 재발급 if (errorResponseBody.errorCode === 'AUTH001') { + // 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음 + if (!hasAuthToken(originalRequest)) { + return Promise.reject(new ApiError(errorResponseBody)); + } + if (isRefreshing) { // 이미 토큰 갱신 중이면 대기열에 추가 return new Promise((resolve, reject) => { From c108e0e16348a3ce5d2904e664d3f08237c9ab0a Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:31:38 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix=20:=20=EB=A6=AC=EB=8D=94=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20=EC=B0=B8=EA=B0=80=EC=9E=90=EB=8F=84=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=EB=90=9C=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EB=82=B4=20=EB=AF=B8=EC=85=98=EC=97=90=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/card/mission-card.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/card/mission-card.tsx b/src/components/card/mission-card.tsx index 62968583..d3bdb1de 100644 --- a/src/components/card/mission-card.tsx +++ b/src/components/card/mission-card.tsx @@ -87,8 +87,12 @@ function isCardClickable( ); } - // 비리더: 진행중, 평가완료만 클릭 가능 (제출마감은 클릭 불가) - return status === 'IN_PROGRESS' || status === 'EVALUATION_COMPLETED'; + // 비리더: 진행중, 제출마감, 평가완료 시 클릭 가능 + return ( + status === 'IN_PROGRESS' || + status === 'ENDED' || + status === 'EVALUATION_COMPLETED' + ); } export default function MissionCard({