From 7043e6d61725f8844f6c1146a66213b837d89cae Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 21:16:50 +0900 Subject: [PATCH 1/9] =?UTF-8?q?fix:=20=EC=9E=84=EC=8B=9C=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EC=9D=98=20=EC=B5=9C=EC=B4=88=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=A0=95=EB=B3=B4=20=EB=93=B1=EB=A1=9D=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=ED=99=95=EC=9D=B8=20API=EC=97=90=20=EC=9E=98?= =?UTF-8?q?=EB=AA=BB=20=EC=A0=81=EC=9A=A9=EB=90=98=EC=96=B4=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20Params=EB=A5=BC=20=EC=A0=9C=EA=B1=B0=ED=95=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/apis/apply/api.ts | 10 ++++------ .../features/apply/steps/IdentityVerificationStep.tsx | 2 +- .../web/src/hooks/apply/useCheckApplyStatusMutation.ts | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/web/src/apis/apply/api.ts b/apps/web/src/apis/apply/api.ts index 9ba6fb95c..2cf66d849 100644 --- a/apps/web/src/apis/apply/api.ts +++ b/apps/web/src/apis/apply/api.ts @@ -39,13 +39,11 @@ export const applyApi = { memberProfileInitialStatusResponseSchema, ), - getStatus: (email: string) => { - const params = new URLSearchParams({ email }); - return httpClient.get( - `${API_ENDPOINT.applyStatus}?${params.toString()}`, + getStatus: () => + httpClient.get( + API_ENDPOINT.applyStatus, applicationStatusResponseSchema, - ); - }, + ), getProfile: () => httpClient.get( diff --git a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx index 4ff65c67e..94c654226 100644 --- a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx +++ b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx @@ -85,7 +85,7 @@ export function IdentityVerificationStep({ const { mutate: checkApplyStatusMutate, isPending: isCheckingStatus } = useCheckApplyStatusMutation(); const handleCheckApplyStatus = (userEmail: string) => { - checkApplyStatusMutate(userEmail, { + checkApplyStatusMutate(undefined, { onSuccess: data => { if (data.result === "PROFILE_NOT_REGISTERED") { dispatch("goToProfile", userEmail); diff --git a/apps/web/src/hooks/apply/useCheckApplyStatusMutation.ts b/apps/web/src/hooks/apply/useCheckApplyStatusMutation.ts index c31a5bec0..212b8659e 100644 --- a/apps/web/src/hooks/apply/useCheckApplyStatusMutation.ts +++ b/apps/web/src/hooks/apply/useCheckApplyStatusMutation.ts @@ -9,7 +9,7 @@ type CheckApplyStatusResult = export function useCheckApplyStatusMutation() { return useMutation({ - mutationFn: async (email: string): Promise => { + mutationFn: async (): Promise => { // 1. 프로필 등록 여부 확인 const isProfileRegistered = await applyApi.getProfileInitialStatus(); @@ -18,7 +18,7 @@ export function useCheckApplyStatusMutation() { } // 2. 프로필 등록된 경우에만 지원 상태 확인 - const { status } = await applyApi.getStatus(email); + const { status } = await applyApi.getStatus(); if (status === "SUBMITTED") { return { result: "SUBMITTED" }; From 065cd30dfd8dacafb6d55b3df6430f4d361b32f8 Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 21:55:38 +0900 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20=EC=9E=84=EC=8B=9C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20API=20=ED=98=B8=EC=B6=9C=20=EC=8B=9C=20=EC=9E=98?= =?UTF-8?q?=EB=AA=BB=20=EC=9E=91=EC=84=B1=EB=90=9C=20params=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/apis/apply/api.ts | 6 ++---- apps/web/src/apis/apply/queryKeys.ts | 8 ++++---- .../apply/steps/registration/RegistrationStep.tsx | 7 ++----- apps/web/src/hooks/apply/useSaveDraftMutation.ts | 11 +++-------- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/apps/web/src/apis/apply/api.ts b/apps/web/src/apis/apply/api.ts index 2cf66d849..47794f52e 100644 --- a/apps/web/src/apis/apply/api.ts +++ b/apps/web/src/apis/apply/api.ts @@ -64,10 +64,8 @@ export const applyApi = { getDraft: () => httpClient.get(API_ENDPOINT.draft, answersResponseSchema), - saveDraft: (jobFamily: JobFamily, answers: AnswersPayload) => { - const params = new URLSearchParams({ jobFamily }); - return httpClient.post(`${API_ENDPOINT.draft}?${params.toString()}`, answers); - }, + saveDraft: (answers: AnswersPayload) => + httpClient.post(API_ENDPOINT.draft, answers), deleteDraft: () => httpClient.delete(API_ENDPOINT.draft), diff --git a/apps/web/src/apis/apply/queryKeys.ts b/apps/web/src/apis/apply/queryKeys.ts index ee3de97e0..860d43e4b 100644 --- a/apps/web/src/apis/apply/queryKeys.ts +++ b/apps/web/src/apis/apply/queryKeys.ts @@ -7,7 +7,7 @@ export const applyQueryKeys = { all: ["apply"] as const, status: { all: () => [...applyQueryKeys.all, "status"] as const, - byEmail: (email: string) => [...applyQueryKeys.status.all(), "email", email] as const, + current: () => [...applyQueryKeys.status.all(), "current"] as const, }, questions: { all: () => [...applyQueryKeys.all, "questions"] as const, @@ -49,9 +49,9 @@ export const applyMutationKeys = { export const applyQueries = { status: { - byEmail: (email: string) => ({ - queryKey: applyQueryKeys.status.byEmail(email), - queryFn: () => applyApi.getStatus(email), + current: () => ({ + queryKey: applyQueryKeys.status.current(), + queryFn: () => applyApi.getStatus(), }), }, profile: { diff --git a/apps/web/src/features/apply/steps/registration/RegistrationStep.tsx b/apps/web/src/features/apply/steps/registration/RegistrationStep.tsx index 76929c6e6..66aeaa14d 100644 --- a/apps/web/src/features/apply/steps/registration/RegistrationStep.tsx +++ b/apps/web/src/features/apply/steps/registration/RegistrationStep.tsx @@ -79,11 +79,8 @@ export function RegistrationStep({ context, onNext, onBack }: RegistrationStepPr //지원서 임시 저장 const handleSaveDraft = useCallback(() => { - saveDraftMutate({ - jobFamily, - answers: { answers, portfolios: formattedPortfolios }, - }); - }, [saveDraftMutate, answers, formattedPortfolios, jobFamily]); + saveDraftMutate({ answers, portfolios: formattedPortfolios }); + }, [saveDraftMutate, answers, formattedPortfolios]); //지원서 제출 const handleSubmit = useCallback(() => { diff --git a/apps/web/src/hooks/apply/useSaveDraftMutation.ts b/apps/web/src/hooks/apply/useSaveDraftMutation.ts index a42a09f37..b7c6cb3fe 100644 --- a/apps/web/src/hooks/apply/useSaveDraftMutation.ts +++ b/apps/web/src/hooks/apply/useSaveDraftMutation.ts @@ -2,16 +2,11 @@ import { toastController } from "@ject/jds"; import { captureException } from "@sentry/react"; import { useMutation, useQueryClient, type UseMutationOptions } from "@tanstack/react-query"; -import { applyApi, applyMutationKeys, applyQueryKeys, type JobFamily } from "@/apis/apply"; +import { applyApi, applyMutationKeys, applyQueryKeys } from "@/apis/apply"; import type { AnswersPayload } from "@/types/apis/application"; -interface SaveDraftVariables { - jobFamily: JobFamily; - answers: AnswersPayload; -} - type UseSaveDraftMutationOptions = Omit< - UseMutationOptions, + UseMutationOptions, "mutationKey" | "mutationFn" >; @@ -21,7 +16,7 @@ export function useSaveDraftMutation(options?: UseSaveDraftMutationOptions) { return useMutation({ mutationKey: applyMutationKeys.draft.save, - mutationFn: ({ jobFamily, answers }: SaveDraftVariables) => applyApi.saveDraft(jobFamily, answers), + mutationFn: (answers: AnswersPayload) => applyApi.saveDraft(answers), retry: 1, ...restOptions, onSuccess: (data, variables, onMutateResult, mutationContext) => { From bdc1a8ed45dd619f9be4af4611ff20b63b16059a Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 22:18:31 +0900 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=EC=84=9C=EB=B2=84=20draft=EB=8A=94?= =?UTF-8?q?=20=EC=9C=A0=EC=A7=80=ED=95=98=EA=B3=A0=20=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=EC=9D=98=20draft=20=EA=B0=92?= =?UTF-8?q?=EB=A7=8C=20=EC=A1=B0=EC=A0=95=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=A9=EB=8B=88=EB=8B=A4.=20-=20useDeleteD?= =?UTF-8?q?raftMutation=EB=8A=94=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=EA=B9=8C=EC=A7=80=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EB=A5=BC=20=ED=95=98=EA=B8=B0=20=EB=95=8C=EB=AC=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../useRegistrationFormWithDraft.ts | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/apps/web/src/features/apply/steps/registration/useRegistrationFormWithDraft.ts b/apps/web/src/features/apply/steps/registration/useRegistrationFormWithDraft.ts index 6783fcaa9..8c55ad56c 100644 --- a/apps/web/src/features/apply/steps/registration/useRegistrationFormWithDraft.ts +++ b/apps/web/src/features/apply/steps/registration/useRegistrationFormWithDraft.ts @@ -1,14 +1,10 @@ import type { Dispatch, SetStateAction } from "react"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { formatDraftPortfolios } from "./utils"; import type { JobFamily, Question } from "@/apis/apply"; -import { - useDeleteDraftMutation, - useDraftSuspenseQuery, - useQuestionsSuspenseQuery, -} from "@/hooks/apply"; +import { useDraftSuspenseQuery, useQuestionsSuspenseQuery } from "@/hooks/apply"; import type { AnswersByQuestionId, PortfolioFile } from "@/types/apis/application"; interface UseRegistrationFormWithDraftReturn { @@ -42,11 +38,11 @@ export function useRegistrationFormWithDraft( ): UseRegistrationFormWithDraftReturn { const { data: questionsData } = useQuestionsSuspenseQuery(jobFamily); const { data: draftData } = useDraftSuspenseQuery(); - const { mutate: deleteDraft } = useDeleteDraftMutation(); const questions: Question[] = questionsData.questionResponses; - // draft의 jobFamily와 현재 선택한 jobFamily가 다르면 draft 무시 + // draft의 jobFamily와 현재 선택한 jobFamily가 다르면 클라이언트에서만 초기화 + // (서버 draft는 유지 - DELETE /apply/temp는 프로필까지 삭제하므로 호출하지 않음) const isJobFamilyMismatch = draftData?.jobFamily != null && draftData.jobFamily !== jobFamily; @@ -63,13 +59,6 @@ export function useRegistrationFormWithDraft( return formatDraftPortfolios(draftData.portfolios); }); - // jobFamily 불일치 시 서버의 draft 삭제 - useEffect(() => { - if (isJobFamilyMismatch) { - deleteDraft(); - } - }, [isJobFamilyMismatch, deleteDraft]); - return { questions, portfolios, From 44be4533f7513ceb27cb91b572e4752610496b12 Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 22:36:35 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EB=8B=A4=EB=A5=B8=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EC=84=9C=EC=97=90=20=EC=A0=91=EA=B7=BC=ED=96=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EC=98=88=EC=99=B8=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=A9=EB=8B=88?= =?UTF-8?q?=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apply/steps/IdentityVerificationStep.tsx | 80 ++++++++++++++++++- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx index 94c654226..33099b71e 100644 --- a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx +++ b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx @@ -3,8 +3,9 @@ import { useEffect, useState } from "react"; import { Controller } from "react-hook-form"; import { useLocation, useNavigate, useSearchParams } from "react-router-dom"; +import { applyApi, type JobFamily } from "@/apis/apply"; import { APPLY_DIALOG, APPLY_MESSAGE } from "@/constants/applyMessages.tsx"; -import { APPLY_TITLE } from "@/constants/applyPageData"; +import { findJobFamilyOption, APPLY_TITLE } from "@/constants/applyPageData"; import { PATH } from "@/constants/path"; import { ApplyStepLayout } from "@/features/shared/components"; import { useCheckApplyStatusMutation, usePinLoginMutation } from "@/hooks/apply"; @@ -33,6 +34,11 @@ interface IdentityVerificationStepProps { ) => void; } +interface JobFamilyMismatchDialog { + isOpen: boolean; + savedJobFamily: JobFamily | null; +} + export function IdentityVerificationStep({ context, dispatch, @@ -41,6 +47,11 @@ export function IdentityVerificationStep({ const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const [isSubmittedDialogOpen, setIsSubmittedDialogOpen] = useState(false); + const [mismatchDialog, setMismatchDialog] = useState({ + isOpen: false, + savedJobFamily: null, + }); + const [verifiedEmail, setVerifiedEmail] = useState(""); //PIN 재설정 후 돌아왔을 때 파라미터 const isPinResetSuccess = searchParams.get("pinReset") === "success"; @@ -97,9 +108,30 @@ export function IdentityVerificationStep({ return; } - // CONTINUE (TEMP_SAVED 또는 JOINED) - toastController.positive(APPLY_MESSAGE.success.continueWriting); - dispatch("goToApply", userEmail); + // CONTINUE (TEMP_SAVED 또는 JOINED) → draft 확인 + void applyApi + .getDraft() + .then(draft => { + // 파트 불일치 체크: draft에 저장된 jobFamily와 현재 접근한 jobFamily가 다른 경우 + if (draft.jobFamily != null && draft.jobFamily !== context.jobFamily) { + setVerifiedEmail(userEmail); + setMismatchDialog({ + isOpen: true, + savedJobFamily: draft.jobFamily, + }); + return; + } + + // 같은 파트 또는 draft 없음 → 이어서 작성 + toastController.positive(APPLY_MESSAGE.success.continueWriting); + dispatch("goToApply", userEmail); + }) + .catch((error: unknown) => { + // draft 조회 실패 시에도 이어서 작성 가능 (빈 폼으로 시작) + handleError(error, "임시저장 데이터 조회 실패"); + toastController.positive(APPLY_MESSAGE.success.continueWriting); + dispatch("goToApply", userEmail); + }); }, onError: error => { handleError(error, "지원 상태 확인 실패"); @@ -132,6 +164,29 @@ export function IdentityVerificationStep({ void navigate(`${PATH.resetPin}?returnTo=${encodeURIComponent(returnTo)}`); }; + // 파트 불일치 다이얼로그: "기존 파트 지원서 이어서 작성하기" 선택 + const handleContinueSavedDraft = () => { + if (mismatchDialog.savedJobFamily) { + void navigate(`${PATH.applyContinue}/${mismatchDialog.savedJobFamily}`); + } + setMismatchDialog({ isOpen: false, savedJobFamily: null }); + }; + + // 파트 불일치 다이얼로그: "새로운 파트로 지원하기" 선택 + const handleStartNewApplication = () => { + setMismatchDialog({ isOpen: false, savedJobFamily: null }); + toastController.positive(APPLY_MESSAGE.success.continueWriting); + dispatch("goToApply", verifiedEmail); + }; + + // 다이얼로그 메시지 생성 + const mismatchDialogContent = mismatchDialog.savedJobFamily + ? APPLY_DIALOG.jobFamilyMismatch( + findJobFamilyOption(mismatchDialog.savedJobFamily).korean, + findJobFamilyOption(context.jobFamily).korean, + ) + : null; + return ( setIsSubmittedDialogOpen(false), }} /> + + {mismatchDialogContent && ( + !open && setMismatchDialog({ isOpen: false, savedJobFamily: null })} + header={mismatchDialogContent.header} + body={mismatchDialogContent.body} + primaryAction={{ + children: mismatchDialogContent.primaryAction, + onClick: handleContinueSavedDraft, + }} + secondaryAction={{ + children: mismatchDialogContent.secondaryAction, + onClick: handleStartNewApplication, + }} + /> + )} ); } From 8ffa105324ca3f828f114b940b8f03af3971b8b7 Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 22:37:37 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=ED=98=84=EC=9E=AC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=ED=95=9C=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20API=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/apis/apply/api.ts | 12 ++++++------ apps/web/src/apis/apply/index.ts | 4 ++-- apps/web/src/apis/apply/queryKeys.ts | 2 +- apps/web/src/apis/apply/schemas.ts | 6 +++--- apps/web/src/constants/apiEndpoint.ts | 2 +- apps/web/src/constants/applyMessages.tsx | 12 ++++++++++++ 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/apps/web/src/apis/apply/api.ts b/apps/web/src/apis/apply/api.ts index 47794f52e..4b91bd39e 100644 --- a/apps/web/src/apis/apply/api.ts +++ b/apps/web/src/apis/apply/api.ts @@ -3,12 +3,12 @@ import axios from "axios"; import { applicationStatusResponseSchema, - memberProfileResponseSchema, + memberMeResponseSchema, memberProfileInitialStatusResponseSchema, questionResponseSchema, answersResponseSchema, type ApplicationStatusResponseSchema, - type MemberProfileResponseSchema, + type MemberMeResponseSchema, type MemberProfileInitialStatusResponseSchema, type QuestionResponseSchema, type AnswersResponseSchema, @@ -45,10 +45,10 @@ export const applyApi = { applicationStatusResponseSchema, ), - getProfile: () => - httpClient.get( - API_ENDPOINT.memberProfile, - memberProfileResponseSchema, + getMe: () => + httpClient.get( + API_ENDPOINT.memberMe, + memberMeResponseSchema, ), updateProfile: (data: MemberProfilePayload) => diff --git a/apps/web/src/apis/apply/index.ts b/apps/web/src/apis/apply/index.ts index a99a7c838..091da7b4d 100644 --- a/apps/web/src/apis/apply/index.ts +++ b/apps/web/src/apis/apply/index.ts @@ -10,13 +10,13 @@ export { export { // 스키마 applicationStatusResponseSchema, - memberProfileResponseSchema, + memberMeResponseSchema, memberProfileInitialStatusResponseSchema, questionResponseSchema, answersResponseSchema, // 스키마 타입 type ApplicationStatusResponseSchema, - type MemberProfileResponseSchema, + type MemberMeResponseSchema, type MemberProfileInitialStatusResponseSchema, type QuestionResponseSchema, type AnswersResponseSchema, diff --git a/apps/web/src/apis/apply/queryKeys.ts b/apps/web/src/apis/apply/queryKeys.ts index 860d43e4b..007c00652 100644 --- a/apps/web/src/apis/apply/queryKeys.ts +++ b/apps/web/src/apis/apply/queryKeys.ts @@ -57,7 +57,7 @@ export const applyQueries = { profile: { me: () => ({ queryKey: applyQueryKeys.profile.me(), - queryFn: applyApi.getProfile, + queryFn: applyApi.getMe, }), initialStatus: () => ({ queryKey: applyQueryKeys.profile.initialStatus(), diff --git a/apps/web/src/apis/apply/schemas.ts b/apps/web/src/apis/apply/schemas.ts index 79cd04087..0f1296ec1 100644 --- a/apps/web/src/apis/apply/schemas.ts +++ b/apps/web/src/apis/apply/schemas.ts @@ -65,16 +65,16 @@ export const interestedDomainSchema = z.enum([ "HR", ]); -export const memberProfileResponseSchema = z.object({ +export const memberMeResponseSchema = z.object({ + id: z.number(), name: z.string(), - phoneNumber: z.string(), careerDetails: careerDetailsSchema, region: regionSchema, experiencePeriod: experiencePeriodSchema, interestedDomains: z.array(interestedDomainSchema), }); -export type MemberProfileResponseSchema = z.infer; +export type MemberMeResponseSchema = z.infer; export const questionInputTypeSchema = z.enum(["TEXT", "URL", "FILE", "SELECT"]); diff --git a/apps/web/src/constants/apiEndpoint.ts b/apps/web/src/constants/apiEndpoint.ts index 0d6a97792..b6104cee1 100644 --- a/apps/web/src/constants/apiEndpoint.ts +++ b/apps/web/src/constants/apiEndpoint.ts @@ -5,7 +5,7 @@ export const API_ENDPOINT = { verifyEmailCode: "/auth/code", checkEmailExists: "/auth/login/exist", applyMember: "/members/apply", - memberProfile: "/members/profile", + memberMe: "/members/me", memberProfileInitialStatus: "/members/profile/initial/status", pinLogin: "/auth/login/pin", registerMember: "/members/apply", diff --git a/apps/web/src/constants/applyMessages.tsx b/apps/web/src/constants/applyMessages.tsx index d83a59fc6..65f92f20d 100644 --- a/apps/web/src/constants/applyMessages.tsx +++ b/apps/web/src/constants/applyMessages.tsx @@ -67,4 +67,16 @@ export const APPLY_DIALOG = { ), primaryAction: "확인", }, + jobFamilyMismatch: (savedJobFamilyKorean: string, currentJobFamilyKorean: string) => ({ + header: `${savedJobFamilyKorean} 지원서가 임시저장되어 있어요`, + body: ( + <> + {currentJobFamilyKorean}로 새로 지원하시면 +
+ 기존 {savedJobFamilyKorean} 지원서는 사라져요. + + ), + primaryAction: `${savedJobFamilyKorean} 지원서 이어서 작성하기`, + secondaryAction: `${currentJobFamilyKorean}로 새로 지원하기`, + }), }; From f39ac019b1793cb37207b75f7514c47515b69dae Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 22:43:06 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=ED=8C=8C=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=8B=9C=20=EC=88=98=ED=96=89=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.=201.=20=ED=98=84=EC=9E=AC=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EB=B0=B1=EC=97=85=202.=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20draft=20+=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=203.=20=EB=B0=B1=EC=97=85=EB=90=9C=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=EB=A1=9C=20=ED=98=84=EC=9E=AC=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EB=B3=B5=EC=9B=90=204.=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EC=84=9C=20=EC=9E=91=EC=84=B1=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apply/steps/IdentityVerificationStep.tsx | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx index 33099b71e..6f08ca18f 100644 --- a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx +++ b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx @@ -8,7 +8,12 @@ import { APPLY_DIALOG, APPLY_MESSAGE } from "@/constants/applyMessages.tsx"; import { findJobFamilyOption, APPLY_TITLE } from "@/constants/applyPageData"; import { PATH } from "@/constants/path"; import { ApplyStepLayout } from "@/features/shared/components"; -import { useCheckApplyStatusMutation, usePinLoginMutation } from "@/hooks/apply"; +import { + useCheckApplyStatusMutation, + useDeleteDraftMutation, + useMemberProfileMutation, + usePinLoginMutation, +} from "@/hooks/apply"; import { useApplyEmailForm } from "@/hooks/useApplyEmailForm"; import { useApplyPinForm } from "@/hooks/useApplyPinForm"; import type { ContinueWritingFunnelSteps } from "@/types/funnel"; @@ -93,7 +98,11 @@ export function IdentityVerificationStep({ const email = watchEmail("email"); const isFormValid = emailFormState.isValid && pinFormState.isValid; + const [isChangingJobFamily, setIsChangingJobFamily] = useState(false); + const { mutate: checkApplyStatusMutate, isPending: isCheckingStatus } = useCheckApplyStatusMutation(); + const { mutateAsync: deleteDraftAsync } = useDeleteDraftMutation(); + const { mutateAsync: updateProfileAsync } = useMemberProfileMutation(); const handleCheckApplyStatus = (userEmail: string) => { checkApplyStatusMutate(undefined, { @@ -173,10 +182,37 @@ export function IdentityVerificationStep({ }; // 파트 불일치 다이얼로그: "새로운 파트로 지원하기" 선택 - const handleStartNewApplication = () => { + const handleStartNewApplication = async () => { + setIsChangingJobFamily(true); setMismatchDialog({ isOpen: false, savedJobFamily: null }); - toastController.positive(APPLY_MESSAGE.success.continueWriting); - dispatch("goToApply", verifiedEmail); + + try { + // 1. 현재 프로필 백업 + const profile = await applyApi.getMe(); + + // 2. 기존 draft + 프로필 삭제 + await deleteDraftAsync(); + + // 3. 프로필 복원 (새로운 jobFamily로) + await updateProfileAsync({ + name: profile.name, + phoneNumber: "", // TODO: getMe 응답에 phoneNumber가 없어서 임시 처리 + careerDetails: profile.careerDetails, + region: profile.region, + experiencePeriod: profile.experiencePeriod, + interestedDomains: profile.interestedDomains, + jobFamily: context.jobFamily, + }); + + // 4. 지원서 작성으로 이동 + toastController.positive(APPLY_MESSAGE.success.continueWriting); + dispatch("goToApply", verifiedEmail); + } catch (error) { + handleError(error, "파트 변경 실패"); + toastController.destructive("파트 변경에 실패했습니다. 다시 시도해주세요."); + } finally { + setIsChangingJobFamily(false); + } }; // 다이얼로그 메시지 생성 @@ -271,17 +307,19 @@ export function IdentityVerificationStep({ {mismatchDialogContent && ( !open && setMismatchDialog({ isOpen: false, savedJobFamily: null })} + open={mismatchDialog.isOpen || isChangingJobFamily} + onOpenChange={open => !open && !isChangingJobFamily && setMismatchDialog({ isOpen: false, savedJobFamily: null })} header={mismatchDialogContent.header} body={mismatchDialogContent.body} primaryAction={{ children: mismatchDialogContent.primaryAction, onClick: handleContinueSavedDraft, + disabled: isChangingJobFamily, }} secondaryAction={{ - children: mismatchDialogContent.secondaryAction, - onClick: handleStartNewApplication, + children: isChangingJobFamily ? "변경 중..." : mismatchDialogContent.secondaryAction, + onClick: () => void handleStartNewApplication(), + disabled: isChangingJobFamily, }} /> )} From f4b1e7ebf4f9ac62af0e47d9bb2a85bdab139c93 Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 22:51:36 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=83=81=EC=88=98=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/constants/applyMessages.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/constants/applyMessages.tsx b/apps/web/src/constants/applyMessages.tsx index 65f92f20d..32072aa73 100644 --- a/apps/web/src/constants/applyMessages.tsx +++ b/apps/web/src/constants/applyMessages.tsx @@ -76,7 +76,7 @@ export const APPLY_DIALOG = { 기존 {savedJobFamilyKorean} 지원서는 사라져요. ), - primaryAction: `${savedJobFamilyKorean} 지원서 이어서 작성하기`, - secondaryAction: `${currentJobFamilyKorean}로 새로 지원하기`, + primaryAction: `지원서 이어서 작성하기`, + secondaryAction: `새로 지원하기`, }), }; From 4abc44b38345ccc37419cfa4890332356e3f40aa Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 22:54:31 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20profile=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9E=84=EC=8B=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apply/steps/IdentityVerificationStep.tsx | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx index 6f08ca18f..42bc33ae1 100644 --- a/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx +++ b/apps/web/src/features/apply/steps/IdentityVerificationStep.tsx @@ -44,10 +44,7 @@ interface JobFamilyMismatchDialog { savedJobFamily: JobFamily | null; } -export function IdentityVerificationStep({ - context, - dispatch, -}: IdentityVerificationStepProps) { +export function IdentityVerificationStep({ context, dispatch }: IdentityVerificationStepProps) { const location = useLocation(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); @@ -88,11 +85,14 @@ export function IdentityVerificationStep({ toastController.basic("PIN 재설정 완료", "새로운 PIN을 입력해 본인 확인을 다시 진행해주세요."); // 3. URL 파라미터 정리 - setSearchParams(prev => { - prev.delete("pinReset"); - prev.delete("email"); - return prev; - }, { replace: true }); + setSearchParams( + prev => { + prev.delete("pinReset"); + prev.delete("email"); + return prev; + }, + { replace: true }, + ); }, [isPinResetSuccess, prefillEmail, setEmailValue, setSearchParams]); const email = watchEmail("email"); @@ -100,7 +100,8 @@ export function IdentityVerificationStep({ const [isChangingJobFamily, setIsChangingJobFamily] = useState(false); - const { mutate: checkApplyStatusMutate, isPending: isCheckingStatus } = useCheckApplyStatusMutation(); + const { mutate: checkApplyStatusMutate, isPending: isCheckingStatus } = + useCheckApplyStatusMutation(); const { mutateAsync: deleteDraftAsync } = useDeleteDraftMutation(); const { mutateAsync: updateProfileAsync } = useMemberProfileMutation(); @@ -196,7 +197,7 @@ export function IdentityVerificationStep({ // 3. 프로필 복원 (새로운 jobFamily로) await updateProfileAsync({ name: profile.name, - phoneNumber: "", // TODO: getMe 응답에 phoneNumber가 없어서 임시 처리 + phoneNumber: "01012345678", // TODO: getMe 응답에 phoneNumber가 없어서 임시 처리 careerDetails: profile.careerDetails, region: profile.region, experiencePeriod: profile.experiencePeriod, @@ -308,7 +309,11 @@ export function IdentityVerificationStep({ {mismatchDialogContent && ( !open && !isChangingJobFamily && setMismatchDialog({ isOpen: false, savedJobFamily: null })} + onOpenChange={open => + !open && + !isChangingJobFamily && + setMismatchDialog({ isOpen: false, savedJobFamily: null }) + } header={mismatchDialogContent.header} body={mismatchDialogContent.body} primaryAction={{ From 7754ff16cd60ad980c3a2a4c5c95177c23ae89cc Mon Sep 17 00:00:00 2001 From: onejuneee Date: Mon, 29 Dec 2025 23:12:32 +0900 Subject: [PATCH 9/9] =?UTF-8?q?fix(JDS):=20input=20=ED=83=9C=EA=B7=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=20=EB=B8=8C=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=EC=A0=80=20autofill=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jds/src/components/Input/TagField/tagField.styles.ts | 6 ++++++ .../jds/src/components/Input/TextField/textField.styles.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/packages/jds/src/components/Input/TagField/tagField.styles.ts b/packages/jds/src/components/Input/TagField/tagField.styles.ts index 8363a886a..cd4a62c22 100644 --- a/packages/jds/src/components/Input/TagField/tagField.styles.ts +++ b/packages/jds/src/components/Input/TagField/tagField.styles.ts @@ -292,6 +292,12 @@ export const StyledTagInput = styled("input", { "&::placeholder": { color: theme.color.semantic.object.assistive, }, + + "&:-webkit-autofill, &:-webkit-autofill:hover, &:-webkit-autofill:focus, &:-webkit-autofill:active": { + WebkitTextFillColor: textColor, + WebkitBoxShadow: `0 0 0 1000px ${theme.color.semantic.surface.standard} inset`, + transition: "background-color 5000s ease-in-out 0s", + }, }; }); diff --git a/packages/jds/src/components/Input/TextField/textField.styles.ts b/packages/jds/src/components/Input/TextField/textField.styles.ts index b167640ce..76e82906b 100644 --- a/packages/jds/src/components/Input/TextField/textField.styles.ts +++ b/packages/jds/src/components/Input/TextField/textField.styles.ts @@ -265,6 +265,12 @@ export const StyledInput = styled("input", { "&::placeholder": { color: theme.color.semantic.object.assistive, }, + + "&:-webkit-autofill, &:-webkit-autofill:hover, &:-webkit-autofill:focus, &:-webkit-autofill:active": { + WebkitTextFillColor: textColor, + WebkitBoxShadow: `0 0 0 1000px ${theme.color.semantic.surface.standard} inset`, + transition: "background-color 5000s ease-in-out 0s", + }, }; });