From ad4a4aabcc272086727a63d2ce5ca0fcea88dc9e Mon Sep 17 00:00:00 2001 From: Jenkins Date: Sat, 17 Jan 2026 03:37:05 +0000 Subject: [PATCH 01/55] feat: new OpenAPI (automated) --- src/api/openapi/docs/TossWebhookPayload.md | 8 ++++++-- src/api/openapi/models/toss-webhook-payload.ts | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/api/openapi/docs/TossWebhookPayload.md b/src/api/openapi/docs/TossWebhookPayload.md index b653e662..29e719d6 100644 --- a/src/api/openapi/docs/TossWebhookPayload.md +++ b/src/api/openapi/docs/TossWebhookPayload.md @@ -8,8 +8,10 @@ Name | Type | Description | Notes **eventType** | **string** | | [optional] [default to undefined] **createdAt** | **string** | | [optional] [default to undefined] **data** | [**PaymentData**](PaymentData.md) | | [optional] [default to undefined] -**paymentStatusChanged** | **boolean** | | [optional] [default to undefined] +**paymentRelatedEvent** | **boolean** | | [optional] [default to undefined] **latestCancel** | [**CancelData**](CancelData.md) | | [optional] [default to undefined] +**paymentStatusChanged** | **boolean** | | [optional] [default to undefined] +**depositCallback** | **boolean** | | [optional] [default to undefined] ## Example @@ -20,8 +22,10 @@ const instance: TossWebhookPayload = { eventType, createdAt, data, - paymentStatusChanged, + paymentRelatedEvent, latestCancel, + paymentStatusChanged, + depositCallback, }; ``` diff --git a/src/api/openapi/models/toss-webhook-payload.ts b/src/api/openapi/models/toss-webhook-payload.ts index bd3385c4..c6c9f33a 100644 --- a/src/api/openapi/models/toss-webhook-payload.ts +++ b/src/api/openapi/models/toss-webhook-payload.ts @@ -24,7 +24,9 @@ export interface TossWebhookPayload { 'eventType'?: string; 'createdAt'?: string; 'data'?: PaymentData; - 'paymentStatusChanged'?: boolean; + 'paymentRelatedEvent'?: boolean; 'latestCancel'?: CancelData; + 'paymentStatusChanged'?: boolean; + 'depositCallback'?: boolean; } From 192f9ed1e347984304a7f2bcd8ef45ed67227b4b Mon Sep 17 00:00:00 2001 From: Jenkins Date: Sat, 17 Jan 2026 06:38:57 +0000 Subject: [PATCH 02/55] feat: new OpenAPI (automated) --- src/api/openapi/.openapi-generator/FILES | 4 +-- src/api/openapi/api/member-api.ts | 30 +++++++++---------- src/api/openapi/docs/MemberApi.md | 10 +++---- ...Request.md => MemberCreationRequestDto.md} | 6 ++-- src/api/openapi/models/index.ts | 2 +- ...uest.ts => member-creation-request-dto.ts} | 26 ++++++++-------- 6 files changed, 39 insertions(+), 39 deletions(-) rename src/api/openapi/docs/{MemberCreationRequest.md => MemberCreationRequestDto.md} (92%) rename src/api/openapi/models/{member-creation-request.ts => member-creation-request-dto.ts} (67%) diff --git a/src/api/openapi/.openapi-generator/FILES b/src/api/openapi/.openapi-generator/FILES index 8ed16123..7d3f9f80 100644 --- a/src/api/openapi/.openapi-generator/FILES +++ b/src/api/openapi/.openapi-generator/FILES @@ -194,7 +194,7 @@ docs/MatchingSystemStatusResponse.md docs/MatchingSystemStatusSchema.md docs/MemberAccountHistoryResponseDto.md docs/MemberApi.md -docs/MemberCreationRequest.md +docs/MemberCreationRequestDto.md docs/MemberCreationResponse.md docs/MemberCreationResponseContent.md docs/MemberFeature.md @@ -480,7 +480,7 @@ models/matching-request-response.ts models/matching-system-status-response.ts models/matching-system-status-schema.ts models/member-account-history-response-dto.ts -models/member-creation-request.ts +models/member-creation-request-dto.ts models/member-creation-response-content.ts models/member-creation-response.ts models/member-feature.ts diff --git a/src/api/openapi/api/member-api.ts b/src/api/openapi/api/member-api.ts index b91c9e9b..49adfdd2 100644 --- a/src/api/openapi/api/member-api.ts +++ b/src/api/openapi/api/member-api.ts @@ -24,7 +24,7 @@ import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError // @ts-ignore import type { ErrorResponse } from '../models'; // @ts-ignore -import type { MemberCreationRequest } from '../models'; +import type { MemberCreationRequestDto } from '../models'; // @ts-ignore import type { MemberCreationResponse } from '../models'; // @ts-ignore @@ -203,13 +203,13 @@ export const MemberApiAxiosParamCreator = function (configuration?: Configuratio /** * 회원가입을 진행하는 엔드포인트 - nickname: 필수, 2~20자 한글/영문/숫자, 특수문자 불가 (중복 체크 필수) - loginId: 선택(소셜 로그인 시 비움), 일반 로그인용 식별자 - jobs: 선택, Enum 리스트 (최대 5개) 값 = [IT_NOBASE_BUSINESS_STARTUP, IT_NOBASE_AUTOMATION, IT_NOBASE_MY_SERVICE, IT_PRACTITIONER_PM_PO_PLANNING, IT_PRACTITIONER_FRONTEND, IT_PRACTITIONER_BACKEND, IT_PRACTITIONER_AI_ML, IT_PRACTITIONER_IOS, IT_PRACTITIONER_ANDROID, IT_PRACTITIONER_DEVOPS, IT_PRACTITIONER_DATA_ANALYSIS, IT_PRACTITIONER_QA, IT_PRACTITIONER_GAME_DEV, IT_PRACTITIONER_DESIGN, IT_PRACTITIONER_MARKETING, IT_PRACTITIONER_ETC] - career: 선택, Enum 값 = [BEGINNER, JOB_SEEKER, JUNIOR, MIDDLE, SENIOR] - studyFormatTypes: 선택, Enum 값 = [PROJECT, MENTORING, SEMINAR, CHALLENGE, BOOK_LECTURE] - goal: 선택, 자유 텍스트 입력(최대 100자) - imageExtension: 선택, Enum 값 = [DEFAULT, JPG, PNG, GIF, WEBP, SVG, JPEG] * @summary [회원가입/로그인 팝업] 회원가입 - * @param {MemberCreationRequest} memberCreationRequest + * @param {MemberCreationRequestDto} memberCreationRequestDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - signUp: async (memberCreationRequest: MemberCreationRequest, options: RawAxiosRequestConfig = {}): Promise => { - // verify required parameter 'memberCreationRequest' is not null or undefined - assertParamExists('signUp', 'memberCreationRequest', memberCreationRequest) + signUp: async (memberCreationRequestDto: MemberCreationRequestDto, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'memberCreationRequestDto' is not null or undefined + assertParamExists('signUp', 'memberCreationRequestDto', memberCreationRequestDto) const localVarPath = `/api/v1/members`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -233,7 +233,7 @@ export const MemberApiAxiosParamCreator = function (configuration?: Configuratio setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(memberCreationRequest, localVarRequestOptions, configuration) + localVarRequestOptions.data = serializeDataIfNeeded(memberCreationRequestDto, localVarRequestOptions, configuration) return { url: toPathString(localVarUrlObj), @@ -300,12 +300,12 @@ export const MemberApiFp = function(configuration?: Configuration) { /** * 회원가입을 진행하는 엔드포인트 - nickname: 필수, 2~20자 한글/영문/숫자, 특수문자 불가 (중복 체크 필수) - loginId: 선택(소셜 로그인 시 비움), 일반 로그인용 식별자 - jobs: 선택, Enum 리스트 (최대 5개) 값 = [IT_NOBASE_BUSINESS_STARTUP, IT_NOBASE_AUTOMATION, IT_NOBASE_MY_SERVICE, IT_PRACTITIONER_PM_PO_PLANNING, IT_PRACTITIONER_FRONTEND, IT_PRACTITIONER_BACKEND, IT_PRACTITIONER_AI_ML, IT_PRACTITIONER_IOS, IT_PRACTITIONER_ANDROID, IT_PRACTITIONER_DEVOPS, IT_PRACTITIONER_DATA_ANALYSIS, IT_PRACTITIONER_QA, IT_PRACTITIONER_GAME_DEV, IT_PRACTITIONER_DESIGN, IT_PRACTITIONER_MARKETING, IT_PRACTITIONER_ETC] - career: 선택, Enum 값 = [BEGINNER, JOB_SEEKER, JUNIOR, MIDDLE, SENIOR] - studyFormatTypes: 선택, Enum 값 = [PROJECT, MENTORING, SEMINAR, CHALLENGE, BOOK_LECTURE] - goal: 선택, 자유 텍스트 입력(최대 100자) - imageExtension: 선택, Enum 값 = [DEFAULT, JPG, PNG, GIF, WEBP, SVG, JPEG] * @summary [회원가입/로그인 팝업] 회원가입 - * @param {MemberCreationRequest} memberCreationRequest + * @param {MemberCreationRequestDto} memberCreationRequestDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async signUp(memberCreationRequest: MemberCreationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.signUp(memberCreationRequest, options); + async signUp(memberCreationRequestDto: MemberCreationRequestDto, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.signUp(memberCreationRequestDto, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['MemberApi.signUp']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); @@ -361,12 +361,12 @@ export const MemberApiFactory = function (configuration?: Configuration, basePat /** * 회원가입을 진행하는 엔드포인트 - nickname: 필수, 2~20자 한글/영문/숫자, 특수문자 불가 (중복 체크 필수) - loginId: 선택(소셜 로그인 시 비움), 일반 로그인용 식별자 - jobs: 선택, Enum 리스트 (최대 5개) 값 = [IT_NOBASE_BUSINESS_STARTUP, IT_NOBASE_AUTOMATION, IT_NOBASE_MY_SERVICE, IT_PRACTITIONER_PM_PO_PLANNING, IT_PRACTITIONER_FRONTEND, IT_PRACTITIONER_BACKEND, IT_PRACTITIONER_AI_ML, IT_PRACTITIONER_IOS, IT_PRACTITIONER_ANDROID, IT_PRACTITIONER_DEVOPS, IT_PRACTITIONER_DATA_ANALYSIS, IT_PRACTITIONER_QA, IT_PRACTITIONER_GAME_DEV, IT_PRACTITIONER_DESIGN, IT_PRACTITIONER_MARKETING, IT_PRACTITIONER_ETC] - career: 선택, Enum 값 = [BEGINNER, JOB_SEEKER, JUNIOR, MIDDLE, SENIOR] - studyFormatTypes: 선택, Enum 값 = [PROJECT, MENTORING, SEMINAR, CHALLENGE, BOOK_LECTURE] - goal: 선택, 자유 텍스트 입력(최대 100자) - imageExtension: 선택, Enum 값 = [DEFAULT, JPG, PNG, GIF, WEBP, SVG, JPEG] * @summary [회원가입/로그인 팝업] 회원가입 - * @param {MemberCreationRequest} memberCreationRequest + * @param {MemberCreationRequestDto} memberCreationRequestDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - signUp(memberCreationRequest: MemberCreationRequest, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.signUp(memberCreationRequest, options).then((request) => request(axios, basePath)); + signUp(memberCreationRequestDto: MemberCreationRequestDto, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.signUp(memberCreationRequestDto, options).then((request) => request(axios, basePath)); }, }; }; @@ -420,12 +420,12 @@ export class MemberApi extends BaseAPI { /** * 회원가입을 진행하는 엔드포인트 - nickname: 필수, 2~20자 한글/영문/숫자, 특수문자 불가 (중복 체크 필수) - loginId: 선택(소셜 로그인 시 비움), 일반 로그인용 식별자 - jobs: 선택, Enum 리스트 (최대 5개) 값 = [IT_NOBASE_BUSINESS_STARTUP, IT_NOBASE_AUTOMATION, IT_NOBASE_MY_SERVICE, IT_PRACTITIONER_PM_PO_PLANNING, IT_PRACTITIONER_FRONTEND, IT_PRACTITIONER_BACKEND, IT_PRACTITIONER_AI_ML, IT_PRACTITIONER_IOS, IT_PRACTITIONER_ANDROID, IT_PRACTITIONER_DEVOPS, IT_PRACTITIONER_DATA_ANALYSIS, IT_PRACTITIONER_QA, IT_PRACTITIONER_GAME_DEV, IT_PRACTITIONER_DESIGN, IT_PRACTITIONER_MARKETING, IT_PRACTITIONER_ETC] - career: 선택, Enum 값 = [BEGINNER, JOB_SEEKER, JUNIOR, MIDDLE, SENIOR] - studyFormatTypes: 선택, Enum 값 = [PROJECT, MENTORING, SEMINAR, CHALLENGE, BOOK_LECTURE] - goal: 선택, 자유 텍스트 입력(최대 100자) - imageExtension: 선택, Enum 값 = [DEFAULT, JPG, PNG, GIF, WEBP, SVG, JPEG] * @summary [회원가입/로그인 팝업] 회원가입 - * @param {MemberCreationRequest} memberCreationRequest + * @param {MemberCreationRequestDto} memberCreationRequestDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - public signUp(memberCreationRequest: MemberCreationRequest, options?: RawAxiosRequestConfig) { - return MemberApiFp(this.configuration).signUp(memberCreationRequest, options).then((request) => request(this.axios, this.basePath)); + public signUp(memberCreationRequestDto: MemberCreationRequestDto, options?: RawAxiosRequestConfig) { + return MemberApiFp(this.configuration).signUp(memberCreationRequestDto, options).then((request) => request(this.axios, this.basePath)); } } diff --git a/src/api/openapi/docs/MemberApi.md b/src/api/openapi/docs/MemberApi.md index 8aaff78d..9bd2e5bb 100644 --- a/src/api/openapi/docs/MemberApi.md +++ b/src/api/openapi/docs/MemberApi.md @@ -194,7 +194,7 @@ const { status, data } = await apiInstance.getParticipatingStudies( [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **signUp** -> MemberCreationResponse signUp(memberCreationRequest) +> MemberCreationResponse signUp(memberCreationRequestDto) 회원가입을 진행하는 엔드포인트 - nickname: 필수, 2~20자 한글/영문/숫자, 특수문자 불가 (중복 체크 필수) - loginId: 선택(소셜 로그인 시 비움), 일반 로그인용 식별자 - jobs: 선택, Enum 리스트 (최대 5개) 값 = [IT_NOBASE_BUSINESS_STARTUP, IT_NOBASE_AUTOMATION, IT_NOBASE_MY_SERVICE, IT_PRACTITIONER_PM_PO_PLANNING, IT_PRACTITIONER_FRONTEND, IT_PRACTITIONER_BACKEND, IT_PRACTITIONER_AI_ML, IT_PRACTITIONER_IOS, IT_PRACTITIONER_ANDROID, IT_PRACTITIONER_DEVOPS, IT_PRACTITIONER_DATA_ANALYSIS, IT_PRACTITIONER_QA, IT_PRACTITIONER_GAME_DEV, IT_PRACTITIONER_DESIGN, IT_PRACTITIONER_MARKETING, IT_PRACTITIONER_ETC] - career: 선택, Enum 값 = [BEGINNER, JOB_SEEKER, JUNIOR, MIDDLE, SENIOR] - studyFormatTypes: 선택, Enum 값 = [PROJECT, MENTORING, SEMINAR, CHALLENGE, BOOK_LECTURE] - goal: 선택, 자유 텍스트 입력(최대 100자) - imageExtension: 선택, Enum 값 = [DEFAULT, JPG, PNG, GIF, WEBP, SVG, JPEG] @@ -204,16 +204,16 @@ const { status, data } = await apiInstance.getParticipatingStudies( import { MemberApi, Configuration, - MemberCreationRequest + MemberCreationRequestDto } from './api'; const configuration = new Configuration(); const apiInstance = new MemberApi(configuration); -let memberCreationRequest: MemberCreationRequest; // +let memberCreationRequestDto: MemberCreationRequestDto; // const { status, data } = await apiInstance.signUp( - memberCreationRequest + memberCreationRequestDto ); ``` @@ -221,7 +221,7 @@ const { status, data } = await apiInstance.signUp( |Name | Type | Description | Notes| |------------- | ------------- | ------------- | -------------| -| **memberCreationRequest** | **MemberCreationRequest**| | | +| **memberCreationRequestDto** | **MemberCreationRequestDto**| | | ### Return type diff --git a/src/api/openapi/docs/MemberCreationRequest.md b/src/api/openapi/docs/MemberCreationRequestDto.md similarity index 92% rename from src/api/openapi/docs/MemberCreationRequest.md rename to src/api/openapi/docs/MemberCreationRequestDto.md index b73f99a0..68b4256e 100644 --- a/src/api/openapi/docs/MemberCreationRequest.md +++ b/src/api/openapi/docs/MemberCreationRequestDto.md @@ -1,4 +1,4 @@ -# MemberCreationRequest +# MemberCreationRequestDto ## Properties @@ -17,9 +17,9 @@ Name | Type | Description | Notes ## Example ```typescript -import { MemberCreationRequest } from './api'; +import { MemberCreationRequestDto } from './api'; -const instance: MemberCreationRequest = { +const instance: MemberCreationRequestDto = { loginId, nickname, name, diff --git a/src/api/openapi/models/index.ts b/src/api/openapi/models/index.ts index 4f090dde..02c29077 100644 --- a/src/api/openapi/models/index.ts +++ b/src/api/openapi/models/index.ts @@ -129,7 +129,7 @@ export * from './matching-request-response'; export * from './matching-system-status-response'; export * from './matching-system-status-schema'; export * from './member-account-history-response-dto'; -export * from './member-creation-request'; +export * from './member-creation-request-dto'; export * from './member-creation-response'; export * from './member-creation-response-content'; export * from './member-feature'; diff --git a/src/api/openapi/models/member-creation-request.ts b/src/api/openapi/models/member-creation-request-dto.ts similarity index 67% rename from src/api/openapi/models/member-creation-request.ts rename to src/api/openapi/models/member-creation-request-dto.ts index d2200851..a39d289e 100644 --- a/src/api/openapi/models/member-creation-request.ts +++ b/src/api/openapi/models/member-creation-request-dto.ts @@ -14,7 +14,7 @@ -export interface MemberCreationRequest { +export interface MemberCreationRequestDto { /** * 회원의 로그인 아이디(소셜 로그인 시 비어있음) */ @@ -30,15 +30,15 @@ export interface MemberCreationRequest { /** * 직무 리스트 (최대 5개) (예: [IT_PRACTITIONER_BACKEND, IT_PRACTITIONER_FRONTEND]) */ - 'jobs'?: Array; + 'jobs'?: Array; /** * 경력 (예: JUNIOR) */ - 'career'?: MemberCreationRequestCareerEnum; + 'career'?: MemberCreationRequestDtoCareerEnum; /** * 관심 스터디 유형 리스트 (예: [\"PROJECT\", \"SEMINAR\"]) */ - 'studyFormatTypes'?: Array; + 'studyFormatTypes'?: Array; /** * 목표 및 다짐 (최대 100자) */ @@ -46,10 +46,10 @@ export interface MemberCreationRequest { /** * 이미지 확장자 - DEFAULT, JPG, PNG, GIF, WEBP, SVG, JPEG */ - 'imageExtension'?: MemberCreationRequestImageExtensionEnum; + 'imageExtension'?: MemberCreationRequestDtoImageExtensionEnum; } -export const MemberCreationRequestJobsEnum = { +export const MemberCreationRequestDtoJobsEnum = { ItNobaseBusinessStartup: 'IT_NOBASE_BUSINESS_STARTUP', ItNobaseAutomation: 'IT_NOBASE_AUTOMATION', ItNobaseMyService: 'IT_NOBASE_MY_SERVICE', @@ -68,8 +68,8 @@ export const MemberCreationRequestJobsEnum = { ItPractitionerEtc: 'IT_PRACTITIONER_ETC' } as const; -export type MemberCreationRequestJobsEnum = typeof MemberCreationRequestJobsEnum[keyof typeof MemberCreationRequestJobsEnum]; -export const MemberCreationRequestCareerEnum = { +export type MemberCreationRequestDtoJobsEnum = typeof MemberCreationRequestDtoJobsEnum[keyof typeof MemberCreationRequestDtoJobsEnum]; +export const MemberCreationRequestDtoCareerEnum = { Beginner: 'BEGINNER', JobSeeker: 'JOB_SEEKER', Junior: 'JUNIOR', @@ -77,8 +77,8 @@ export const MemberCreationRequestCareerEnum = { Senior: 'SENIOR' } as const; -export type MemberCreationRequestCareerEnum = typeof MemberCreationRequestCareerEnum[keyof typeof MemberCreationRequestCareerEnum]; -export const MemberCreationRequestStudyFormatTypesEnum = { +export type MemberCreationRequestDtoCareerEnum = typeof MemberCreationRequestDtoCareerEnum[keyof typeof MemberCreationRequestDtoCareerEnum]; +export const MemberCreationRequestDtoStudyFormatTypesEnum = { Project: 'PROJECT', Mentoring: 'MENTORING', Seminar: 'SEMINAR', @@ -86,8 +86,8 @@ export const MemberCreationRequestStudyFormatTypesEnum = { BookLecture: 'BOOK_LECTURE' } as const; -export type MemberCreationRequestStudyFormatTypesEnum = typeof MemberCreationRequestStudyFormatTypesEnum[keyof typeof MemberCreationRequestStudyFormatTypesEnum]; -export const MemberCreationRequestImageExtensionEnum = { +export type MemberCreationRequestDtoStudyFormatTypesEnum = typeof MemberCreationRequestDtoStudyFormatTypesEnum[keyof typeof MemberCreationRequestDtoStudyFormatTypesEnum]; +export const MemberCreationRequestDtoImageExtensionEnum = { Default: 'DEFAULT', Jpg: 'JPG', Png: 'PNG', @@ -97,6 +97,6 @@ export const MemberCreationRequestImageExtensionEnum = { Jpeg: 'JPEG' } as const; -export type MemberCreationRequestImageExtensionEnum = typeof MemberCreationRequestImageExtensionEnum[keyof typeof MemberCreationRequestImageExtensionEnum]; +export type MemberCreationRequestDtoImageExtensionEnum = typeof MemberCreationRequestDtoImageExtensionEnum[keyof typeof MemberCreationRequestDtoImageExtensionEnum]; From af632e8cce19940d79f945e0390584058f9d952e Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:53:08 +0900 Subject: [PATCH 03/55] =?UTF-8?q?fix:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/api/{creat-group-study.ts => create-group-study.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/features/study/group/api/{creat-group-study.ts => create-group-study.ts} (100%) diff --git a/src/features/study/group/api/creat-group-study.ts b/src/features/study/group/api/create-group-study.ts similarity index 100% rename from src/features/study/group/api/creat-group-study.ts rename to src/features/study/group/api/create-group-study.ts From 17a41fd19986e2e161991914362cf88b78aea249 Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:53:30 +0900 Subject: [PATCH 04/55] =?UTF-8?q?fix:=20createGroupStudy=20=EC=9E=84?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/study/group/const/use-group-study-mutation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/study/group/const/use-group-study-mutation.ts b/src/features/study/group/const/use-group-study-mutation.ts index b7aab825..848ffd30 100644 --- a/src/features/study/group/const/use-group-study-mutation.ts +++ b/src/features/study/group/const/use-group-study-mutation.ts @@ -1,5 +1,5 @@ import { useMutation } from '@tanstack/react-query'; -import { createGroupStudy } from '../api/creat-group-study'; +import { createGroupStudy } from '../api/create-group-study'; import { GroupStudyFormRequest } from '../api/group-study-types'; import { updateGroupStudy } from '../api/update-group-study'; From 68444859f1b3e8257e8ef93b4021d1ab2319d43c Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:20:46 +0900 Subject: [PATCH 05/55] =?UTF-8?q?fix:=20=EB=AF=B8=EC=85=98=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=EC=97=90=20=EB=82=A0=EC=A7=9C=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EB=A7=A4=EC=B2=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modals/create-mission-modal.tsx | 31 ++++++++-- src/components/modals/edit-mission-modal.tsx | 43 ++++++++++--- .../use-mission-date-disabled-matcher.ts | 33 ++++++++++ src/utils/time.ts | 61 +++++++++++++++++-- 4 files changed, 148 insertions(+), 20 deletions(-) create mode 100644 src/hooks/common/use-mission-date-disabled-matcher.ts diff --git a/src/components/modals/create-mission-modal.tsx b/src/components/modals/create-mission-modal.tsx index 4cd2c7f5..f620779e 100644 --- a/src/components/modals/create-mission-modal.tsx +++ b/src/components/modals/create-mission-modal.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import dayjs from 'dayjs'; import { Plus, XIcon } from 'lucide-react'; import { useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; @@ -9,8 +10,11 @@ import FormField from '@/components/ui/form/form-field'; import { BaseInput, TextAreaInput } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; import { useGroupStudyDetailQuery } from '@/features/study/group/model/use-study-query'; -import { useCreateMission } from '@/hooks/queries/mission-api'; -import { createDateDisabledMatcher } from '@/utils/time'; +import { + MissionPeriod, + useMissionDateDisabledMatcher, +} from '@/hooks/common/use-mission-date-disabled-matcher'; +import { useCreateMission, useGetMissions } from '@/hooks/queries/mission-api'; // Form Schema const CreateMissionFormSchema = z.object({ @@ -45,7 +49,12 @@ export default function CreateMissionModal({ }: CreateMissionModalProps) { const [open, setOpen] = useState(false); const { data: studyData } = useGroupStudyDetailQuery(groupStudyId); + const { data: existingMissions } = useGetMissions({ + groupStudyId, + pageSize: 100, + }); + const studyStartDate = studyData?.basicInfo?.startDate; const studyEndDate = studyData?.basicInfo?.endDate; return ( @@ -76,7 +85,9 @@ export default function CreateMissionModal({ setOpen(false)} /> @@ -87,15 +98,25 @@ export default function CreateMissionModal({ interface CreateMissionFormProps { groupStudyId: number; + studyStartDate?: string; studyEndDate?: string; + existingMissions?: MissionPeriod[]; onClose: () => void; } function CreateMissionForm({ groupStudyId, + studyStartDate, studyEndDate, + existingMissions, onClose, }: CreateMissionFormProps) { + const disabledMatcher = useMissionDateDisabledMatcher({ + studyStartDate, + studyEndDate, + existingMissions, + }); + const methods = useForm({ resolver: zodResolver(CreateMissionFormSchema), mode: 'onChange', @@ -113,8 +134,8 @@ function CreateMissionForm({ const { mutate: createMission } = useCreateMission(); const onValidSubmit = (values: CreateMissionFormValues) => { - const startDate = values.dateRange.from.toISOString(); - const endDate = values.dateRange.to.toISOString(); + const startDate = dayjs(values.dateRange.from).format('YYYY-MM-DD'); + const endDate = dayjs(values.dateRange.to).format('YYYY-MM-DD'); createMission( { @@ -217,7 +238,7 @@ function CreateMissionForm({ mode="range" selected={field.value} onSelect={(date) => field.onChange(date)} - disabled={createDateDisabledMatcher(studyEndDate)} + disabled={disabledMatcher} /> )} /> diff --git a/src/components/modals/edit-mission-modal.tsx b/src/components/modals/edit-mission-modal.tsx index 4ea8341d..c7457180 100644 --- a/src/components/modals/edit-mission-modal.tsx +++ b/src/components/modals/edit-mission-modal.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import dayjs from 'dayjs'; import { XIcon } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; @@ -9,8 +10,15 @@ import FormField from '@/components/ui/form/form-field'; import { BaseInput, TextAreaInput } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; import { useGroupStudyDetailQuery } from '@/features/study/group/model/use-study-query'; -import { useGetMission, useUpdateMission } from '@/hooks/queries/mission-api'; -import { createDateDisabledMatcher } from '@/utils/time'; +import { + MissionPeriod, + useMissionDateDisabledMatcher, +} from '@/hooks/common/use-mission-date-disabled-matcher'; +import { + useGetMission, + useGetMissions, + useUpdateMission, +} from '@/hooks/queries/mission-api'; // Form Schema const EditMissionFormSchema = z.object({ @@ -42,7 +50,12 @@ export default function EditMissionModal({ const [open, setOpen] = useState(false); const { data: missionData, isLoading } = useGetMission(missionId); const { data: studyData } = useGroupStudyDetailQuery(groupStudyId); + const { data: existingMissions } = useGetMissions({ + groupStudyId, + pageSize: 100, + }); + const studyStartDate = studyData?.basicInfo?.startDate; const studyEndDate = studyData?.basicInfo?.endDate; return ( @@ -77,7 +90,9 @@ export default function EditMissionModal({ setOpen(false)} /> ) : null} @@ -97,16 +112,26 @@ interface EditMissionFormProps { missionEndDate?: string; }; missionId: number; + studyStartDate?: string; studyEndDate?: string; + existingMissions?: MissionPeriod[]; onClose: () => void; } function EditMissionForm({ missionData, missionId, + studyStartDate, studyEndDate, + existingMissions, onClose, }: EditMissionFormProps) { + const disabledMatcher = useMissionDateDisabledMatcher({ + studyStartDate, + studyEndDate, + existingMissions, + excludeMissionId: missionId, + }); const methods = useForm({ resolver: zodResolver(EditMissionFormSchema), mode: 'onChange', @@ -126,8 +151,10 @@ function EditMissionForm({ }, }); + const { handleSubmit, formState, control, reset } = methods; + useEffect(() => { - methods.reset({ + reset({ title: missionData.missionTitle || '', description: missionData.missionDescription || '', weekNum: missionData.weekNum?.toString() || '', @@ -141,15 +168,13 @@ function EditMissionForm({ : new Date(), }, }); - }, [missionData, methods]); - - const { handleSubmit, formState, control } = methods; + }, [missionData, reset]); const { mutate: updateMission } = useUpdateMission(); const onValidSubmit = (values: EditMissionFormValues) => { - const startDate = values.dateRange.from.toISOString(); - const endDate = values.dateRange.to.toISOString(); + const startDate = dayjs(values.dateRange.from).format('YYYY-MM-DD'); + const endDate = dayjs(values.dateRange.to).format('YYYY-MM-DD'); updateMission( { @@ -252,7 +277,7 @@ function EditMissionForm({ mode="range" selected={field.value} onSelect={(date) => field.onChange(date)} - disabled={createDateDisabledMatcher(studyEndDate)} + disabled={disabledMatcher} /> )} /> diff --git a/src/hooks/common/use-mission-date-disabled-matcher.ts b/src/hooks/common/use-mission-date-disabled-matcher.ts new file mode 100644 index 00000000..1fcb5bec --- /dev/null +++ b/src/hooks/common/use-mission-date-disabled-matcher.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react'; +import { createMissionDateDisabledMatcher } from '@/utils/time'; + +export interface MissionPeriod { + missionId?: number; + startDate?: string; + endDate?: string; +} + +export interface UseMissionDateDisabledMatcherOptions { + studyStartDate?: string; + studyEndDate?: string; + existingMissions?: MissionPeriod[]; + excludeMissionId?: number; +} + +export function useMissionDateDisabledMatcher({ + studyStartDate, + studyEndDate, + existingMissions, + excludeMissionId, +}: UseMissionDateDisabledMatcherOptions) { + return useMemo( + () => + createMissionDateDisabledMatcher({ + studyStartDate, + studyEndDate, + existingMissions, + excludeMissionId, + }), + [studyStartDate, studyEndDate, existingMissions, excludeMissionId], + ); +} diff --git a/src/utils/time.ts b/src/utils/time.ts index 29f042a6..064d9694 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -91,23 +91,72 @@ export const getKoreaDisplayMonday = (base?: Date) => { return dow === 0 || dow === 6 ? addDays(monday, 7) : monday; }; -export const createDateDisabledMatcher = (endDateString?: string) => { +interface MissionPeriod { + missionId?: number; + startDate?: string; + endDate?: string; +} + +interface CreateMissionDateDisabledMatcherOptions { + studyStartDate?: string; + studyEndDate?: string; + existingMissions?: MissionPeriod[]; + excludeMissionId?: number; +} + +export const createMissionDateDisabledMatcher = ( + options: CreateMissionDateDisabledMatcherOptions, +) => { + const { studyStartDate, studyEndDate, existingMissions, excludeMissionId } = options; + return (date: Date) => { + const normalizedDate = new Date(date); + normalizedDate.setHours(0, 0, 0, 0); + const today = new Date(); today.setHours(0, 0, 0, 0); - if (date < today) { + if (normalizedDate < today) { return true; } - if (endDateString) { - const endDate = new Date(endDateString); - endDate.setHours(23, 59, 59, 999); - if (date > endDate) { + if (studyStartDate) { + const startDate = new Date(studyStartDate); + startDate.setHours(0, 0, 0, 0); + if (normalizedDate < startDate) { + return true; + } + } + + if (studyEndDate) { + const endDate = new Date(studyEndDate); + endDate.setHours(0, 0, 0, 0); + if (normalizedDate > endDate) { return true; } } + if (existingMissions) { + const targetTime = normalizedDate.getTime(); + for (const mission of existingMissions) { + if (excludeMissionId && mission.missionId === excludeMissionId) continue; + + if (mission.startDate && mission.endDate) { + const missionStart = new Date(mission.startDate); + missionStart.setHours(0, 0, 0, 0); + const missionEnd = new Date(mission.endDate); + missionEnd.setHours(0, 0, 0, 0); + + if ( + targetTime >= missionStart.getTime() && + targetTime <= missionEnd.getTime() + ) { + return true; + } + } + } + } + return false; }; }; From 3c671bd1632bf8146259fdba456511a71ea0db3f Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:31:52 +0900 Subject: [PATCH 06/55] =?UTF-8?q?fix:=20'excludeMissionId'=EB=A5=BC=20'edi?= =?UTF-8?q?tingMissionId'=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC?= =?UTF-8?q?=20=ED=8E=B8=EC=A7=91=20=EC=A4=91=EC=9D=B8=20=EB=AF=B8=EC=85=98?= =?UTF-8?q?=20ID=EB=A5=BC=20=EB=AA=85=ED=99=95=ED=9E=88=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/modals/edit-mission-modal.tsx | 2 +- src/hooks/common/use-mission-date-disabled-matcher.ts | 8 ++++---- src/utils/time.ts | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/modals/edit-mission-modal.tsx b/src/components/modals/edit-mission-modal.tsx index c7457180..abc362b2 100644 --- a/src/components/modals/edit-mission-modal.tsx +++ b/src/components/modals/edit-mission-modal.tsx @@ -130,7 +130,7 @@ function EditMissionForm({ studyStartDate, studyEndDate, existingMissions, - excludeMissionId: missionId, + editingMissionId: missionId, }); const methods = useForm({ resolver: zodResolver(EditMissionFormSchema), diff --git a/src/hooks/common/use-mission-date-disabled-matcher.ts b/src/hooks/common/use-mission-date-disabled-matcher.ts index 1fcb5bec..26339977 100644 --- a/src/hooks/common/use-mission-date-disabled-matcher.ts +++ b/src/hooks/common/use-mission-date-disabled-matcher.ts @@ -11,14 +11,14 @@ export interface UseMissionDateDisabledMatcherOptions { studyStartDate?: string; studyEndDate?: string; existingMissions?: MissionPeriod[]; - excludeMissionId?: number; + editingMissionId?: number; } export function useMissionDateDisabledMatcher({ studyStartDate, studyEndDate, existingMissions, - excludeMissionId, + editingMissionId, }: UseMissionDateDisabledMatcherOptions) { return useMemo( () => @@ -26,8 +26,8 @@ export function useMissionDateDisabledMatcher({ studyStartDate, studyEndDate, existingMissions, - excludeMissionId, + editingMissionId, }), - [studyStartDate, studyEndDate, existingMissions, excludeMissionId], + [studyStartDate, studyEndDate, existingMissions, editingMissionId], ); } diff --git a/src/utils/time.ts b/src/utils/time.ts index 064d9694..3507f69a 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -101,13 +101,14 @@ interface CreateMissionDateDisabledMatcherOptions { studyStartDate?: string; studyEndDate?: string; existingMissions?: MissionPeriod[]; - excludeMissionId?: number; + editingMissionId?: number; // 편집 중인 미션 ID } export const createMissionDateDisabledMatcher = ( options: CreateMissionDateDisabledMatcherOptions, ) => { - const { studyStartDate, studyEndDate, existingMissions, excludeMissionId } = options; + const { studyStartDate, studyEndDate, existingMissions, editingMissionId } = options; + console.log(editingMissionId,'editingMissionId') return (date: Date) => { const normalizedDate = new Date(date); @@ -139,7 +140,7 @@ export const createMissionDateDisabledMatcher = ( if (existingMissions) { const targetTime = normalizedDate.getTime(); for (const mission of existingMissions) { - if (excludeMissionId && mission.missionId === excludeMissionId) continue; + if (editingMissionId && mission.missionId === editingMissionId) continue; if (mission.startDate && mission.endDate) { const missionStart = new Date(mission.startDate); From c303486b3ebc78b47140f6c7d7a22aa430e2f7d1 Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:38:39 +0900 Subject: [PATCH 07/55] =?UTF-8?q?fix:=20createMissionDateDisabledMatcher?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=BD=94=EB=93=9C=20=ED=8F=AC=EB=A7=B7=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EC=BD=98=EC=86=94=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/time.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utils/time.ts b/src/utils/time.ts index 3507f69a..bcacc6c8 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -107,8 +107,9 @@ interface CreateMissionDateDisabledMatcherOptions { export const createMissionDateDisabledMatcher = ( options: CreateMissionDateDisabledMatcherOptions, ) => { - const { studyStartDate, studyEndDate, existingMissions, editingMissionId } = options; - console.log(editingMissionId,'editingMissionId') + const { studyStartDate, studyEndDate, existingMissions, editingMissionId } = + options; + console.log(editingMissionId, 'editingMissionId'); return (date: Date) => { const normalizedDate = new Date(date); @@ -140,7 +141,8 @@ export const createMissionDateDisabledMatcher = ( if (existingMissions) { const targetTime = normalizedDate.getTime(); for (const mission of existingMissions) { - if (editingMissionId && mission.missionId === editingMissionId) continue; + if (editingMissionId && mission.missionId === editingMissionId) + continue; if (mission.startDate && mission.endDate) { const missionStart = new Date(mission.startDate); From 1ad0efe8071786a34aa4fc1d7e34267aadcdfaf6 Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:39:34 +0900 Subject: [PATCH 08/55] =?UTF-8?q?fix:=20=ED=8F=89=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B3=BC=EC=A0=9C=20=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=EC=9D=98=20=EC=BD=94=EB=A9=98=ED=8A=B8=20=EC=B5=9C=EB=8C=80=20?= =?UTF-8?q?=EA=B8=B8=EC=9D=B4=EB=A5=BC=201000=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/modals/create-evaluation-modal.tsx | 4 ++-- src/components/modals/edit-evaluation-modal.tsx | 2 +- src/components/modals/edit-homework-modal.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/modals/create-evaluation-modal.tsx b/src/components/modals/create-evaluation-modal.tsx index 57d39bc3..e6697bb7 100644 --- a/src/components/modals/create-evaluation-modal.tsx +++ b/src/components/modals/create-evaluation-modal.tsx @@ -23,7 +23,7 @@ const CreateEvaluationFormSchema = z.object({ 'C_MINUS', 'F', ]), - comment: z.string().min(1, '정성 코멘트를 입력해주세요.'), + comment: z.string().min(1, '정성 코멘트를 입력해주세요.').max(1000), }); type CreateEvaluationFormValues = z.infer; @@ -147,7 +147,7 @@ function CreateEvaluationForm({ id="comment" placeholder="정성 코멘트를 입력해 주세요." className="min-h-[230px]" - maxLength={5000} + maxLength={1000} /> diff --git a/src/components/modals/edit-evaluation-modal.tsx b/src/components/modals/edit-evaluation-modal.tsx index 243568b4..65c18b58 100644 --- a/src/components/modals/edit-evaluation-modal.tsx +++ b/src/components/modals/edit-evaluation-modal.tsx @@ -157,7 +157,7 @@ function EditEvaluationForm({ id="comment" placeholder="정성 코멘트를 입력해 주세요." className="min-h-[230px]" - maxLength={5000} + maxLength={1000} /> diff --git a/src/components/modals/edit-homework-modal.tsx b/src/components/modals/edit-homework-modal.tsx index 9ad0a937..13bd9149 100644 --- a/src/components/modals/edit-homework-modal.tsx +++ b/src/components/modals/edit-homework-modal.tsx @@ -11,7 +11,7 @@ import { useEditHomework } from '@/hooks/queries/group-study-homework-api'; import { BaseInput, TextAreaInput } from '../ui/input'; const EditHomeworkFormSchema = z.object({ - textContent: z.string().min(1, '과제 상세 내용을 입력해주세요.'), + textContent: z.string().min(1, '과제 상세 내용을 입력해주세요.').max(1000), attachmentLink: z.string().optional(), }); @@ -135,7 +135,7 @@ function EditHomeworkForm({ id="textContent" className="min-h-[230px]" placeholder="학습한 내용을 자세히 작성해 주세요." - maxLength={5000} + maxLength={1000} /> From 419d4e73d0abf50e121d12dd1a73d4bf6c4da905 Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Sun, 18 Jan 2026 08:42:35 +0900 Subject: [PATCH 09/55] =?UTF-8?q?fix:=20=EB=AF=B8=EC=85=98=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=82=A0=EC=A7=9C=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94=20=EB=A7=A4=EC=B2=98=EB=A5=BC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=95=98=EA=B3=A0=20=EA=B4=80=EB=A0=A8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modals/create-mission-modal.tsx | 18 +++++----- src/components/modals/edit-mission-modal.tsx | 21 ++++++------ .../use-mission-date-disabled-matcher.ts | 33 ------------------- src/utils/time.ts | 11 +++---- 4 files changed, 23 insertions(+), 60 deletions(-) delete mode 100644 src/hooks/common/use-mission-date-disabled-matcher.ts diff --git a/src/components/modals/create-mission-modal.tsx b/src/components/modals/create-mission-modal.tsx index f620779e..ad1bc783 100644 --- a/src/components/modals/create-mission-modal.tsx +++ b/src/components/modals/create-mission-modal.tsx @@ -10,11 +10,11 @@ import FormField from '@/components/ui/form/form-field'; import { BaseInput, TextAreaInput } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; import { useGroupStudyDetailQuery } from '@/features/study/group/model/use-study-query'; +import { useCreateMission, useGetMissions } from '@/hooks/queries/mission-api'; import { + createDisabledDateMatcherForMission, MissionPeriod, - useMissionDateDisabledMatcher, -} from '@/hooks/common/use-mission-date-disabled-matcher'; -import { useCreateMission, useGetMissions } from '@/hooks/queries/mission-api'; +} from '@/utils/time'; // Form Schema const CreateMissionFormSchema = z.object({ @@ -111,12 +111,6 @@ function CreateMissionForm({ existingMissions, onClose, }: CreateMissionFormProps) { - const disabledMatcher = useMissionDateDisabledMatcher({ - studyStartDate, - studyEndDate, - existingMissions, - }); - const methods = useForm({ resolver: zodResolver(CreateMissionFormSchema), mode: 'onChange', @@ -238,7 +232,11 @@ function CreateMissionForm({ mode="range" selected={field.value} onSelect={(date) => field.onChange(date)} - disabled={disabledMatcher} + disabled={createDisabledDateMatcherForMission({ + studyStartDate, + studyEndDate, + existingMissions, + })} /> )} /> diff --git a/src/components/modals/edit-mission-modal.tsx b/src/components/modals/edit-mission-modal.tsx index abc362b2..3b2e668a 100644 --- a/src/components/modals/edit-mission-modal.tsx +++ b/src/components/modals/edit-mission-modal.tsx @@ -10,15 +10,15 @@ import FormField from '@/components/ui/form/form-field'; import { BaseInput, TextAreaInput } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; import { useGroupStudyDetailQuery } from '@/features/study/group/model/use-study-query'; -import { - MissionPeriod, - useMissionDateDisabledMatcher, -} from '@/hooks/common/use-mission-date-disabled-matcher'; import { useGetMission, useGetMissions, useUpdateMission, } from '@/hooks/queries/mission-api'; +import { + createDisabledDateMatcherForMission, + MissionPeriod, +} from '@/utils/time'; // Form Schema const EditMissionFormSchema = z.object({ @@ -126,12 +126,6 @@ function EditMissionForm({ existingMissions, onClose, }: EditMissionFormProps) { - const disabledMatcher = useMissionDateDisabledMatcher({ - studyStartDate, - studyEndDate, - existingMissions, - editingMissionId: missionId, - }); const methods = useForm({ resolver: zodResolver(EditMissionFormSchema), mode: 'onChange', @@ -277,7 +271,12 @@ function EditMissionForm({ mode="range" selected={field.value} onSelect={(date) => field.onChange(date)} - disabled={disabledMatcher} + disabled={createDisabledDateMatcherForMission({ + studyStartDate, + studyEndDate, + existingMissions, + editingMissionId: missionId, + })} /> )} /> diff --git a/src/hooks/common/use-mission-date-disabled-matcher.ts b/src/hooks/common/use-mission-date-disabled-matcher.ts deleted file mode 100644 index 26339977..00000000 --- a/src/hooks/common/use-mission-date-disabled-matcher.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useMemo } from 'react'; -import { createMissionDateDisabledMatcher } from '@/utils/time'; - -export interface MissionPeriod { - missionId?: number; - startDate?: string; - endDate?: string; -} - -export interface UseMissionDateDisabledMatcherOptions { - studyStartDate?: string; - studyEndDate?: string; - existingMissions?: MissionPeriod[]; - editingMissionId?: number; -} - -export function useMissionDateDisabledMatcher({ - studyStartDate, - studyEndDate, - existingMissions, - editingMissionId, -}: UseMissionDateDisabledMatcherOptions) { - return useMemo( - () => - createMissionDateDisabledMatcher({ - studyStartDate, - studyEndDate, - existingMissions, - editingMissionId, - }), - [studyStartDate, studyEndDate, existingMissions, editingMissionId], - ); -} diff --git a/src/utils/time.ts b/src/utils/time.ts index bcacc6c8..4c87577a 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -91,25 +91,24 @@ export const getKoreaDisplayMonday = (base?: Date) => { return dow === 0 || dow === 6 ? addDays(monday, 7) : monday; }; -interface MissionPeriod { +export interface MissionPeriod { missionId?: number; startDate?: string; endDate?: string; } -interface CreateMissionDateDisabledMatcherOptions { +export interface CreateDisabledDateMatcherForMissionOptions { studyStartDate?: string; studyEndDate?: string; existingMissions?: MissionPeriod[]; - editingMissionId?: number; // 편집 중인 미션 ID + editingMissionId?: number; } -export const createMissionDateDisabledMatcher = ( - options: CreateMissionDateDisabledMatcherOptions, +export const createDisabledDateMatcherForMission = ( + options: CreateDisabledDateMatcherForMissionOptions, ) => { const { studyStartDate, studyEndDate, existingMissions, editingMissionId } = options; - console.log(editingMissionId, 'editingMissionId'); return (date: Date) => { const normalizedDate = new Date(date); From 93962869b5064a6185ddb582978db9350edeb989 Mon Sep 17 00:00:00 2001 From: yeun38 Date: Mon, 19 Jan 2026 21:05:02 +0900 Subject: [PATCH 10/55] =?UTF-8?q?fix:=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=EC=83=81=ED=83=9C=20=EC=9E=AC=ED=98=B8?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/summary/study-info-summary.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/summary/study-info-summary.tsx b/src/components/summary/study-info-summary.tsx index 0283c116..2555572a 100644 --- a/src/components/summary/study-info-summary.tsx +++ b/src/components/summary/study-info-summary.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useQueryClient } from '@tanstack/react-query'; import dayjs from 'dayjs'; import { ChevronDown, ChevronUp } from 'lucide-react'; import { useRouter } from 'next/navigation'; @@ -24,6 +25,7 @@ interface Props { export default function SummaryStudyInfo({ data }: Props) { const router = useRouter(); + const queryClient = useQueryClient(); const [isExpanded, setIsExpanded] = useState(false); const memberId = useUserStore((state) => state.memberId); @@ -125,7 +127,10 @@ export default function SummaryStudyInfo({ data }: Props) { alert('스터디 링크가 복사되었습니다!'); }; - const handleApplySuccess = () => { + const handleApplySuccess = async () => { + await queryClient.invalidateQueries({ + queryKey: ['groupStudyMemberStatus', groupStudyId], + }); if (price > 0) { router.push(`/payment/${groupStudyId}`); } From d27d1ea2f4b9d0fda8ee0f7624f4bee58d94b8c5 Mon Sep 17 00:00:00 2001 From: yeun38 Date: Mon, 19 Jan 2026 21:22:22 +0900 Subject: [PATCH 11/55] =?UTF-8?q?fix:=EB=AA=A8=EC=A7=91=EC=A4=91=EC=9D=B8?= =?UTF-8?q?=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=83=81=ED=83=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/filtering/study-filter.tsx | 18 +++++++++--------- src/components/pages/group-study-list-page.tsx | 8 ++++---- .../pages/premium-study-list-page.tsx | 8 ++++---- src/components/ui/toggle/button.tsx | 4 ++-- src/hooks/queries/study-query.ts | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/filtering/study-filter.tsx b/src/components/filtering/study-filter.tsx index 94b6522c..031dae3b 100644 --- a/src/components/filtering/study-filter.tsx +++ b/src/components/filtering/study-filter.tsx @@ -15,7 +15,7 @@ export interface StudyFilterValues { type: string[]; targetRoles: string[]; method: string[]; - inProgress: boolean; + recruiting: boolean; } interface StudyFilterProps { @@ -155,9 +155,9 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) { [values, onChange], ); - const handleInProgressChange = useCallback( + const handleRecruitingChange = useCallback( (pressed: boolean) => { - onChange({ ...values, inProgress: pressed }); + onChange({ ...values, recruiting: pressed }); }, [values, onChange], ); @@ -167,7 +167,7 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) { type: [], targetRoles: [], method: [], - inProgress: false, + recruiting: false, }); }, [onChange]); @@ -175,7 +175,7 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) { values.type.length > 0 || values.targetRoles.length > 0 || values.method.length > 0 || - values.inProgress; + values.recruiting; return (
@@ -203,11 +203,11 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) { - 진행 중만 보기 + 모집 중만 보기 {hasAnyFilter && ( diff --git a/src/components/pages/group-study-list-page.tsx b/src/components/pages/group-study-list-page.tsx index e2b69db2..29ac5cc9 100644 --- a/src/components/pages/group-study-list-page.tsx +++ b/src/components/pages/group-study-list-page.tsx @@ -37,9 +37,9 @@ export default function GroupStudyListPage() { const targetRoles = searchParams.get('targetRoles')?.split(',').filter(Boolean) ?? []; const method = searchParams.get('method')?.split(',').filter(Boolean) ?? []; - const inProgress = searchParams.get('inProgress') === 'true'; + const recruiting = searchParams.get('recruiting') === 'true'; - return { type, targetRoles, method, inProgress }; + return { type, targetRoles, method, recruiting }; }, [searchParams]); const currentPage = Number(searchParams.get('page')) || 1; @@ -61,7 +61,7 @@ export default function GroupStudyListPage() { filterValues.method.length > 0 ? (filterValues.method as GetGroupStudiesMethodEnum[]) : undefined, - inProgress: filterValues.inProgress || undefined, + recruiting: filterValues.recruiting || undefined, }); const allStudies = useMemo(() => data?.content ?? [], [data?.content]); @@ -100,7 +100,7 @@ export default function GroupStudyListPage() { ? values.targetRoles.join(',') : undefined, method: values.method.length > 0 ? values.method.join(',') : undefined, - inProgress: values.inProgress ? 'true' : undefined, + recruiting: values.recruiting ? 'true' : undefined, }); }, [updateSearchParams], diff --git a/src/components/pages/premium-study-list-page.tsx b/src/components/pages/premium-study-list-page.tsx index 0d88fb46..9471f58e 100644 --- a/src/components/pages/premium-study-list-page.tsx +++ b/src/components/pages/premium-study-list-page.tsx @@ -37,9 +37,9 @@ export default function PremiumStudyListPage() { const targetRoles = searchParams.get('targetRoles')?.split(',').filter(Boolean) ?? []; const method = searchParams.get('method')?.split(',').filter(Boolean) ?? []; - const inProgress = searchParams.get('inProgress') === 'true'; + const recruiting = searchParams.get('recruiting') === 'true'; - return { type, targetRoles, method, inProgress }; + return { type, targetRoles, method, recruiting }; }, [searchParams]); const currentPage = Number(searchParams.get('page')) || 1; @@ -61,7 +61,7 @@ export default function PremiumStudyListPage() { filterValues.method.length > 0 ? (filterValues.method as GetGroupStudiesMethodEnum[]) : undefined, - inProgress: filterValues.inProgress || undefined, + recruiting: filterValues.recruiting || undefined, }); const allStudies = useMemo(() => data?.content ?? [], [data?.content]); @@ -100,7 +100,7 @@ export default function PremiumStudyListPage() { ? values.targetRoles.join(',') : undefined, method: values.method.length > 0 ? values.method.join(',') : undefined, - inProgress: values.inProgress ? 'true' : undefined, + recruiting: values.recruiting ? 'true' : undefined, }); }, [updateSearchParams], diff --git a/src/components/ui/toggle/button.tsx b/src/components/ui/toggle/button.tsx index 88cfd8a3..e64016ac 100644 --- a/src/components/ui/toggle/button.tsx +++ b/src/components/ui/toggle/button.tsx @@ -13,8 +13,8 @@ export const toggleButtonVariants = cva( variants: { color: { primary: [ - 'data-[state=on]:bg-fill-brand-default-default', - 'data-[state=on]:text-text-inverse', + 'data-[state=on]:bg-fill-brand-subtle-default', + 'data-[state=on]:text-text-brand', 'data-[state=on]:border-border-brand', ], gray: [ diff --git a/src/hooks/queries/study-query.ts b/src/hooks/queries/study-query.ts index b6429d6e..97348178 100644 --- a/src/hooks/queries/study-query.ts +++ b/src/hooks/queries/study-query.ts @@ -17,7 +17,7 @@ interface GetStudiesParams { type?: GetGroupStudiesTypeEnum[]; targetRoles?: GetGroupStudiesTargetRolesEnum[]; method?: GetGroupStudiesMethodEnum[]; - inProgress?: boolean; + recruiting?: boolean; } export const useGetStudies = ({ @@ -27,7 +27,7 @@ export const useGetStudies = ({ type, targetRoles, method, - inProgress, + recruiting, }: GetStudiesParams) => { return useQuery({ queryKey: [ @@ -38,7 +38,7 @@ export const useGetStudies = ({ type, targetRoles, method, - inProgress, + recruiting, ], queryFn: async () => { const { data } = await groupStudyManagementApi.getGroupStudies( @@ -48,7 +48,7 @@ export const useGetStudies = ({ type, targetRoles, method, - inProgress, + recruiting, ); return data.content; From cb603683d73f915cb4a3e12b9d088a4c23dbeb37 Mon Sep 17 00:00:00 2001 From: yeun38 Date: Mon, 19 Jan 2026 21:47:41 +0900 Subject: [PATCH 12/55] =?UTF-8?q?fix:=20=EA=B0=9C=EC=84=A4/=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=ED=83=80=EC=9E=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/group/api/create-group-study.ts | 6 +- .../study/group/api/group-study-types.ts | 61 ++++++++++++- .../study/group/api/update-group-study.ts | 4 +- .../group/const/use-group-study-mutation.ts | 9 +- .../group/model/group-study-form.schema.ts | 87 +++++++++++++++---- .../study/group/ui/group-study-form-modal.tsx | 7 +- 6 files changed, 144 insertions(+), 30 deletions(-) diff --git a/src/features/study/group/api/create-group-study.ts b/src/features/study/group/api/create-group-study.ts index 66c5c36f..20a8dd00 100644 --- a/src/features/study/group/api/create-group-study.ts +++ b/src/features/study/group/api/create-group-study.ts @@ -1,8 +1,8 @@ import { axiosInstance } from '@/api/client/axios'; -import { GroupStudyFormRequest } from './group-study-types'; +import { GroupStudyCreateRequest } from './group-study-types'; -// CS 스터디 매칭 신청 -export const createGroupStudy = async (payload: GroupStudyFormRequest) => { +// 그룹 스터디 개설 +export const createGroupStudy = async (payload: GroupStudyCreateRequest) => { try { const res = await axiosInstance.post('/group-studies', payload); diff --git a/src/features/study/group/api/group-study-types.ts b/src/features/study/group/api/group-study-types.ts index 71c92e42..696da154 100644 --- a/src/features/study/group/api/group-study-types.ts +++ b/src/features/study/group/api/group-study-types.ts @@ -56,7 +56,65 @@ export interface BasicInfo { updatedAt: string; } -// 요청용 BasicInfo (백엔드 request body에서 createdAt, updatedAt 제외됨) +// ============================================================ +// Create/Update API 공통 타입 +// ============================================================ + +/** Create/Update API에서 공통으로 사용되는 BasicInfo 필드 */ +export interface BasicInfoCommon { + type: StudyType; + targetRoles: TargetRole[]; + maxMembersCount: number; + experienceLevels: ExperienceLevel[]; + method: StudyMethod; + regularMeeting: RegularMeeting; + location: string; + startDate: string; + endDate: string; + price: number; + studyLeaderParticipation: boolean; +} + +/** Create/Update API에서 공통으로 사용되는 Request 구조 */ +export interface GroupStudyRequestCommon { + detailInfo: DetailInfo; + interviewPost: { + interviewPost: string[]; + }; + thumbnailExtension: ThumbnailExtension; +} + +// ============================================================ +// Create API 전용 타입 +// ============================================================ + +/** Create API용 BasicInfo (classification 필수) */ +export interface BasicInfoCreateRequest extends BasicInfoCommon { + classification: 'GROUP_STUDY' | 'PREMIUM_STUDY'; +} + +/** Create API 전체 Request */ +export interface GroupStudyCreateRequest extends GroupStudyRequestCommon { + basicInfo: BasicInfoCreateRequest; +} + +// ============================================================ +// Update API 전용 타입 +// ============================================================ + +/** Update API용 BasicInfo (classification 없음 - 수정 불가) */ +export type BasicInfoUpdateRequest = BasicInfoCommon; + +/** Update API 전체 Request */ +export interface GroupStudyUpdateRequest extends GroupStudyRequestCommon { + basicInfo: BasicInfoUpdateRequest; +} + +// ============================================================ +// 기존 타입 (하위 호환성 유지) +// ============================================================ + +/** @deprecated BasicInfoCreateRequest 사용 권장 */ export type BasicInfoRequest = Omit; export interface BasicInfoDetail extends BasicInfo { @@ -162,6 +220,7 @@ export interface ApplyGroupStudyResponse { createdAt: string; } +/** @deprecated GroupStudyCreateRequest 또는 GroupStudyUpdateRequest 사용 권장 */ export interface GroupStudyFormRequest { basicInfo: BasicInfoRequest; detailInfo: DetailInfo; diff --git a/src/features/study/group/api/update-group-study.ts b/src/features/study/group/api/update-group-study.ts index 3d4eb0d1..2cccdf1f 100644 --- a/src/features/study/group/api/update-group-study.ts +++ b/src/features/study/group/api/update-group-study.ts @@ -1,13 +1,13 @@ import { axiosInstance } from '@/api/client/axios'; import { GroupStudyCreationResponse, - GroupStudyFormRequest, + GroupStudyUpdateRequest, } from './group-study-types'; // 그룹 스터디 수정 export const updateGroupStudy = async ( groupStudyId: number, - payload: GroupStudyFormRequest, + payload: GroupStudyUpdateRequest, ): Promise => { try { const res = await axiosInstance.put( diff --git a/src/features/study/group/const/use-group-study-mutation.ts b/src/features/study/group/const/use-group-study-mutation.ts index 848ffd30..51fede36 100644 --- a/src/features/study/group/const/use-group-study-mutation.ts +++ b/src/features/study/group/const/use-group-study-mutation.ts @@ -1,19 +1,22 @@ import { useMutation } from '@tanstack/react-query'; import { createGroupStudy } from '../api/create-group-study'; -import { GroupStudyFormRequest } from '../api/group-study-types'; +import { + GroupStudyCreateRequest, + GroupStudyUpdateRequest, +} from '../api/group-study-types'; import { updateGroupStudy } from '../api/update-group-study'; // 그룹 스터디 개설 mutation export const useCreateGroupStudyMutation = () => { return useMutation({ - mutationFn: (payload: GroupStudyFormRequest) => createGroupStudy(payload), + mutationFn: (payload: GroupStudyCreateRequest) => createGroupStudy(payload), }); }; // 그룹 스터디 수정 mutation export const useUpdateGroupStudyMutation = (groupStudyId: number) => { return useMutation({ - mutationFn: (payload: GroupStudyFormRequest) => + mutationFn: (payload: GroupStudyUpdateRequest) => updateGroupStudy(groupStudyId, payload), }); }; diff --git a/src/features/study/group/model/group-study-form.schema.ts b/src/features/study/group/model/group-study-form.schema.ts index a9b8c4ba..fa3b61b9 100644 --- a/src/features/study/group/model/group-study-form.schema.ts +++ b/src/features/study/group/model/group-study-form.schema.ts @@ -1,5 +1,11 @@ import { z } from 'zod'; -import { GroupStudyFormRequest } from '../api/group-study-types'; +import { + BasicInfoCommon, + GroupStudyCreateRequest, + GroupStudyFormRequest, + GroupStudyRequestCommon, + GroupStudyUpdateRequest, +} from '../api/group-study-types'; import { STUDY_TYPES, TARGET_ROLE_OPTIONS, @@ -127,28 +133,35 @@ export function buildOpenGroupDefaultValues( }; } -export function toOpenGroupRequest( - v: GroupStudyFormValues, -): GroupStudyFormRequest { +// ============================================================ +// 변환 함수 (내부 헬퍼) +// ============================================================ + +/** 공통 BasicInfo 필드 변환 */ +function buildBasicInfoCommon(v: GroupStudyFormValues): BasicInfoCommon { const isPremiumStudy = v.classification === 'PREMIUM_STUDY'; + + return { + studyLeaderParticipation: v.studyLeaderParticipation, + type: v.type, + targetRoles: v.targetRoles, + maxMembersCount: Number(v.maxMembersCount), + experienceLevels: v.experienceLevels ?? [], + method: v.method, + regularMeeting: v.regularMeeting, + location: v.location.trim(), + startDate: v.startDate.trim(), + endDate: v.endDate.trim(), + price: isPremiumStudy ? Number(v.price) || 0 : 0, + }; +} + +/** 공통 Request 구조 변환 */ +function buildCommonRequest(v: GroupStudyFormValues): GroupStudyRequestCommon { const thumbnailExt = v.thumbnailExtension === 'DEFAULT' ? 'JPG' : v.thumbnailExtension; return { - basicInfo: { - studyLeaderParticipation: v.studyLeaderParticipation, - classification: v.classification, - type: v.type, - targetRoles: v.targetRoles, - maxMembersCount: Number(v.maxMembersCount), - experienceLevels: v.experienceLevels ?? [], - method: v.method, - regularMeeting: v.regularMeeting, - location: v.location.trim(), - startDate: v.startDate.trim(), - endDate: v.endDate.trim(), - price: isPremiumStudy ? Number(v.price) || 0 : 0, - }, detailInfo: { thumbnailExtension: thumbnailExt, title: v.title, @@ -161,3 +174,41 @@ export function toOpenGroupRequest( thumbnailExtension: thumbnailExt, }; } + +// ============================================================ +// Create/Update 전용 변환 함수 +// ============================================================ + +/** Create API용 Request 변환 */ +export function toCreateRequest( + v: GroupStudyFormValues, +): GroupStudyCreateRequest { + return { + ...buildCommonRequest(v), + basicInfo: { + ...buildBasicInfoCommon(v), + classification: v.classification, + }, + }; +} + +/** Update API용 Request 변환 */ +export function toUpdateRequest( + v: GroupStudyFormValues, +): GroupStudyUpdateRequest { + return { + ...buildCommonRequest(v), + basicInfo: buildBasicInfoCommon(v), + }; +} + +// ============================================================ +// 기존 함수 (하위 호환성 유지) +// ============================================================ + +/** @deprecated toCreateRequest 또는 toUpdateRequest 사용 권장 */ +export function toOpenGroupRequest( + v: GroupStudyFormValues, +): GroupStudyFormRequest { + return toCreateRequest(v); +} diff --git a/src/features/study/group/ui/group-study-form-modal.tsx b/src/features/study/group/ui/group-study-form-modal.tsx index ec98daec..ff19a2dd 100644 --- a/src/features/study/group/ui/group-study-form-modal.tsx +++ b/src/features/study/group/ui/group-study-form-modal.tsx @@ -20,7 +20,8 @@ import { buildOpenGroupDefaultValues, GroupStudyFormValues, StudyClassification, - toOpenGroupRequest, + toCreateRequest, + toUpdateRequest, } from '../model/group-study-form.schema'; import { useGroupStudyDetailQuery } from '../model/use-study-query'; @@ -140,7 +141,7 @@ export default function GroupStudyFormModal({ const handleCreate = async (values: GroupStudyFormValues) => { try { - const body = toOpenGroupRequest(values); + const body = toCreateRequest(values); const created = await createGroupStudy(body); if (values.thumbnailFile) { @@ -166,7 +167,7 @@ export default function GroupStudyFormModal({ const handleEdit = async (values: GroupStudyFormValues) => { try { - const body = toOpenGroupRequest(values); + const body = toUpdateRequest(values); const updated = await updateGroupStudy(body); if (values.thumbnailFile) { From e13054eebc3cdefac5792a01cf6526f9b641f765 Mon Sep 17 00:00:00 2001 From: yeun38 Date: Mon, 19 Jan 2026 23:08:09 +0900 Subject: [PATCH 13/55] =?UTF-8?q?=EC=B6=94=EA=B0=80=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/pages/premium-study-detail-page.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/pages/premium-study-detail-page.tsx b/src/components/pages/premium-study-detail-page.tsx index cac6719d..7cda1c5c 100644 --- a/src/components/pages/premium-study-detail-page.tsx +++ b/src/components/pages/premium-study-detail-page.tsx @@ -42,8 +42,11 @@ export default function PremiumStudyDetailPage({ const activeTab = (searchParams.get('tab') as StudyTabValue) || 'intro'; - const { data: studyDetail, isLoading } = - useGroupStudyDetailQuery(groupStudyId); + const { + data: studyDetail, + isLoading, + refetch: refetchStudyDetail, + } = useGroupStudyDetailQuery(groupStudyId); const leaderId = studyDetail?.basicInfo.leader.memberId; @@ -120,6 +123,7 @@ export default function PremiumStudyDetailPage({ alert('스터디 삭제에 실패하였습니다.'); }, onSettled: () => { + refetchStudyDetail().catch(() => {}); router.push('/premium-study'); setShowModal(false); }, From e13414d047f81c9d281a9e6cc935e020216950df Mon Sep 17 00:00:00 2001 From: Hyeonjun0527 Date: Tue, 20 Jan 2026 01:31:24 +0900 Subject: [PATCH 14/55] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/manifest.json | 22 ++++++++++++++++++++++ src/features/auth/model/types.ts | 2 ++ src/features/auth/ui/sign-up-modal.tsx | 25 ++++++++++++++++++++----- src/hooks/common/use-auth.ts | 19 +++++++++++++------ 4 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 public/manifest.json diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 00000000..f2c27928 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "ZERO-ONE - 개발자 스터디 플랫폼", + "short_name": "ZERO-ONE", + "description": "1:1, 그룹 스터디, 멘토링 등 다양한 방식의 학습을 지원하는 개발자 전문 스터디 플랫폼", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#000000", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "/icons/logo.svg", + "sizes": "any", + "type": "image/svg+xml" + } + ] +} + diff --git a/src/features/auth/model/types.ts b/src/features/auth/model/types.ts index c9be65f3..f891d496 100644 --- a/src/features/auth/model/types.ts +++ b/src/features/auth/model/types.ts @@ -2,6 +2,8 @@ export interface SignUpResponse { content: { generatedMemberId: string; + accessToken: string; + refreshToken: string; }; status: number; message: string; diff --git a/src/features/auth/ui/sign-up-modal.tsx b/src/features/auth/ui/sign-up-modal.tsx index 878615f0..0b308003 100644 --- a/src/features/auth/ui/sign-up-modal.tsx +++ b/src/features/auth/ui/sign-up-modal.tsx @@ -111,9 +111,18 @@ export default function SignupModal({ signUp.mutate(signUpPayload, { onSuccess: async (data) => { - const memberId = data.content.generatedMemberId; - if (memberId) { + console.log('Sign up response:', data); + + const content = data?.content; + const memberId = content?.generatedMemberId; + const accessToken = content?.accessToken; + const refreshToken = content?.refreshToken; + + if (memberId && accessToken && refreshToken) { setCookie('memberId', memberId); + setCookie('accessToken', accessToken); + // refreshToken도 쿠키에 저장 (필요 시 secure, httpOnly 설정 등 고려) + setCookie('refresh_token', refreshToken); // 이미지 업로드 if (signupData.file) { @@ -134,10 +143,10 @@ export default function SignupModal({ dl_member_id: hashValue(memberId), ...attributionParams, }); - - // 모달 닫지 않고 success step으로 이동 - setCurrentStep('success'); } + + // 모달 닫지 않고 success step으로 이동 (토큰 여부와 관계없이 성공 시 이동) + setCurrentStep('success'); }, onError: (error) => { console.error('회원가입 실패:', error); @@ -147,6 +156,12 @@ export default function SignupModal({ }; const handleNext = () => { + // goal 단계에서는 바로 완료 처리 + if (currentStep === 'goal') { + handleComplete(); + return; + } + const currentIndex = STEPS.indexOf(currentStep); if (currentIndex < STEPS.length - 1) { setCurrentStep(STEPS[currentIndex + 1]); diff --git a/src/hooks/common/use-auth.ts b/src/hooks/common/use-auth.ts index c639294e..a0a7f673 100644 --- a/src/hooks/common/use-auth.ts +++ b/src/hooks/common/use-auth.ts @@ -3,13 +3,13 @@ import { useMemo } from 'react'; import { getCookie } from '@/api/client/cookie'; import { decodeJwt } from '@/utils/jwt'; -type RoleId = 'ROLE_MEMBER' | 'ROLE_ADMIN' | 'ROLE_MENTOR'; +type RoleId = 'ROLE_MEMBER' | 'ROLE_ADMIN' | 'ROLE_MENTOR' | 'ROLE_GUEST'; type AuthVendor = 'GOOGLE' | 'KAKAO'; interface DecodedToken { roleIds: RoleId[]; authVendor: AuthVendor; - memberId: number; + memberId: number | null; } interface UseAuthReturn { @@ -25,15 +25,20 @@ function isDecodedToken(value: unknown): value is DecodedToken { // roleIds가 배열이고, 모든 요소가 유효한 RoleId인지 확인 if (!Array.isArray(obj.roleIds)) return false; - const validRoles: RoleId[] = ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_MENTOR']; + const validRoles: RoleId[] = ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_MENTOR', 'ROLE_GUEST']; if (!obj.roleIds.every((role) => validRoles.includes(role))) return false; // authVendor가 유효한 값인지 확인 const validVendors: AuthVendor[] = ['GOOGLE', 'KAKAO']; if (!validVendors.includes(obj.authVendor as AuthVendor)) return false; - // memberId가 숫자인지 확인 - if (typeof obj.memberId !== 'number') return false; + // memberId가 숫자이거나 null인지 확인 + if ( + typeof obj.memberId !== 'number' && + obj.memberId !== null && + obj.memberId !== undefined + ) + return false; return true; } @@ -61,9 +66,11 @@ export function useAuth(): UseAuthReturn { } }, [accessToken]); + const isGuest = decodedToken?.roleIds.includes('ROLE_GUEST'); + return { accessToken, data: decodedToken, - isAuthenticated: !!accessToken && !!decodedToken, + isAuthenticated: !!accessToken && !!decodedToken && !isGuest, }; } From dad999a58c1ee08df7349edec8ac42965d22d93b Mon Sep 17 00:00:00 2001 From: Hyeonjun0527 Date: Tue, 20 Jan 2026 01:33:13 +0900 Subject: [PATCH 15/55] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth/ui/sign-up-modal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/features/auth/ui/sign-up-modal.tsx b/src/features/auth/ui/sign-up-modal.tsx index 0b308003..bde03504 100644 --- a/src/features/auth/ui/sign-up-modal.tsx +++ b/src/features/auth/ui/sign-up-modal.tsx @@ -111,7 +111,6 @@ export default function SignupModal({ signUp.mutate(signUpPayload, { onSuccess: async (data) => { - console.log('Sign up response:', data); const content = data?.content; const memberId = content?.generatedMemberId; From 10d7381f103e32bcfbbd8e9bf020bcd8e0d4583b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A4=80?= Date: Tue, 20 Jan 2026 01:40:58 +0900 Subject: [PATCH 16/55] =?UTF-8?q?Revert=20"fix:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/manifest.json | 22 ---------------------- src/features/auth/model/types.ts | 2 -- src/features/auth/ui/sign-up-modal.tsx | 24 +++++------------------- src/hooks/common/use-auth.ts | 19 ++++++------------- 4 files changed, 11 insertions(+), 56 deletions(-) delete mode 100644 public/manifest.json diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index f2c27928..00000000 --- a/public/manifest.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "ZERO-ONE - 개발자 스터디 플랫폼", - "short_name": "ZERO-ONE", - "description": "1:1, 그룹 스터디, 멘토링 등 다양한 방식의 학습을 지원하는 개발자 전문 스터디 플랫폼", - "start_url": "/", - "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#000000", - "icons": [ - { - "src": "/favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "/icons/logo.svg", - "sizes": "any", - "type": "image/svg+xml" - } - ] -} - diff --git a/src/features/auth/model/types.ts b/src/features/auth/model/types.ts index f891d496..c9be65f3 100644 --- a/src/features/auth/model/types.ts +++ b/src/features/auth/model/types.ts @@ -2,8 +2,6 @@ export interface SignUpResponse { content: { generatedMemberId: string; - accessToken: string; - refreshToken: string; }; status: number; message: string; diff --git a/src/features/auth/ui/sign-up-modal.tsx b/src/features/auth/ui/sign-up-modal.tsx index bde03504..878615f0 100644 --- a/src/features/auth/ui/sign-up-modal.tsx +++ b/src/features/auth/ui/sign-up-modal.tsx @@ -111,17 +111,9 @@ export default function SignupModal({ signUp.mutate(signUpPayload, { onSuccess: async (data) => { - - const content = data?.content; - const memberId = content?.generatedMemberId; - const accessToken = content?.accessToken; - const refreshToken = content?.refreshToken; - - if (memberId && accessToken && refreshToken) { + const memberId = data.content.generatedMemberId; + if (memberId) { setCookie('memberId', memberId); - setCookie('accessToken', accessToken); - // refreshToken도 쿠키에 저장 (필요 시 secure, httpOnly 설정 등 고려) - setCookie('refresh_token', refreshToken); // 이미지 업로드 if (signupData.file) { @@ -142,10 +134,10 @@ export default function SignupModal({ dl_member_id: hashValue(memberId), ...attributionParams, }); - } - // 모달 닫지 않고 success step으로 이동 (토큰 여부와 관계없이 성공 시 이동) - setCurrentStep('success'); + // 모달 닫지 않고 success step으로 이동 + setCurrentStep('success'); + } }, onError: (error) => { console.error('회원가입 실패:', error); @@ -155,12 +147,6 @@ export default function SignupModal({ }; const handleNext = () => { - // goal 단계에서는 바로 완료 처리 - if (currentStep === 'goal') { - handleComplete(); - return; - } - const currentIndex = STEPS.indexOf(currentStep); if (currentIndex < STEPS.length - 1) { setCurrentStep(STEPS[currentIndex + 1]); diff --git a/src/hooks/common/use-auth.ts b/src/hooks/common/use-auth.ts index a0a7f673..c639294e 100644 --- a/src/hooks/common/use-auth.ts +++ b/src/hooks/common/use-auth.ts @@ -3,13 +3,13 @@ import { useMemo } from 'react'; import { getCookie } from '@/api/client/cookie'; import { decodeJwt } from '@/utils/jwt'; -type RoleId = 'ROLE_MEMBER' | 'ROLE_ADMIN' | 'ROLE_MENTOR' | 'ROLE_GUEST'; +type RoleId = 'ROLE_MEMBER' | 'ROLE_ADMIN' | 'ROLE_MENTOR'; type AuthVendor = 'GOOGLE' | 'KAKAO'; interface DecodedToken { roleIds: RoleId[]; authVendor: AuthVendor; - memberId: number | null; + memberId: number; } interface UseAuthReturn { @@ -25,20 +25,15 @@ function isDecodedToken(value: unknown): value is DecodedToken { // roleIds가 배열이고, 모든 요소가 유효한 RoleId인지 확인 if (!Array.isArray(obj.roleIds)) return false; - const validRoles: RoleId[] = ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_MENTOR', 'ROLE_GUEST']; + const validRoles: RoleId[] = ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_MENTOR']; if (!obj.roleIds.every((role) => validRoles.includes(role))) return false; // authVendor가 유효한 값인지 확인 const validVendors: AuthVendor[] = ['GOOGLE', 'KAKAO']; if (!validVendors.includes(obj.authVendor as AuthVendor)) return false; - // memberId가 숫자이거나 null인지 확인 - if ( - typeof obj.memberId !== 'number' && - obj.memberId !== null && - obj.memberId !== undefined - ) - return false; + // memberId가 숫자인지 확인 + if (typeof obj.memberId !== 'number') return false; return true; } @@ -66,11 +61,9 @@ export function useAuth(): UseAuthReturn { } }, [accessToken]); - const isGuest = decodedToken?.roleIds.includes('ROLE_GUEST'); - return { accessToken, data: decodedToken, - isAuthenticated: !!accessToken && !!decodedToken && !isGuest, + isAuthenticated: !!accessToken && !!decodedToken, }; } From 1115f0c62ca2d37f281a24bea64f0aaeee2c8d60 Mon Sep 17 00:00:00 2001 From: Hyeonjun0527 Date: Tue, 20 Jan 2026 01:31:24 +0900 Subject: [PATCH 17/55] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/manifest.json | 22 ++++++++++++++++++++++ src/features/auth/model/types.ts | 2 ++ src/features/auth/ui/sign-up-modal.tsx | 25 ++++++++++++++++++++----- src/hooks/common/use-auth.ts | 19 +++++++++++++------ 4 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 public/manifest.json diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 00000000..f2c27928 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "ZERO-ONE - 개발자 스터디 플랫폼", + "short_name": "ZERO-ONE", + "description": "1:1, 그룹 스터디, 멘토링 등 다양한 방식의 학습을 지원하는 개발자 전문 스터디 플랫폼", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#000000", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "/icons/logo.svg", + "sizes": "any", + "type": "image/svg+xml" + } + ] +} + diff --git a/src/features/auth/model/types.ts b/src/features/auth/model/types.ts index c9be65f3..f891d496 100644 --- a/src/features/auth/model/types.ts +++ b/src/features/auth/model/types.ts @@ -2,6 +2,8 @@ export interface SignUpResponse { content: { generatedMemberId: string; + accessToken: string; + refreshToken: string; }; status: number; message: string; diff --git a/src/features/auth/ui/sign-up-modal.tsx b/src/features/auth/ui/sign-up-modal.tsx index 878615f0..ce659078 100644 --- a/src/features/auth/ui/sign-up-modal.tsx +++ b/src/features/auth/ui/sign-up-modal.tsx @@ -111,9 +111,17 @@ export default function SignupModal({ signUp.mutate(signUpPayload, { onSuccess: async (data) => { - const memberId = data.content.generatedMemberId; - if (memberId) { + + const content = data?.content; + const memberId = content?.generatedMemberId; + const accessToken = content?.accessToken; + const refreshToken = content?.refreshToken; + + if (memberId && accessToken && refreshToken) { setCookie('memberId', memberId); + setCookie('accessToken', accessToken); + // refreshToken도 쿠키에 저장 (필요 시 secure, httpOnly 설정 등 고려) + setCookie('refresh_token', refreshToken); // 이미지 업로드 if (signupData.file) { @@ -134,10 +142,10 @@ export default function SignupModal({ dl_member_id: hashValue(memberId), ...attributionParams, }); - - // 모달 닫지 않고 success step으로 이동 - setCurrentStep('success'); } + + // 모달 닫지 않고 success step으로 이동 (토큰 여부와 관계없이 성공 시 이동) + setCurrentStep('success'); }, onError: (error) => { console.error('회원가입 실패:', error); @@ -147,6 +155,13 @@ export default function SignupModal({ }; const handleNext = () => { + // goal 단계에서는 바로 완료 처리 + if (currentStep === 'goal') { + handleComplete(); + + return; + } + const currentIndex = STEPS.indexOf(currentStep); if (currentIndex < STEPS.length - 1) { setCurrentStep(STEPS[currentIndex + 1]); diff --git a/src/hooks/common/use-auth.ts b/src/hooks/common/use-auth.ts index c639294e..a0a7f673 100644 --- a/src/hooks/common/use-auth.ts +++ b/src/hooks/common/use-auth.ts @@ -3,13 +3,13 @@ import { useMemo } from 'react'; import { getCookie } from '@/api/client/cookie'; import { decodeJwt } from '@/utils/jwt'; -type RoleId = 'ROLE_MEMBER' | 'ROLE_ADMIN' | 'ROLE_MENTOR'; +type RoleId = 'ROLE_MEMBER' | 'ROLE_ADMIN' | 'ROLE_MENTOR' | 'ROLE_GUEST'; type AuthVendor = 'GOOGLE' | 'KAKAO'; interface DecodedToken { roleIds: RoleId[]; authVendor: AuthVendor; - memberId: number; + memberId: number | null; } interface UseAuthReturn { @@ -25,15 +25,20 @@ function isDecodedToken(value: unknown): value is DecodedToken { // roleIds가 배열이고, 모든 요소가 유효한 RoleId인지 확인 if (!Array.isArray(obj.roleIds)) return false; - const validRoles: RoleId[] = ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_MENTOR']; + const validRoles: RoleId[] = ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_MENTOR', 'ROLE_GUEST']; if (!obj.roleIds.every((role) => validRoles.includes(role))) return false; // authVendor가 유효한 값인지 확인 const validVendors: AuthVendor[] = ['GOOGLE', 'KAKAO']; if (!validVendors.includes(obj.authVendor as AuthVendor)) return false; - // memberId가 숫자인지 확인 - if (typeof obj.memberId !== 'number') return false; + // memberId가 숫자이거나 null인지 확인 + if ( + typeof obj.memberId !== 'number' && + obj.memberId !== null && + obj.memberId !== undefined + ) + return false; return true; } @@ -61,9 +66,11 @@ export function useAuth(): UseAuthReturn { } }, [accessToken]); + const isGuest = decodedToken?.roleIds.includes('ROLE_GUEST'); + return { accessToken, data: decodedToken, - isAuthenticated: !!accessToken && !!decodedToken, + isAuthenticated: !!accessToken && !!decodedToken && !isGuest, }; } From 384b070e5ccf69eaf01e12e8f96be92b5b45c119 Mon Sep 17 00:00:00 2001 From: Hyeonjun0527 Date: Tue, 20 Jan 2026 01:50:19 +0900 Subject: [PATCH 18/55] =?UTF-8?q?fix:=20=ED=94=84=EB=A6=AC=ED=8B=B0?= =?UTF-8?q?=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth/ui/sign-up-modal.tsx | 3 +-- src/hooks/common/use-auth.ts | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/features/auth/ui/sign-up-modal.tsx b/src/features/auth/ui/sign-up-modal.tsx index ce659078..9a9f70e7 100644 --- a/src/features/auth/ui/sign-up-modal.tsx +++ b/src/features/auth/ui/sign-up-modal.tsx @@ -111,12 +111,11 @@ export default function SignupModal({ signUp.mutate(signUpPayload, { onSuccess: async (data) => { - const content = data?.content; const memberId = content?.generatedMemberId; const accessToken = content?.accessToken; const refreshToken = content?.refreshToken; - + if (memberId && accessToken && refreshToken) { setCookie('memberId', memberId); setCookie('accessToken', accessToken); diff --git a/src/hooks/common/use-auth.ts b/src/hooks/common/use-auth.ts index a0a7f673..1f56f3d1 100644 --- a/src/hooks/common/use-auth.ts +++ b/src/hooks/common/use-auth.ts @@ -25,7 +25,12 @@ function isDecodedToken(value: unknown): value is DecodedToken { // roleIds가 배열이고, 모든 요소가 유효한 RoleId인지 확인 if (!Array.isArray(obj.roleIds)) return false; - const validRoles: RoleId[] = ['ROLE_MEMBER', 'ROLE_ADMIN', 'ROLE_MENTOR', 'ROLE_GUEST']; + const validRoles: RoleId[] = [ + 'ROLE_MEMBER', + 'ROLE_ADMIN', + 'ROLE_MENTOR', + 'ROLE_GUEST', + ]; if (!obj.roleIds.every((role) => validRoles.includes(role))) return false; // authVendor가 유효한 값인지 확인 From 41c1c0607380cc6897fa99d910f551f001fe42dc Mon Sep 17 00:00:00 2001 From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:54:19 +0900 Subject: [PATCH 19/55] =?UTF-8?q?fix:=20=ED=95=9C=EA=B5=AD=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=ED=8F=AC=EB=A7=B7=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=82=A0=EC=A7=9C=20=EB=B9=84=EA=B5=90?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/study/group/ui/step/step1-group.tsx | 13 ++++++------- src/utils/time.ts | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/features/study/group/ui/step/step1-group.tsx b/src/features/study/group/ui/step/step1-group.tsx index 0db6cac5..46745af6 100644 --- a/src/features/study/group/ui/step/step1-group.tsx +++ b/src/features/study/group/ui/step/step1-group.tsx @@ -12,6 +12,7 @@ import FormField from '@/components/ui/form/form-field'; import { BaseInput } from '@/components/ui/input'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio'; import { GroupItems } from '@/components/ui/toggle'; +import { formatKoreaYMD } from '@/utils/time'; import { TargetRole } from '../../api/group-study-types'; import { STUDY_TYPES, @@ -101,11 +102,11 @@ export default function Step1OpenGroupStudy() { value={typeField.value} onValueChange={typeField.onChange} > - {STUDY_TYPES.map((type, index) => ( + {STUDY_TYPES.map((type) => (
- +