Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions frontend/src/apis/application/createApplication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import API_BASE_URL from '@/constants/api';
import { secureFetch } from '@/apis/auth/secureFetch';
import { ApplicationFormData } from '@/types/application';

export const createApplication = async (
data: ApplicationFormData,
clubId: string,
) => {
try {
Comment on lines +5 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

입력 파라미터 검증 추가 필요

dataclubId 파라미터에 대한 검증이 없습니다. 잘못된 데이터로 API 호출이 발생할 수 있습니다.

다음과 같이 입력 검증을 추가하는 것을 권장합니다:

export const createApplication = async (
  data: ApplicationFormData,
  clubId: string,
) => {
+  if (!clubId || !clubId.trim()) {
+    throw new Error('clubId가 필요합니다.');
+  }
+  if (!data || !data.title || !data.questions) {
+    throw new Error('유효하지 않은 지원서 데이터입니다.');
+  }
  try {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const createApplication = async (
data: ApplicationFormData,
clubId: string,
) => {
try {
export const createApplication = async (
data: ApplicationFormData,
clubId: string,
) => {
if (!clubId || !clubId.trim()) {
throw new Error('clubId가 필요합니다.');
}
if (!data || !data.title || !data.questions) {
throw new Error('유효하지 않은 지원서 데이터입니다.');
}
try {
// …
🤖 Prompt for AI Agents
In frontend/src/apis/application/createApplication.ts around lines 5 to 9, there
is no validation for the input parameters `data` and `clubId`, which may lead to
API calls with invalid data. Add validation checks to ensure `data` conforms to
the expected ApplicationFormData structure and `clubId` is a non-empty string
before proceeding with the API call. If validation fails, handle the error
appropriately, such as throwing an error or returning early.

const response = await secureFetch(
`${API_BASE_URL}/api/club/${clubId}/application`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
},
);

if (!response.ok) {
throw new Error('지원서 제출에 실패했습니다.');
}

const result = await response.json();
return result.data;
Comment on lines +25 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

API 응답 구조 확인 필요

result.data를 반환하고 있는데, 백엔드 API가 실제로 이런 구조로 응답하는지 확인이 필요합니다.

다음 스크립트를 실행하여 API 응답 구조를 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Description: API 응답 구조 확인을 위해 관련 코드 검색

# updateApplication과 getApplication의 응답 처리 방식 확인
echo "=== Checking response handling in other API files ==="
rg -A 5 "result\.data|response\.json\(\)" --glob "frontend/src/apis/application/*.ts"

# 백엔드 API 응답 구조 관련 타입 정의 확인
echo -e "\n=== Checking API response type definitions ==="
fd -e ts -e tsx . frontend/src/types | xargs rg -l "ApiResponse|Response" | head -5 | xargs cat

Length of output: 2025


🏁 Script executed:

#!/bin/bash
# 1. List any type definition files under src
echo "=== Listing type definition files under src ==="
find frontend/src -type f \( -name "*type*.ts" -o -name "*types*.ts" \) | sed 's/^/FOUND: /'

# 2. Search for generic ApiResponse or Response interfaces
echo -e "\n=== Searching for ApiResponse or Response interface definitions ==="
rg -H "interface .*Response" frontend/src
rg -H "type .*Response" frontend/src

# 3. Search for ApiResponse<T> usage
echo -e "\n=== Searching for ApiResponse< usage ==="
rg -H "ApiResponse<" frontend/src

# 4. Search for 'statuscode' string in code to find sample payloads
echo -e "\n=== Searching for sample payloads with 'statuscode' ==="
rg -H "statuscode" -A2 frontend/src

Length of output: 1537


🏁 Script executed:

#!/bin/bash
# Display contents of the mock API handlers to inspect response structures for createApplication
echo "=== Showing mock API handlers for application endpoints ==="
sed -n '1,200p' frontend/src/mocks/api/applyHandlers.test.ts

Length of output: 4973


API 응답 구조에 맞춰 반환값 수정 필요

createApplication.ts에서 result.data를 반환하고 있는데, 실제 MSW 핸들러 및 백엔드 API는 { data: … } 래퍼 없이 응답 본문을 바로 반환합니다. 따라서 result.dataundefined가 되어 올바른 값을 얻지 못합니다.

  • 수정 대상:

    • frontend/src/apis/application/createApplication.ts (25–26줄)
  • 변경 제안:

- const result = await response.json();
- return result.data;
+ const result = await response.json();
+ return result;

동일한 패턴을 사용하는 getApplication.tsupdateApplication.ts도 함께 검토 후 일관되게 수정해주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const result = await response.json();
return result.data;
const result = await response.json();
return result;
🤖 Prompt for AI Agents
In frontend/src/apis/application/createApplication.ts at lines 25-26, the code
returns result.data, but the actual API response does not wrap the data inside a
data property; it returns the response body directly. To fix this, change the
return statement to return the entire result object instead of result.data.
Also, review and apply the same fix to getApplication.ts and
updateApplication.ts to maintain consistency in handling API responses.

} catch (error) {
console.error('지원서 제출 중 오류 발생:', error);
throw error;
}
};

export default createApplication;
20 changes: 20 additions & 0 deletions frontend/src/apis/application/getApplication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import API_BASE_URL from '@/constants/api';

const getApplication = async (clubId: string) => {
try {
const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

API 엔드포인트 일관성 확인 필요

/api/club/${clubId}/apply 엔드포인트를 사용하고 있는데, updateApplication.ts에서는 /api/club/${clubId}/application을 사용합니다. 일관성을 확인해주세요.

다음 스크립트로 API 엔드포인트 사용 패턴을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# API 엔드포인트 패턴 확인

echo "=== application 관련 API 엔드포인트 검색 ==="
rg "/api/club.*/(apply|application)" --type ts --type tsx -A 2 -B 2

echo "=== API 엔드포인트 상수 확인 ==="
rg "application|apply" frontend/src/constants/ --type ts

Length of output: 397


아래 스크립트로 updateApplication.ts 파일에서 사용 중인 API 엔드포인트를 확인해주세요.


🏁 Script executed:

#!/bin/bash
# updateApplication.ts 위치 찾기
echo "=== updateApplication.ts 파일 경로 ==="
update_file=$(fd updateApplication.ts)
echo "$update_file"

# 해당 파일에서 fetch 사용 부분 추출
echo "=== updateApplication.ts 내 fetch 호출 스니펫 ==="
rg "fetch" -n "$update_file"

Length of output: 441


updateApplication.ts 파일에서 실제 사용 중인 API 엔드포인트를 확인하기 위해 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# updateApplication.ts에서 API 엔드포인트 확인

file="frontend/src/apis/application/updateApplication.ts"
echo "=== 파일 경로 ==="
echo "$file"

echo "=== '/api/club' 포함 라인 확인 ==="
rg "/api/club" -n "$file"

Length of output: 404


API 엔드포인트 일관성 유지 필요

getApplication.tsupdateApplication.ts에서 동일한 리소스를 가리키면서도 각각 /apply/application을 사용하고 있습니다. 둘 중 하나로 통일해주세요.

  • getApplication.ts (5행)
    - const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`);
    + const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/application`);
  • updateApplication.ts (11행)
    `${API_BASE_URL}/api/club/${clubId}/application`

위 예시처럼 두 파일 모두 /application을 사용하거나, 반대로 /apply로 통일하시기 바랍니다.

🤖 Prompt for AI Agents
In frontend/src/apis/application/getApplication.ts at line 5, the API endpoint
uses `/apply` while updateApplication.ts uses `/application`. To maintain
consistency, change the endpoint in getApplication.ts from `/apply` to
`/application` so both files use the same `/api/club/${clubId}/application`
endpoint.

if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}

const result = await response.json();
return result.data;
} catch (error) {
// [x] FIXME:
// {"statuscode":"800-1","message":"지원서가 존재하지 않습니다.","data":null}
console.error('Error fetching club details', error);
throw error;
}
};

export default getApplication;
33 changes: 33 additions & 0 deletions frontend/src/apis/application/updateApplication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import API_BASE_URL from '@/constants/api';
import { secureFetch } from '@/apis/auth/secureFetch';
import { ApplicationFormData } from '@/types/application';

export const updateApplication = async (
data: ApplicationFormData,
clubId: string,
) => {
try {
const response = await secureFetch(
`${API_BASE_URL}/api/club/${clubId}/application`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
},
);

if (!response.ok) {
throw new Error('지원서 수정에 실패했습니다.');
}

const result = await response.json();
return result.data;
} catch (error) {
console.error('지원서 수정 중 오류 발생:', error);
throw error;
}
};

export default updateApplication;
3 changes: 2 additions & 1 deletion frontend/src/constants/INITIAL_FORM_DATA.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ApplicationFormData } from '@/types/application';

const INITIAL_FORM_DATA: ApplicationFormData = {
form_title: '',
title: '',
questions: [
{
id: 1,
title: '',
description: '',
type: 'SHORT_TEXT',
options: { required: true },
items: [],
},
{
id: 2,
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/hooks/queries/application/useGetApplication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import getApplication from '@/apis/application/getApplication';

export const useGetApplication = (clubId: string) => {
return useQuery({
queryKey: ['applicationForm', clubId],
queryFn: () => getApplication(clubId),
retry: false,
});
};
2 changes: 1 addition & 1 deletion frontend/src/mocks/api/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const applyHandlers = [
return HttpResponse.json(
{
clubId,
form_title: mockData.form_title,
form_title: mockData.title,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

API 응답 구조 불일치 문제

mockData.title을 참조하면서도 API 응답에서는 여전히 form_title 키를 사용하고 있습니다. 이는 클라이언트 코드와의 불일치를 야기할 수 있습니다.

다음과 같이 수정하여 일관성을 맞춰주세요:

-        form_title: mockData.title,
+        title: mockData.title,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
form_title: mockData.title,
title: mockData.title,
🤖 Prompt for AI Agents
In frontend/src/mocks/api/apply.ts at line 28, the key 'form_title' is used
while referencing mockData.title, causing inconsistency with the API response
structure. Rename the key from 'form_title' to 'title' to match the mockData
property and ensure consistency with the client code.

questions: mockData.questions,
},
{ status: 200 },
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/mocks/data/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface Question {
}

export const mockData: ApplicationFormData = {
form_title: '2025_2_지원서',
title: '2025_2_지원서',
questions: [
{
id: 1,
Expand Down
67 changes: 53 additions & 14 deletions frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
// 지원서 제작하기 : 지원서 제작 컴포넌트
// 지원서 수정과 제작을 맡을 컴포넌트
// Todo: 질문 삭제 및 질문 추가 기능 구현
import { useState } from 'react';
import { useState, useEffect } from 'react';
import QuestionBuilder from '@/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder';
import { QuestionType } from '@/types/application';
import { Question } from '@/types/application';
import { mockData } from '@/mocks/data/mockData';
import { ApplicationFormData } from '@/types/application';
import { PageContainer } from '@/styles/PageContainer.styles';
import * as Styled from './CreateApplicationForm.styles';
import INITIAL_FORM_DATA from '@/constants/INITIAL_FORM_DATA';
import { QuestionDivider } from './CreateApplicationForm.styles';
import { useAdminClubContext } from '@/context/AdminClubContext';
import { useGetApplication } from '@/hooks/queries/application/useGetApplication';
import createApplication from '@/apis/application/createApplication';
import updateApplication from '@/apis/application/updateApplication';

const CreateApplicationForm = () => {
const [formData, setFormData] = useState<ApplicationFormData>(
mockData ?? INITIAL_FORM_DATA,
);
const { clubId } = useAdminClubContext();
if (!clubId) return null;

const { data, isLoading, isError } = useGetApplication(clubId);

Comment on lines +19 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

로딩 및 에러 상태 처리 필요

isLoadingisError 상태를 활용하지 않고 있습니다. 사용자 경험 개선을 위해 이러한 상태들을 UI에 반영해야 합니다.

다음과 같이 로딩 및 에러 상태를 처리하세요:

  const { data, isLoading, isError } = useGetApplication(clubId);

+  if (isLoading) {
+    return <div>로딩 중...</div>;
+  }
+
+  if (isError) {
+    return <div>지원서를 불러오는 중 오류가 발생했습니다.</div>;
+  }
+
  const [formData, setFormData] =
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data, isLoading, isError } = useGetApplication(clubId);
const { data, isLoading, isError } = useGetApplication(clubId);
if (isLoading) {
return <div>로딩 중...</div>;
}
if (isError) {
return <div>지원서를 불러오는 중 오류가 발생했습니다.</div>;
}
const [formData, setFormData] =
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx around
lines 19 to 20, the isLoading and isError states from useGetApplication are not
being handled. Update the component to display appropriate UI feedback during
loading (e.g., a spinner or loading message) and error states (e.g., an error
message) to improve user experience.

const [formData, setFormData] =
useState<ApplicationFormData>(INITIAL_FORM_DATA);

useEffect(() => {
if (data) {
setFormData(data);
}
}, [data]);

const [nextId, setNextId] = useState(() => {
const questions = mockData?.questions ?? INITIAL_FORM_DATA.questions;
const questions = data?.questions ?? INITIAL_FORM_DATA.questions;
if (questions.length === 0) return 1;
const maxId = Math.max(...questions.map((q) => q.id));
const maxId = Math.max(...questions.map((q: Question) => q.id));
return maxId + 1;
});

Expand All @@ -29,6 +40,7 @@ const CreateApplicationForm = () => {
title: '',
description: '',
type: 'SHORT_TEXT',
items: [],
options: { required: false },
};
setFormData((prev) => ({
Expand Down Expand Up @@ -61,7 +73,7 @@ const CreateApplicationForm = () => {
const handleFormTitleChange = (value: string) => {
setFormData((prev) => ({
...prev,
form_title: value,
title: value,
}));
};

Expand All @@ -87,7 +99,7 @@ const CreateApplicationForm = () => {
? q.items && q.items.length >= 2
? q.items
: [{ value: '' }, { value: '' }]
: undefined,
: [],
};
}),
}));
Expand All @@ -102,12 +114,37 @@ const CreateApplicationForm = () => {
}));
};

const handleSubmit = async () => {
if (!clubId) return;
const reorderedQuestions = formData.questions.map((q, idx) => ({
...q,
id: idx + 1,
}));
Comment on lines +119 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

질문 ID 재정렬의 영향 확인 필요

저장할 때마다 질문 ID를 1부터 순차적으로 재정렬하고 있습니다. 다른 곳에서 이 ID를 참조하거나 백엔드에서 특정 ID 값에 의존하는 경우 문제가 발생할 수 있습니다.

다음 스크립트를 실행하여 질문 ID 사용 현황을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Description: 질문 ID가 다른 곳에서 참조되는지 확인

# 질문 ID 참조 패턴 검색
echo "=== Searching for question ID references ==="
rg -A 3 "question\.id|q\.id|questionId" --glob "frontend/src/**/*.{ts,tsx}" | grep -v "CreateApplicationForm"

# 백엔드 API에서 질문 ID 처리 방식 확인
echo -e "\n=== Checking backend question ID handling ==="
fd -e ts -e js . backend/src | xargs rg -A 3 "question.*id|questionId" | head -20

Length of output: 4395


질문 ID 재정렬 중단 및 안정적 ID 유지 필요

저장할 때마다 질문 ID를 1부터 다시 매기면, 아래 컴포넌트들이 기존 ID로 데이터를 조회·처리하기 때문에 의도치 않은 동작(답안 손실, 키 재생성 등)이 발생할 수 있습니다.
기존 ID를 그대로 유지하고, 새 질문 추가 시에만 고유 ID를 부여하도록 수정해 주세요.

  • 영향을 받는 위치

    • frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx
      • key={q.id}
      • selectedAnswers={getAnswersById(q.id)}
    • frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx
      • id={question.id}
      • onAnswerChange={(value) => onChange(question.id, value)}
  • 제안하는 수정 예시

- const reorderedQuestions = formData.questions.map((q, idx) => ({
-   ...q,
-   id: idx + 1,
- }));
+ // ID 재정렬 제거: 기존 ID 유지
+ const reorderedQuestions = formData.questions;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const reorderedQuestions = formData.questions.map((q, idx) => ({
...q,
id: idx + 1,
}));
// ID 재정렬 제거: 기존 ID 유지
const reorderedQuestions = formData.questions;
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx around
lines 119 to 122, the code currently reassigns question IDs sequentially
starting from 1 on every save, which can break references in other components
relying on stable IDs. To fix this, stop reordering the question IDs on save and
preserve the existing IDs as they are. When adding new questions, assign them
unique IDs without altering existing ones, ensuring consistent ID usage across
components like AnswerApplicationForm.tsx and QuestionAnswerer.tsx.


const payload: ApplicationFormData = {
...formData,
questions: reorderedQuestions,
};
try {
if (data) {
await updateApplication(payload, clubId);
alert('지원서가 성공적으로 수정되었습니다.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

alert() 대신 토스트 알림 사용 권장

브라우저의 alert()는 사용자 경험을 저해합니다. 토스트 알림이나 모달을 사용하는 것이 좋습니다.

토스트 알림 라이브러리를 사용하거나 커스텀 알림 컴포넌트를 구현하시겠습니까?

Also applies to: 134-134, 137-137

🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx at lines
131, 134, and 137, replace the use of browser alert() calls with a toast
notification system to improve user experience. Choose a toast notification
library or implement a custom toast component, then update these lines to
trigger toast messages instead of alert dialogs for success or error feedback.

} else {
await createApplication(payload, clubId);
alert('지원서가 성공적으로 생성되었습니다.');
}
} catch (error) {
alert('지원서 저장에 실패했습니다.');
console.error(error);
}
};
Comment on lines +117 to +140
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

API 호출 중 로딩 상태 추가 필요

API 호출 중에 로딩 상태가 없어 사용자가 여러 번 클릭하거나 중복 제출이 발생할 수 있습니다.

다음과 같이 로딩 상태를 추가하세요:

+  const [isSubmitting, setIsSubmitting] = useState(false);
+
  const handleSubmit = async () => {
    if (!clubId) return;
+    if (isSubmitting) return;
+    
+    setIsSubmitting(true);
    const reorderedQuestions = formData.questions.map((q, idx) => ({
      ...q,
      id: idx + 1,
    }));

    const payload: ApplicationFormData = {
      ...formData,
      questions: reorderedQuestions,
    };
    try {
      if (data) {
        await updateApplication(payload, clubId);
        alert('지원서가 성공적으로 수정되었습니다.');
      } else {
        await createApplication(payload, clubId);
        alert('지원서가 성공적으로 생성되었습니다.');
      }
    } catch (error) {
      alert('지원서 저장에 실패했습니다.');
      console.error(error);
+    } finally {
+      setIsSubmitting(false);
    }
  };

그리고 버튼에 로딩 상태를 반영하세요:

-          <Styled.submitButton onClick={handleSubmit}>
-            저장하기
+          <Styled.submitButton onClick={handleSubmit} disabled={isSubmitting}>
+            {isSubmitting ? '저장 중...' : '저장하기'}
          </Styled.submitButton>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSubmit = async () => {
if (!clubId) return;
const reorderedQuestions = formData.questions.map((q, idx) => ({
...q,
id: idx + 1,
}));
const payload: ApplicationFormData = {
...formData,
questions: reorderedQuestions,
};
try {
if (data) {
await updateApplication(payload, clubId);
alert('지원서가 성공적으로 수정되었습니다.');
} else {
await createApplication(payload, clubId);
alert('지원서가 성공적으로 생성되었습니다.');
}
} catch (error) {
alert('지원서 저장에 실패했습니다.');
console.error(error);
}
};
// add loading state
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async () => {
if (!clubId) return;
if (isSubmitting) return;
setIsSubmitting(true);
const reorderedQuestions = formData.questions.map((q, idx) => ({
...q,
id: idx + 1,
}));
const payload: ApplicationFormData = {
...formData,
questions: reorderedQuestions,
};
try {
if (data) {
await updateApplication(payload, clubId);
alert('지원서가 성공적으로 수정되었습니다.');
} else {
await createApplication(payload, clubId);
alert('지원서가 성공적으로 생성되었습니다.');
}
} catch (error) {
alert('지원서 저장에 실패했습니다.');
console.error(error);
} finally {
setIsSubmitting(false);
}
};
// ...
<Styled.submitButton onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? '저장 중...' : '저장하기'}
</Styled.submitButton>
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx around
lines 117 to 140, the handleSubmit function lacks a loading state during API
calls, which can cause multiple submissions if the user clicks repeatedly. Add a
loading state variable (e.g., isLoading) to the component state, set it to true
before starting the API call, and set it back to false after the call completes
or fails. Also, update the submit button to reflect this loading state by
disabling it and optionally showing a loading indicator while isLoading is true.


return (
<>
<PageContainer>
<Styled.FormTitle
type='text'
value={formData.form_title}
value={formData.title}
onChange={(e) => handleFormTitleChange(e.target.value)}
placeholder='지원서 제목을 입력하세요'
></Styled.FormTitle>
Expand Down Expand Up @@ -135,7 +172,9 @@ const CreateApplicationForm = () => {
질문 추가 +
</Styled.AddQuestionButton>
<Styled.ButtonWrapper>
<Styled.submitButton>제출하기</Styled.submitButton>
<Styled.submitButton onClick={handleSubmit}>
저장하기
</Styled.submitButton>
</Styled.ButtonWrapper>
</PageContainer>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { mockData } from '@/mocks/data/mockData';
import { PageContainer } from '@/styles/PageContainer.styles';
import * as Styled from './AnswerApplicationForm.styles';
import Header from '@/components/common/Header/Header';
Expand All @@ -7,13 +6,23 @@ import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail';
import ClubProfile from '@/pages/ClubDetailPage/components/ClubProfile/ClubProfile';
import { useAnswers } from '@/hooks/useAnswers';
import QuestionAnswerer from '@/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer';
import { useGetApplication } from '@/hooks/queries/application/useGetApplication';
import { Question } from '@/types/application';

const AnswerApplicationForm = () => {
const { clubId } = useParams<{ clubId: string }>();
const { data: clubDetail, error } = useGetClubDetail(clubId || '');
const {
data: formData,
isLoading,
isError,
} = useGetApplication(clubId || '');
if (!clubId) return null;
if (!clubDetail) return null;
const { onAnswerChange, getAnswersById } = useAnswers();
if (!clubDetail) {
return null;

if (!clubId || isLoading || !formData || !clubDetail) {
return <div>로딩 중...</div>;
}

if (error) {
Expand All @@ -31,9 +40,9 @@ const AnswerApplicationForm = () => {
category={clubDetail.category}
tags={clubDetail.tags}
/>
<Styled.FormTitle>{mockData.form_title}</Styled.FormTitle>
<Styled.FormTitle>{formData.title}</Styled.FormTitle>
<Styled.QuestionsWrapper>
{mockData.questions.map((q) => (
{formData.questions.map((q: Question) => (
<QuestionAnswerer
key={q.id}
question={q}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/types/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface Question {
options: {
required: boolean;
};
items?: { value: string }[];
items: { value: string }[];
}

export interface QuestionBuilderProps extends Question {
Expand Down Expand Up @@ -46,7 +46,7 @@ export interface ChoiceProps extends QuestionComponentProps {
}

export interface ApplicationFormData {
form_title: string;
title: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

프로퍼티 이름 변경 검증 필요

form_titletitle로 변경한 것은 명명 일관성 측면에서 좋은 개선입니다.

다음 스크립트로 모든 파일에서 form_title 사용이 완전히 제거되었는지 확인해주세요:


🏁 Script executed:

#!/bin/bash
# form_title 사용이 남아있는지 확인

echo "=== form_title 사용 검색 ==="
rg "form_title" --type ts --type tsx

echo "=== ApplicationFormData 타입 사용 검색 ==="
rg -A 3 -B 3 "ApplicationFormData" --type ts --type tsx

Length of output: 290


다음 스크립트로 form_titleApplicationFormData가 남아있는지 TS/TSX 파일을 대상으로 재확인해주세요:


🏁 Script executed:

#!/bin/bash
# form_title 사용 여부 확인
echo "=== form_title 검색 ==="
rg "form_title" -g '*.ts' -g '*.tsx'

# ApplicationFormData 타입 사용 여부 확인
echo "=== ApplicationFormData 검색 ==="
rg -A3 -B3 "ApplicationFormData" -g '*.ts' -g '*.tsx'

Length of output: 5635


form_title 잔여 참조 수정 필요

form_titletitle로 변경한 것은 올바른 개선이지만, 아래 파일들에서 여전히 form_title을 사용하고 있어 런타임 에러나 테스트 실패를 일으킬 수 있습니다. 해당 참조를 모두 title로 대체해주세요:

• frontend/src/mocks/api/apply.ts

  • form_title: mockData.title,
    • frontend/src/mocks/api/applyHandlers.test.ts
  • form_title: string;
  • expect((data as ClubApplyResponse).form_title)… (여러 테스트)

위 파일들 외에도 다른 mock/data나 테스트 코드에 남아있는지 한 번 더 확인 부탁드립니다.

🤖 Prompt for AI Agents
In frontend/src/types/application.ts at line 49, the property name was changed
from form_title to title for consistency, but there are still references to
form_title in other files causing potential runtime errors and test failures.
Search for all occurrences of form_title in the codebase, especially in
frontend/src/mocks/api/apply.ts and
frontend/src/mocks/api/applyHandlers.test.ts, and replace them with title to
ensure consistency and prevent errors. Also verify no other mock or test files
contain form_title and update those references accordingly.

questions: Question[];
}

Expand Down