From e975e6c6f4819e5f4e4a8236ab561695a07fd402 Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sat, 20 Dec 2025 02:19:27 +0900 Subject: [PATCH 01/10] =?UTF-8?q?refactor:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20UI=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20=ED=81=B4=EB=9F=BD=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ClubLogoEditor, 클럽 이름 표시 제거 - 사이드바 스타일 개선 (padding, gap, border-radius) - SideBar props 제거 (clubLogo, clubName) --- .../components/SideBar/SideBar.styles.ts | 63 ++++++------------- .../AdminPage/components/SideBar/SideBar.tsx | 24 ++----- 2 files changed, 24 insertions(+), 63 deletions(-) diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts b/frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts index 472c4e056..48fc40315 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts @@ -3,60 +3,39 @@ import styled from 'styled-components'; export const SidebarWrapper = styled.aside` display: flex; flex-direction: column; - align-items: left; - word-wrap: break-word; - overflow-wrap: break-word; - white-space: normal; - width: 168px; position: sticky; - top: 98px; + top: 110px; + width: 190px; + padding: 12px 10px 10px; + border-radius: 20px; + background-color: #ffffff; height: fit-content; `; export const SidebarHeader = styled.p` - font-size: 1.5rem; + padding: 0 8px; + font-size: 1.25rem; font-weight: bold; - letter-spacing: 0; - margin-left: 11px; - margin-bottom: 19px; `; -export const ClubLogo = styled.img` - width: 168px; - height: 168px; - background: #ededed; - border-radius: 10px; -`; - -export const ClubTitle = styled.p` - text-align: center; - margin-top: 14px; - font-size: 2rem; - font-weight: bold; - height: 76px; - max-width: 163px; -`; - -export const Divider = styled.hr` +export const SidebarDivider = styled.hr` width: 100%; - border: 1px solid; - color: #C5C5C5; - height: 0; - margin: 16px 0px 16px 0px; + margin: 10px 0 12px; + border: none; + border-top: 1px solid #c5c5c5; `; export const SidebarButtonContainer = styled.ul` display: flex; flex-direction: column; - gap: 16px; + gap: 12px; list-style: none; `; export const SidebarCategoryTitle = styled.p` - align-items: center; - padding: 6px 0px 6px 10px; + padding: 6px 10px; font-size: 0.75rem; - font-weight: medium; + font-weight: 500; color: #989898; `; @@ -65,23 +44,17 @@ export const SidebarButton = styled.button` box-sizing: border-box; display: flex; align-items: center; - cursor: pointer; - width: 100%; - height: 37px; + padding: 8px 10px; border-radius: 10px; - - padding-left: 10px; - font-size: 1rem; - font-weight: medium; - + font-weight: 500; + cursor: pointer; transition: background-color 0.1s ease; &.active { background-color: rgba(255, 117, 67, 1); color: white; - font-weight: medium; + font-weight: 600; } `; - diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx index 27390fa4d..8796ed13d 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx @@ -1,16 +1,10 @@ -import React, { useMemo } from 'react'; +import { useMemo } from 'react'; import * as Styled from './SideBar.styles'; import { useNavigate, useLocation } from 'react-router-dom'; -import ClubLogoEditor from '@/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor'; import { logout } from '@/apis/auth/logout'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import { ADMIN_EVENT } from '@/constants/eventName'; -interface SideBarProps { - clubName: string; - clubLogo: string; -} - interface TabItem { label: string; path: string; @@ -42,13 +36,11 @@ const tabs: TabCategory[] = [ }, { category: '계정 관리', - items: [ - { label: '비밀번호 수정', path: '/admin/account-edit' }, - ], + items: [{ label: '비밀번호 수정', path: '/admin/account-edit' }], }, ]; -const SideBar = ({ clubLogo, clubName }: SideBarProps) => { +const SideBar = () => { const trackEvent = useMixpanelTrack(); const location = useLocation(); const navigate = useNavigate(); @@ -80,7 +72,7 @@ const SideBar = ({ clubLogo, clubName }: SideBarProps) => { } trackEvent(ADMIN_EVENT.LOGOUT_BUTTON_CLICKED); - + localStorage.removeItem('accessToken'); navigate('/admin/login', { replace: true }); } catch (error) { @@ -91,11 +83,7 @@ const SideBar = ({ clubLogo, clubName }: SideBarProps) => { return ( 설정 - - - - {clubName} - + {tabs.map((tab, tabIndex) => ( @@ -115,7 +103,7 @@ const SideBar = ({ clubLogo, clubName }: SideBarProps) => { ))} - + 로그아웃 From 52f670495f78135373f8e2452499ddd80114b593 Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sat, 20 Dec 2025 04:22:39 +0900 Subject: [PATCH 02/10] =?UTF-8?q?refactor:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20UI=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PageContainer 제거하고 Background/Layout 구조로 분리 - 전체 배경색(#f2f2f2) 적용 - 컴포넌트 네이밍 개선 (Content → MainContent) - Divider 제거하고 gap으로 간격 조정 --- .../src/pages/AdminPage/AdminPage.styles.ts | 33 +++++++++---------- frontend/src/pages/AdminPage/AdminPage.tsx | 23 +++++-------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/frontend/src/pages/AdminPage/AdminPage.styles.ts b/frontend/src/pages/AdminPage/AdminPage.styles.ts index 3fe8954d2..bbdae9e93 100644 --- a/frontend/src/pages/AdminPage/AdminPage.styles.ts +++ b/frontend/src/pages/AdminPage/AdminPage.styles.ts @@ -1,24 +1,23 @@ import styled from 'styled-components'; - -export const AdminPageContainer = styled.div` - display: flex; - margin-top: 98px; - align-items: flex-start; +export const Background = styled.div` + background-color: #f2f2f2; + min-height: 100vh; `; -export const Divider = styled.div` - position: sticky; - top: 98px; - width: 1px; - height: calc(100vh - 98px); - background-color: #dcdcdc; - margin: 0 34px; - flex-shrink: 0; +export const Layout = styled.div` + display: flex; + gap: 30px; + max-width: 1180px; + margin: 0 auto; + width: 100%; + padding-top: 110px; `; - -export const Content = styled.main` +export const MainContent = styled.main` width: 100%; - max-width: 977px; - padding: 62px 58px; + max-width: 960px; + background-color: #ffffff; + padding: 54px; + border-radius: 20px; + margin-bottom: 50px; `; diff --git a/frontend/src/pages/AdminPage/AdminPage.tsx b/frontend/src/pages/AdminPage/AdminPage.tsx index b3c3b0291..8534f9ae1 100644 --- a/frontend/src/pages/AdminPage/AdminPage.tsx +++ b/frontend/src/pages/AdminPage/AdminPage.tsx @@ -1,11 +1,10 @@ import Header from '@/components/common/Header/Header'; -import { PageContainer } from '@/styles/PageContainer.styles'; import SideBar from '@/pages/AdminPage/components/SideBar/SideBar'; import { Outlet } from 'react-router-dom'; -import * as Styled from './AdminPage.styles'; + import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { Divider } from './components/SideBar/SideBar.styles'; +import * as Styled from './AdminPage.styles'; const AdminPage = () => { const { clubId } = useAdminClubContext(); @@ -20,18 +19,14 @@ const AdminPage = () => { return ( <>
- - - - - + + + + - - - + + + ); }; From 6ba935129a840eaab14094c3b2e151cf658ce7df Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sat, 20 Dec 2025 04:23:27 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=83=AD=EC=9D=98=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=80=EB=90=9C=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20ContentSection=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContentSection/ContentSection.styles.ts | 28 +++++++++++++ .../ContentSection/ContentSection.tsx | 40 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 frontend/src/pages/AdminPage/components/ContentSection/ContentSection.styles.ts create mode 100644 frontend/src/pages/AdminPage/components/ContentSection/ContentSection.tsx diff --git a/frontend/src/pages/AdminPage/components/ContentSection/ContentSection.styles.ts b/frontend/src/pages/AdminPage/components/ContentSection/ContentSection.styles.ts new file mode 100644 index 000000000..7f686575c --- /dev/null +++ b/frontend/src/pages/AdminPage/components/ContentSection/ContentSection.styles.ts @@ -0,0 +1,28 @@ +import styled from 'styled-components'; + +export const ContentSection = styled.section` + display: flex; + flex-direction: column; + gap: 30px; +`; + +export const ContentSectionHeader = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + min-height: 42px; +`; + +export const ContentSectionTitle = styled.h2` + font-size: 1.5rem; + font-weight: bold; + letter-spacing: 0; + margin: 0; +`; + +export const ContentSectionBody = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; diff --git a/frontend/src/pages/AdminPage/components/ContentSection/ContentSection.tsx b/frontend/src/pages/AdminPage/components/ContentSection/ContentSection.tsx new file mode 100644 index 000000000..56807b8c1 --- /dev/null +++ b/frontend/src/pages/AdminPage/components/ContentSection/ContentSection.tsx @@ -0,0 +1,40 @@ +import { ReactNode } from 'react'; +import * as Styled from './ContentSection.styles'; + +interface ContentSectionProps { + children: ReactNode; +} + +interface ContentSectionHeaderProps { + title: string; + action?: ReactNode; +} + +interface ContentSectionBodyProps { + children: ReactNode; +} + +const ContentSectionRoot = ({ children }: ContentSectionProps) => { + return {children}; +}; + +const ContentSectionHeader = ({ + title, + action, +}: ContentSectionHeaderProps) => { + return ( + + {title} + {action &&
{action}
} +
+ ); +}; + +const ContentSectionBody = ({ children }: ContentSectionBodyProps) => { + return {children}; +}; + +export const ContentSection = Object.assign(ContentSectionRoot, { + Header: ContentSectionHeader, + Body: ContentSectionBody, +}); From b1b1bc3002a018c00bc1f3118cb4a5aa583b5f61 Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sat, 20 Dec 2025 04:24:39 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=83=AD=EC=97=90=20ContentSe?= =?UTF-8?q?ction=20=EC=A0=81=EC=9A=A9=ED=95=98=EC=97=AC=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모든 탭에서 동일한 헤더 및 레이아웃 스타일 적용 - UI 구조 통일로 유지보수 편의성 향상 --- .../AccountEditTab/AccountEditTab.styles.ts | 20 +- .../tabs/AccountEditTab/AccountEditTab.tsx | 114 +++++------ .../ApplicantsListTab/ApplicantsListTab.tsx | 5 +- .../tabs/ApplicantsTab/ApplicantsTab.tsx | 12 +- .../ApplicationListTab/ApplicationListTab.tsx | 5 +- .../ClubInfoEditTab/ClubInfoEditTab.styles.ts | 32 +--- .../tabs/ClubInfoEditTab/ClubInfoEditTab.tsx | 181 ++++++++---------- .../tabs/PhotoEditTab/PhotoEditTab.styles.ts | 18 +- .../tabs/PhotoEditTab/PhotoEditTab.tsx | 68 +++---- .../RecruitEditTab/RecruitEditTab.styles.ts | 35 +--- .../tabs/RecruitEditTab/RecruitEditTab.tsx | 99 +++++----- 11 files changed, 262 insertions(+), 327 deletions(-) diff --git a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts index 947797e51..e6ac463ba 100644 --- a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts @@ -1,16 +1,9 @@ import styled from 'styled-components'; -export const IdInputContainer = styled.div` +export const Container = styled.div` display: flex; - flex-direction: row; - align-items: flex-end; - gap: 10px; -`; - -export const ForgotPasswordText = styled.h3` - color: #cdcdcd; - font-weight: 300; - margin-bottom: 5px; + flex-direction: column; + gap: 60px; `; export const SuccessMessage = styled.p` @@ -31,10 +24,9 @@ export const ErrorMessage = styled.p` export const GuidanceBox = styled.div` padding: 16px; - margin-bottom: 24px; /* 입력 필드와의 간격 */ - background-color: #f8f9fa; /* 부드러운 배경색 */ - border-radius: 8px; /* 둥근 모서리 */ - border: 1px solid #e9ecef; /* 옅은 테두리 */ + background-color: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; `; export const GuidanceText = styled.p` diff --git a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx index f7df36d7f..2b506f80c 100644 --- a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx @@ -6,6 +6,7 @@ import { changePassword } from '@/apis/auth/changePassword'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import useTrackPageView from '@/hooks/useTrackPageView'; +import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^])(?!.*\s).{8,20}$/; @@ -65,62 +66,63 @@ const AccountEditTab = () => { }; return ( -
-

비밀번호 수정

-
- - - 비밀번호는 영문, 숫자, 특수문자(!@#$%^)를 포함하여 8자 이상 20자 이하로 입력해야 합니다. - - - 비밀번호를 잊으신 경우 모아동 관리자에게 연락 주세요. - - - - setNewPassword(e.target.value)} - onClear={() => { - setNewPassword(''); - trackEvent(ADMIN_EVENT.NEW_PASSWORD_CLEAR_BUTTON_CLICKED); - }} - maxLength={20} - isError={isPasswordValid} - isSuccess={newPassword.length > 0 && !isPasswordValid} - helperText={isPasswordValid ? '영문, 숫자, 특수문자 포함 8~20자' : ''} - /> -
- - setConfirmPassword(e.target.value)} - onClear={() => { - setConfirmPassword(''); - trackEvent(ADMIN_EVENT.CONFIRM_PASSWORD_CLEAR_BUTTON_CLICKED); - }} - maxLength={20} - isError={isPasswordMatching} - isSuccess={confirmPassword.length > 0 && !isPasswordMatching} - helperText={isPasswordMatching ? '비밀번호가 일치하지 않습니다.' : ''} - /> -
- - {successMessage && {successMessage}} -
- - -
+ + + + + + + + 비밀번호는 영문, 숫자, 특수문자(!@#$%^)를 포함하여 8자 이상 20자 이하로 입력해야 합니다. + + + 비밀번호를 잊으신 경우 모아동 관리자에게 연락 주세요. + + + + setNewPassword(e.target.value)} + onClear={() => { + setNewPassword(''); + trackEvent(ADMIN_EVENT.NEW_PASSWORD_CLEAR_BUTTON_CLICKED); + }} + maxLength={20} + isError={isPasswordValid} + isSuccess={newPassword.length > 0 && !isPasswordValid} + helperText={isPasswordValid ? '영문, 숫자, 특수문자 포함 8~20자' : ''} + /> + + setConfirmPassword(e.target.value)} + onClear={() => { + setConfirmPassword(''); + trackEvent(ADMIN_EVENT.CONFIRM_PASSWORD_CLEAR_BUTTON_CLICKED); + }} + maxLength={20} + isError={isPasswordMatching} + isSuccess={confirmPassword.length > 0 && !isPasswordMatching} + helperText={isPasswordMatching ? '비밀번호가 일치하지 않습니다.' : ''} + /> + + {successMessage && {successMessage}} + + + + + ); }; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx index 2ed56cef5..caf201222 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx @@ -11,6 +11,7 @@ import { useQueryClient } from '@tanstack/react-query'; import styled from 'styled-components'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; import expandArrow from '@/assets/images/icons/ExpandArrow.svg'; +import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; const ApplicationListTab = () => { const {data: allforms, isLoading, isError, error} = useGetApplicationlist(); @@ -116,7 +117,9 @@ const ApplicationListTab = () => { return ( - 지원서 목록 +
+ +
게시된 지원서 diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx index f538fca29..cb540cb28 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx @@ -13,6 +13,7 @@ import { useUpdateApplicant } from '@/hooks/queries/applicants/useUpdateApplican import { AVAILABLE_STATUSES } from '@/constants/status'; import { CustomDropDown } from '@/components/common/CustomDropDown/CustomDropDown'; import { useGetApplicants } from '@/hooks/queries/applicants/useGetApplicants'; +import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; const ApplicantsTab = () => { const { applicationFormId } = useParams<{ applicationFormId: string }>(); @@ -228,14 +229,9 @@ const ApplicantsTab = () => { return ( <> - - 지원 현황 - {/* - - - - */} - +
+ +
diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx index 6220a6f88..2c05e49d8 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx @@ -12,6 +12,7 @@ import { updateApplicationStatus } from '@/apis/application/updateApplication'; import { useQueryClient } from '@tanstack/react-query'; import styled from 'styled-components'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; +import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; const ApplicationListTab = () => { const { data: allforms, isLoading, isError, error } = useGetApplicationlist(); @@ -117,7 +118,9 @@ const ApplicationListTab = () => { return ( - 지원서 목록 +
+ +
게시된 지원서 diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.styles.ts b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.styles.ts index e76d17c4f..408264a16 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.styles.ts @@ -1,23 +1,15 @@ import styled from 'styled-components'; -export const TitleButtonContainer = styled.div` +export const Container = styled.div` display: flex; - flex-direction: row; - justify-content: space-between; -`; - -export const InfoTitle = styled.h2` - font-size: 1.5rem; - font-weight: bold; - letter-spacing: 0; - margin-bottom: 30px; + flex-direction: column; + gap: 60px; `; -export const InfoGroup = styled.div` +export const FieldGroup = styled.div` display: flex; flex-direction: column; gap: 30px; - margin-bottom: 140px; `; export const PresidentContainer = styled.div` @@ -27,20 +19,6 @@ export const PresidentContainer = styled.div` max-width: 700px; `; -export const TagEditGroup = styled.div` - display: flex; - flex-direction: column; - gap: 30px; - margin-bottom: 120px; -`; - -export const SNSInputGroup = styled.div` - display: flex; - flex-direction: column; - gap: 30px; - margin-top: 30px; -`; - export const SNSRow = styled.div` display: flex; flex-direction: column; @@ -49,7 +27,7 @@ export const SNSRow = styled.div` align-items: flex-start; `; -export const SNSCheckboxLabel = styled.label` +export const SNSLabel = styled.label` display: flex; align-items: center; gap: 10px; diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx index 834ee2893..d4b1c0ceb 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { useOutletContext } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import { ClubDetail } from '@/types/club'; @@ -14,7 +14,7 @@ import * as Styled from './ClubInfoEditTab.styles'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import useTrackPageView from '@/hooks/useTrackPageView'; - +import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; const ClubInfoEditTab = () => { const trackEvent = useMixpanelTrack(); @@ -119,118 +119,101 @@ const ClubInfoEditTab = () => { }; return ( - <> - - 동아리 기본 정보 수정 - - - - - setClubName(e.target.value)} - onClear={() => { - trackEvent(ADMIN_EVENT.CLUB_NAME_CLEAR_BUTTON_CLICKED); - setClubName(''); - }} - width='40%' - maxLength={10} - showMaxChar={true} + + + + 저장하기 + + } /> - + setClubPresidentName(e.target.value)} + label='동아리명' + placeholder='동아리명' + value={clubName} + onChange={(e) => setClubName(e.target.value)} onClear={() => { - trackEvent(ADMIN_EVENT.CLUB_PRESIDENT_CLEAR_BUTTON_CLICKED); - setClubPresidentName(''); + trackEvent(ADMIN_EVENT.CLUB_NAME_CLEAR_BUTTON_CLICKED); + setClubName(''); }} - maxLength={5} + width='40%' + maxLength={20} + showMaxChar={true} /> setTelephoneNumber(e.target.value)} + maxLength={20} + showMaxChar={true} + value={introduction} + onChange={(e) => setIntroduction(e.target.value)} onClear={() => { - trackEvent(ADMIN_EVENT.TELEPHONE_NUMBER_CLEAR_BUTTON_CLICKED); - setTelephoneNumber(''); + trackEvent(ADMIN_EVENT.CLUB_INTRODUCTION_CLEAR_BUTTON_CLICKED); + setIntroduction(''); }} /> - - - - 동아리 태그 수정 - - setIntroduction(e.target.value)} - onClear={() => { - trackEvent(ADMIN_EVENT.CLUB_INTRODUCTION_CLEAR_BUTTON_CLICKED); - setIntroduction(''); - }} - /> - + - + - - - - 동아리 SNS 링크 - - {Object.entries(SNS_CONFIG).map(([rawKey, { label, placeholder }]) => { - const key = rawKey as SNSPlatform; - - return ( - - {label} - handleSocialLinkChange(key, e.target.value)} - onClear={() => { - trackEvent(ADMIN_EVENT.CLUB_SNS_LINK_CLEAR_BUTTON_CLICKED, { - snsPlatform: label, - }); - setSocialLinks((prev) => ({ ...prev, [key]: '' })); - setSnsErrors((prev) => ({ ...prev, [key]: '' })); - }} - isError={snsErrors[key] !== ''} - helperText={snsErrors[key]} - /> - - ); - })} - - + + + + + + + + + {Object.entries(SNS_CONFIG).map( + ([rawKey, { label, placeholder }]) => { + const key = rawKey as SNSPlatform; + + return ( + + {label} + + handleSocialLinkChange(key, e.target.value) + } + onClear={() => { + trackEvent( + ADMIN_EVENT.CLUB_SNS_LINK_CLEAR_BUTTON_CLICKED, + { + snsPlatform: label, + }, + ); + setSocialLinks((prev) => ({ ...prev, [key]: '' })); + setSnsErrors((prev) => ({ ...prev, [key]: '' })); + }} + isError={snsErrors[key] !== ''} + helperText={snsErrors[key]} + /> + + ); + }, + )} + + +
); }; diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts index 0a4a9d93a..3af22a5e8 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts @@ -1,16 +1,9 @@ import styled from 'styled-components'; -export const PhotoEditorContainer = styled.div` +export const Container = styled.div` display: flex; flex-direction: column; - width: 100%; -`; - -export const ImageContainer = styled.div` - display: flex; - flex-direction: column; - gap: 15px; - overflow: hidden; + gap: 60px; `; export const ImageGrid = styled.div` @@ -21,13 +14,6 @@ export const ImageGrid = styled.div` max-width: 770px; `; -export const InfoTitle = styled.h2` - font-size: 1.5rem; - font-weight: bold; - letter-spacing: 0; - margin-bottom: 46px; -`; - export const Label = styled.p` font-size: 1.125rem; margin-bottom: 12px; diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx index de38d2a8a..cfd3c5f64 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useOutletContext } from 'react-router-dom'; import * as Styled from './PhotoEditTab.styles'; import ImageUpload from '@/pages/AdminPage/tabs/PhotoEditTab/components/ImageUpload/ImageUpload'; @@ -9,6 +9,7 @@ import { useQueryClient } from '@tanstack/react-query'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import useTrackPageView from '@/hooks/useTrackPageView'; +import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; const MAX_IMAGES = 5; @@ -78,39 +79,40 @@ const PhotoEditTab = () => { }; return ( - - 활동 사진 편집 - 활동사진 추가 (최대 5장) - - -
- 활동사진 수정 - - {imageList.map((image, index) => ( - { - trackEvent(ADMIN_EVENT.IMAGE_DELETE_BUTTON_CLICKED); - deleteImage(index); - }} + + + + + +
+ 활동사진 추가 (최대 5장) + - ))} - {/*{imageList.length < MAX_IMAGES && (*/} - {/* */} - {/*)}*/} - - - +
+ +
+ 활동사진 수정 + + {imageList.map((image, index) => ( + { + trackEvent(ADMIN_EVENT.IMAGE_DELETE_BUTTON_CLICKED); + deleteImage(index); + }} + /> + ))} + +
+
+
+
); }; + export default PhotoEditTab; diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts index c85951fe0..123449232 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts @@ -1,40 +1,21 @@ import styled from 'styled-components'; -export const RecruitEditorContainer = styled.div` +export const Container = styled.div` display: flex; flex-direction: column; -`; - -export const TitleButtonContainer = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; -`; - -export const InfoTitle = styled.h2` - font-size: 1.5rem; - font-weight: bold; - letter-spacing: 0; - margin-bottom: 46px; -`; - -export const InfoGroup = styled.div` - display: flex; - flex-direction: column; - gap: 30px; - margin-bottom: 140px; + gap: 60px; `; export const RecruitPeriodContainer = styled.div` - display: flex; - gap: 16px; + display: flex; + gap: 16px; max-width: 706px; `; export const AlwaysRecruitButton = styled.button<{ $active: boolean }>` border-radius: 6px; height: 45px; - padding: 0px 16px ; + padding: 0px 16px; font-weight: 600; font-size: 1rem; cursor: pointer; @@ -43,10 +24,12 @@ export const AlwaysRecruitButton = styled.button<{ $active: boolean }>` color: ${({ $active }) => ($active ? '#fff' : '#797979')}; background: ${({ $active }) => ($active ? '#FF7543' : 'rgba(0,0,0,0.05)')}; border: ${({ $active }) => ($active ? 'none' : '1px solid #C5C5C5')}; - transition: background-color 0.12s ease, transform 0.06s ease; + transition: + background-color 0.12s ease, + transform 0.06s ease; &:active { - transform: translateY(1px); + transform: translateY(1px); } `; diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx index 6673d7286..c16e21f9d 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useOutletContext } from 'react-router-dom'; import * as Styled from './RecruitEditTab.styles'; import Calendar from '@/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar'; @@ -13,6 +13,7 @@ import { setYear } from 'date-fns'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import useTrackPageView from '@/hooks/useTrackPageView'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; const RecruitEditTab = () => { const trackEvent = useMixpanelTrack(); @@ -135,53 +136,59 @@ const RecruitEditTab = () => { }; return ( - - - 동아리 모집 정보 수정 - - - -
- 모집 기간 설정 - - - - 상시모집 - - -
- setRecruitmentTarget(e.target.value)} - onClear={() => { - trackEvent(ADMIN_EVENT.RECRUITMENT_TARGET_CLEAR_BUTTON_CLICKED); - setRecruitmentTarget(''); - }} - maxLength={10} + + + + 저장하기 + + } /> -
- 소개글 수정 - -
-
-
+ +
+ 모집 기간 + + + + 상시모집 + + +
+ + setRecruitmentTarget(e.target.value)} + onClear={() => { + trackEvent(ADMIN_EVENT.RECRUITMENT_TARGET_CLEAR_BUTTON_CLICKED); + setRecruitmentTarget(''); + }} + maxLength={10} + /> + +
+ 소개글 + +
+
+ +
); }; export default RecruitEditTab; From 1e5258d65a261362e210c326ae30f69d5958dc68 Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sun, 21 Dec 2025 14:33:49 +0900 Subject: [PATCH 05/10] =?UTF-8?q?style:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20Label=20=EC=97=AC=EB=B0=B1=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Label 컴포넌트의 margin-bottom 값을 12px에서 8px로 변경하여 UI 일관성 개선 --- .../common/InputField/InputField.styles.ts | 21 +++++++++++-------- .../tabs/PhotoEditTab/PhotoEditTab.styles.ts | 2 +- .../RecruitEditTab/RecruitEditTab.styles.ts | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/common/InputField/InputField.styles.ts b/frontend/src/components/common/InputField/InputField.styles.ts index 3ab608b8b..e7f8afe7f 100644 --- a/frontend/src/components/common/InputField/InputField.styles.ts +++ b/frontend/src/components/common/InputField/InputField.styles.ts @@ -13,7 +13,7 @@ export const InputContainer = styled.div<{ width: string; readOnly?: boolean }>` export const Label = styled.label` font-size: 1.125rem; - margin-bottom: 12px; + margin-bottom: 8px; font-weight: 600; `; @@ -23,13 +23,17 @@ export const InputWrapper = styled.div` align-items: center; `; -export const Input = styled.input<{ hasError?: boolean; readOnly?: boolean; isSuccess?: boolean; }>` +export const Input = styled.input<{ + hasError?: boolean; + readOnly?: boolean; + isSuccess?: boolean; +}>` flex: 1; height: 45px; padding: 12px 18px; border: 1px solid ${({ hasError, isSuccess }) => - hasError ? 'red' : isSuccess ? '#28a745' : '#c5c5c5'}; + hasError ? 'red' : isSuccess ? '#28a745' : '#c5c5c5'}; background-color: transparent; border-radius: 6px; outline: none; @@ -50,10 +54,10 @@ export const Input = styled.input<{ hasError?: boolean; readOnly?: boolean; isSu readOnly ? '#c5c5c5' : hasError - ? 'red' - : isSuccess - ? '#28a745' - : '#007bff'}; + ? 'red' + : isSuccess + ? '#28a745' + : '#007bff'}; ${({ readOnly }) => readOnly && 'cursor: pointer;'} } @@ -65,7 +69,7 @@ export const Input = styled.input<{ hasError?: boolean; readOnly?: boolean; isSu color: rgba(0, 0, 0, 0.3); transition: color 0.25s ease; } - + &:focus::placeholder { color: transparent; } @@ -125,4 +129,3 @@ export const HelperText = styled.div` white-space: nowrap; z-index: 1; `; - diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts index 3af22a5e8..a8c74b814 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.styles.ts @@ -16,6 +16,6 @@ export const ImageGrid = styled.div` export const Label = styled.p` font-size: 1.125rem; - margin-bottom: 12px; + margin-bottom: 8px; font-weight: 600; `; diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts index 123449232..d08edcd0a 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts @@ -35,6 +35,6 @@ export const AlwaysRecruitButton = styled.button<{ $active: boolean }>` export const Label = styled.p` font-size: 1.125rem; - margin-bottom: 12px; + margin-bottom: 8px; font-weight: 600; `; From 66ff2f25a4cb31712eeb80026af9df416832da08 Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sun, 21 Dec 2025 14:36:52 +0900 Subject: [PATCH 06/10] =?UTF-8?q?style:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=82=AC=EC=A7=84=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=B2=84=ED=8A=BC=20width=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx index 8dad7e2f8..32f6f25b6 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx @@ -81,9 +81,7 @@ const PhotoEditTab = () => { - - 활동사진 추가 (최대 {MAX_FILE_COUNT}장) - + 활동사진 추가 (최대 {MAX_FILE_COUNT}장)
{ }} /> -
From 7ab9b199218d28be70e647cdd4369eb29b085495 Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sun, 21 Dec 2025 14:37:31 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=EB=B0=94=EC=9D=98=20=EB=A1=9C=EA=B3=A0=20=EC=97=90=EB=94=94?= =?UTF-8?q?=ED=84=B0=EB=A5=BC=20ClubInfoEditTab=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사이드바 레이아웃 변경으로 로고 에디터를 동아리 기본 정보 수정 탭으로 이동하고, 페이지 내 배치에 맞춰 드롭다운 방식에서 버튼 방식으로 UI 개선 - 드롭다운 메뉴 제거 - 이미지 수정/초기화 버튼을 가로 배치 - 안내 텍스트를 버튼 하단에 배치 - ClubInfoEditTab에 ClubLogoEditor 통합 --- .../ClubLogoEditor/ClubLogoEditor.styles.ts | 108 +++++++++--------- .../ClubLogoEditor/ClubLogoEditor.tsx | 104 ++++++----------- .../tabs/ClubInfoEditTab/ClubInfoEditTab.tsx | 8 +- 3 files changed, 93 insertions(+), 127 deletions(-) diff --git a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.styles.ts b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.styles.ts index 0efa1df1f..fa745f2b6 100644 --- a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.styles.ts +++ b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.styles.ts @@ -1,81 +1,83 @@ import styled from 'styled-components'; +export const Label = styled.label` + display: block; + padding: 0 4px; + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 4px; +`; + +export const ContentWrapper = styled.div` + display: flex; + align-items: center; + gap: 16px; +`; + export const ClubLogoWrapper = styled.div` position: relative; - width: 168px; - height: 168px; + width: 100px; + height: 100px; `; export const ClubLogo = styled.img` - width: 168px; - height: 168px; + width: 100px; + height: 100px; background: #ededed; - border-radius: 10px; + border-radius: 20px; object-fit: cover; `; -export const EditButton = styled.button` - position: absolute; - bottom: 8px; - right: 8px; - width: 32px; - height: 32px; - background: transparent; - border: none; - outline: none; - border-radius: 50%; +export const ButtonTextGroup = styled.div` display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - - img { - width: 24px; - height: 24px; - } + flex-direction: column; + gap: 6px; `; -export const EditMenu = styled.div` - position: absolute; - left: 100%; - transform: translateY(-50%); - margin-left: 8px; - background: #fff; - box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.4); - border-radius: 18px; - overflow: hidden; - min-width: 160px; - z-index: 2; +export const ButtonGroup = styled.div` + display: flex; + gap: 6px; `; -export const EditMenuItem = styled.button` - width: 100%; - display: flex; - align-items: center; - gap: 10px; - padding: 12px 16px; +export const UploadButton = styled.button` + padding: 10px 20px; + border: 1px solid #ff6b35; + border-radius: 80px; background: white; - border: none; - font-size: 16px; - color: #333; + color: #ff6b35; + font-size: 12px; + line-height: 140%; + font-weight: 600; cursor: pointer; - transition: background-color 0.2s; + transition: all 0.2s; &:hover { - background-color: #f1f1f1; + background: #ff6b35; + color: white; } +`; - img { - width: 15px; - height: 15px; +export const ResetButton = styled.button` + padding: 10px 20px; + border: 1px solid #999; + border-radius: 80px; + background: white; + color: #999; + font-size: 12px; + line-height: 140%; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: #999; + color: white; } `; -export const Divider = styled.div` - width: 90%; - height: 1px; - background: rgba(0, 0, 0, 0.12); - margin: 0 auto; +export const HelpText = styled.p` + font-size: 12px; + color: #c5c5c5; `; export const HiddenFileInput = styled.input` diff --git a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx index b17361264..c8dca7e87 100644 --- a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx +++ b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx @@ -1,10 +1,6 @@ -import React, { useState, useRef, useCallback, useEffect } from 'react'; +import React, { useRef } from 'react'; import * as Styled from './ClubLogoEditor.styles'; import defaultLogo from '@/assets/images/logos/default_profile_image.svg'; -import changeImageIcon from '@/assets/images/icons/change_image_button_icon.svg'; -import changeImageIconHover from '@/assets/images/icons/change_image_button_icon_hover.svg'; -import editIcon from '@/assets/images/icons/pencil_icon_2.svg'; -import deleteIcon from '@/assets/images/icons/cancel_button_icon.svg'; import { useUploadLogo, useDeleteLogo, @@ -24,8 +20,6 @@ const ClubLogoEditor = ({ clubLogo }: ClubLogoEditorProps) => { const { clubId } = useAdminClubContext(); if (!clubId) return null; - const [isMenuOpen, setIsMenuOpen] = useState(false); - const menuRef = useRef(null); const fileInputRef = useRef(null); const uploadMutation = useUploadLogo(); @@ -34,10 +28,6 @@ const ClubLogoEditor = ({ clubLogo }: ClubLogoEditorProps) => { const isClubLogoEmpty = !clubLogo || clubLogo.trim() === ''; const displayedLogo = isClubLogoEmpty ? defaultLogo : clubLogo; - const toggleMenu = useCallback(() => { - setIsMenuOpen((prev) => !prev); - }, [trackEvent]); - const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; @@ -50,6 +40,7 @@ const ClubLogoEditor = ({ clubLogo }: ClubLogoEditorProps) => { return; } + trackEvent(ADMIN_EVENT.CLUB_LOGO_UPLOAD_BUTTON_CLICKED); uploadMutation.mutate({ clubId, file }); }; @@ -66,71 +57,42 @@ const ClubLogoEditor = ({ clubLogo }: ClubLogoEditorProps) => { if (!window.confirm('정말 로고를 기본 이미지로 되돌릴까요?')) return; + trackEvent(ADMIN_EVENT.CLUB_LOGO_RESET_BUTTON_CLICKED); deleteMutation.mutate(clubId); }; - useEffect(() => { - if (!isMenuOpen) return; - - const handleOutsideClick = (e: MouseEvent) => { - if (menuRef.current && !menuRef.current.contains(e.target as Node)) { - setIsMenuOpen(false); - } - }; - - document.addEventListener('mousedown', handleOutsideClick); - - return () => { - document.removeEventListener('mousedown', handleOutsideClick); - }; - }, [isMenuOpen]); - return ( - - - - - 로고 수정 + 로고 + + + + + + + + + 이미지 수정 + + + {!isClubLogoEmpty && ( + + 초기화 + + )} + + + 동아리 로고 이미지를 넣어주세요. + + + - - - {isMenuOpen && ( - - { - trackEvent(ADMIN_EVENT.CLUB_LOGO_EDIT_BUTTON_CLICKED); - triggerFileInput(); - setIsMenuOpen(false); - }} - > - 사진 수정 아이콘 - 사진 수정하기 - - - - - { - trackEvent(ADMIN_EVENT.CLUB_LOGO_RESET_BUTTON_CLICKED); - handleLogoReset(); - setIsMenuOpen(false); - }} - > - 초기화 아이콘 - 초기화하기 - - - )} - - - + + ); }; diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx index d4b1c0ceb..6ad81e77c 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx @@ -1,8 +1,7 @@ import { useState, useEffect } from 'react'; import { useOutletContext } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; -import { ClubDetail } from '@/types/club'; -import { SNSPlatform } from '@/types/club'; +import { ClubDetail, SNSPlatform } from '@/types/club'; import { useUpdateClubDetail } from '@/hooks/queries/club/useUpdateClubDetail'; import { validateSocialLink } from '@/utils/validateSocialLink'; import { SNS_CONFIG } from '@/constants/snsConfig'; @@ -10,11 +9,13 @@ import InputField from '@/components/common/InputField/InputField'; import Button from '@/components/common/Button/Button'; import SelectTags from '@/pages/AdminPage/tabs/ClubInfoEditTab/components/SelectTags/SelectTags'; import MakeTags from '@/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags'; -import * as Styled from './ClubInfoEditTab.styles'; +import ClubLogoEditor from '@/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor'; + import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import useTrackPageView from '@/hooks/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; +import * as Styled from './ClubInfoEditTab.styles'; const ClubInfoEditTab = () => { const trackEvent = useMixpanelTrack(); @@ -131,6 +132,7 @@ const ClubInfoEditTab = () => { /> + Date: Sun, 21 Dec 2025 16:48:57 +0900 Subject: [PATCH 08/10] =?UTF-8?q?fix:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9E=90=EC=9C=A0=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=9E=85=EB=A0=A5=20=ED=95=84=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B2=84=ED=8A=BC=EC=9D=B4=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20=20=20-=20background-url=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=97=90=EC=84=9C=20img=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MakeTags/MakeTags.styles.ts | 28 +++++++++++++------ .../components/MakeTags/MakeTags.tsx | 13 +++++++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.styles.ts b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.styles.ts index feb823e7d..a720a6907 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.styles.ts @@ -1,5 +1,4 @@ import styled from 'styled-components'; -import deleteIcon from '@/assets/images/icons/delete_button_icon.svg'; export const Label = styled.label` display: block; @@ -36,19 +35,32 @@ export const TagTextInput = styled.input` background: transparent; font-size: 0.875rem; color: #4b4b4b; - width: 110px; - padding-right: 10px; + width: 100px; + padding-right: 30px; `; export const RemoveButton = styled.button` position: absolute; top: 50%; - right: 10px; + right: 8px; transform: translateY(-50%); - width: 16px; - height: 16px; - background: url(${deleteIcon}) no-repeat center; - background-size: contain; + width: 20px; + height: 20px; + padding: 0; + background: transparent; border: none; cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + + img { + width: 16px; + height: 16px; + opacity: 0.5; + } + + &:hover img { + opacity: 1; + } `; diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx index 9243e3ff8..82de909ab 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx @@ -1,6 +1,7 @@ import * as Styled from './MakeTags.styles'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import { ADMIN_EVENT } from '@/constants/eventName'; +import deleteButton from '@/assets/images/icons/delete_button_icon.svg'; interface MakeTagsProps { value: string[]; @@ -29,7 +30,7 @@ const MakeTags = ({ value, onChange }: MakeTagsProps) => { } return tag; }); - + trackEvent(ADMIN_EVENT.CLUB_TAG_CLEAR_BUTTON_CLICKED, { tagIndex: index + 1, }); @@ -48,9 +49,17 @@ const MakeTags = ({ value, onChange }: MakeTagsProps) => { value={tag} maxLength={5} onChange={(e) => updateTag(index, e.target.value)} + placeholder={`자유 태그 ${index + 1}`} + aria-label={`자유 태그 ${index + 1}`} /> {tag.length > 0 && ( - clearTag(index)} /> + clearTag(index)} + aria-label={`자유 태그 ${index + 1} 삭제`} + type='button' + > + + )} ))} From 210b047a0809ae30443d617fcfd93b61e5972780 Mon Sep 17 00:00:00 2001 From: Junseo Kim Date: Sun, 21 Dec 2025 22:22:01 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20PhotoEditTab=EC=9D=98=20onCha?= =?UTF-8?q?nge=20=ED=95=B8=EB=93=A4=EB=9F=AC=EB=A5=BC=20=EB=B3=84=EB=8F=84?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인라인 onChange 핸들러를 handleFileChange 함수로 추출 - 파일 크기 검증 로직의 가독성 개선 --- .../tabs/PhotoEditTab/PhotoEditTab.tsx | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx index 32f6f25b6..65ec10ec9 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx @@ -62,6 +62,26 @@ const PhotoEditTab = () => { inputRef.current?.click(); }; + /** 파일 선택 변경 */ + const handleFileChange = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const oversizedFile = Array.from(files).find( + (file) => file.size > MAX_FILE_SIZE, + ); + + if (oversizedFile) { + alert( + `선택한 사진 중 ${oversizedFile.name}의 용량이 제한을 초과했습니다.`, + ); + e.target.value = ''; + return; + } + + handleFiles(files); + }; + /** 이미지 삭제 */ const deleteImage = (index: number) => { if (isLoading) return; @@ -90,24 +110,7 @@ const PhotoEditTab = () => { accept='image/*' multiple hidden - onChange={(e) => { - const files = e.target.files; - if (!files || files.length === 0) return; - - const oversizedFile = Array.from(files).find( - (file) => file.size > MAX_FILE_SIZE, - ); - - if (oversizedFile) { - alert( - `선택한 사진 중 ${oversizedFile.name}의 용량이 제한을 초과했습니다.`, - ); - e.target.value = ''; - return; - } - - handleFiles(files); - }} + onChange={handleFileChange} />