diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8d20dfbdd..91317ba3c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -75,16 +75,9 @@ const App = () => { } /> - {/*//todo /:clubid로 수정 필요*/} - {/*}*/} - {/*/>*/} - } /> - {/*TODO: CreateForm은 관리자 기능이므로 추후 /admin/* 경로 안으로 이동 필요*/} } + path='/application/:clubId' + element={} /> } /> diff --git a/frontend/src/components/common/InputField/InputField.tsx b/frontend/src/components/common/InputField/InputField.tsx index ed1a08233..2bba35996 100644 --- a/frontend/src/components/common/InputField/InputField.tsx +++ b/frontend/src/components/common/InputField/InputField.tsx @@ -79,7 +79,7 @@ const InputField = ({ hasError={isError} readOnly={readOnly} style={{ - background: bgColor || '#F5F5F5', + background: bgColor || '#FFF', color: textColor, borderColor: borderColor, }} diff --git a/frontend/src/pages/AdminPage/application/CreateApplicationForm.styles.ts b/frontend/src/pages/AdminPage/application/CreateApplicationForm.styles.ts index af2ea3f57..cc9650e56 100644 --- a/frontend/src/pages/AdminPage/application/CreateApplicationForm.styles.ts +++ b/frontend/src/pages/AdminPage/application/CreateApplicationForm.styles.ts @@ -31,7 +31,7 @@ export const AddQuestionButton = styled.button` font-weight: 500; background: white; color: #555; - margin-bottom: 200px; + margin-bottom: 60px; cursor: pointer; `; @@ -41,3 +41,30 @@ export const QuestionDivider = styled.hr` border: none; border-top: 1px solid #ddd; `; + +export const ButtonWrapper = styled.div` + display: flex; + justify-content: flex-end; +`; + +export const submitButton = styled.button` + padding: 10px 56px; + background-color: #ff5414; + border-radius: 10px; + border: none; + color: #fff; + font-size: 1.25rem; + font-weight: 600; + letter-spacing: -0.4px; + transition: background-color 0.2s; + margin: 50px 0; + + &:hover { + background-color: #ffad8e; + animation: pulse 0.4s ease-in-out; + } + + &:active { + transform: scale(0.95); + } +`; diff --git a/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx b/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx index 13a92f6ed..a37828f2d 100644 --- a/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx +++ b/frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx @@ -134,6 +134,9 @@ const CreateApplicationForm = () => { 질문 추가 + + + 제출하기 + ); diff --git a/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.styles.ts b/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.styles.ts index 02ff36905..c731731af 100644 --- a/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.styles.ts +++ b/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.styles.ts @@ -9,6 +9,12 @@ export const FormTitle = styled.h1` margin-bottom: 46px; `; +export const QuestionsWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 50px; +`; + export const ButtonWrapper = styled.div` display: flex; justify-content: flex-end; diff --git a/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx b/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx index 8e4527ef4..b728f844a 100644 --- a/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx +++ b/frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx @@ -9,8 +9,7 @@ import { useAnswers } from '@/hooks/useAnswers'; import QuestionAnswerer from '@/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer'; const AnswerApplicationForm = () => { - //const { clubId } = useParams<{ clubId: string }>(); - const clubId = '67e54ae51cfd27718dd40bec'; // 하드코딩된 club ID + const { clubId } = useParams<{ clubId: string }>(); const { data: clubDetail, error } = useGetClubDetail(clubId || ''); const { onAnswerChange, getAnswersById } = useAnswers(); if (!clubDetail) { @@ -33,14 +32,16 @@ const AnswerApplicationForm = () => { tags={clubDetail.tags} /> {mockData.form_title} - {mockData.questions.map((q) => ( - - ))} + + {mockData.questions.map((q) => ( + + ))} + 제출하기 diff --git a/frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx b/frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx index 629a8a2ee..b80465c50 100644 --- a/frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx +++ b/frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx @@ -47,14 +47,13 @@ const QuestionAnswerer = ({ case 'CHOICE': case 'MULTI_CHOICE': return ( - <> - // onChange(question.id, value)} - // /> + onChange(question.id, value)} + /> ); default: diff --git a/frontend/src/pages/AdminPage/application/fields/Choice.tsx b/frontend/src/pages/AdminPage/application/fields/Choice.tsx index 4595bf44b..70431d8ca 100644 --- a/frontend/src/pages/AdminPage/application/fields/Choice.tsx +++ b/frontend/src/pages/AdminPage/application/fields/Choice.tsx @@ -4,16 +4,10 @@ import QuestionDescription from '@/pages/AdminPage/application/components/Questi import InputField from '@/components/common/InputField/InputField'; import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; import { ChoiceProps } from '@/types/application'; -import { useState } from 'react'; const MIN_ITEMS = 2; const MAX_ITEMS = 6; -// todo inputField clear 버튼 빼기 -// todo 입력 필드안에 항목 삭제 아이콘 추가 -// todo mode : answer일때 다중, 단일 조건에 따라 선택 가능하도록 UI 및 기능 추가 필요 -// todo isMulti나 질문 타입을 props로 받아서 단일 / 다중 판단 하면 될듯 - const Choice = ({ id, title, @@ -25,9 +19,10 @@ const Choice = ({ items = [], isMulti, onItemsChange, + answer = [], + onAnswerChange, }: ChoiceProps) => { - const [selected, setSelected] = useState([]); - + // — 아이템 텍스트 변경(빌더 모드 전용) const handleItemChange = (index: number, newValue: string) => { const updated = items.map((item, i) => i === index ? { ...item, value: newValue } : item, @@ -35,27 +30,36 @@ const Choice = ({ onItemsChange?.(updated); }; + // — 아이템 추가(빌더 모드 전용) const handleAddItem = () => { if (items.length >= MAX_ITEMS) return; onItemsChange?.([...items, { value: '' }]); }; + // — 아이템 삭제(빌더 모드 전용) const handleDeleteItem = (index: number) => { if (items.length <= MIN_ITEMS) return; const updated = items.filter((_, i) => i !== index); onItemsChange?.(updated); }; - const handleSelect = (index: number) => { + const handleSelect = (idx: number) => { if (mode !== 'answer') return; + const value = items[idx].value; + if (!value) return; + if (isMulti) { - setSelected((prev) => - prev.includes(index) - ? prev.filter((i) => i !== index) - : [...prev, index], - ); + if (Array.isArray(answer)) { + if (answer.includes(value)) { + onAnswerChange?.(answer.filter((v) => v !== value)); + } else { + onAnswerChange?.([...answer, value]); + } + } + // 다중 선택: 이미 포함되어 있으면 제거, 아니면 추가 } else { - setSelected([index]); + // 단일 선택: 클릭된 값만 넘김 + onAnswerChange?.(value); } }; @@ -73,51 +77,47 @@ const Choice = ({ mode={mode} onDescriptionChange={onDescriptionChange} /> - {items.map((item, index) => ( - handleSelect(index)} - data-selected={ - mode === 'answer' && selected.includes(index) ? 'true' : undefined - } - > - handleItemChange(index, e.target.value)} - placeholder={APPLICATION_FORM.CHOICE.placeholder} - disabled={false} - readOnly={mode === 'answer'} - showClearButton={false} - bgColor={ - mode === 'answer' && selected.includes(index) - ? '#FFE4DA' - : undefined - } - textColor={ - mode === 'answer' - ? selected.includes(index) - ? 'rgba(0,0,0,0.8)' - : 'rgba(0,0,0,0.3)' - : undefined - } - borderColor={ - mode === 'answer' && selected.includes(index) - ? '#FF5414' - : undefined - } - /> - {mode === 'builder' && items.length > MIN_ITEMS && ( - { - e.stopPropagation(); - handleDeleteItem(index); - }} - > - 삭제 - - )} - - ))} + + {items.map((item, index) => { + // ▶ selected 대신, answer.includes(item.value)로 판별 + const isSelected = mode === 'answer' && answer.includes(item.value); + + return ( + handleSelect(index)} + data-selected={isSelected ? 'true' : undefined} + > + handleItemChange(index, e.target.value)} + placeholder={APPLICATION_FORM.CHOICE.placeholder} + readOnly={mode === 'answer'} + showClearButton={false} + bgColor={isSelected ? '#FFE4DA' : '#F5F5F5'} + textColor={ + mode === 'answer' + ? isSelected + ? 'rgba(0,0,0,0.8)' + : 'rgba(0,0,0,0.3)' + : undefined + } + borderColor={isSelected ? '#FF5414' : undefined} + /> + + {mode === 'builder' && items.length > MIN_ITEMS && ( + { + e.stopPropagation(); + handleDeleteItem(index); + }} + > + 삭제 + + )} + + ); + })} {mode === 'builder' && items.length < MAX_ITEMS && ( diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx index 48a30cddf..4bf4a9137 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx @@ -14,8 +14,8 @@ const tabs = [ { label: '기본 정보 수정', path: '/admin/club-info' }, { label: '모집 정보 수정', path: '/admin/recruit-edit' }, { label: '활동 사진 수정', path: '/admin/photo-edit' }, - { label: '계정 관리', path: '/admin/account-edit' }, { label: '지원서 관리', path: '/admin/application-edit' }, + { label: '계정 관리', path: '/admin/account-edit' }, ]; const SideBar = ({ clubLogo, clubName }: SideBarProps) => { @@ -44,7 +44,6 @@ const SideBar = ({ clubLogo, clubName }: SideBarProps) => { localStorage.removeItem('accessToken'); navigate('/admin/login', { replace: true }); } catch (error) { - console.error(error); alert('로그아웃에 실패했습니다.'); } }; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx index 78652f82a..bb0d43a94 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx @@ -1,5 +1,7 @@ import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import styled from 'styled-components'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; interface ButtonProps { recruitmentForm?: string; @@ -39,11 +41,16 @@ const ClubApplyButton = ({ recruitmentForm, presidentPhoneNumber, }: ButtonProps) => { + const { clubId } = useParams<{ clubId: string }>(); const trackEvent = useMixpanelTrack(); + const navigate = useNavigate(); const handleClick = () => { trackEvent('Club Apply Button Clicked'); + //TODO: 지원서를 작성한 동아리의 경우에만 리다이렉트 + //navigate(`/application/${clubId}`); + // [x] FIXME: recruitmentForm 있을 때는 리다이렉트 if (presidentPhoneNumber) { alert(`${presidentPhoneNumber} 으로 연락하여 지원해 주세요.`); diff --git a/frontend/src/types/application.ts b/frontend/src/types/application.ts index fac022d49..2296ccc02 100644 --- a/frontend/src/types/application.ts +++ b/frontend/src/types/application.ts @@ -41,6 +41,8 @@ export interface ChoiceProps extends QuestionComponentProps { items?: { value: string }[]; isMulti?: boolean; onItemsChange?: (newItems: { value: string }[]) => void; + answer?: string | string[]; + onAnswerChange?: (value: string[] | string) => void; } export interface ApplicationFormData {