Repository: JECT-Study/JECT-Official-WebSite-Client
Pull request #146: feat: PIN 재설정 기능 구현
Author: login: WonJuneKim
Labels: name: ✨feature
description: 구현, 개선 사항 관련 부분
name: 👩🏻‍💻frontend
description: 프론트엔드 작업
Created: 2025-04-14T18:54:53Z | Merged: 2025-04-15T16:24:44Z

## 💡 작업 내용

- [x] PIN 재설정 플로우 구현
- [x] 지원 플로우 중 버튼 텍스트 변경
- [x] 기존 제출과 PIN 재설정 이벤트 핸들러 분리

## 💡 자세한 설명

### ✅ 수정 사항 반영

#134 에서 요청하셨던 버튼의 문구을 요구사항에 맞게 수정하였습니다.

### ✅ 하단 버튼 설정 추가
```typescript
    <BlockButton
        type='submit'
        form={isResetPin ? 'resetPinForm' : 'registerForm'}
        disabled={isSubmitButtonDisabled}
        size='lg'
        style='solid'
        hierarchy='accent'
        rightIcon={
          !isResetPin && <Icon name='forward' size='md' fillColor={rightIconFillColor} />
        }
      >
        {isResetPin ? 'PIN 다시 설정 완료하기' : '다음 단계로 진행하기'}
    </BlockButton>
```
와 같이 `isResetPin` 값에 따라 관리하는 `<form>` 이 변경되며, label도 이에 맞춰서 변경됩니다.

## 📗 참고 자료 (선택)

## 📢 리뷰 요구 사항 (선택)
### ✅ PIN 재설정 성공 흐름 관련
(ApplyVerifyEmail의 213 line~ 233 line에서) **resetPinMutate 성공** 시 
```typescript
onSuccess: response => {
          if (response.status === 'SUCCESS') {
            addToast('PIN을 다시 설정했어요', 'positive');
            setStep(1);
            setStoredEmail('');
            setIsAuthCodeExpired(false);
            setVerificationToken(null);

            resetEmailForm();
            resetVerificationForm();
            resetPinForm();
          }
        },
```
와 같이 PIN 재설정 flow에서 사용했던 각 state와 form의 입력값을 초기화하는 과정이 필요한데, 이 흐름이 어떤 동작인지 명확하게 느껴지는 바가 떨어진다고 생각합니다. 

이에 대한 해결방법으로 인증 과정의 전체 상태를 관리하는 Zustand 스토어를 생성하거나, 일련의 동작을 수행하는 별도의 함수를 만드는 방법 등이 있는데, 지금 상태로도 괜찮은지 혹은 제시된 방법(혹은 그 외) 중에 적절하다고 생각하시는 방법이 있는지 질문드려요!


## ✅ 셀프 체크리스트

- [x] 머지할 브랜치 확인했나요?
- [x] 이슈는 close 했나요?
- [x] Reviewers, Labels, Projects를 등록했나요?
- [x] 기능이 잘 동작하나요?
- [x] 불필요한 코드는 제거했나요?

closes #113 


Linked issue #113: feat: PIN 재설정 기능 구현
## 💡 구현 기능
P-C-7 에 해당하는 PIN 재설정 플로우 구현

## ✅ 체크리스트
- [ ] PIN 재설정 api 적용
- [ ] PIN 재설정 플로우 구현

## ⏰ 예상 소요 시간


Diff:
diff --git a/src/apis/apply.ts b/src/apis/apply.ts
index 3213f1440..c2fbe9c72 100644
--- a/src/apis/apply.ts
+++ b/src/apis/apply.ts
@@ -9,6 +9,8 @@ import {
   PinLoginResponse,
   RegisterMemberPayload,
   RegisterMemberResponse,
+  ResetPinPayload,
+  ResetPinResponse,
   VerificationEmailCodePayload,
   VerificationEmailCodeQueryParams,
   VerificationEmailCodeResponse,
@@ -69,3 +71,11 @@ export const postRegisterMember = async (
     options,
   );
 };
