diff --git a/frontend/src/constants/eventName.ts b/frontend/src/constants/eventName.ts
index 0fd757188..f5d8a8774 100644
--- a/frontend/src/constants/eventName.ts
+++ b/frontend/src/constants/eventName.ts
@@ -97,6 +97,7 @@ export const PAGE_VIEW = {
// 관리자
LOGIN_PAGE: '로그인페이지',
+ CLUB_INTRO_EDIT_PAGE: '동아리 소개 수정 페이지',
CLUB_INFO_EDIT_PAGE: '동아리 기본 정보 수정 페이지',
RECRUITMENT_INFO_EDIT_PAGE: '동아리 모집 정보 수정 페이지',
PHOTO_EDIT_PAGE: '동아리 활동 사진 수정 페이지',
diff --git a/frontend/src/pages/AdminPage/AdminRoutes.tsx b/frontend/src/pages/AdminPage/AdminRoutes.tsx
index 758c047e4..8c5c7e687 100644
--- a/frontend/src/pages/AdminPage/AdminRoutes.tsx
+++ b/frontend/src/pages/AdminPage/AdminRoutes.tsx
@@ -9,7 +9,7 @@ import ClubInfoEditTab from '@/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEdit
import PhotoEditTab from '@/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab';
import RecruitEditTab from '@/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab';
import ApplicantsTab from './tabs/ApplicantsTab/ApplicantsTab';
-import ClubIntroTab from './tabs/ClubIntroTab/ClubIntroTab';
+import ClubIntroEditTab from './tabs/ClubIntroEditTab/ClubIntroEditTab';
export default function AdminRoutes() {
return (
@@ -35,7 +35,7 @@ export default function AdminRoutes() {
path='applicants-list/:applicationFormId/:questionId'
element={}
/>
- } />
+ } />
);
diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx
index 37249bbae..b9b06f27d 100644
--- a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx
+++ b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx
@@ -20,9 +20,17 @@ import {
import * as Styled from './ApplicationEditTab.styles';
import { QuestionDivider } from './ApplicationEditTab.styles';
+const externalApplicationUrlAllowed = [
+ 'https://forms.gle',
+ 'https://docs.google.com/forms',
+ 'https://form.naver.com',
+ 'https://naver.me',
+ 'https://everytime.kr',
+];
+
const ApplicationEditTab = () => {
- const navigate = useNavigate();
const queryClient = useQueryClient();
+ const navigate = useNavigate();
const { applicationFormId: formId } = useParams<{
applicationFormId?: string;
}>();
@@ -37,12 +45,9 @@ const ApplicationEditTab = () => {
const [formData, setFormData] =
useState(INITIAL_FORM_DATA);
-
const [nextId, setNextId] = useState(1);
-
const [applicationFormMode, setApplicationFormMode] =
useState(ApplicationFormMode.INTERNAL);
-
const [externalApplicationUrl, setExternalApplicationUrl] = useState('');
useEffect(() => {
@@ -129,20 +134,13 @@ const ApplicationEditTab = () => {
if (applicationFormMode === ApplicationFormMode.INTERNAL) {
payload.questions = reorderedQuestions;
} else if (applicationFormMode === ApplicationFormMode.EXTERNAL) {
- const externalApplicationUrlAllowed = [
- 'https://forms.gle',
- 'https://docs.google.com/forms',
- 'https://form.naver.com',
- 'https://naver.me',
- ];
-
const isValidUrl = externalApplicationUrlAllowed.some((url) =>
externalApplicationUrl.startsWith(url),
);
if (!isValidUrl) {
alert(
- '외부 지원서 링크는 Google Forms 또는 Naver Form 링크여야 합니다.',
+ '외부 지원서 링크는 Google Forms, Naver Form 또는 Everytime 링크여야 합니다.',
);
return;
}
@@ -368,7 +366,7 @@ const ExternalApplicationComponent = ({
/>
- 현재 구글폼, 네이버폼 링크만 제출가능합니다.
+ 현재 구글폼, 네이버폼, 에브리타임 링크만 제출가능합니다.
>
);
diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
index bf9b26ad9..e748b6168 100644
--- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
+++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
@@ -23,9 +23,32 @@ const DIVISION_LABELS: Record = {
과동: '과동아리',
};
+const divisions = [
+ {
+ value: '중동',
+ label: DIVISION_LABELS['중동'],
+ color: TAG_COLORS['중동'],
+ },
+ {
+ value: '과동',
+ label: DIVISION_LABELS['과동'],
+ color: TAG_COLORS['과동'],
+ },
+];
+
+const categories = [
+ { value: '봉사', label: '봉사', color: TAG_COLORS['봉사'] },
+ { value: '종교', label: '종교', color: TAG_COLORS['종교'] },
+ { value: '취미교양', label: '취미교양', color: TAG_COLORS['취미교양'] },
+ { value: '학술', label: '학술', color: TAG_COLORS['학술'] },
+ { value: '운동', label: '운동', color: TAG_COLORS['운동'] },
+ { value: '공연', label: '공연', color: TAG_COLORS['공연'] },
+];
+
const ClubInfoEditTab = () => {
const trackEvent = useMixpanelTrack();
useTrackPageView(PAGE_VIEW.CLUB_INFO_EDIT_PAGE);
+ const queryClient = useQueryClient();
const clubDetail = useOutletContext();
const { mutate: updateClub } = useUpdateClubDetail();
@@ -49,28 +72,6 @@ const ClubInfoEditTab = () => {
x: '',
});
- const queryClient = useQueryClient();
- const divisions = [
- {
- value: '중동',
- label: DIVISION_LABELS['중동'],
- color: TAG_COLORS['중동'],
- },
- {
- value: '과동',
- label: DIVISION_LABELS['과동'],
- color: TAG_COLORS['과동'],
- },
- ];
- const categories = [
- { value: '봉사', label: '봉사', color: TAG_COLORS['봉사'] },
- { value: '종교', label: '종교', color: TAG_COLORS['종교'] },
- { value: '취미교양', label: '취미교양', color: TAG_COLORS['취미교양'] },
- { value: '학술', label: '학술', color: TAG_COLORS['학술'] },
- { value: '운동', label: '운동', color: TAG_COLORS['운동'] },
- { value: '공연', label: '공연', color: TAG_COLORS['공연'] },
- ];
-
useEffect(() => {
if (clubDetail) {
setClubName(clubDetail.name);
diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/ClubIntroTab.styles.ts b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.styles.ts
similarity index 100%
rename from frontend/src/pages/AdminPage/tabs/ClubIntroTab/ClubIntroTab.styles.ts
rename to frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.styles.ts
diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/ClubIntroTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx
similarity index 91%
rename from frontend/src/pages/AdminPage/tabs/ClubIntroTab/ClubIntroTab.tsx
rename to frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx
index 60274a4d4..09b58222c 100644
--- a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/ClubIntroTab.tsx
+++ b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx
@@ -9,13 +9,13 @@ import useMixpanelTrack from '@/hooks/useMixpanelTrack';
import useTrackPageView from '@/hooks/useTrackPageView';
import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection';
import { Award, ClubDetail, FAQ, IdealCandidate } from '@/types/club';
-import * as Styled from './ClubIntroTab.styles';
+import * as Styled from './ClubIntroEditTab.styles';
import AwardEditor from './components/AwardEditor/AwardEditor';
import FAQEditor from './components/FAQEditor/FAQEditor';
-const ClubIntroTab = () => {
+const ClubIntroEditTab = () => {
const trackEvent = useMixpanelTrack();
- useTrackPageView(PAGE_VIEW.CLUB_INFO_EDIT_PAGE);
+ useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE);
const clubDetail = useOutletContext();
const { mutate: updateClub } = useUpdateClubDetail();
@@ -99,7 +99,7 @@ const ClubIntroTab = () => {
setIntroDescription(e.target.value)}
@@ -108,7 +108,7 @@ const ClubIntroTab = () => {
/>
setActivityDescription(e.target.value)}
@@ -119,7 +119,7 @@ const ClubIntroTab = () => {
@@ -130,7 +130,7 @@ const ClubIntroTab = () => {
/>
setBenefits(e.target.value)}
@@ -145,4 +145,4 @@ const ClubIntroTab = () => {
);
};
-export default ClubIntroTab;
+export default ClubIntroEditTab;
diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/AwardEditor/AwardEditor.styles.ts b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/AwardEditor/AwardEditor.styles.ts
similarity index 100%
rename from frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/AwardEditor/AwardEditor.styles.ts
rename to frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/AwardEditor/AwardEditor.styles.ts
diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/AwardEditor/AwardEditor.tsx b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/AwardEditor/AwardEditor.tsx
similarity index 98%
rename from frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/AwardEditor/AwardEditor.tsx
rename to frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/AwardEditor/AwardEditor.tsx
index 8e172511b..9737129b0 100644
--- a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/AwardEditor/AwardEditor.tsx
+++ b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/AwardEditor/AwardEditor.tsx
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import deleteButton from '@/assets/images/icons/delete_button_icon.svg';
import selectIcon from '@/assets/images/icons/selectArrow.svg';
import { CustomDropDown } from '@/components/common/CustomDropDown/CustomDropDown';
-import { Award } from '../../ClubIntroTab';
+import { Award } from '@/types/club';
import * as Styled from './AwardEditor.styles';
interface AwardEditorProps {
@@ -140,7 +140,7 @@ const AwardEditor = ({ awards, onChange }: AwardEditorProps) => {
return (
- 🏆 이런 상을 받았어요
+ 이런 상을 받았어요
void;
+}
+
+const FAQEditor = ({ faqs, onChange }: FAQEditorProps) => {
+ const [shouldFocusLast, setShouldFocusLast] = useState(false);
+ const questionInputRefs = useRef>({});
+
+ useEffect(() => {
+ const hasAnyMissingId = faqs.some((faq) => !faq.id);
+ if (hasAnyMissingId) {
+ const faqsWithIds = faqs.map((faq) => ({
+ ...faq,
+ id: faq.id || `faq-${Date.now()}-${Math.random()}`,
+ }));
+ onChange(faqsWithIds);
+ }
+ }, [faqs, onChange]);
+
+ const handleAddFAQ = () => {
+ const newFAQ: FAQ = {
+ id: `faq-${Date.now()}-${Math.random()}`,
+ question: '',
+ answer: '',
+ };
+ onChange([...faqs, newFAQ]);
+ setShouldFocusLast(true);
+ };
+
+ const handleRemoveFAQ = (id: string) => {
+ onChange(faqs.filter((faq) => faq.id !== id));
+ };
+
+ const handleUpdateQuestion = (id: string, value: string) => {
+ const updatedFAQs = faqs.map((faq) =>
+ faq.id === id ? { ...faq, question: value } : faq,
+ );
+ onChange(updatedFAQs);
+ };
+
+ const handleUpdateAnswer = (id: string, value: string) => {
+ const updatedFAQs = faqs.map((faq) =>
+ faq.id === id ? { ...faq, answer: value } : faq,
+ );
+ onChange(updatedFAQs);
+ };
+
+ useEffect(() => {
+ if (shouldFocusLast && faqs.length > 0) {
+ const lastFAQ = faqs[faqs.length - 1];
+ if (lastFAQ.id) {
+ const inputRef = questionInputRefs.current[lastFAQ.id];
+ if (inputRef) {
+ inputRef.focus();
+ }
+ }
+ setShouldFocusLast(false);
+ }
+ }, [faqs, shouldFocusLast]);
+
+ return (
+
+ 자주 묻는 질문 (FAQ)
+
+ + FAQ 추가
+
+ {faqs.length === 0 ? (
+
+ FAQ를 추가하여 지원자들의 자주 묻는 질문에 답변해보세요.
+
+ ) : (
+
+ {faqs
+ .filter((faq): faq is FAQ & { id: string } => !!faq.id)
+ .map((faq, index) => (
+
+
+ Q{index + 1}
+ handleRemoveFAQ(faq.id)}>
+
+
+
+
+ {
+ questionInputRefs.current[faq.id] = element;
+ }}
+ placeholder='질문을 입력하세요'
+ value={faq.question}
+ onChange={(event) =>
+ handleUpdateQuestion(faq.id, event.target.value)
+ }
+ maxLength={100}
+ />
+
+
+ handleUpdateAnswer(faq.id, event.target.value)
+ }
+ maxLength={300}
+ />
+
+
+ 질문: {faq.question.length}/100 | 답변: {faq.answer.length}
+ /300
+
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default FAQEditor;
diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/FAQEditor/FAQEditor.tsx b/frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/FAQEditor/FAQEditor.tsx
deleted file mode 100644
index 805963cea..000000000
--- a/frontend/src/pages/AdminPage/tabs/ClubIntroTab/components/FAQEditor/FAQEditor.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { useEffect, useRef, useState } from 'react';
-import deleteButton from '@/assets/images/icons/delete_button_icon.svg';
-import { FAQ } from '../../ClubIntroTab';
-import * as Styled from './FAQEditor.styles';
-
-interface FAQEditorProps {
- faqs: FAQ[];
- onChange: (faqs: FAQ[]) => void;
-}
-
-const FAQEditor = ({ faqs, onChange }: FAQEditorProps) => {
- const [shouldFocusLast, setShouldFocusLast] = useState(false);
- const questionInputRefs = useRef>({});
-
- const handleAddFAQ = () => {
- const newFAQ: FAQ = {
- id: `faq-${Date.now()}-${Math.random()}`,
- question: '',
- answer: '',
- };
- onChange([...faqs, newFAQ]);
- setShouldFocusLast(true);
- };
-
- const handleRemoveFAQ = (id: string) => {
- onChange(faqs.filter((faq) => faq.id !== id));
- };
-
- const handleUpdateQuestion = (id: string, value: string) => {
- const updatedFAQs = faqs.map((faq) =>
- faq.id === id ? { ...faq, question: value } : faq,
- );
- onChange(updatedFAQs);
- };
-
- const handleUpdateAnswer = (id: string, value: string) => {
- const updatedFAQs = faqs.map((faq) =>
- faq.id === id ? { ...faq, answer: value } : faq,
- );
- onChange(updatedFAQs);
- };
-
- useEffect(() => {
- if (shouldFocusLast && faqs.length > 0) {
- const lastFAQ = faqs[faqs.length - 1];
- const inputRef = questionInputRefs.current[lastFAQ.id];
- if (inputRef) {
- inputRef.focus();
- }
- setShouldFocusLast(false);
- }
- }, [faqs, shouldFocusLast]);
-
- return (
-
- ❓ 자주 묻는 질문 (FAQ)
-
- + FAQ 추가
-
- {faqs.length === 0 ? (
-
- FAQ를 추가하여 지원자들의 자주 묻는 질문에 답변해보세요.
-
- ) : (
-
- {faqs.map((faq, index) => (
-
-
- Q{index + 1}
- handleRemoveFAQ(faq.id)}>
-
-
-
-
- {
- questionInputRefs.current[faq.id] = element;
- }}
- placeholder='질문을 입력하세요'
- value={faq.question}
- onChange={(event) =>
- handleUpdateQuestion(faq.id, event.target.value)
- }
- maxLength={100}
- />
-
-
- handleUpdateAnswer(faq.id, event.target.value)
- }
- maxLength={300}
- />
-
-
- 질문: {faq.question.length}/100 | 답변: {faq.answer.length}/300
-
-
- ))}
-
- )}
-
- );
-};
-
-export default FAQEditor;
diff --git a/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.styles.ts b/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.styles.ts
index dfaf2af63..655515ca5 100644
--- a/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.styles.ts
+++ b/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.styles.ts
@@ -210,6 +210,7 @@ export const AnswerBox = styled.div`
${setTypography(typography.paragraph.p3)};
color: ${colors.gray[800]};
line-height: 1.5;
+ white-space: pre-line;
display: flex;
gap: 8px;
diff --git a/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx b/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx
index d6d1ab702..a3ad1ab13 100644
--- a/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx
+++ b/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx
@@ -61,9 +61,10 @@ const ClubIntroContent = ({
)}
+
{awards && awards.length > 0 && (
- 🏆 동아리 성과
+ 동아리 성과
{awards.map((award) => (
@@ -80,7 +81,6 @@ const ClubIntroContent = ({
)}
-
{idealCandidate?.content?.trim() && (
이런 사람이 오면 좋아요
@@ -89,7 +89,6 @@ const ClubIntroContent = ({
)}
-
{benefits?.trim() && (
동아리 부원이 가지는 혜택
@@ -98,7 +97,6 @@ const ClubIntroContent = ({
)}
-
{faqs && faqs.length > 0 && (
FAQ
diff --git a/frontend/src/types/club.ts b/frontend/src/types/club.ts
index d38e5c3be..37f1e69b8 100644
--- a/frontend/src/types/club.ts
+++ b/frontend/src/types/club.ts
@@ -1,10 +1,6 @@
import { SNS_CONFIG } from '@/constants/snsConfig';
-export type RecruitmentStatus =
- | 'OPEN'
- | 'CLOSED'
- | 'UPCOMING'
- | 'ALWAYS';
+export type RecruitmentStatus = 'OPEN' | 'CLOSED' | 'UPCOMING' | 'ALWAYS';
export interface Club {
id: string;
@@ -56,6 +52,7 @@ export interface IdealCandidate {
}
export interface FAQ {
+ id?: string;
question: string;
answer: string;
}