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
27 changes: 25 additions & 2 deletions src/api/client/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
axiosInstanceForMultipart.interceptors.request.use(onRequestClient);

// refresh token을 사용해서 access token을 재갱신하는 함수
const refreshAccessToken = async (): Promise<string | null> => {

Check warning on line 44 in src/api/client/axios.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead

Check warning on line 44 in src/api/client/axios.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead
try {
const response = await axios.get<{ content: { accessToken: string } }>(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/auth/access-token/refresh`,
Expand All @@ -59,8 +59,7 @@
}

return null;
} catch (error) {
alert('토큰 갱신에 실패했습니다. 다시 로그인해주세요');
} catch {
window.location.href = '/login';

return null;
Expand All @@ -72,11 +71,11 @@
// 토큰 갱신을 기다리는 요청들 저장
let failedQueue: Array<{
resolve: (value: string) => void;
reject: (error: any) => void;

Check warning on line 74 in src/api/client/axios.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 74 in src/api/client/axios.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
}> = [];

// 대기 중인 요청들을 처리하는 함수
const processFailedQueue = (error: unknown, token: string | null = null) => {

Check warning on line 78 in src/api/client/axios.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead

Check warning on line 78 in src/api/client/axios.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
Expand All @@ -88,6 +87,20 @@
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) => {
Expand All @@ -102,6 +115,11 @@

// 유효하지 않은 accessToken인 경우, 재발급
if (errorResponseBody.errorCode === 'AUTH001') {
// 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음
if (!hasAuthToken(originalRequest)) {
return Promise.reject(new ApiError(errorResponseBody));
}

if (isRefreshing) {
// 이미 토큰 갱신 중이면 대기열에 추가
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -171,6 +189,11 @@

// 유효하지 않은 accessToken인 경우, 재발급
if (errorResponseBody.errorCode === 'AUTH001') {
// 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음
if (!hasAuthToken(originalRequest)) {
return Promise.reject(new ApiError(errorResponseBody));
}

if (isRefreshing) {
// 이미 토큰 갱신 중이면 대기열에 추가
return new Promise((resolve, reject) => {
Expand Down
27 changes: 25 additions & 2 deletions src/api/client/axiosV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
axiosInstanceForMultipartV2.interceptors.request.use(onRequestClient);

// refresh token을 사용해서 access token을 재갱신하는 함수
const refreshAccessToken = async (): Promise<string | null> => {

Check warning on line 44 in src/api/client/axiosV2.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead

Check warning on line 44 in src/api/client/axiosV2.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead
try {
const response = await axios.get<{ content: { accessToken: string } }>(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/auth/access-token/refresh`,
Expand All @@ -59,8 +59,7 @@
}

return null;
} catch (error) {
alert('토큰 갱신에 실패했습니다. 다시 로그인해주세요');
} catch {
window.location.href = '/login';

return null;
Expand All @@ -72,11 +71,11 @@
// 토큰 갱신을 기다리는 요청들 저장
let failedQueue: Array<{
resolve: (value: string) => void;
reject: (error: any) => void;

Check warning on line 74 in src/api/client/axiosV2.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 74 in src/api/client/axiosV2.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
}> = [];

// 대기 중인 요청들을 처리하는 함수
const processFailedQueue = (error: unknown, token: string | null = null) => {

Check warning on line 78 in src/api/client/axiosV2.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead

Check warning on line 78 in src/api/client/axiosV2.ts

View workflow job for this annotation

GitHub Actions / lint

Usage of "null" is deprecated except when describing legacy APIs; use "undefined" instead
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
Expand All @@ -88,6 +87,20 @@
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) => {
Expand All @@ -102,6 +115,11 @@

// 유효하지 않은 accessToken인 경우, 재발급
if (errorResponseBody.errorCode === 'AUTH001') {
// 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음
if (!hasAuthToken(originalRequest)) {
return Promise.reject(new ApiError(errorResponseBody));
}

if (isRefreshing) {
// 이미 토큰 갱신 중이면 대기열에 추가
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -171,6 +189,11 @@

// 유효하지 않은 accessToken인 경우, 재발급
if (errorResponseBody.errorCode === 'AUTH001') {
// 로그아웃 직후/비회원 요청처럼 인증 정보가 없는 경우에는 재발급 시도를 하지 않음
if (!hasAuthToken(originalRequest)) {
return Promise.reject(new ApiError(errorResponseBody));
}

if (isRefreshing) {
// 이미 토큰 갱신 중이면 대기열에 추가
return new Promise((resolve, reject) => {
Expand Down
8 changes: 6 additions & 2 deletions src/components/card/mission-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
20 changes: 17 additions & 3 deletions src/components/summary/study-info-summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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') {
Expand All @@ -148,6 +159,9 @@ export default function SummaryStudyInfo({ data }: Props) {
if (myApplicationStatus?.status === 'REJECTED') {
return '신청 거절됨';
}
if (isDeadlinePassed) {
return '모집 마감';
}

return '신청하기';
};
Expand Down
Loading