+
+export const putResetPin = async (data: ResetPinPayload) => {
+  return await requestHandler<ResetPinResponse, ResetPinPayload>(
+    'put',
+    API_ENDPOINT.resetPin,
+    data,
+  );
+};
diff --git a/src/constants/apiEndpoint.ts b/src/constants/apiEndpoint.ts
index 3d03e000c..9d1146801 100644
--- a/src/constants/apiEndpoint.ts
+++ b/src/constants/apiEndpoint.ts
@@ -7,6 +7,7 @@ export const API_ENDPOINT = {
   checkEmailExists: '/auth/login/exist',
   memberProfileInitial: '/members/profile/initial',
   registerMember: '/members',
+  resetPin: '/members/pin',
   refreshToken: '/auth/refresh',
   uploadPortfolio: '/upload/portfolios',
   question: '/apply/questions',
diff --git a/src/hooks/useResetPinMutation.ts b/src/hooks/useResetPinMutation.ts
new file mode 100644
index 000000000..1080a05dc
--- /dev/null
+++ b/src/hooks/useResetPinMutation.ts
@@ -0,0 +1,27 @@
+import { useMutation, UseMutationResult } from '@tanstack/react-query';
+import { AxiosError } from 'axios';
+
+import { putResetPin } from '@/apis/apply';
+import { ResetPinPayload, ResetPinResponse } from '@/types/apis/apply';
+import { ApiResponse } from '@/types/apis/response';
+
+export const useResetPinMutation = (): UseMutationResult<
+  ApiResponse<ResetPinResponse>,
+  AxiosError,
+  ResetPinPayload,
+  unknown
+> => {
+  return useMutation({
+    mutationKey: ['putResetPin'],
+    mutationFn: putResetPin,
+    onMutate: variables => {
+      console.log('useResetPinMutation 시작, variables:', variables);
+    },
+    onSuccess: data => {
+      console.log('useResetPinMutation 성공:', data);
+    },
+    onError: error => {
+      console.error('useResetPinMutation 에러:', error);
+    },
+  });
+};
diff --git a/src/pages/ApplyVerifyEmail.tsx b/src/pages/ApplyVerifyEmail.tsx
index cd94b669c..e3575ac5b 100644
--- a/src/pages/ApplyVerifyEmail.tsx
+++ b/src/pages/ApplyVerifyEmail.tsx
@@ -17,9 +17,17 @@ import { useApplyPinForm } from '@/hooks/useApplyPinForm';
 import { useCheckEmailExistsMutation } from '@/hooks/useCheckEmailExistMutation';
 import { useEmailAuthCodeMutation } from '@/hooks/useEmailAuthCodeMutation';
 import { useRegisterMemberMutation } from '@/hooks/useRegisterMemberMutation';
+import { useResetPinMutation } from '@/hooks/useResetPinMutation';
 import { useVerificationEmailCodeMutation } from '@/hooks/useVerificationEmailCodeMutation';
-import { Email, RegisterMemberPayload, VerificationEmailCodePayload } from '@/types/apis/apply';
+import { useToastActions } from '@/stores/toastStore';
+import {
+  Email,
+  RegisterMemberPayload,
+  ResetPinPayload,
+  VerificationEmailCodePayload,
+} from '@/types/apis/apply';
 import { CreateSubmitHandler } from '@/utils/formHelpers';
+import { tokenUtils } from '@/utils/interceptor';
 
 interface ApplyVerifyEmailProps {
   isResetPin?: boolean;
@@ -32,6 +40,7 @@ function ApplyVerifyEmail({
   setIsNewApplicant,
   setUserEmail,
 }: ApplyVerifyEmailProps) {
+  const { addToast } = useToastActions();
   const navigate = useNavigate();
   const [storedEmail, setStoredEmail] = useState('');
   const [isPinHidden, setIsPinHidden] = useState(true);
@@ -42,10 +51,13 @@ function ApplyVerifyEmail({
   const [isAuthCodeExpired, setIsAuthCodeExpired] = useState(false);
   const [emailButtonText, setEmailButtonText] = useState('인증번호 받기');
 
+  const templateType = isResetPin ? 'PIN_RESET' : 'AUTH_CODE';
+
   const {
     register: registerEmail,
     handleSubmit: handleSubmitEmail,
     formState: { errors: errorsEmail, isValid: isEmailValid },
+    reset: resetEmailForm,
   } = useApplyEmailForm();
 
   const {
@@ -54,12 +66,14 @@ function ApplyVerifyEmail({
     setError: setVerificationError,
     watch: watchVerification,
     formState: { errors: errorsVerification, isValid: isVerificationValid },
+    reset: resetVerificationForm,
   } = useApplyAuthCodeForm();
 
   const {
     register: registerPin,
     handleSubmit: handleSubmitPin,
     formState: { errors: errorsPin, isValid: isPinValid },
+    reset: resetPinForm,
   } = useApplyPinForm();
 
   const { mutate: checkEmailMutate } = useCheckEmailExistsMutation();
@@ -68,6 +82,7 @@ function ApplyVerifyEmail({
     useVerificationEmailCodeMutation();
   const { mutate: registerMemberMutate, isPending: isRegisteringMember } =
     useRegisterMemberMutation();
+  const { mutate: resetPinMutate, isPending: isResettingPin } = useResetPinMutation();
 
   const authCodeValue = watchVerification('authCode');
 
@@ -99,10 +114,22 @@ function ApplyVerifyEmail({
             setIsNewApplicant(false);
             return;
           }
+
           setStoredEmail(email);
-          emailMutate({ email, template: 'CERTIFICATE' });
-          setStep(2);
-          setIsAuthCodeExpired(false);
+
+          emailMutate(
+            { email, template: templateType },
+            {
+              onSuccess: () => {
+                setStep(2);
+                setIsAuthCodeExpired(false);
+              },
+              onError: error => {
+                console.error('이메일 인증 코드 발송 실패:', error);
+                //TODO: 이메일 인증 코드 추가 예외처리 필요
+              },
+            },
+          );
         },
         onError: error => {
           console.error('이메일 존재 여부 확인 실패:', error);
@@ -120,7 +147,7 @@ function ApplyVerifyEmail({
     verifyEmailCodeMutate(
       {
         payload: { email: storedEmail, authCode },
-        queryParams: { template: 'CERTIFICATE' },
+        queryParams: { template: templateType },
       },
       {
         onSuccess: response => {
@@ -145,8 +172,13 @@ function ApplyVerifyEmail({
             });
             return;
           }
+
           if (response.data?.token) {
-            setVerificationToken(response.data.token);
+            if (isResetPin) {
+              tokenUtils.setAccessToken(response.data.token);
+            } else {
+              setVerificationToken(response.data.token);
+            }
           }
 
           setStep(3);
@@ -190,6 +222,35 @@ function ApplyVerifyEmail({
     );
   };
 
+  const onResetPinSubmit = ({ pin }: ResetPinPayload) => {
+    console.log('PIN 재설정 API 요청 준비:', { pin });
+
+    resetPinMutate(
+      { pin },
+      {
+        onSuccess: response => {
+          console.log('PIN 재설정 성공:', response);
+
+          if (response.status === 'SUCCESS') {
+            addToast('PIN을 다시 설정했어요', 'positive');
+            //TODO: 지원 초기 상태로 state들을 초기화 로직(Zustand, 혹은 외부 함수로)
+            setStep(1);
+            setStoredEmail('');
+            setIsAuthCodeExpired(false);
+            setVerificationToken(null);
+
+            resetEmailForm();
+            resetVerificationForm();
+            resetPinForm();
+          }
+        },
+        onError: error => {
+          console.error('PIN 재설정 실패:', error);
+        },
+      },
+    );
+  };
+
   const handleEmailFormSubmit = CreateSubmitHandler<{ email: string }, Email>(
     handleSubmitEmail,
     onEmailSubmit,
@@ -205,6 +266,11 @@ function ApplyVerifyEmail({
     RegisterMemberPayload
   >(handleSubmitPin, onRegisterMemberSubmit);
 
+  const handleResetPinFormSubmit = CreateSubmitHandler<{ pin: string }, ResetPinPayload>(
+    handleSubmitPin,
+    onResetPinSubmit,
+  );
+
   const handleTermsChange = (e: ChangeEvent<HTMLInputElement>) => {
     setIsTermsChecked(e.target.checked);
   };
@@ -239,7 +305,9 @@ function ApplyVerifyEmail({
   };
 
   const isSubmitButtonDisabled =
-    !isPinValid || isRegisteringMember || (!isReVerification && !isTermsChecked);
+    !isPinValid ||
+    (isResetPin ? isResettingPin : isRegisteringMember) ||
+    (!isReVerification && !isTermsChecked);
 
   const rightIconFillColor = isSubmitButtonDisabled
     ? 'fill-accent-trans-hero-dark'
@@ -303,9 +371,9 @@ function ApplyVerifyEmail({
 
             {step >= 3 && (
               <form
-                id='registerForm'
+                id={isResetPin ? 'resetPinForm' : 'registerForm'}
                 className='gap-7xl flex flex-col'
-                onSubmit={handleRegisterMemberFormSubmit}
+                onSubmit={isResetPin ? handleResetPinFormSubmit : handleRegisterMemberFormSubmit}
               >
                 <InputField
                   type={isPinHidden ? 'password' : 'text'}
@@ -350,7 +418,7 @@ function ApplyVerifyEmail({
             </div>
           )}
           <div className='gap-md flex flex-col'>
-            {step === 2 && (
+            {step === 2 && !isResetPin && (
               <LabelButton
                 size='xs'
                 hierarchy='tertiary'
@@ -364,14 +432,16 @@ function ApplyVerifyEmail({
             )}
             <BlockButton
               type='submit'
-              form='registerForm'
+              form={isResetPin ? 'resetPinForm' : 'registerForm'}
               disabled={isSubmitButtonDisabled}
               size='lg'
               style='solid'
               hierarchy='accent'
-              rightIcon={<Icon name='forward' size='md' fillColor={rightIconFillColor} />}
+              rightIcon={
+                !isResetPin && <Icon name='forward' size='md' fillColor={rightIconFillColor} />
+              }
             >
-              다음 단계로 진행하기
+              {isResetPin ? 'PIN 다시 설정 완료하기' : '다음 단계로 진행하기'}
             </BlockButton>
           </div>
         </div>
diff --git a/src/pages/ApplyVerifyPin.tsx b/src/pages/ApplyVerifyPin.tsx
index 69c2d4910..e6f84f865 100644
--- a/src/pages/ApplyVerifyPin.tsx
+++ b/src/pages/ApplyVerifyPin.tsx
@@ -34,6 +34,11 @@ function ApplyVerifyPin({ email }: ApplyVerifyPinProps) {
 
   const { mutate: pinLoginMutate, isPending: isPinLoginLoading } = usePinLoginMutation();
 
+  const rightIconFillColor =
+    !isPinValid || isPinLoginLoading
+      ? 'fill-accent-trans-hero-dark'
+      : 'fill-object-static-inverse-hero-dark';
+
   const onPinSubmit = ({ pin }: PinLoginPayload) => {
     const payload = { email, pin };
     console.log('PIN 유효성 검사 통과, 로그인 API 요청 payload:', payload);
@@ -140,8 +145,9 @@ function ApplyVerifyPin({ email }: ApplyVerifyPinProps) {
             size='lg'
             style='solid'
             hierarchy='accent'
+            rightIcon={<Icon name='forward' size='md' fillColor={rightIconFillColor} />}
           >
-            PIN 다시 설정 완료하기
+            다음 단계로 진행하기
           </BlockButton>
         </div>
       </section>
diff --git a/src/types/apis/apply.ts b/src/types/apis/apply.ts
index b6079874a..6c2e21e75 100644
--- a/src/types/apis/apply.ts
+++ b/src/types/apis/apply.ts
@@ -5,7 +5,7 @@ export interface Email {
 export type EmailExistsResponse = boolean;
 
 export interface EmailAuthPayload extends Email {
-  template: 'CERTIFICATE' | 'PIN_RESET';
+  template: 'AUTH_CODE' | 'PIN_RESET';
 }
 
 export interface VerificationEmailCodePayload {
@@ -14,7 +14,7 @@ export interface VerificationEmailCodePayload {
 }
 
 export interface VerificationEmailCodeQueryParams {
-  template: 'CERTIFICATE' | 'PIN_RESET';
+  template: 'AUTH_CODE' | 'PIN_RESET';
 }
 
 export interface VerificationEmailCodeResponse {
@@ -46,3 +46,9 @@ export interface RegisterMemberResponse {
   accessToken: string;
   refreshToken: string;
 }
+
+export interface ResetPinPayload {
+  pin: string;
+}
+
+export type ResetPinResponse = null;


Reviews:
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:35:12Z
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:37:22Z
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:38:08Z
user: login: rkdcodus
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:39:14Z
user: login: rkdcodus
body: 확인했습니다!! 고생하셨습니다 👍 

조금 다른 이야기인데 궁금,, 고민인게 쿠키 인증으로 완전히 바뀌는 거라면 로컬에서 작업할 때 인증이 필요한 api를 테스트할 수 있는 방법이 뭐가 있을까요..!? 백엔드랑 논의는 해봐야겠지만!!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:45:32Z
user: login: WonJuneKim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:00:25Z
user: login: WonJuneKim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:00:52Z
user: login: rkdcodus
body: 좋습니다! 수고하셨습니당 👍
쿠키 인증으로 api 잘되는지는 논의했던 개발용 배포 혹은 스크립트로 수동 배포해서 테스트해보고 이슈 터지면 그때 해결하시죠!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T15:37:35Z

Review comments:
diff_hunk: @@ -190,6 +210,34 @@ function ApplyVerifyEmail({
     );
   };
 
+  const onResetPinSubmit = ({ pin }: ResetPinPayload) => {
+    console.log('PIN 재설정 API 요청 준비:', { pin });
+
+    resetPinMutate(
+      { pin },
+      {
+        onSuccess: response => {
+          console.log('PIN 재설정 성공:', response);
+
+          if (response.status === 'SUCCESS') {
+            addToast('PIN을 다시 설정했어요', 'positive');
+            setStep(1);
+            setStoredEmail('');
+            setIsAuthCodeExpired(false);
+            setVerificationToken(null);
+
+            resetEmailForm();
+            resetVerificationForm();
+            resetPinForm();
+          }
path: src/pages/ApplyVerifyEmail.tsx
user: login: rkdcodus
body: 저도 원준님 의견에 동의합니다! 
자금 상태로도 초기화하는 과정이구나를 인지할 수는 있는데 이들을 별도 함수로 묶어서 함수명을 통해 명시해주면 훨씬 좋을 것 같아요
state가 많은 편이라 zustand로 정리하면 전반적으로 코드 정리에 도움이 될 거 같은데 공수가 많이 들거 같아요 (액션 부분이..!) 지금 상황으로서는 별도의 함수로 분리해주는게 적절하지 않을까 싶습니다!

created_at: 2025-04-15T10:35:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -100,7 +115,7 @@ function ApplyVerifyEmail({
             return;
           }
           setStoredEmail(email);
-          emailMutate({ email, template: 'CERTIFICATE' });
+          emailMutate({ email, template: templateType });
path: src/pages/ApplyVerifyEmail.tsx
user: login: rkdcodus
body: ````ts
setStep(2);
setIsAuthCodeExpired(false);
````
이 부분 emailMutate가 성공했을 때 진행해주는게 좋을 거 같아요!
created_at: 2025-04-15T10:37:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -140,8 +145,9 @@ function ApplyVerifyPin({ email }: ApplyVerifyPinProps) {
             size='lg'
             style='solid'
             hierarchy='accent'
+            rightIcon={<Icon name='forward' size='md' fillColor={rightIconFillColor} />}
           >
-            PIN 다시 설정 완료하기
+            다음 단계로 진행하기
path: src/pages/ApplyVerifyPin.tsx
user: login: rkdcodus
body: 수정되었군요!! 확인했습니다 ㅎㅎ 😊
created_at: 2025-04-15T10:38:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,6 +210,34 @@ function ApplyVerifyEmail({
     );
   };
 
+  const onResetPinSubmit = ({ pin }: ResetPinPayload) => {
+    console.log('PIN 재설정 API 요청 준비:', { pin });
+
+    resetPinMutate(
+      { pin },
+      {
+        onSuccess: response => {
+          console.log('PIN 재설정 성공:', response);
+
+          if (response.status === 'SUCCESS') {
+            addToast('PIN을 다시 설정했어요', 'positive');
+            setStep(1);
+            setStoredEmail('');
+            setIsAuthCodeExpired(false);
+            setVerificationToken(null);
+
+            resetEmailForm();
+            resetVerificationForm();
+            resetPinForm();
+          }
path: src/pages/ApplyVerifyEmail.tsx
user: login: rkdcodus
body: 나중에 리팩토링을 어차피 할거라면 지금 이대로 가도 괜찮을 거 같기도해요 ㅎㅎ
created_at: 2025-04-15T10:39:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -100,7 +115,7 @@ function ApplyVerifyEmail({
             return;
           }
           setStoredEmail(email);
-          emailMutate({ email, template: 'CERTIFICATE' });
+          emailMutate({ email, template: templateType });
path: src/pages/ApplyVerifyEmail.tsx
user: login: WonJuneKim
body: [8b0f477](https://github.com/JECT-Study/JECT-Official-WebSite-Client/pull/146/commits/8b0f477b3259880dc7c968769d16ae13201f28f4) 이렇게 emailMutate 콜백 객체도 성공, 실패로 분할 하였습니다!
created_at: 2025-04-15T13:00:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,6 +210,34 @@ function ApplyVerifyEmail({
     );
   };
 
+  const onResetPinSubmit = ({ pin }: ResetPinPayload) => {
+    console.log('PIN 재설정 API 요청 준비:', { pin });
+
+    resetPinMutate(
+      { pin },
+      {
+        onSuccess: response => {
+          console.log('PIN 재설정 성공:', response);
+
+          if (response.status === 'SUCCESS') {
+            addToast('PIN을 다시 설정했어요', 'positive');
+            setStep(1);
+            setStoredEmail('');
+            setIsAuthCodeExpired(false);
+            setVerificationToken(null);
+
+            resetEmailForm();
+            resetVerificationForm();
+            resetPinForm();
+          }
path: src/pages/ApplyVerifyEmail.tsx
user: login: WonJuneKim
body: 해당 부분 최적화 작업은 어떤 방법으로 최적화할지 고민이 좀 필요할 거 같아서 [35e63ed](https://github.com/JECT-Study/JECT-Official-WebSite-Client/pull/146/commits/35e63edb3af17369aa91defb7b57e3969749ec0a) 로 남겨놓았습니다!
created_at: 2025-04-15T13:00:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-14T18:56:46Z
body: # [Qodana](https://qodana.cloud/projects/8E44j/reports/rMKnJ) for JS

**It seems all right 👌**

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ [View the detailed Qodana report](https://qodana.cloud/projects/8E44j/reports/rMKnJ)
<details>
<summary>Detected 76 dependencies</summary>

# Third-party software list

This page lists the third-party software dependencies used in project

| Dependency                                                                                           | Version | Licenses                                                         |
|------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------|
| [@esbuild/linux-x64](https://www.npmjs.com/package/@esbuild/linux-x64)                               | 0.24.2  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@hookform/resolvers](https://www.npmjs.com/package/@hookform/resolvers)                             | 4.1.3   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@rollup/rollup-linux-x64-gnu](https://www.npmjs.com/package/@rollup/rollup-linux-x64-gnu)           | 4.31.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@rollup/rollup-linux-x64-musl](https://www.npmjs.com/package/@rollup/rollup-linux-x64-musl)         | 4.31.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@standard-schema/utils](https://www.npmjs.com/package/@standard-schema/utils)                       | 0.3.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/node](https://www.npmjs.com/package/@tailwindcss/node)                                 | 4.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/oxide-linux-x64-gnu](https://www.npmjs.com/package/@tailwindcss/oxide-linux-x64-gnu)   | 4.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/oxide-linux-x64-musl](https://www.npmjs.com/package/@tailwindcss/oxide-linux-x64-musl) | 4.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/oxide](https://www.npmjs.com/package/@tailwindcss/oxide)                               | 4.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tailwindcss/vite](https://www.npmjs.com/package/@tailwindcss/vite)                                 | 4.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tanstack/query-core](https://www.npmjs.com/package/@tanstack/query-core)                           | 5.66.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@tanstack/react-query](https://www.npmjs.com/package/@tanstack/react-query)                         | 5.66.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/cookie](https://www.npmjs.com/package/@types/cookie)                                         | 0.6.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/estree](https://www.npmjs.com/package/@types/estree)                                         | 1.0.6   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/node](https://www.npmjs.com/package/@types/node)                                             | 22.13.1 | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/prop-types](https://www.npmjs.com/package/@types/prop-types)                                 | 15.7.14 | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [@types/react](https://www.npmjs.com/package/@types/react)                                           | 18.3.18 | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [asynckit](https://www.npmjs.com/package/asynckit)                                                   | 0.4.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [axios](https://www.npmjs.com/package/axios)                                                         | 1.8.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [call-bind-apply-helpers](https://www.npmjs.com/package/call-bind-apply-helpers)                     | 1.0.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [clsx](https://www.npmjs.com/package/clsx)                                                           | 2.1.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [combined-stream](https://www.npmjs.com/package/combined-stream)                                     | 1.0.8   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [cookie](https://www.npmjs.com/package/cookie)                                                       | 1.0.2   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [csstype](https://www.npmjs.com/package/csstype)                                                     | 3.1.3   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [delayed-stream](https://www.npmjs.com/package/delayed-stream)                                       | 1.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [detect-libc](https://www.npmjs.com/package/detect-libc)                                             | 1.0.3   | [Apache-2.0](http://www.apache.org/licenses/)                    |
| [dunder-proto](https://www.npmjs.com/package/dunder-proto)                                           | 1.0.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [enhanced-resolve](https://www.npmjs.com/package/enhanced-resolve)                                   | 5.18.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-define-property](https://www.npmjs.com/package/es-define-property)                               | 1.0.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-errors](https://www.npmjs.com/package/es-errors)                                                 | 1.3.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-object-atoms](https://www.npmjs.com/package/es-object-atoms)                                     | 1.1.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [es-set-tostringtag](https://www.npmjs.com/package/es-set-tostringtag)                               | 2.1.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [esbuild](https://www.npmjs.com/package/esbuild)                                                     | 0.24.2  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [follow-redirects](https://www.npmjs.com/package/follow-redirects)                                   | 1.15.9  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [form-data](https://www.npmjs.com/package/form-data)                                                 | 4.0.2   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [function-bind](https://www.npmjs.com/package/function-bind)                                         | 1.1.2   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [get-intrinsic](https://www.npmjs.com/package/get-intrinsic)                                         | 1.2.7   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [get-proto](https://www.npmjs.com/package/get-proto)                                                 | 1.0.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [gopd](https://www.npmjs.com/package/gopd)                                                           | 1.2.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [graceful-fs](https://www.npmjs.com/package/graceful-fs)                                             | 4.2.11  | [ISC](https://www.isc.org/licenses/)                             |
| [gsap](https://www.npmjs.com/package/gsap)                                                           | 3.12.7  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [has-symbols](https://www.npmjs.com/package/has-symbols)                                             | 1.1.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [has-tostringtag](https://www.npmjs.com/package/has-tostringtag)                                     | 1.0.2   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [hasown](https://www.npmjs.com/package/hasown)                                                       | 2.0.2   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [jiti](https://www.npmjs.com/package/jiti)                                                           | 2.4.2   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [js-tokens](https://www.npmjs.com/package/js-tokens)                                                 | 4.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [lightningcss-linux-x64-gnu](https://www.npmjs.com/package/lightningcss-linux-x64-gnu)               | 1.29.1  | [MPL-2.0](http://mpl.mozilla.org/2012/01/03/announcing-mpl-2-0/) |
| [lightningcss-linux-x64-musl](https://www.npmjs.com/package/lightningcss-linux-x64-musl)             | 1.29.1  | [MPL-2.0](http://mpl.mozilla.org/2012/01/03/announcing-mpl-2-0/) |
| [lightningcss](https://www.npmjs.com/package/lightningcss)                                           | 1.29.1  | [MPL-2.0](http://mpl.mozilla.org/2012/01/03/announcing-mpl-2-0/) |
| [loose-envify](https://www.npmjs.com/package/loose-envify)                                           | 1.4.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [lottie-react](https://www.npmjs.com/package/lottie-react)                                           | 2.4.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [lottie-web](https://www.npmjs.com/package/lottie-web)                                               | 5.12.2  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [math-intrinsics](https://www.npmjs.com/package/math-intrinsics)                                     | 1.1.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [mime-db](https://www.npmjs.com/package/mime-db)                                                     | 1.52.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [mime-types](https://www.npmjs.com/package/mime-types)                                               | 2.1.35  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [nanoid](https://www.npmjs.com/package/nanoid)                                                       | 3.3.8   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [picocolors](https://www.npmjs.com/package/picocolors)                                               | 1.1.1   | [ISC](https://www.isc.org/licenses/)                             |
| [postcss](https://www.npmjs.com/package/postcss)                                                     | 8.5.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [proxy-from-env](https://www.npmjs.com/package/proxy-from-env)                                       | 1.1.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-dom](https://www.npmjs.com/package/react-dom)                                                 | 18.3.1  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-hook-form](https://www.npmjs.com/package/react-hook-form)                                     | 7.54.2  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-router-dom](https://www.npmjs.com/package/react-router-dom)                                   | 7.1.5   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react-router](https://www.npmjs.com/package/react-router)                                           | 7.1.5   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [react](https://www.npmjs.com/package/react)                                                         | 18.3.1  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [rollup](https://www.npmjs.com/package/rollup)                                                       | 4.31.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [scheduler](https://www.npmjs.com/package/scheduler)                                                 | 0.23.2  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [set-cookie-parser](https://www.npmjs.com/package/set-cookie-parser)                                 | 2.7.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [source-map-js](https://www.npmjs.com/package/source-map-js)                                         | 1.2.1   | [BSD-3-Clause](http://www.opensource.org/licenses/BSD-3-Clause)  |
| [tailwindcss](https://www.npmjs.com/package/tailwindcss)                                             | 4.0.0   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [tapable](https://www.npmjs.com/package/tapable)                                                     | 2.2.1   | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [turbo-stream](https://www.npmjs.com/package/turbo-stream)                                           | 2.4.0   | [ISC](https://www.isc.org/licenses/)                             |
| [undici-types](https://www.npmjs.com/package/undici-types)                                           | 6.20.0  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [vite](https://www.npmjs.com/package/vite)                                                           | 6.0.11  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [yaml](https://www.npmjs.com/package/yaml)                                                           | 2.7.0   | [ISC](https://www.isc.org/licenses/)                             |
| [zod](https://www.npmjs.com/package/zod)                                                             | 3.24.2  | [MIT](http://opensource.org/licenses/mit-license.php)            |
| [zustand](https://www.npmjs.com/package/zustand)                                                     | 5.0.3   | [MIT](http://opensource.org/licenses/mit-license.php)            |
</details>
<details>
<summary>Contact Qodana team</summary>

Contact us at [qodana-support@jetbrains.com](mailto:qodana-support@jetbrains.com)
  - Or via our issue tracker: https://jb.gg/qodana-issue
  - Or share your feedback: https://jb.gg/qodana-discussions
</details>
<!-- JetBrains/qodana-action@v2024.3.4 : Qodana for JS -->
author_association: CONTRIBUTOR
user: login: WonJuneKim
created_at: 2025-04-15T11:37:22Z
body: > 확인했습니다!! 고생하셨습니다 👍
> 
> 조금 다른 이야기인데 궁금,, 고민인게 쿠키 인증으로 완전히 바뀌는 거라면 로컬에서 작업할 때 인증이 필요한 api를 테스트할 수 있는 방법이 뭐가 있을까요..!? 백엔드랑 논의는 해봐야겠지만!!

- 이 부분도 어제 논의를 해봤는데, 처음 이 로직을 구현할 때에는 정상적으로 api가 response가 왔지만, 어제 수정된 이후로 서버에서 `Authorization Header` 의 인증 과정을 `httpOnly : true` 로 처리된 쿠키를 통해 서버 내부에 판단하는 로직으로 수정되었습니다.

이에 대한 해결책으로 로컬에서 실제 사이트에서 부여되는 쿠키를 개발자 모드를 통해 주입해봤지만 기본 개발 환경 자체가 http://localhost (https 가 아닌 http 환경)이기 때문에 해당 쿠키는 서버로 아예 가지 않습니다. 

- 서버에서 개발용 서버를 하나 더 두고, 인증 권한을 낮춘 로직을 반영하여 개발 서버 전용으로 인증/인가 관련 로직을 테스트할 수 있는 환경을 제공해 주신다고 하네요!
author_association: CONTRIBUTOR