diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index be74dc2d9..16933b390 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -15,6 +15,8 @@ import AccountEditTab from '@/pages/AdminPage/tabs/AccountEditTab/AccountEditTab
import LoginTab from '@/pages/AdminPage/auth/LoginTab/LoginTab';
import PrivateRoute from '@/pages/AdminPage/auth/PrivateRoute/PrivateRoute';
import PhotoEditTab from '@/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab';
+import AnswerApplicationForm from './pages/AdminPage/application/answer/AnswerApplicationForm';
+import CreateApplicationForm from './pages/AdminPage/application/CreateApplicationForm';
// TODO: 지원서 개발 완료 후 활성화
// import AnswerApplicationForm from '@/pages/AdminPage/application/answer/AnswerApplicationForm';
// import CreateApplicationForm from '@/pages/AdminPage/application/CreateApplicationForm';
@@ -73,10 +75,10 @@ const App = () => {
/>
{/*🔒 메인 브랜치에서는 접근 차단 (배포용 차단 목적)*/}
{/*develop-fe 브랜치에서는 접근 가능하도록 풀고 개발 예정*/}
- {/*}*/}
- {/*/>*/}
+ }
+ />
@@ -85,10 +87,10 @@ const App = () => {
/>
{/*🔒 사용자용 지원서 작성 페이지도 메인에서는 비활성화 처리 */}
{/*🛠 develop-fe에서는 다시 노출 예정*/}
- {/*}*/}
- {/*/>*/}
+ }
+ />
} />
diff --git a/frontend/src/apis/application/applyToClub.ts b/frontend/src/apis/application/applyToClub.ts
new file mode 100644
index 000000000..3d50d6ed0
--- /dev/null
+++ b/frontend/src/apis/application/applyToClub.ts
@@ -0,0 +1,36 @@
+import API_BASE_URL from '@/constants/api';
+import { AnswerItem } from '@/types/application';
+
+export const applyToClub = async (
+ clubId: string,
+ answers: AnswerItem[],
+) => {
+ try {
+ const response = await fetch(
+ `${API_BASE_URL}/api/club/${clubId}/apply`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ questions: [
+ ...answers
+ ]
+ }),
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error('답변 제출에 실패했습니다.');
+ }
+
+ const result = await response.json();
+ return result.data;
+ } catch (error) {
+ console.error('답변 제출 중 오류 발생:', error);
+ throw error;
+ }
+};
+
+export default applyToClub;
\ No newline at end of file
diff --git a/frontend/src/apis/application/getApplication.ts b/frontend/src/apis/application/getApplication.ts
index a9ec0e0bc..366d3339b 100644
--- a/frontend/src/apis/application/getApplication.ts
+++ b/frontend/src/apis/application/getApplication.ts
@@ -4,7 +4,8 @@ const getApplication = async (clubId: string) => {
try {
const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`);
if (!response.ok) {
- throw new Error(`Failed to fetch: ${response.statusText}`);
+ console.error(`Failed to fetch: ${response.statusText}`)
+ throw new Error((await response.json()).message);
}
const result = await response.json();
diff --git a/frontend/src/constants/INITIAL_FORM_DATA.ts b/frontend/src/constants/INITIAL_FORM_DATA.ts
index e97cf76f1..2e7e82aaa 100644
--- a/frontend/src/constants/INITIAL_FORM_DATA.ts
+++ b/frontend/src/constants/INITIAL_FORM_DATA.ts
@@ -3,8 +3,17 @@ import { ApplicationFormData } from '@/types/application';
const INITIAL_FORM_DATA: ApplicationFormData = {
title: '',
questions: [
+ //맨 처음은 이름
{
id: 1,
+ title: '이름을 입력해주세요',
+ description: '지원자의 이름을 입력해주세요. (예: 홍길동)',
+ type: 'SHORT_TEXT',
+ options: { required: true },
+ items: [],
+ },
+ {
+ id: 2,
title: '',
description: '',
type: 'SHORT_TEXT',
@@ -12,7 +21,7 @@ const INITIAL_FORM_DATA: ApplicationFormData = {
items: [],
},
{
- id: 2,
+ id: 3,
title: '',
description: '',
type: 'CHOICE',
diff --git a/frontend/src/hooks/useAnswers.ts b/frontend/src/hooks/useAnswers.ts
index 6cf5dfead..51c096c4b 100644
--- a/frontend/src/hooks/useAnswers.ts
+++ b/frontend/src/hooks/useAnswers.ts
@@ -7,14 +7,14 @@ export const useAnswers = () => {
const updateSingleAnswer = (id: number, value: string) => {
setAnswers((prev) => [
...prev.filter((a) => a.id !== id),
- { id, answer: value },
+ { id, value: value },
]);
};
const updateMultiAnswer = (id: number, values: string[]) => {
setAnswers((prev) => [
...prev.filter((a) => a.id !== id),
- ...values.map((v) => ({ id, answer: v })),
+ ...values.map((v) => ({ id, value: v })),
]);
};
@@ -27,7 +27,7 @@ export const useAnswers = () => {
};
const getAnswersById = (id: number) =>
- answers.filter((a) => a.id === id).map((a) => a.answer);
+ answers.filter((a) => a.id === id).map((a) => a.value);
- return { onAnswerChange, getAnswersById };
+ return { onAnswerChange, getAnswersById, answers };
};
diff --git a/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx b/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx
index bc3be5bcc..2ae388853 100644
--- a/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx
+++ b/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx
@@ -158,6 +158,7 @@ const CreateApplicationForm = () => {
options={question.options}
items={question.items}
type={question.type}
+ readOnly={index === 0} //인덱스 0번은 이름을 위한 고정 부분이므로 수정 불가
onTitleChange={handleTitleChange(question.id)}
onDescriptionChange={handleDescriptionChange(question.id)}
onItemsChange={handleItemsChange(question.id)}
diff --git a/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx b/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx
index dd9fe6f9d..80336e013 100644
--- a/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx
+++ b/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx
@@ -1,7 +1,7 @@
import { PageContainer } from '@/styles/PageContainer.styles';
import * as Styled from './AnswerApplicationForm.styles';
import Header from '@/components/common/Header/Header';
-import { useParams } from 'react-router-dom';
+import { useNavigate, useParams } from 'react-router-dom';
import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail';
import ClubProfile from '@/pages/ClubDetailPage/components/ClubProfile/ClubProfile';
import { useAnswers } from '@/hooks/useAnswers';
@@ -9,19 +9,27 @@ import QuestionAnswerer from '@/pages/AdminPage/application/components/QuestionA
import { useGetApplication } from '@/hooks/queries/application/useGetApplication';
import { Question } from '@/types/application';
import Spinner from '@/components/common/Spinner/Spinner';
+import applyToClub from '@/apis/application/applyToClub';
const AnswerApplicationForm = () => {
const { clubId } = useParams<{ clubId: string }>();
+ const navigate = useNavigate();
if (!clubId) return null;
const { data: clubDetail, error } = useGetClubDetail(clubId);
- const { data: formData, isLoading, isError } = useGetApplication(clubId);
+ const { data: formData, isLoading, isError, error: applicationError } = useGetApplication(clubId);
- const { onAnswerChange, getAnswersById } = useAnswers();
+ const { onAnswerChange, getAnswersById, answers } = useAnswers();
if (isLoading) return ;
+
+ if (isError) {
+ alert(applicationError.message)
+ navigate(`/club/${clubId}`)
+ return
문제가 발생했어요. 잠시 후 다시 시도해 주세요.
;
+ }
- if (error || isError) {
+ if (error) {
return 문제가 발생했어요. 잠시 후 다시 시도해 주세요.
;
}
@@ -34,6 +42,16 @@ const AnswerApplicationForm = () => {
);
}
+ const handleSubmit = async () => {
+ try {
+ await applyToClub(clubId, answers);
+ alert('답변이 성공적으로 제출되었습니다.');
+ // TODO: 필요시 페이지 이동 등 추가
+ } catch (e) {
+ alert('답변 제출에 실패했습니다. 잠시 후 다시 시도해 주세요.');
+ }
+ };
+
return (
<>
@@ -57,7 +75,7 @@ const AnswerApplicationForm = () => {
))}
- 제출하기
+ 제출하기
>
diff --git a/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.styles.ts b/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.styles.ts
index 8da426be7..05d359afe 100644
--- a/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.styles.ts
+++ b/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.styles.ts
@@ -75,7 +75,9 @@ export const SelectionToggleButton = styled.button<{ active: boolean }>`
color 0.2s ease;
`;
-export const QuestionWrapper = styled.div`
+export const QuestionWrapper = styled.div<{readOnly?: boolean}>`
display: flex;
gap: 36px;
+ pointer-events: ${({ readOnly }) => (readOnly ? 'none' : 'auto')};
+ cursor: ${({ readOnly }) => (readOnly ? 'not-allowed' : 'auto')};
`;
diff --git a/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.tsx b/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.tsx
index c84c57d03..e52d10117 100644
--- a/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.tsx
+++ b/frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.tsx
@@ -16,6 +16,7 @@ const QuestionBuilder = ({
options,
items,
type,
+ readOnly,
onTitleChange,
onItemsChange,
onDescriptionChange,
@@ -117,7 +118,7 @@ const QuestionBuilder = ({
};
return (
-
+
onRequiredChange?.(!options?.required)}
@@ -133,7 +134,9 @@ const QuestionBuilder = ({
}}
/>
{renderSelectionToggle()}
-
+ {!readOnly && (
+
+ )}
{renderFieldByQuestionType()}
diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
index d6b3b305d..f160cd25a 100644
--- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
+++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
@@ -31,9 +31,6 @@ const SideBar = ({ clubLogo, clubName }: SideBarProps) => {
if (tab.label === '계정 관리') {
alert('계정 관리 기능은 아직 준비 중이에요. ☺️');
return;
- } else if (tab.label === '지원 관리') {
- alert('동아리 지원 관리 기능은 곧 오픈돼요!\n조금만 기다려주세요 🚀');
- return;
}
navigate(tab.path);
};
diff --git a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
index bb0d43a94..415c4366e 100644
--- a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
+++ b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
@@ -4,8 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail';
interface ButtonProps {
- recruitmentForm?: string;
- presidentPhoneNumber?: string;
+ isRecruiting: boolean;
}
const Button = styled.button`
@@ -38,8 +37,7 @@ const Button = styled.button`
`;
const ClubApplyButton = ({
- recruitmentForm,
- presidentPhoneNumber,
+ isRecruiting
}: ButtonProps) => {
const { clubId } = useParams<{ clubId: string }>();
const trackEvent = useMixpanelTrack();
@@ -49,14 +47,11 @@ const ClubApplyButton = ({
trackEvent('Club Apply Button Clicked');
//TODO: 지원서를 작성한 동아리의 경우에만 리다이렉트
- //navigate(`/application/${clubId}`);
-
- // [x] FIXME: recruitmentForm 있을 때는 리다이렉트
- if (presidentPhoneNumber) {
- alert(`${presidentPhoneNumber} 으로 연락하여 지원해 주세요.`);
- } else {
- alert('모집이 마감되었습니다. 다음에 지원해 주세요.');
+ if (!isRecruiting) {
+ alert('지원모집이 마감되었습니다. 다음에 지원해 주세요.')
+ return;
}
+ navigate(`/application/${clubId}`);
};
return ;
diff --git a/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx b/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
index 3aca35fae..307af9b5c 100644
--- a/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
+++ b/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
@@ -28,10 +28,7 @@ const ClubDetailFooter = ({
);
diff --git a/frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx b/frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
index 028c5744f..e56432559 100644
--- a/frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
+++ b/frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
@@ -43,10 +43,7 @@ const ClubDetailHeader = ({
logo={logo}
/>
);
diff --git a/frontend/src/types/application.ts b/frontend/src/types/application.ts
index 7a8375e19..e880eab61 100644
--- a/frontend/src/types/application.ts
+++ b/frontend/src/types/application.ts
@@ -14,6 +14,7 @@ export interface Question {
}
export interface QuestionBuilderProps extends Question {
+ readOnly: boolean;
onTitleChange: (value: string) => void;
onDescriptionChange: (value: string) => void;
onItemsChange?: (newItems: { value: string }[]) => void;
@@ -52,5 +53,5 @@ export interface ApplicationFormData {
export interface AnswerItem {
id: number;
- answer: string;
+ value: string;
}