From 4a164879929c491c3fc402f94d772b8fbaf208b5 Mon Sep 17 00:00:00 2001 From: llddang <77055208+llddang@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:34:25 +0900 Subject: [PATCH] =?UTF-8?q?Feature/#227=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: formik 컴포넌트 위치 이동 * feat: Footer styled components 대신 tailwind로 작성 및 위치 이동 * feat: IconButton tailwind 로 변경 및 컴포넌트 위치 이동 * refactor: footer 폴더 위치 이동 * design: items-content:center 에서 justify-content: center 로 수정 * feat: Header 컴포넌트 styled component가 아닌 tailwind css사용하도록 수정 및 폴더 위치 이동 * feat: 홈페이지에서 사용되는 GoPageIcon 폴더 위치 이동 및 tailwind css 적용 * refactor: MarkdownViewer 폴더 위치 이동 * refactor: PageTitle 컴포넌트 위치 이동 * feat: 팀 빌딩 카드 컴포넌트 위치 이동 및 css 라이브러리 교체 * feat: SignPageTabButton 컴포넌트 폴더위치 이동 * feat: PageSubTitle 컴포넌트 폴더 위치 이동 * feat: SearchBox 폴더 위치 이동 * feat: MilestoneOverviewTable 파일 이동 및 css 라이브러리 교체 * feat: PeriodSearchBox 폴더 위치 이동 및 css 라이브러리 교체 * feat: MilestoneGroupLabel 폴더 위치 이동 * feat: MilestoneCircleChart 폴더 이동 및 css 라이브러리 교체 * refactor: components 폴더명 수정 * refactor: 중복되는 컴포넌트 삭제 * refactor: Pagination 폴더 위치 이동 * feat: 홈화면의 공지사항 폴더 위치 이동 및 css 라이브러리 교체 * feat: 홈의 외부 링크 폴더 위치 이동 및 css 라이브러리 교체 * feat: 홈화면의 팀빌딩 세션 폴더 위치 이동 및 css 라이브러리 교체 #227 * feat: 홈화면의 로그인 세션 폴더 위치 이동 및 css 라이브러리 변경 #227 * feat: 사이드바 폴더 위치 이동 및 css 라이브러리 수정 #227 * refactor: 함수 선언 방식 변경 (Arrow -> Named Function) #227 * feat: 홈 화면의 pnuLink 세션 폴더 위치 이동 및 css 라이브러리 변경 #227 * feat: 홈 화면의 마일스톤 폴더 위치 이동 및 css 라이브러리 변경 #227 * feat: 홈 화면의 css 라이브러리 변경 #227 * feat: milestone 폴더 구조 변경 및 css 라이브러리 변정 #227 * fix: map 관련하여 key 오류가 나지 않도록 수정 #227 * design: 사이드바가 있는 layout 수정 #227 * refactor: milestone status label 컴포넌트 경로 이동 및 리펙토링 #227 * design: 마이페이지의 마일스톤 실적 컴포넌트 경로 이동 및 디자인 수정 #227 * refactor: 마일스톤 상태 라벨 컴포넌트 추가 #227 * refactor: 마일스톤 상세 테이블 및 마이페이지의 마일스톤 세션 폴더 이동 #227 * refactor: 마이페이지의 학생 정보 세션 폴더 이동 #227 * refactor: 함수 선언 방식 변경 및 마일스톤 overview 컴포넌트 위치 이동 #227 * refactor: 함수 선언 방식 및 내 정보 수정 페이지 이동 #227 * refactor: 함수 선언 방식 변경 및 마이페이지의 마일스톤 상세 내역 폴더 위치 이동 #227 * refactor: 마일스톤 내역 테이블 컴포넌트 #227 * fix: 더이상 사용되지 않는 환경 설정 변경 (images.domains -> images.remotePatterns) #227 * refactor: 마일스톤 내역 테이블의 명 변경 (MilestoneHistoryTable -> MilestoneAcceptedTable) #227 * refactor: 마일스톤 실적 등록 내역 #227 * refactor: 마일스톤 내역 삭제 버튼 #227 * refactor: 마일스톤 카테고리 드롭다운 컴포넌트 #227 * refactor: 마이페이지의 모든 페이지들 폴더 변경 및 함수 선언 방식 등 변경 #227 * feat: textInput에 툴팁 기능 추가 #227 * feat: dataPicker에 key 값 추가 #227 * refactor: 팀빌딩 페이지 #227 * refactor: 해커톤 페이지 #227 * refactor: 아이디/비밀번호 찾는 페이지의 tabButton 렌더 방식 및 컴포넌트 명 변경 #227 * refactor: 아이디,비밀번호 찾는 페이지의 footer 컴포넌트 #227 * feat: 비밀번호를 찾는 페이지의 form #227 * refactor: 로그인 form 컴포넌트 #227 * refactor: 로그인 페이지 #227 * refactor: 아이디 비밀번호 찾는 페이지 #227 * refactor: 로그인 폼 css 라이브러리 변경 #227 * refactor: 로그아웃 페이지 #227 * refactor: 회원가입 페이지 #227 * refactor: 필요없는 style 파일 제거 #227 * refactor: 어드민 헤더 #227 * refactor: 관리자 페이지의 사이드바 #227 * refactor: 관리자 페이지의 레이아웃 #227 * refactor: 관리자 페이지의 페이지네이션 컴포넌트 #227 * refactor: 관리자페이지의 검색 박스 컴포넌트 #227 * refactor: 관리자 페이지에서 교직원 목록 페이지 및 관련 컴포넌트 #227 * refactor: 클라이언트의 레이아웃 #227 * design: 홈화면의 로그인 세션의 input의 placehold 스타일 통일 #227 * design: textInput에서 value가 남아 있는 오류 해결 #227 * refactor: 교직원 등록 페이지 #227 * refactor: 관리자페이지의 학생 목록 페이지 및 관련 컴포넌트 #227 * refactor: 관리자의 팀빌딩 페이지 #227 * refactor: 관리자의 마일스톤 등록 및 랭킹 페이지 #227 * refactor: 마일스톤 목록와 상세 페이지 및 관련 컴포넌트 #227 * chore: eslint에서 airbnb 문법 없앰 #227 * feat: 빌드 에러 및 워닝 해결 #227 --- frontend/.babelrc | 4 - frontend/.eslintrc.json | 56 +- frontend/babel.config.json | 9 - frontend/next.config.mjs | 16 +- frontend/package-lock.json | 478 ++++++++---------- frontend/package.json | 10 +- frontend/src/adminComponents/Footer/index.tsx | 16 - .../Header/components/Navigator/index.tsx | 31 -- .../Header/components/Navigator/styled.ts | 32 -- .../Header/components/UserName/index.tsx | 16 - frontend/src/adminComponents/Header/index.tsx | 32 -- frontend/src/adminComponents/Header/styled.ts | 33 -- .../src/adminComponents/Sidebar/index.tsx | 53 -- .../src/adminComponents/Sidebar/styled.ts | 51 -- frontend/src/adminConstants.ts | 61 --- .../(auth)/components/FindFooter/index.tsx | 18 - .../src/app/(client)/(auth)/find-id/page.tsx | 70 ++- .../(client)/(auth)/find-password/page.tsx | 43 +- .../components/InputUserInfo/index.tsx | 81 --- .../src/app/(client)/(auth)/sign-in/page.tsx | 46 +- .../src/app/(client)/(auth)/sign-in/styled.ts | 59 --- .../src/app/(client)/(auth)/sign-out/page.tsx | 22 +- .../components/EmailTextInput/index.tsx | 50 -- .../components/SignUpSecondPage/index.tsx | 133 ----- .../src/app/(client)/(auth)/sign-up/page.tsx | 24 +- frontend/src/app/(client)/(auth)/styled.ts | 69 --- .../vote/components/ReadmeViewer/index.tsx | 15 - .../src/app/(client)/(withSidebar)/layout.tsx | 15 - .../(client)/(withSidebar)/milestone/page.tsx | 55 -- .../(withSidebar)/milestone/styled.ts | 63 --- .../components/MilestoneRowBarTable/index.tsx | 30 -- .../StudentInfoLabel/index.tsx | 14 - .../components/MilestoneDetail/styled.ts | 62 --- .../MilestoneHistoryTable/styled.ts | 31 -- .../components/MilestoneOverview/styled.ts | 10 - .../my-page/milestone/register/page.tsx | 22 - .../(withSidebar)/my-page/milestone/styled.ts | 18 - .../(client)/(withSidebar)/my-page/page.tsx | 18 - .../src/app/(client)/(withSidebar)/styled.ts | 33 -- .../components/Announcement/index.tsx | 33 -- .../components/Announcement/styled.ts | 35 -- .../components/ExternalLink/index.tsx | 23 - .../components/ExternalLink/styled.ts | 44 -- .../(client)/components/GoPageIcon/index.tsx | 28 - .../(client)/components/Milestone/styled.ts | 11 - .../(client)/components/PageTitle/index.tsx | 27 - .../app/(client)/components/PnuLink/styled.ts | 34 -- .../app/(client)/components/Sidebar/index.tsx | 82 --- .../app/(client)/components/Sidebar/styled.ts | 120 ----- .../SignIn/components/InputUserInfo/index.tsx | 73 --- .../SignIn/components/InputUserInfo/styled.ts | 46 -- .../app/(client)/components/SignIn/index.tsx | 21 - .../app/(client)/components/SignIn/styled.ts | 47 -- .../components/TeamBuildings/index.tsx | 42 -- .../components/TeamBuildings/styled.ts | 56 -- .../src/app/(client)/components/styled.ts | 25 - .../HackathonInformationTypeButtons/index.tsx | 0 .../hackathon/[slug]/layout.tsx | 4 +- .../hackathon/[slug]/page.tsx | 2 +- .../hackathon/[slug]/prize/page.tsx | 0 .../HackathonTeamCreateModal/index.tsx | 8 +- .../HackathonTeamReadModal/index.tsx | 4 +- .../vote/components/ReadmeViewer/index.tsx | 25 + .../TeamCreateInputSection/index.tsx | 0 .../vote/components/TeamMemberInput/index.tsx | 4 +- .../hackathon/[slug]/vote/page.tsx | 2 +- .../src/app/(client)/hackathon/layout.tsx | 5 + .../{(withSidebar) => }/hackathon/page.tsx | 6 +- .../hackathon/sw-contest/page.tsx | 4 +- frontend/src/app/(client)/layout-styled.ts | 19 - frontend/src/app/(client)/layout.tsx | 25 +- .../src/app/(client)/milestone/layout.tsx | 5 + frontend/src/app/(client)/milestone/page.tsx | 58 +++ .../edit => my-page/info-edit}/page.tsx | 10 +- frontend/src/app/(client)/my-page/layout.tsx | 5 + .../(client)/my-page/milestone-list/page.tsx | 23 + .../milestone-register}/page.tsx | 32 +- .../my-page/milestone/page.tsx | 40 +- frontend/src/app/(client)/my-page/page.tsx | 19 + frontend/src/app/(client)/page.tsx | 51 +- frontend/src/app/(client)/styled.ts | 50 -- .../src/app/(client)/team-building/layout.tsx | 5 + .../team-building/page.tsx | 10 +- .../src/app/admin/contest/create/page.tsx | 6 +- .../src/app/admin/contest/{list => }/page.tsx | 6 +- .../src/app/admin/faculty/{list => }/page.tsx | 26 +- .../src/app/admin/faculty/register/page.tsx | 30 +- frontend/src/app/admin/layout.tsx | 34 +- .../list/components/MemberTable/index.tsx | 41 -- frontend/src/app/admin/member/page.tsx | 8 - .../[slug]/components/FilePreview/index.tsx | 0 .../index.tsx | 6 +- .../milestone/{list => }/[slug]/not-found.tsx | 2 +- .../milestone/{list => }/[slug]/page.tsx | 18 +- .../MilestoneHistoryTable/index.tsx | 142 ------ .../app/admin/milestone/{list => }/page.tsx | 23 +- .../src/app/admin/milestone/rank/page.tsx | 87 ++-- .../src/app/admin/milestone/register/page.tsx | 26 +- .../admin/{member/list => student}/page.tsx | 23 +- frontend/src/app/admin/styled.ts | 64 --- frontend/src/app/admin/team-building/page.tsx | 6 +- frontend/src/app/layout.tsx | 3 +- frontend/src/components/Footer/index.tsx | 18 - frontend/src/components/Footer/styled.ts | 54 -- frontend/src/components/GoPageIcon/index.tsx | 28 - .../Header/HeaderAccordion/index.tsx | 18 - .../Header/HeaderAccordion/styled.ts | 51 -- .../src/components/Header/Sidebar/index.tsx | 64 --- .../src/components/Header/Sidebar/styled.ts | 63 --- frontend/src/components/Header/index.tsx | 108 ---- frontend/src/components/Header/styled.ts | 52 -- frontend/src/components/IconButton/index.tsx | 42 -- frontend/src/components/IconButton/styled.ts | 19 - .../src/components/MilestoneChart/index.tsx | 39 -- .../src/components/MilestoneChart/styled.ts | 93 ---- .../MilestonePeriodSearchForm/index.tsx | 40 -- .../MilestonePeriodSearchForm/styled.ts | 28 - .../src/components/MilestoneTable/index.tsx | 37 -- .../src/components/MilestoneTable/styled.ts | 50 -- frontend/src/components/SubTitle/index.tsx | 20 - frontend/src/components/TabButton/index.tsx | 29 -- .../src/components/TeamBuilding/index.tsx | 51 -- .../src/components/TeamBuilding/styled.ts | 87 ---- frontend/src/components/Title/index.tsx | 13 - frontend/src/components/common/IconButton.tsx | 30 ++ .../src/components/common/PageSubTitle.tsx | 20 + frontend/src/components/common/PageTitle.tsx | 13 + .../common/Pagination.tsx} | 11 +- .../src/components/common/PeriodSearchBox.tsx | 39 ++ .../common/admin/AdminPagination.tsx} | 22 +- .../admin/AdminSearchBox.tsx} | 20 +- .../formik/DatePicker.tsx} | 6 +- .../index.tsx => common/formik/Dropdown.tsx} | 3 - .../common/formik/DropdownDdang.tsx} | 2 - .../formik/EmailTextInput.tsx} | 2 +- .../formik/FileUploader.tsx} | 0 .../formik/ImageUploader.tsx} | 0 .../index.tsx => common/formik/TextInput.tsx} | 20 +- .../common/formik/TextInputDdang.tsx} | 0 .../src/components/layout/AdminFooter.tsx | 7 + .../src/components/layout/AdminHeader.tsx | 77 +++ .../src/components/layout/AdminSidebar.tsx | 60 +++ frontend/src/components/layout/Footer.tsx | 29 ++ frontend/src/components/layout/Header.tsx | 214 ++++++++ frontend/src/components/layout/Sidebar.tsx | 57 +++ .../src/components/layout/SidebarLayout.tsx | 12 + .../ui/admin/faculty/FacultyMemberTable.tsx} | 19 +- .../AdminMilestoneDownloadButton.tsx} | 8 +- .../milestone/AdminMilestoneFilePreview.tsx | 55 ++ .../AdminMilestoneStatusChangeButton.tsx | 105 ++++ .../admin/milestone/AdminMilestoneTable.tsx | 111 ++++ .../ui/admin/student/StudentMemberTable.tsx | 40 ++ .../components/ui/auth/AuthFindPageFooter.tsx | 18 + .../ui/auth/AuthFindPageTabButton.tsx | 28 + .../ui/auth/AuthFindPasswordForm.tsx} | 14 +- .../src/components/ui/auth/AuthSignInForm.tsx | 93 ++++ .../ui/auth/AuthSignUpFirst.tsx} | 18 +- .../ui/auth/AuthSignUpMajorDropdown.tsx} | 13 +- .../components/ui/auth/AuthSignUpSecond.tsx | 133 +++++ .../hackathon/MarkdownViewer.css} | 0 .../hackathon/MarkdownViewer.tsx} | 8 +- .../src/components/ui/home/GoPageIcon.tsx | 16 + .../components/ui/home/HomeAnnouncement.tsx | 40 ++ .../components/ui/home/HomeExternalLink.tsx | 28 + .../ui/home/HomeMilestone.tsx} | 70 ++- .../ui/home/HomePnuLink.tsx} | 39 +- .../src/components/ui/home/HomeSignIn.tsx | 104 ++++ .../components/ui/home/HomeTeamBuilding.tsx | 46 ++ .../ui/milestone/MilestoneAcceptedTable.tsx} | 21 +- .../milestone/MilestoneCategoryDropdown.tsx} | 12 +- .../ui/milestone/MilestoneCircleChart.tsx | 53 ++ .../ui/milestone/MilestoneDeleteButton.tsx} | 10 +- .../ui/milestone/MilestoneDetailTable.tsx | 34 ++ .../milestone/MilestoneGroupLabel.tsx} | 9 +- .../ui/milestone/MilestoneHistoryTable.tsx} | 62 +-- .../ui/milestone/MilestoneOverviewTable.tsx | 43 ++ .../ui/milestone/MilestoneStatusLabel.tsx} | 9 +- .../ui/my-page/MyPageMilestone.tsx} | 30 +- .../ui/my-page/MyPageMilestoneDetail.tsx} | 25 +- .../ui/my-page/MyPageMilestoneHistory.tsx} | 25 +- .../ui/my-page/MyPageMilestoneOverview.tsx} | 26 +- .../ui/my-page/MyPageStudentInfo.tsx} | 38 +- .../ui/team-building/TeamBuildingCard.tsx | 75 +++ frontend/src/constants.ts | 82 --- frontend/src/data/adminCategory.ts | 11 +- frontend/src/data/clientCategory.ts | 7 +- frontend/src/lib/hooks/useAdminApi.ts | 1 - frontend/src/lib/hooks/useApi.ts | 6 +- frontend/src/lib/hooks/useAxios.ts | 2 - frontend/src/lib/utils/utils.tsx | 3 - frontend/src/middleware.ts | 2 +- frontend/src/store/auth.slice.ts | 1 - frontend/src/store/index.ts | 1 - frontend/src/store/store.tsx | 2 - .../theme/StyledComponentsRegistry/index.tsx | 3 - frontend/src/types/error.ts | 2 - frontend/tailwind.config.js | 19 + 197 files changed, 2550 insertions(+), 4331 deletions(-) delete mode 100644 frontend/.babelrc delete mode 100644 frontend/babel.config.json delete mode 100644 frontend/src/adminComponents/Footer/index.tsx delete mode 100644 frontend/src/adminComponents/Header/components/Navigator/index.tsx delete mode 100644 frontend/src/adminComponents/Header/components/Navigator/styled.ts delete mode 100644 frontend/src/adminComponents/Header/components/UserName/index.tsx delete mode 100644 frontend/src/adminComponents/Header/index.tsx delete mode 100644 frontend/src/adminComponents/Header/styled.ts delete mode 100644 frontend/src/adminComponents/Sidebar/index.tsx delete mode 100644 frontend/src/adminComponents/Sidebar/styled.ts delete mode 100644 frontend/src/adminConstants.ts delete mode 100644 frontend/src/app/(client)/(auth)/components/FindFooter/index.tsx delete mode 100644 frontend/src/app/(client)/(auth)/sign-in/components/InputUserInfo/index.tsx delete mode 100644 frontend/src/app/(client)/(auth)/sign-in/styled.ts delete mode 100644 frontend/src/app/(client)/(auth)/sign-up/components/SignUpFirstPage/components/EmailTextInput/index.tsx delete mode 100644 frontend/src/app/(client)/(auth)/sign-up/components/SignUpSecondPage/index.tsx delete mode 100644 frontend/src/app/(client)/(auth)/styled.ts delete mode 100644 frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx delete mode 100644 frontend/src/app/(client)/(withSidebar)/layout.tsx delete mode 100644 frontend/src/app/(client)/(withSidebar)/milestone/page.tsx delete mode 100644 frontend/src/app/(client)/(withSidebar)/milestone/styled.ts delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneRowBarTable/index.tsx delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/components/StudentInfoSection/StudentInfoLabel/index.tsx delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/styled.ts delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/styled.ts delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/styled.ts delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/page.tsx delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/milestone/styled.ts delete mode 100644 frontend/src/app/(client)/(withSidebar)/my-page/page.tsx delete mode 100644 frontend/src/app/(client)/(withSidebar)/styled.ts delete mode 100644 frontend/src/app/(client)/components/Announcement/index.tsx delete mode 100644 frontend/src/app/(client)/components/Announcement/styled.ts delete mode 100644 frontend/src/app/(client)/components/ExternalLink/index.tsx delete mode 100644 frontend/src/app/(client)/components/ExternalLink/styled.ts delete mode 100644 frontend/src/app/(client)/components/GoPageIcon/index.tsx delete mode 100644 frontend/src/app/(client)/components/Milestone/styled.ts delete mode 100644 frontend/src/app/(client)/components/PageTitle/index.tsx delete mode 100644 frontend/src/app/(client)/components/PnuLink/styled.ts delete mode 100644 frontend/src/app/(client)/components/Sidebar/index.tsx delete mode 100644 frontend/src/app/(client)/components/Sidebar/styled.ts delete mode 100644 frontend/src/app/(client)/components/SignIn/components/InputUserInfo/index.tsx delete mode 100644 frontend/src/app/(client)/components/SignIn/components/InputUserInfo/styled.ts delete mode 100644 frontend/src/app/(client)/components/SignIn/index.tsx delete mode 100644 frontend/src/app/(client)/components/SignIn/styled.ts delete mode 100644 frontend/src/app/(client)/components/TeamBuildings/index.tsx delete mode 100644 frontend/src/app/(client)/components/TeamBuildings/styled.ts delete mode 100644 frontend/src/app/(client)/components/styled.ts rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/components/HackathonInformationTypeButtons/index.tsx (100%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/layout.tsx (85%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/page.tsx (93%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/prize/page.tsx (100%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/vote/components/HackathonTeamCreateModal/index.tsx (98%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/vote/components/HackathonTeamReadModal/index.tsx (97%) create mode 100644 frontend/src/app/(client)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/vote/components/TeamCreateInputSection/index.tsx (100%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/vote/components/TeamMemberInput/index.tsx (94%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/[slug]/vote/page.tsx (97%) create mode 100644 frontend/src/app/(client)/hackathon/layout.tsx rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/page.tsx (97%) rename frontend/src/app/(client)/{(withSidebar) => }/hackathon/sw-contest/page.tsx (61%) delete mode 100644 frontend/src/app/(client)/layout-styled.ts create mode 100644 frontend/src/app/(client)/milestone/layout.tsx create mode 100644 frontend/src/app/(client)/milestone/page.tsx rename frontend/src/app/(client)/{(withSidebar)/my-page/edit => my-page/info-edit}/page.tsx (60%) create mode 100644 frontend/src/app/(client)/my-page/layout.tsx create mode 100644 frontend/src/app/(client)/my-page/milestone-list/page.tsx rename frontend/src/app/(client)/{(withSidebar)/my-page/milestone/register/write => my-page/milestone-register}/page.tsx (90%) rename frontend/src/app/(client)/{(withSidebar) => }/my-page/milestone/page.tsx (50%) create mode 100644 frontend/src/app/(client)/my-page/page.tsx delete mode 100644 frontend/src/app/(client)/styled.ts create mode 100644 frontend/src/app/(client)/team-building/layout.tsx rename frontend/src/app/(client)/{(withSidebar) => }/team-building/page.tsx (60%) rename frontend/src/app/admin/contest/{list => }/page.tsx (79%) rename frontend/src/app/admin/faculty/{list => }/page.tsx (73%) delete mode 100644 frontend/src/app/admin/member/list/components/MemberTable/index.tsx delete mode 100644 frontend/src/app/admin/member/page.tsx rename frontend/src/app/admin/milestone/{list => }/[slug]/components/FilePreview/index.tsx (100%) rename frontend/src/app/admin/milestone/{list => }/[slug]/components/MilestoneHistoryStatusChangeButton/index.tsx (94%) rename frontend/src/app/admin/milestone/{list => }/[slug]/not-found.tsx (93%) rename frontend/src/app/admin/milestone/{list => }/[slug]/page.tsx (90%) delete mode 100644 frontend/src/app/admin/milestone/list/components/MilestoneHistoryTable/index.tsx rename frontend/src/app/admin/milestone/{list => }/page.tsx (74%) rename frontend/src/app/admin/{member/list => student}/page.tsx (66%) delete mode 100644 frontend/src/app/admin/styled.ts delete mode 100644 frontend/src/components/Footer/index.tsx delete mode 100644 frontend/src/components/Footer/styled.ts delete mode 100644 frontend/src/components/GoPageIcon/index.tsx delete mode 100644 frontend/src/components/Header/HeaderAccordion/index.tsx delete mode 100644 frontend/src/components/Header/HeaderAccordion/styled.ts delete mode 100644 frontend/src/components/Header/Sidebar/index.tsx delete mode 100644 frontend/src/components/Header/Sidebar/styled.ts delete mode 100644 frontend/src/components/Header/index.tsx delete mode 100644 frontend/src/components/Header/styled.ts delete mode 100644 frontend/src/components/IconButton/index.tsx delete mode 100644 frontend/src/components/IconButton/styled.ts delete mode 100644 frontend/src/components/MilestoneChart/index.tsx delete mode 100644 frontend/src/components/MilestoneChart/styled.ts delete mode 100644 frontend/src/components/MilestonePeriodSearchForm/index.tsx delete mode 100644 frontend/src/components/MilestonePeriodSearchForm/styled.ts delete mode 100644 frontend/src/components/MilestoneTable/index.tsx delete mode 100644 frontend/src/components/MilestoneTable/styled.ts delete mode 100644 frontend/src/components/SubTitle/index.tsx delete mode 100644 frontend/src/components/TabButton/index.tsx delete mode 100644 frontend/src/components/TeamBuilding/index.tsx delete mode 100644 frontend/src/components/TeamBuilding/styled.ts delete mode 100644 frontend/src/components/Title/index.tsx create mode 100644 frontend/src/components/common/IconButton.tsx create mode 100644 frontend/src/components/common/PageSubTitle.tsx create mode 100644 frontend/src/components/common/PageTitle.tsx rename frontend/src/{app/(client)/components/Pagination/index.tsx => components/common/Pagination.tsx} (83%) create mode 100644 frontend/src/components/common/PeriodSearchBox.tsx rename frontend/src/{adminComponents/Pagination/index.tsx => components/common/admin/AdminPagination.tsx} (85%) rename frontend/src/components/{SearchBox/index.tsx => common/admin/AdminSearchBox.tsx} (78%) rename frontend/src/components/{Formik/DatePicker/index.tsx => common/formik/DatePicker.tsx} (92%) rename frontend/src/components/{Formik/Dropdown/index.tsx => common/formik/Dropdown.tsx} (95%) rename frontend/src/{app/(client)/components/Formik/Dropdown/index.tsx => components/common/formik/DropdownDdang.tsx} (97%) rename frontend/src/components/{Formik/EmailTextInput/index.tsx => common/formik/EmailTextInput.tsx} (97%) rename frontend/src/components/{Formik/FileUploader/index.tsx => common/formik/FileUploader.tsx} (100%) rename frontend/src/components/{Formik/ImageUploader/index.tsx => common/formik/ImageUploader.tsx} (100%) rename frontend/src/components/{Formik/TextInput/index.tsx => common/formik/TextInput.tsx} (61%) rename frontend/src/{app/(client)/components/Formik/TextInput/index.tsx => components/common/formik/TextInputDdang.tsx} (100%) create mode 100644 frontend/src/components/layout/AdminFooter.tsx create mode 100644 frontend/src/components/layout/AdminHeader.tsx create mode 100644 frontend/src/components/layout/AdminSidebar.tsx create mode 100644 frontend/src/components/layout/Footer.tsx create mode 100644 frontend/src/components/layout/Header.tsx create mode 100644 frontend/src/components/layout/Sidebar.tsx create mode 100644 frontend/src/components/layout/SidebarLayout.tsx rename frontend/src/{app/admin/faculty/list/components/MemberTable/index.tsx => components/ui/admin/faculty/FacultyMemberTable.tsx} (90%) rename frontend/src/{app/admin/milestone/list/components/MilestoneHistoryTable/MilestoneHistoryExcelFileDownloadButton.tsx/index.tsx => components/ui/admin/milestone/AdminMilestoneDownloadButton.tsx} (80%) create mode 100644 frontend/src/components/ui/admin/milestone/AdminMilestoneFilePreview.tsx create mode 100644 frontend/src/components/ui/admin/milestone/AdminMilestoneStatusChangeButton.tsx create mode 100644 frontend/src/components/ui/admin/milestone/AdminMilestoneTable.tsx create mode 100644 frontend/src/components/ui/admin/student/StudentMemberTable.tsx create mode 100644 frontend/src/components/ui/auth/AuthFindPageFooter.tsx create mode 100644 frontend/src/components/ui/auth/AuthFindPageTabButton.tsx rename frontend/src/{app/(client)/(auth)/find-password/components/FindForm/index.tsx => components/ui/auth/AuthFindPasswordForm.tsx} (92%) create mode 100644 frontend/src/components/ui/auth/AuthSignInForm.tsx rename frontend/src/{app/(client)/(auth)/sign-up/components/SignUpFirstPage/index.tsx => components/ui/auth/AuthSignUpFirst.tsx} (94%) rename frontend/src/{app/(client)/(auth)/sign-up/components/SignUpSecondPage/components/MajorDropdown/index.tsx => components/ui/auth/AuthSignUpMajorDropdown.tsx} (80%) create mode 100644 frontend/src/components/ui/auth/AuthSignUpSecond.tsx rename frontend/src/components/{MarkdownViewer/markdown.css => ui/hackathon/MarkdownViewer.css} (100%) rename frontend/src/components/{MarkdownViewer/index.tsx => ui/hackathon/MarkdownViewer.tsx} (94%) create mode 100644 frontend/src/components/ui/home/GoPageIcon.tsx create mode 100644 frontend/src/components/ui/home/HomeAnnouncement.tsx create mode 100644 frontend/src/components/ui/home/HomeExternalLink.tsx rename frontend/src/{app/(client)/components/Milestone/index.tsx => components/ui/home/HomeMilestone.tsx} (55%) rename frontend/src/{app/(client)/components/PnuLink/index.tsx => components/ui/home/HomePnuLink.tsx} (61%) create mode 100644 frontend/src/components/ui/home/HomeSignIn.tsx create mode 100644 frontend/src/components/ui/home/HomeTeamBuilding.tsx rename frontend/src/{app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx => components/ui/milestone/MilestoneAcceptedTable.tsx} (86%) rename frontend/src/{app/(client)/(withSidebar)/my-page/milestone/register/write/components/MilestoneDropdown/index.tsx => components/ui/milestone/MilestoneCategoryDropdown.tsx} (88%) create mode 100644 frontend/src/components/ui/milestone/MilestoneCircleChart.tsx rename frontend/src/{app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryDeleteButton/index.tsx => components/ui/milestone/MilestoneDeleteButton.tsx} (76%) create mode 100644 frontend/src/components/ui/milestone/MilestoneDetailTable.tsx rename frontend/src/components/{MilestoneGroupLabel/index.tsx => ui/milestone/MilestoneGroupLabel.tsx} (86%) rename frontend/src/{app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx => components/ui/milestone/MilestoneHistoryTable.tsx} (53%) create mode 100644 frontend/src/components/ui/milestone/MilestoneOverviewTable.tsx rename frontend/src/{app/(client)/(withSidebar)/my-page/components/MilestoneHistoryStatusLabel/index.tsx => components/ui/milestone/MilestoneStatusLabel.tsx} (82%) rename frontend/src/{app/(client)/(withSidebar)/my-page/components/MilestoneSection/index.tsx => components/ui/my-page/MyPageMilestone.tsx} (75%) rename frontend/src/{app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx => components/ui/my-page/MyPageMilestoneDetail.tsx} (58%) rename frontend/src/{app/(client)/(withSidebar)/my-page/components/MilestoneHistorySection/index.tsx => components/ui/my-page/MyPageMilestoneHistory.tsx} (65%) rename frontend/src/{app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx => components/ui/my-page/MyPageMilestoneOverview.tsx} (60%) rename frontend/src/{app/(client)/(withSidebar)/my-page/components/StudentInfoSection/index.tsx => components/ui/my-page/MyPageStudentInfo.tsx} (76%) create mode 100644 frontend/src/components/ui/team-building/TeamBuildingCard.tsx diff --git a/frontend/.babelrc b/frontend/.babelrc deleted file mode 100644 index 4543c7d8..00000000 --- a/frontend/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": ["next/babel","@babel/preset-env" ], - "plugins": [["styled-components", { "ssr": true }]] -} diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index f3b119b0..325be0ca 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,56 +1,4 @@ { - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint", "react", "tailwindcss"], - "extends": [ - "next/core-web-vitals", - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended", - "airbnb", - "airbnb/hooks", - "airbnb-typescript", - "plugin:import/typescript", - "plugin:import/recommended" - ], - "parserOptions": { - "project": "./tsconfig.json" - }, - "rules": { - "prettier/prettier": ["error", { "endOfLine": "auto" }], - "max-len": ["warn", { "code": 120 }], - "react/react-in-jsx-scope": "off", - "import/extensions": "off", - "react/function-component-definition": [ - "error", - { - "namedComponents": ["arrow-function"] - } - ], - "import/order": [ - "error", - { - "groups": ["builtin", "external", "internal", ["sibling", "parent", "index"], "type"], - "pathGroupsExcludedImportTypes": [], - "newlines-between": "always", - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ], - "object-curly-newline": "off", - "react/jsx-one-expression-per-line": "off", - "no-param-reassign": "off", - "import/prefer-default-export": "off", - "react/jsx-props-no-spreading": "off", - "react/require-default-props": "off" - }, - "settings": { - "import/resolver": { - "node": {}, - "typescript": { - "directory": "./src" - } - }, - "import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx"] } - } + "extends": ["next/core-web-vitals"], + "rules": { "react-hooks/exhaustive-deps": "off", "@next/next/no-img-element": "off" } } diff --git a/frontend/babel.config.json b/frontend/babel.config.json deleted file mode 100644 index bc15f0b6..00000000 --- a/frontend/babel.config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-private-methods"], - "overrides": [ - { - "include": "./node_modules/linkifyjs", - "presets": ["@babel/preset-env"] - } - ] -} diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index e2b7bfd6..e6bbfe75 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -1,11 +1,19 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, - compiler: { - styledComponents: true, - }, images: { - domains: ['localhost', 'swcss.pusan.ac.kr'], + remotePatterns: [ + { + protocol: 'http', + hostname: 'localhost', + pathname: '**', + }, + { + protocol: 'https', + hostname: 'swcss.pusan.ac.kr', + pathname: '**', + }, + ], }, }; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 546fc20c..5bd4e384 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -65,18 +65,12 @@ "@types/react-cookies": "^0.1.3", "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.13", + "@typescript-eslint/parser": "^8.16.0", "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^17.1.0", + "eslint": "^8.57.1", "eslint-config-next": "14.2.3", "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.39", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", @@ -1911,22 +1905,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -2314,6 +2308,12 @@ "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", "integrity": "sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==" }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "node_modules/@rushstack/eslint-patch": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", @@ -2982,13 +2982,6 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "peer": true - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -3095,13 +3088,6 @@ "@types/react": "*" } }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true, - "peer": true - }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -3138,76 +3124,27 @@ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", - "dev": true, - "peer": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -3216,57 +3153,29 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", - "dev": true, - "peer": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -3274,22 +3183,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -3313,60 +3222,33 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", "dev": true, - "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@typescript-eslint/types": "8.16.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, "node_modules/@ungap/structured-clone": { @@ -4034,12 +3916,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4574,16 +4450,17 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -4628,80 +4505,111 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-airbnb": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", - "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "node_modules/eslint-config-next": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.3.tgz", + "integrity": "sha512-ZkNztm3Q7hjqvB1rRlOX8P9E/cXRL9ajRcs8jufEtwMfTVYRqnmtnaSu57QqHyBlovMuiB8LEzfLBkh5RYV6Fg==", "dev": true, "dependencies": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" - }, - "engines": { - "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + "@next/eslint-plugin-next": "14.2.3", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0" + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/eslint-config-airbnb-typescript": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", - "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", - "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3" + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-config-next": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.3.tgz", - "integrity": "sha512-ZkNztm3Q7hjqvB1rRlOX8P9E/cXRL9ajRcs8jufEtwMfTVYRqnmtnaSu57QqHyBlovMuiB8LEzfLBkh5RYV6Fg==", + "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "14.2.3", - "@rushstack/eslint-patch": "^1.3.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, - "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", - "typescript": ">=3.3.1" + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { "typescript": { @@ -4709,6 +4617,50 @@ } } }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-config-next/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -4767,9 +4719,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -4793,34 +4745,36 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { @@ -5972,9 +5926,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { "hasown": "^2.0.2" @@ -6628,9 +6582,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -8739,9 +8693,9 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, "engines": { "node": ">=16" diff --git a/frontend/package.json b/frontend/package.json index ded78e62..9368489a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -66,18 +66,12 @@ "@types/react-cookies": "^0.1.3", "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.13", + "@typescript-eslint/parser": "^8.16.0", "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^17.1.0", + "eslint": "^8.57.1", "eslint-config-next": "14.2.3", "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.39", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", diff --git a/frontend/src/adminComponents/Footer/index.tsx b/frontend/src/adminComponents/Footer/index.tsx deleted file mode 100644 index ed5c44f6..00000000 --- a/frontend/src/adminComponents/Footer/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { COLOR, FONT_STYLE } from '@/adminConstants'; - -const Footer = () => ( -
- copyright ⓒ KOREA LEARNING CONSULTING CENTER. All Right Reserved -
-); - -export default Footer; diff --git a/frontend/src/adminComponents/Header/components/Navigator/index.tsx b/frontend/src/adminComponents/Header/components/Navigator/index.tsx deleted file mode 100644 index 20fb99af..00000000 --- a/frontend/src/adminComponents/Header/components/Navigator/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client'; - -import { usePathname } from 'next/navigation'; - -import { adminCategories } from '@/data/adminCategory'; - -import { HeaderLinker, HeaderLinkerPoint } from './styled'; - -const Navigator = () => { - const pathname = usePathname(); - return ( - <> - {adminCategories.map((item) => { - if (pathname.includes(item.url)) { - return ( - - {item.title} - - ); - } - return ( - - {item.title} - - ); - })} - - ); -}; - -export default Navigator; diff --git a/frontend/src/adminComponents/Header/components/Navigator/styled.ts b/frontend/src/adminComponents/Header/components/Navigator/styled.ts deleted file mode 100644 index a6b10756..00000000 --- a/frontend/src/adminComponents/Header/components/Navigator/styled.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Link from 'next/link'; -import styled from 'styled-components'; - -import { ADMIN_HEADER_HEIGHT, COLOR, FONT_STYLE } from '@/adminConstants'; - -export const HeaderLinker = styled(Link)` - position: relative; - display: flex; - align-items: center; - height: ${ADMIN_HEADER_HEIGHT}; - padding: 0 20px; - color: ${COLOR.comment}; - font: ${FONT_STYLE.base.normal}; - &:after { - content: ''; - background: ${COLOR.border}; - position: absolute; - top: 15%; - left: 0; - height: 70%; - width: 1px; - } -`; - -export const HeaderLinkerPoint = styled(HeaderLinker)` - background-color: ${COLOR.primary.main}; - color: ${COLOR.white}; - &:after { - height: 0; - width: 0; - } -`; diff --git a/frontend/src/adminComponents/Header/components/UserName/index.tsx b/frontend/src/adminComponents/Header/components/UserName/index.tsx deleted file mode 100644 index 0897c0a1..00000000 --- a/frontend/src/adminComponents/Header/components/UserName/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import { COLOR, FONT_STYLE } from '@/constants'; -import { useAppSelector } from '@/lib/hooks/redux'; - -const UserName = () => { - const auth = useAppSelector((state) => state.auth).value; - - return ( - - 반갑습니다! {auth.name}님 - - ); -}; - -export default UserName; diff --git a/frontend/src/adminComponents/Header/index.tsx b/frontend/src/adminComponents/Header/index.tsx deleted file mode 100644 index a693aa0d..00000000 --- a/frontend/src/adminComponents/Header/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import Image from 'next/image'; - -import { AdminBlackLink, AdminGrayLink } from '@/app/admin/styled'; -import { BORDER_RADIUS, FONT_STYLE } from '@/constants'; - -import Navigator from './components/Navigator'; -import UserName from './components/UserName'; -import { HeaderLayout, HeaderWrapper, LogoLink } from './styled'; - -const Header = () => ( - - -
- - SW_logo - - -
-
- - - 사이트 메인으로 - - - 로그아웃 - -
-
-
-); - -export default Header; diff --git a/frontend/src/adminComponents/Header/styled.ts b/frontend/src/adminComponents/Header/styled.ts deleted file mode 100644 index 85b2046f..00000000 --- a/frontend/src/adminComponents/Header/styled.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { ADMIN_SIDEBAR_WIDTH, COLOR } from '@/adminConstants'; - -export const HeaderWrapper = styled.div` - position: fixed; - width: 100vw; - min-width: 1200px; - background: ${COLOR.white}; - border-bottom: 2px solid ${COLOR.primary.main}; - z-index: 1; -`; - -export const HeaderLayout = styled.div` - width: 100%; - height: 100%; - padding-right: 8px; - display: flex; - justify-content: space-between; - align-items: center; -`; - -export const LogoLink = styled(Link)` - width: ${ADMIN_SIDEBAR_WIDTH}; - margin: auto; - padding-left: 10px; - display: flex; - align-items: center; - justify-content: center; -`; diff --git a/frontend/src/adminComponents/Sidebar/index.tsx b/frontend/src/adminComponents/Sidebar/index.tsx deleted file mode 100644 index e0d0d739..00000000 --- a/frontend/src/adminComponents/Sidebar/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client'; - -import { usePathname } from 'next/navigation'; -import { useEffect, useState } from 'react'; - -import { adminCategories } from '@/data/adminCategory'; - -import * as S from './styled'; - -const Sidebar = () => { - const [currTab, setCurrTab] = useState(''); - const pathname = usePathname(); - - const handleTabClick = (tab: string) => { - setCurrTab(tab); - }; - - useEffect(() => { - setCurrTab(pathname); - }, [pathname]); - - return ( - - - {adminCategories.map((item) => ( - - {currTab.includes(item.url) ? ( - handleTabClick(item.url)}> - {item.title} - - ) : ( - handleTabClick(item.url)}>{item.title} - )} - {item.sub.map((subItem) => ( - - {subItem.title} - - ))} - - ))} - - - ); -}; - -export default Sidebar; diff --git a/frontend/src/adminComponents/Sidebar/styled.ts b/frontend/src/adminComponents/Sidebar/styled.ts deleted file mode 100644 index c40d4435..00000000 --- a/frontend/src/adminComponents/Sidebar/styled.ts +++ /dev/null @@ -1,51 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { ADMIN_HEADER_HEIGHT, ADMIN_SIDEBAR_WIDTH, COLOR } from '@/adminConstants'; - -export const SidebarWrapper = styled.div` - position: fixed; - width: ${ADMIN_SIDEBAR_WIDTH}; - height: calc(100vh - ${ADMIN_HEADER_HEIGHT}); - margin-top: calc(${ADMIN_HEADER_HEIGHT} + 2px); - box-shadow: 0px 0px 7px 0px rgba(0, 0, 0, 0.3); - background-color: ${COLOR.white}; - z-index: 1; -`; - -export const SidebarLayout = styled.div` - display: flex; - width: 100%; - height: 100%; - flex-direction: column; -`; - -export const SidebarContentLayout = styled.div` - overflow: hidden; - transition: max-height 0.6s ease-in-out; -`; - -export const SidebarContentTitle = styled.div` - display: block; - max-height: 42px; - padding: 10px 20px; - border-bottom: 1px solid ${COLOR.border}; - cursor: default; - overflow: hidden; -`; - -export const SidebarContentPointTitle = styled(SidebarContentTitle)` - background-color: ${COLOR.secondary.main}; - color: ${COLOR.white}; -`; - -export const SidebarContentSubTitle = styled(Link)` - display: block; - padding: 10px 30px; - border-bottom: 1px solid ${COLOR.border}; - background-color: ${COLOR.background.light}; - cursor: default; - color: ${COLOR.comment}; -`; diff --git a/frontend/src/adminConstants.ts b/frontend/src/adminConstants.ts deleted file mode 100644 index 2192f56e..00000000 --- a/frontend/src/adminConstants.ts +++ /dev/null @@ -1,61 +0,0 @@ -export const COLOR = { - white: '#FFFFFF', - black_text: '#333333', - comment: '#4E5963', - border: '#DEE2E6', - background: { - light: '#F0F0F0', - base: '#B0B0B0', - }, - primary: { - light: '#3B80C7', - main: '#095DB3', - dark: '#053566', - }, - secondary: { - light: '#F0F0F0', - main: '#7F7F7F', - dark: '#3F3F3F', - }, - semantic: { - error: 'B30818', - }, -}; - -export const FONT_STYLE = { - xs: { - normal: '400 12px "Noto Sans KR", sans-serif', - semibold: '600 12px "Noto Sans KR", sans-serif', - bold: '700 12px "Noto Sans KR", sans-serif', - }, - sm: { - normal: '400 14px "Noto Sans KR", sans-serif', - semibold: '600 14px "Noto Sans KR", sans-serif', - bold: '700 14px "Noto Sans KR", sans-serif', - }, - base: { - normal: '400 16px "Noto Sans KR", sans-serif', - semibold: '600 16px "Noto Sans KR", sans-serif', - bold: '700 16px "Noto Sans KR", sans-serif', - }, - lg: { - normal: '400 20px "Noto Sans KR", sans-serif', - semibold: '600 20px "Noto Sans KR", sans-serif', - bold: '700 20px "Noto Sans KR", sans-serif', - }, - xl: { - normal: '400 32px "Noto Sans KR", sans-serif', - semibold: '600 32px "Noto Sans KR", sans-serif', - bold: '700 32px "Noto Sans KR", sans-serif', - }, -}; - -export const BORDER_RADIUS = { - sm: '10px', - md: '20px', - lg: '30px', - full: '100%', -}; - -export const ADMIN_HEADER_HEIGHT = '55px'; -export const ADMIN_SIDEBAR_WIDTH = '220px'; diff --git a/frontend/src/app/(client)/(auth)/components/FindFooter/index.tsx b/frontend/src/app/(client)/(auth)/components/FindFooter/index.tsx deleted file mode 100644 index d8c76693..00000000 --- a/frontend/src/app/(client)/(auth)/components/FindFooter/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -const FindFooter = () => ( -
-
- 로그인 화면{' '} - - 돌아가기 - -
-
- 처음오셨나요?{' '} - - 회원가입 - -
-
-); - -export default FindFooter; diff --git a/frontend/src/app/(client)/(auth)/find-id/page.tsx b/frontend/src/app/(client)/(auth)/find-id/page.tsx index 9a430e64..25d8280b 100644 --- a/frontend/src/app/(client)/(auth)/find-id/page.tsx +++ b/frontend/src/app/(client)/(auth)/find-id/page.tsx @@ -1,41 +1,35 @@ -import PageTitle from '@/app/(client)/components/PageTitle'; -import TabButton from '@/components/TabButton'; +import PageTitle from '@/components/common/PageTitle'; +import AuthFindPageTabButton from '@/components/ui/auth/AuthFindPageTabButton'; +import AuthFindPageFooter from '@/components/ui/auth/AuthFindPageFooter'; -import FindFooter from '../components/FindFooter'; - -const findTabs = [ - { name: '아이디 찾기', url: '/find-id' }, - { name: '비밀번호 찾기', url: '/find-password' }, -]; - -const Page = () => ( -
-
- - -
- - 아이디는 부산대 메일이며, -
학생지원시스템에서 확인하실 수 있습니다. -
- - 학생지원시스템 바로가기 - - - ※ 부산대 메일이 없다면{' '} - - 여기 +export default function FindIdPage() { + return ( +
+
+ + +
+ + 아이디는 부산대 메일이며, +
학생지원시스템에서 확인하실 수 있습니다. +
+
+ 학생지원시스템 바로가기 - 에서 신청할 수 있습니다. - -
- + + ※ 부산대 메일이 없다면{' '} + + 여기 + + 에서 신청할 수 있습니다. + +
+ +
-
-
-); - -export default Page; + + ); +} diff --git a/frontend/src/app/(client)/(auth)/find-password/page.tsx b/frontend/src/app/(client)/(auth)/find-password/page.tsx index ad3ca869..df26c628 100644 --- a/frontend/src/app/(client)/(auth)/find-password/page.tsx +++ b/frontend/src/app/(client)/(auth)/find-password/page.tsx @@ -1,28 +1,17 @@ -import PageTitle from '@/app/(client)/components/PageTitle'; -import TabButton from '@/components/TabButton'; +import PageTitle from '@/components/common/PageTitle'; +import AuthFindPageTabButton from '@/components/ui/auth/AuthFindPageTabButton'; +import AuthFindPageFooter from '@/components/ui/auth/AuthFindPageFooter'; +import AuthFindPasswordForm from '@/components/ui/auth/AuthFindPasswordForm'; -import FindForm from './components/FindForm'; -import FindFooter from '../components/FindFooter'; - -const findTabs = [ - { name: '아이디 찾기', url: '/find-id' }, - { name: '비밀번호 찾기', url: '/find-password' }, -]; - -const Page = () => ( -
-
- - - - -
-
-); - -export default Page; +export default function FindPasswordPage() { + return ( +
+
+ + + + +
+
+ ); +} diff --git a/frontend/src/app/(client)/(auth)/sign-in/components/InputUserInfo/index.tsx b/frontend/src/app/(client)/(auth)/sign-in/components/InputUserInfo/index.tsx deleted file mode 100644 index 15ab95c2..00000000 --- a/frontend/src/app/(client)/(auth)/sign-in/components/InputUserInfo/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; - -import { InputFixedText, Input, InputLabel, InputWrapper, SignButton } from '@/app/(client)/(auth)/styled'; -import { useAppDispatch, useAppSelector } from '@/lib/hooks/redux'; -import { signIn } from '@/store/auth.slice'; -import { useSignInMutation } from '@/lib/hooks/useApi'; -import { useState } from 'react'; -import { toast } from 'react-toastify'; - -const InputUserInfo = () => { - const [userInfo, setUserInfo] = useState({ - email: '', - password: '', - }); - - const router = useRouter(); - - const { mutate: signInMutation } = useSignInMutation(); - - const dispatch = useAppDispatch(); - const auth = useAppSelector((state) => state.auth).value; - - if (auth.isAuth) router.push('/'); - - const handleInputChange = (e: React.ChangeEvent) => { - setUserInfo((prev) => { - return { - ...prev, - [e.target.id]: e.target.value, - }; - }); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - signInMutation(userInfo, { - onSuccess(data, variables, context) { - dispatch( - signIn({ - id: data.member_id, - token: `Bearer ${data.token}`, - name: data.name, - email: data.email, - isModerator: data.is_moderator, - }), - ); - router.push('/'); - }, - onError(error, variables, context) { - toast.error(error.message); - }, - }); - }; - - return ( - <> -
- - 아이디(이메일) - - @pusan.ac.kr - - - 비밀번호 - - - 로그인 -
- - ); -}; - -export default InputUserInfo; diff --git a/frontend/src/app/(client)/(auth)/sign-in/page.tsx b/frontend/src/app/(client)/(auth)/sign-in/page.tsx index d9715153..ff0c249e 100644 --- a/frontend/src/app/(client)/(auth)/sign-in/page.tsx +++ b/frontend/src/app/(client)/(auth)/sign-in/page.tsx @@ -1,27 +1,25 @@ -import { FONT_STYLE } from '@/constants'; +import Link from 'next/link'; -import InputUserInfo from './components/InputUserInfo'; -import { Divisor, FindLink, SignInContentWrapper, SignInPageWrapper, SignUpLink, SuggestionComment } from './styled'; -import { Description, Title, TitleContent } from '../styled'; +import PageTitle from '@/components/common/PageTitle'; +import AuthSignInForm from '@/components/ui/auth/AuthSignInForm'; -const Page = () => ( - - - - 로그인 - PNU SW역량시스템 첫 사용시 회원가입이 필요합니다. - - - - - 아이디 / 비밀번호 찾기 - -
- 처음오셨나요? 회원가입 +export default function SignInPage() { + return ( +
+
+
+
- - - -); - -export default Page; + +
+
+ 아이디 / 비밀번호 찾기 +
+
+ 처음오셨나요? 회원가입 +
+
+
+
+ ); +} diff --git a/frontend/src/app/(client)/(auth)/sign-in/styled.ts b/frontend/src/app/(client)/(auth)/sign-in/styled.ts deleted file mode 100644 index f612909a..00000000 --- a/frontend/src/app/(client)/(auth)/sign-in/styled.ts +++ /dev/null @@ -1,59 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { SIGN_WIDTH, RESPONSIVE_WIDTH, FONT_STYLE, COLOR } from '@/constants'; - -export const SignInPageWrapper = styled.div` - max-width: ${SIGN_WIDTH}; - margin: auto; - padding: 80px 0 40px; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - padding-top: 50px; - } -`; - -export const SignInContentWrapper = styled.div` - padding: 70px 20px 20px; - display: flex; - flex-direction: column; - gap: 20px; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.mobile}) { - padding: 20px; - } -`; - -export const SuggestionComment = styled.div` - display: grid; - grid-template-columns: 1fr 1fr; - text-align: center; - color: ${COLOR.comment}; -`; - -export const Divisor = styled.div` - position: relative; - font: ${FONT_STYLE.xs.normal}; - z-index: 0; - - &::after { - content: ''; - position: absolute; - bottom: 25%; - right: 0; - height: 50%; - border-right: 1px solid ${COLOR.comment}; -`; - -export const FindLink = styled(Link)` - font: ${FONT_STYLE.xs.normal}; - color: ${COLOR.comment}; -`; - -export const SignUpLink = styled(Link)` - font: ${FONT_STYLE.xs.normal}; - color: ${COLOR.comment}; - text-decoration: underline; -`; diff --git a/frontend/src/app/(client)/(auth)/sign-out/page.tsx b/frontend/src/app/(client)/(auth)/sign-out/page.tsx index 1f3bb99d..3146d0c2 100644 --- a/frontend/src/app/(client)/(auth)/sign-out/page.tsx +++ b/frontend/src/app/(client)/(auth)/sign-out/page.tsx @@ -1,26 +1,22 @@ -/* eslint-disable react-hooks/exhaustive-deps */ - 'use client'; import { useRouter } from 'next/navigation'; import { useAppDispatch, useAppSelector } from '@/lib/hooks/redux'; import { signOut } from '@/store/auth.slice'; +import { useEffect } from 'react'; -const Page = () => { +export default function SignOutPage() { const router = useRouter(); const dispatch = useAppDispatch(); - const auth = useAppSelector((state) => state.auth).value; + const auth = useAppSelector((state) => state.auth.value); - if (!auth.isAuth) router.push('/'); + useEffect(() => { + if (!auth.isAuth) router.push('/', { scroll: false }); - // TODO: api 연결 - dispatch(signOut()); - setTimeout(() => { - router.refresh(); - }, 0); + dispatch(signOut()); + router.push('/sign-in', { scroll: false }); + }, [auth, router, dispatch]); return null; -}; - -export default Page; +} diff --git a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpFirstPage/components/EmailTextInput/index.tsx b/frontend/src/app/(client)/(auth)/sign-up/components/SignUpFirstPage/components/EmailTextInput/index.tsx deleted file mode 100644 index e4ca465e..00000000 --- a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpFirstPage/components/EmailTextInput/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -type BuiltInTextInputProps = React.DetailedHTMLProps, HTMLInputElement>; - -interface CustomTextInputProps { - name: string; - label: string; - errorText?: string; - onKeyDownEnter?(): void; - onChangeText?(text: string): void; -} - -export type TextInputProps = BuiltInTextInputProps & CustomTextInputProps; - -export const EmailTextInput = ({ ...props }: TextInputProps) => { - const { label, errorText, onKeyDownEnter, onChangeText, ...inputProps } = props; - const hasError = errorText !== undefined; - - return ( -
- -
- { - if (e.key === 'Enter') { - onKeyDownEnter?.(); - } - inputProps.onKeyDown?.(e); - }} - onChange={(e) => { - inputProps.onChange?.(e); - onChangeText?.(e.target.value); - }} - className={`m-0 w-full rounded-sm border-[1px] border-border p-3 text-base ${hasError && 'border-red-400'}`} - /> -

@pusan.ac.kr

-
- {errorText && {errorText}} -
- ); -}; - -export default EmailTextInput; diff --git a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpSecondPage/index.tsx b/frontend/src/app/(client)/(auth)/sign-up/components/SignUpSecondPage/index.tsx deleted file mode 100644 index 18b73e63..00000000 --- a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpSecondPage/index.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* eslint-disable react/button-has-type */ -/* eslint-disable import/no-extraneous-dependencies */ - -'use client'; - -import { Formik, Form } from 'formik'; -import * as Yup from 'yup'; - -import { Dropdown } from '@/app/(client)/components/Formik/Dropdown'; -import { TextInput } from '@/app/(client)/components/Formik/TextInput'; -import { careerCategory } from '@/data/signUp'; - -import MajorDropdown from './components/MajorDropdown'; - -export interface SecondInfo { - majorCollegeId: number; - majorId: number; - minorCollegeId: number; - minorId: number; - doubleMajorCollegeId: number; - doubleMajorId: number; - career: number; - careerDetail: string; -} - -const validationSchema = Yup.object().shape({ - majorId: Yup.number().min(1, '필수 입력란입니다. 전공을 선택해주세요.'), - minorId: Yup.number().min(0, '필수 입력란입니다. 부전공 선택해주세요.'), - doubleMajorId: Yup.number().min(0, '필수 입력란입니다. 복수전공을 선택해주세요.'), - career: Yup.number().min(1, '필수 입력란입니다. 진로 분류를 선택해주세요.'), - careerDetail: Yup.string().required('필수 입력란입니다. 진로 상세 계획을 입력해주세요.'), -}); - -export interface SignUpSecondPageProps { - initialValues: SecondInfo; - handlePrevButtonClick: (value: SecondInfo) => void; - handleSubmitButtonClick: (value: SecondInfo) => void; -} - -const SignUpSecondPage = ({ initialValues, handlePrevButtonClick, handleSubmitButtonClick }: SignUpSecondPageProps) => ( - { - handleSubmitButtonClick(values); - setSubmitting(false); - }} - > - {({ isSubmitting, values, touched, handleChange, handleBlur, setFieldValue, errors }) => ( -
- - - -
- - -
-
- - -
- - )} -
-); - -export default SignUpSecondPage; diff --git a/frontend/src/app/(client)/(auth)/sign-up/page.tsx b/frontend/src/app/(client)/(auth)/sign-up/page.tsx index 7b8aacfc..5ca4f0c7 100644 --- a/frontend/src/app/(client)/(auth)/sign-up/page.tsx +++ b/frontend/src/app/(client)/(auth)/sign-up/page.tsx @@ -1,19 +1,19 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { toast } from 'react-toastify'; -import PageTitle from '@/app/(client)/components/PageTitle'; +import PageTitle from '@/components/common/PageTitle'; import { SignUpPhase } from '@/data/signUp'; -import SignUpFirstPage, { FirstInfo } from './components/SignUpFirstPage'; -import SignUpSecondPage, { SecondInfo } from './components/SignUpSecondPage'; import { useSignUpMutation } from '@/lib/hooks/useApi'; -import { toast } from 'react-toastify'; -import { useRouter } from 'next/navigation'; +import AuthSignUpFirst, { FirstInfo } from '@/components/ui/auth/AuthSignUpFirst'; +import AuthSignUpSecond, { SecondInfo } from '@/components/ui/auth/AuthSignUpSecond'; type UserInformation = FirstInfo & SecondInfo; -const Page = () => { +export default function SignUpPage() { const [userInfo, setUserInfo] = useState({ email: '', authCode: '', @@ -63,12 +63,12 @@ const Page = () => { return (
- + {phase === SignUpPhase.one && ( - + )} {phase === SignUpPhase.two && ( - {
); -}; - -export default Page; +} diff --git a/frontend/src/app/(client)/(auth)/styled.ts b/frontend/src/app/(client)/(auth)/styled.ts deleted file mode 100644 index 01098781..00000000 --- a/frontend/src/app/(client)/(auth)/styled.ts +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const TitleWrapper = styled.div` - display: flex; -`; - -export const TitleContent = styled.div` - display: flex; - flex-direction: column; -`; - -export const Title = styled.p` - font: ${FONT_STYLE.xl.semibold}; - cursor: default; -`; - -export const Description = styled.p` - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.comment}; - cursor: default; -`; - -export const InputWrapper = styled.div` - position: relative; - display: flex; - flex-direction: column; -`; - -export const InputLabel = styled.label` - font: ${FONT_STYLE.sm.semibold}; - color: ${COLOR.black_text}; -`; - -export const Input = styled.input` - border: 1px solid ${COLOR.border}; - border-radius: ${BORDER_RADIUS.sm}; - padding: 8px; - font: ${FONT_STYLE.base.normal}; - color: ${COLOR.black_text}; - outline: none; - &::placeholder { - color: ${COLOR.border}; - font-style: italic; - } -`; - -export const InputFixedText = styled.div` - position: absolute; - color: ${COLOR.comment}; - right: 10px; - bottom: calc(50% - 10px); - transform: translate(0, 50%); - font: ${FONT_STYLE.sm.normal}; -`; - -export const SignButton = styled.button` - width: 100%; - border: none; - border-radius: ${BORDER_RADIUS.sm}; - padding: 8px; - margin-top: 16px; - background-color: ${COLOR.primary.main}; - color: ${COLOR.white}; - font: ${FONT_STYLE.base.normal}; -`; diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx b/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx deleted file mode 100644 index b6022309..00000000 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useGithubReadme } from '@/lib/hooks/useGithubReadme'; -import MarkdownViewer from '@/components/MarkdownViewer'; - -interface ReadmeViewerProps { - repoUrl: string; -} - -const ReadmeViewer = ({ repoUrl }: ReadmeViewerProps) => { - const { data: readme, isPending, isError } = useGithubReadme(repoUrl); - if (isPending) return
README 문서를 불러오는 중입니다.
; - if(isError||!readme) return
README 문서를 불러오는 데 실패했습니다.
; - return ; -}; - -export default ReadmeViewer; diff --git a/frontend/src/app/(client)/(withSidebar)/layout.tsx b/frontend/src/app/(client)/(withSidebar)/layout.tsx deleted file mode 100644 index bf9d55d8..00000000 --- a/frontend/src/app/(client)/(withSidebar)/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable @next/next/no-page-custom-font */ - -import Sidebar from '@/app/(client)/components/Sidebar'; - -import { Content, ContentWrapper, PageWithSidebarWrapper } from './styled'; - -const Layout = ({ children }: Readonly<{ children: React.ReactNode }>) => ( - - - - {children} - - -); -export default Layout; diff --git a/frontend/src/app/(client)/(withSidebar)/milestone/page.tsx b/frontend/src/app/(client)/(withSidebar)/milestone/page.tsx deleted file mode 100644 index 9bf6d0b0..00000000 --- a/frontend/src/app/(client)/(withSidebar)/milestone/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Image from 'next/image'; - -import * as S from './styled'; -import Link from 'next/link'; - -const Page = () => ( - - - - - - 마일스톤이란 - 마일스톤은 전공자들의 SW역량을 종합적으로 평가하기 위한 역량평가지수입니다. -
- 학생들은 교내외 여러 활동들을 통하여 실전적 SW역량, 글로벌 역량, 커뮤니케이션 역량을 균형있게 함양하고 - SW중심대학사업단에서는 학생들의 적립된 마일스톤 점수에 따라 매년 장학생을 선발하고 있습니다. -
-
- - 마일스톤 등록하러 가기 - -
- - - - - - 마일스톤 획득 방법 - 각 영역별 활동 수행 시, 책정 기준에 따라 마일스톤을 획득할 수 있습니다. -
- 상세 내용은 아래 표를 참고해주세요. -
- - 마일스톤 평가기간 - 전년도 9월부터 당해년도 8월까지의 실적 -
※ SW 창업, 오픈소스 SW 컨트리뷰션의 경우 당해년도 1월부터 9월까지의 실적만을 반영함. -
- - 마일스톤 확인 방법 - SW역량지원시스템에서는 나의 마일스톤 현황을 한 눈에 볼 수 있도록 제공하고 있습니다. -
- 로그인 후, 메인 페이지와 마이페이지에서 확인하실 수 있습니다. -
-
- - - -
-); - -export default Page; diff --git a/frontend/src/app/(client)/(withSidebar)/milestone/styled.ts b/frontend/src/app/(client)/(withSidebar)/milestone/styled.ts deleted file mode 100644 index f074a49b..00000000 --- a/frontend/src/app/(client)/(withSidebar)/milestone/styled.ts +++ /dev/null @@ -1,63 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE, RESPONSIVE_WIDTH } from '@/constants'; - -interface ResponsiveImageProps { - maxHeight: string; - maxWidth: string; - backgroundImage?: string; -} - -export const Content = styled.div` - width: 100%; - background-color: white; - padding: 60px 20px 20px 20px; - border-radius: ${BORDER_RADIUS.sm}; -`; - -export const Title = styled.div` - color: ${COLOR.primary.main}; - font: ${FONT_STYLE.xl.bold}; - margin: 20px auto; -`; - -export const Description = styled.div` - text-align: center; -`; - -export const InformationList = styled.ul` - margin-top: 70px; -`; - -export const Information = styled.li` - display: flex; - margin-bottom: 40px; - @media screen and (max-width: ${RESPONSIVE_WIDTH.mobile}) { - display: block; - } -`; - -export const InformationTitle = styled.div` - color: ${COLOR.primary.main}; - font: ${FONT_STYLE.lg.semibold}; - width: 220px; - @media screen and (max-width: ${RESPONSIVE_WIDTH.mobile}) { - margin-bottom: 10px; - } -`; -export const ImageWrapper = styled.div` - display: flex; - justify-content: center; - ${({ backgroundImage }) => backgroundImage && `background-image: url(${backgroundImage});`} - background-position: center; - img { - position: relative !important; - height: 100%; - width: 100%; - max-height: ${(props) => props.maxHeight}; - max-width: ${(props) => props.maxWidth}; - object-fit: cover; - } -`; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneRowBarTable/index.tsx b/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneRowBarTable/index.tsx deleted file mode 100644 index fc961996..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneRowBarTable/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-disable implicit-arrow-linebreak */ - -import { MilestoneScoreDto } from '@/types/common.dto'; - -interface MilestoneRowBarTableProps { - milestoneScores: MilestoneScoreDto[] | undefined; -} - -const MilestoneRowBarTable = ({ milestoneScores }: MilestoneRowBarTableProps) => - milestoneScores?.map((milestoneScore) => ( -
-
{milestoneScore.name}
-
-
-
-
-
- {milestoneScore.score}/{milestoneScore.limitScore} -
-
-
- )); - -export default MilestoneRowBarTable; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/components/StudentInfoSection/StudentInfoLabel/index.tsx b/frontend/src/app/(client)/(withSidebar)/my-page/components/StudentInfoSection/StudentInfoLabel/index.tsx deleted file mode 100644 index ac0b261e..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/components/StudentInfoSection/StudentInfoLabel/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { ReactNode } from 'react'; - -interface StudentInfoLabelProps { - label: string; - value: ReactNode | string; -} - -const StudentInfoLabel = ({ label, value }: StudentInfoLabelProps) => ( -

- {label} - {value} -

-); -export default StudentInfoLabel; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/styled.ts b/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/styled.ts deleted file mode 100644 index 4925a363..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/styled.ts +++ /dev/null @@ -1,62 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { BORDER_RADIUS } from '@/adminConstants'; -import { COLOR, FONT_STYLE } from '@/constants'; - -interface GroupButtonProps { - isSelected: boolean; -} - -export const GroupButton = styled.button` - color: ${({ isSelected }) => (isSelected ? COLOR.black_text : COLOR.comment)}; - border: none; - background-color: white; - flex-grow: 1; - height: 30px; - font: ${FONT_STYLE.base.normal}; - border-bottom: ${({ isSelected }) => (isSelected ? '2px solid black' : '0px solid black')}; - - &:hover { - color: ${COLOR.black_text}; - border-bottom: 2px solid black; - } -`; - -export const TableRow = styled.div` - padding: 7px 0px; - border-bottom: 1px solid ${COLOR.border}; - font: ${FONT_STYLE.sm.normal}; - display: flex; - align-items: center; - gap: 8px; - justify-content: space-between; -`; - -export const TableRowTitle = styled.div` - flex-shrink: 1; -`; - -export const TableRowBar = styled.div` - background-color: ${COLOR.background.light}; - width: 75px; - height: 10px; - border-radius: ${BORDER_RADIUS.lg}; - overflow: hidden; -`; - -interface TableRowBarFillProps { - ratio: number; -} - -export const TableRowBarFill = styled.div` - width: ${({ ratio }) => 100 * ratio}%; - background-color: ${COLOR.primary.main}; - height: 100%; -`; - -export const TableRowScore = styled.div` - font: ${FONT_STYLE.xs.normal}; - min-width: 45px; -`; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/styled.ts b/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/styled.ts deleted file mode 100644 index 2ca24d68..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/styled.ts +++ /dev/null @@ -1,31 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { COLOR, FONT_STYLE } from '@/constants'; - -export const TableBody = styled.tbody` - border-top: 2px solid ${COLOR.black_text}; - border-bottom: 2px solid ${COLOR.black_text}; - font: ${FONT_STYLE.sm.normal}; - > :last-child { - border-bottom: 0px solid; - } -`; - -export const TableRow = styled.tr` - border-bottom: 1px solid ${COLOR.border}; - text-align: center; - > td, - > th { - padding: 10px; - } - - > td:first-child { - text-align: left; - } -`; - -export const HistoryDescription = styled.td` - width: 540px; -`; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/styled.ts b/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/styled.ts deleted file mode 100644 index adb5c13d..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/styled.ts +++ /dev/null @@ -1,10 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -export const MilestoneWrapper = styled.div` - min-width: 330px; - display: flex; - flex-direction: column; - gap: 16px; -`; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/page.tsx b/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/page.tsx deleted file mode 100644 index c6fc020e..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import Link from 'next/link'; - -import PageTitle from '@/app/(client)/components/PageTitle'; - -import MilestoneHistoryTable from './components/MilestoneHistoryTable'; - -const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | undefined } }) => { - const pageNumber = searchParams?.page ? parseInt(searchParams.page, 10) : 1; - return ( -
-
- - - 실적 등록 - -
- -
- ); -}; - -export default Page; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/styled.ts b/frontend/src/app/(client)/(withSidebar)/my-page/milestone/styled.ts deleted file mode 100644 index d8e5dcb4..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/styled.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const Content = styled.div` - width: 100%; - background-color: white; - padding: 20px; - border-radius: ${BORDER_RADIUS.sm}; -`; - -export const SubTitle = styled.div` - color: ${COLOR.black_text}; - font: ${FONT_STYLE.lg.bold}; - margin-bottom: 25px; -`; diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/page.tsx b/frontend/src/app/(client)/(withSidebar)/my-page/page.tsx deleted file mode 100644 index 1fa6adb5..00000000 --- a/frontend/src/app/(client)/(withSidebar)/my-page/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import SubTitle from '@/components/SubTitle'; - -import MilestoneHistorySection from './components/MilestoneHistorySection'; -import MilestoneSection from './components/MilestoneSection'; -import StudentInfoSection from './components/StudentInfoSection'; - -const Page = () => ( -
- - - -
- -
아직 준비중인 기능이에요.
-
-
-); -export default Page; diff --git a/frontend/src/app/(client)/(withSidebar)/styled.ts b/frontend/src/app/(client)/(withSidebar)/styled.ts deleted file mode 100644 index 98e06192..00000000 --- a/frontend/src/app/(client)/(withSidebar)/styled.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { COLOR, CONTENT_WIDTH, RESPONSIVE_WIDTH } from '@/constants'; - -export const PageWithSidebarWrapper = styled.div` - width: 100%; - min-height: calc(100vh - 200px); - display: flex; - background-color: ${COLOR.background.light}; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - display: block; - background-color: ${COLOR.white}; - } -`; - -export const ContentWrapper = styled.div` - flex-grow: 1; -`; - -export const Content = styled.div` - width: ${CONTENT_WIDTH}; - height: 100%; - padding: 100px 20px 20px; - overflow: hidden; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - padding: 20px; - width: 100%; - } -`; diff --git a/frontend/src/app/(client)/components/Announcement/index.tsx b/frontend/src/app/(client)/components/Announcement/index.tsx deleted file mode 100644 index c79a47cc..00000000 --- a/frontend/src/app/(client)/components/Announcement/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import RSSParser from 'rss-parser'; - -import GoPageIcon from '@/components/GoPageIcon'; - -import { AnnouncementDate, AnnouncementItem, AnnouncementTitle } from './styled'; -import { Description, Title, TitleContent, TitleWrapper } from '../styled'; - -const Announcement = async () => { - const ANNOUNCEMENT_URL = 'https://swedu.pusan.ac.kr/swedu/31630/subview.do'; - const parser = new RSSParser(); - const announcements = await parser.parseURL('https://swedu.pusan.ac.kr/bbs/swedu/6906/rssList.do?row=50'); - return ( -
- - - 공지사항 - 소프트웨어융합교육원의 공지사항을 알려드려요. - - - -
- {announcements.items.slice(0, 4).map((item) => ( - - {item.title} - {item.pubDate?.slice(0, 10).replaceAll('-', '.')} - - ))} -
-
- ); -}; - -export default Announcement; diff --git a/frontend/src/app/(client)/components/Announcement/styled.ts b/frontend/src/app/(client)/components/Announcement/styled.ts deleted file mode 100644 index 5e79b3a8..00000000 --- a/frontend/src/app/(client)/components/Announcement/styled.ts +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const AnnouncementItem = styled(Link)` - border: 1px solid ${COLOR.border}; - border-radius: ${BORDER_RADIUS.sm}; - padding: 10px; - display: flex; - justify-content: space-between; - gap: 20px; - &:hover > * { - color: ${COLOR.primary.main}; - } -`; - -export const AnnouncementTitle = styled.span` - min-width: 0; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font: ${FONT_STYLE.base.semibold}; - color: ${COLOR.black_text}; -`; - -export const AnnouncementDate = styled.span` - width: 90px; - flex-shirk: 1; - text-align: right; - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.comment}; -`; diff --git a/frontend/src/app/(client)/components/ExternalLink/index.tsx b/frontend/src/app/(client)/components/ExternalLink/index.tsx deleted file mode 100644 index 196b7d35..00000000 --- a/frontend/src/app/(client)/components/ExternalLink/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Image from 'next/image'; - -import { externalLinkInfos } from '@/data/externalLink'; - -import { ImageTitle, ImageWrapper, ItemWrapper, LinkWrapper } from './styled'; - -const ExternalLink = () => ( - - {externalLinkInfos.map((link) => { - const markup = { __html: link.title }; - return ( - - - {link.title} - - - - ); - })} - -); - -export default ExternalLink; diff --git a/frontend/src/app/(client)/components/ExternalLink/styled.ts b/frontend/src/app/(client)/components/ExternalLink/styled.ts deleted file mode 100644 index 1aaa7ca6..00000000 --- a/frontend/src/app/(client)/components/ExternalLink/styled.ts +++ /dev/null @@ -1,44 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE, RESPONSIVE_WIDTH } from '@/constants'; - -export const LinkWrapper = styled.div` - border-radius: ${BORDER_RADIUS.sm}; - padding: 10px; - display: grid; - grid-template-columns: repeat(6, 1fr); - row-gap: 20px; - background-color: ${COLOR.primary.light}; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.tablet}) { - grid-template-columns: repeat(3, 1fr); - } -`; - -export const ItemWrapper = styled(Link)` - display: flex; - flex-direction: column; - gap: 12px; - justify-content: center; - align-items: center; -`; - -export const ImageWrapper = styled.div` - width: 80px; - height: 80px; - display: flex; - justify-content: center; - align-items: center; - background-color: ${COLOR.white}; - border-radius: ${BORDER_RADIUS.full}; -`; - -export const ImageTitle = styled.div` - height: 40px; - color: ${COLOR.black_text}; - font: ${FONT_STYLE.xs.semibold}; - text-align: center; -`; diff --git a/frontend/src/app/(client)/components/GoPageIcon/index.tsx b/frontend/src/app/(client)/components/GoPageIcon/index.tsx deleted file mode 100644 index 15237611..00000000 --- a/frontend/src/app/(client)/components/GoPageIcon/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { VscAdd } from '@react-icons/all-files/vsc/VscAdd'; -import Link from 'next/link'; - -import { COLOR, FONT_STYLE } from '@/constants'; - -interface GoPageIconProps { - name: string; - url: string; -} - -const GoPageIcon = ({ name, url }: GoPageIconProps) => ( - - - {name} - -); - -export default GoPageIcon; diff --git a/frontend/src/app/(client)/components/Milestone/styled.ts b/frontend/src/app/(client)/components/Milestone/styled.ts deleted file mode 100644 index a4f6c49d..00000000 --- a/frontend/src/app/(client)/components/Milestone/styled.ts +++ /dev/null @@ -1,11 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -export const MilestoneChartWrapper = styled.div` - margin: 12px; - display: flex; - gap: 20px; - align-items: center; - justify-content: space-around; -`; diff --git a/frontend/src/app/(client)/components/PageTitle/index.tsx b/frontend/src/app/(client)/components/PageTitle/index.tsx deleted file mode 100644 index 7db1c284..00000000 --- a/frontend/src/app/(client)/components/PageTitle/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { VscAdd } from '@react-icons/all-files/vsc/VscAdd'; -import Link from 'next/link'; - -export interface PageTitleProps { - title: string; - description: string; - urlText: string; - url: string; -} - -const PageTitle = ({ title, description, urlText, url }: PageTitleProps) => ( -
-
-

{title}

-

{description}

-
- {urlText !== '' && ( - - - {urlText} - - )} -
-); - -export default PageTitle; diff --git a/frontend/src/app/(client)/components/PnuLink/styled.ts b/frontend/src/app/(client)/components/PnuLink/styled.ts deleted file mode 100644 index 5af102d9..00000000 --- a/frontend/src/app/(client)/components/PnuLink/styled.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { VscChevronLeft } from '@react-icons/all-files/vsc/VscChevronLeft'; -import { VscChevronRight } from '@react-icons/all-files/vsc/VscChevronRight'; -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const ButtonWrapper = styled.div` - display: flex; - gap: 10px; - align-items: center; -`; - -export const PrevButton = styled(VscChevronLeft)` - font: ${FONT_STYLE.xl.semibold}; - color: ${COLOR.black_text}; - border: 1px solid ${COLOR.comment}; - border-radius: ${BORDER_RADIUS.full}; -`; - -export const NextButton = styled(VscChevronRight)` - font: ${FONT_STYLE.xl.semibold}; - color: ${COLOR.black_text}; - border: 1px solid ${COLOR.comment}; - border-radius: ${BORDER_RADIUS.full}; -`; - -export const PnuLinker = styled(Link)` - width: 162px; - height: 52px; - display: block; - border: 1px solid ${COLOR.border}; -`; diff --git a/frontend/src/app/(client)/components/Sidebar/index.tsx b/frontend/src/app/(client)/components/Sidebar/index.tsx deleted file mode 100644 index d1700094..00000000 --- a/frontend/src/app/(client)/components/Sidebar/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ - -'use client'; - -import { VscChevronDown } from '@react-icons/all-files/vsc/VscChevronDown'; -import { VscChevronUp } from '@react-icons/all-files/vsc/VscChevronUp'; -import { usePathname } from 'next/navigation'; -import { useCallback, useEffect, useState } from 'react'; - -import { headerInfos } from '@/data/clientCategory'; -import { CategoryDto, SubCategoryDto } from '@/types/common.dto'; - -import * as S from './styled'; - -const Sidebar = () => { - const pathname = usePathname(); - const [currentCategory, setCurrentCategory] = useState(); - const [currentSubCategory, setCurrentSubCategory] = useState(); - const [isOpenNavigationBar, setIsOpenNavigationBar] = useState(false); - - const findMatchPath = useCallback(() => { - let maxOverlap = 0; - let bestMatch: SubCategoryDto | null = null; - currentCategory?.sub.forEach((category) => { - const { url } = category; - if (pathname.startsWith(url) && url.length > maxOverlap) { - maxOverlap = url.length; - bestMatch = category; - } - }); - return bestMatch; - }, [pathname, currentCategory]); - - useEffect(() => { - setCurrentCategory(headerInfos.filter((headerInfo) => pathname.startsWith(headerInfo.url))[0]); - setIsOpenNavigationBar(false); - }, [pathname]); - - useEffect(() => { - setCurrentSubCategory(findMatchPath()); - }, [findMatchPath]); - - return ( - <> - - {currentCategory?.title} - {currentCategory?.description} - - {currentCategory?.sub.map((sub) => ( - - {sub.title} - - ))} - - - - setIsOpenNavigationBar((prev) => !prev)}> - {currentSubCategory?.title} - {isOpenNavigationBar ? : } - - - - {currentCategory?.sub.map((sub) => ( - - {sub.title} - - ))} - - - - ); -}; - -export default Sidebar; diff --git a/frontend/src/app/(client)/components/Sidebar/styled.ts b/frontend/src/app/(client)/components/Sidebar/styled.ts deleted file mode 100644 index 01d77a24..00000000 --- a/frontend/src/app/(client)/components/Sidebar/styled.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* eslint-disable implicit-arrow-linebreak */ -/* eslint-disable @typescript-eslint/indent */ -/* eslint-disable operator-linebreak */ - -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { COLOR, FONT_STYLE, RESPONSIVE_WIDTH } from '@/constants'; - -interface SidebarCategoryProps { - isCurrentCategory: boolean; -} - -interface SidebarCategoryListProps { - isOpen: boolean; -} - -export const SidebarWrapper = styled.div` - width: 290px; - min-height: calc(100vh - 200px); - padding: 120px 20px 0px 20px; - border-right: 1px solid ${COLOR.border}; - background-color: ${COLOR.white}; - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - display: none; - } -`; - -export const SidebarCategoryTitle = styled.p` - font: ${FONT_STYLE.xl.semibold}; -`; - -export const SidebarCategoryDescription = styled.p` - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.comment}; - margin-top: 30px; - word-break: keep-all; -`; - -export const SidebarCategoryList = styled.div` - width: 100%; - height: 100%; - display: flex; - margin-top: 30px; - flex-direction: column; - z-index: 49; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - transition: all 0.6s ease-in-out; - position: absolute; - top: 45px; - left: 0; - height: fit-content; - max-height: 0; - overflow: hidden; - margin-top: 0px; - background-color: ${COLOR.background.light}; - ${({ isOpen }) => isOpen && 'max-height: 100vh;'} - } -`; - -export const SidebarCategory = styled(Link)` - font: ${FONT_STYLE.lg.semibold}; - margin-bottom: 20px; - color: ${COLOR.comment}; - line-height: 40px; - - ${({ isCurrentCategory }) => - isCurrentCategory && - `color: ${COLOR.black_text}; - text-decoration: underline; - text-underline-offset: 12px; - `} - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - margin-bottom: 0px; - padding: 10px 15px; - border-bottom: 1px solid ${COLOR.border}; - color: ${COLOR.comment}; - font: ${FONT_STYLE.sm.normal}; - ${({ isCurrentCategory }) => - isCurrentCategory && - `color: ${COLOR.primary.main}; - text-decoration: none;`}; - } -`; - -export const SidebarMobileWrapper = styled.div` - width: 100%; - margin-top: 50px; - height: inherit; - position: relative; - - @media screen and (min-width: ${RESPONSIVE_WIDTH.desktop}) { - display: none; - } -`; - -export const SidebarMobileButton = styled.button` - display: none; - width: 100%; - height: 45px; - background-color: ${COLOR.secondary.main}; - color: ${COLOR.white}; - padding: 0px 15px; - z-index: 49; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - display: flex; - justify-content: space-between; - align-items: center; - &:active, - &:focus { - outline: none; - border: none; - } - } -`; diff --git a/frontend/src/app/(client)/components/SignIn/components/InputUserInfo/index.tsx b/frontend/src/app/(client)/components/SignIn/components/InputUserInfo/index.tsx deleted file mode 100644 index dc94e70b..00000000 --- a/frontend/src/app/(client)/components/SignIn/components/InputUserInfo/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { useState } from 'react'; - -import { useAppDispatch } from '@/lib/hooks/redux'; -import { signIn } from '@/store/auth.slice'; - -import { FixedEmail, InputID, InputPW, SignInButton } from './styled'; -import { useSignInMutation } from '@/lib/hooks/useApi'; -import { toast } from 'react-toastify'; - -const InputUserInfo = () => { - const [userInfo, setUserInfo] = useState({ - email: '', - password: '', - }); - - const { mutate: signInMutation } = useSignInMutation(); - - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleInputChange = (e: React.ChangeEvent) => { - setUserInfo((prev) => { - return { - ...prev, - [e.target.id]: e.target.value, - }; - }); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - signInMutation(userInfo, { - onSuccess(data, variables, context) { - dispatch( - signIn({ - id: data.member_id, - token: `Bearer ${data.token}`, - name: data.name, - email: data.email, - isModerator: data.is_moderator, - }), - ); - router.refresh(); - }, - onError(error, variables, context) { - toast.error(error.message); - }, - }); - }; - - return ( -
-
- - - @pusan.ac.kr -
- 로그인 -
- ); -}; - -export default InputUserInfo; diff --git a/frontend/src/app/(client)/components/SignIn/components/InputUserInfo/styled.ts b/frontend/src/app/(client)/components/SignIn/components/InputUserInfo/styled.ts deleted file mode 100644 index ce5cd987..00000000 --- a/frontend/src/app/(client)/components/SignIn/components/InputUserInfo/styled.ts +++ /dev/null @@ -1,46 +0,0 @@ -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const Input = styled.input` - min-width: 232px; - border: 1px solid ${COLOR.border}; - border-radius: ${BORDER_RADIUS.sm}; - padding: 8px; - font: ${FONT_STYLE.base.normal}; - outline: none; - &::placeholder { - color: ${COLOR.border}; - font-style: italic; - } -`; - -export const InputID = styled(Input)` - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -`; - -export const InputPW = styled(Input)` - border-top-left-radius: 0; - border-top-right-radius: 0; -`; - -export const FixedEmail = styled.div` - position: absolute; - color: ${COLOR.comment}; - right: 10px; - top: 25%; - transform: translate(0, -50%); - font: ${FONT_STYLE.xs.normal}; -`; - -export const SignInButton = styled.button` - min-width: 70px; - max-width: 110px; - border: none; - border-radius: ${BORDER_RADIUS.sm}; - flex-grow: 1; - background-color: ${COLOR.primary.main}; - color: ${COLOR.white}; - font: ${FONT_STYLE.base.normal}; -`; diff --git a/frontend/src/app/(client)/components/SignIn/index.tsx b/frontend/src/app/(client)/components/SignIn/index.tsx deleted file mode 100644 index fdf336aa..00000000 --- a/frontend/src/app/(client)/components/SignIn/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { FONT_STYLE } from '@/adminConstants'; - -import InputUserInfo from './components/InputUserInfo'; -import { AlertComment, Divisor, FindLink, SignUpLink, SuggestionComment } from './styled'; - -const SignIn = () => ( -
- 로그인이 필요한 서비스입니다. - - - - 아이디 / 비밀번호 찾기 - -
- 처음오셨나요? 회원가입 -
-
-
-); - -export default SignIn; diff --git a/frontend/src/app/(client)/components/SignIn/styled.ts b/frontend/src/app/(client)/components/SignIn/styled.ts deleted file mode 100644 index 261a61fb..00000000 --- a/frontend/src/app/(client)/components/SignIn/styled.ts +++ /dev/null @@ -1,47 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const AlertComment = styled.div` - padding: 8px; - border-radius: ${BORDER_RADIUS.sm}; - background-color: ${COLOR.background.light}; - font: ${FONT_STYLE.base.normal}; - text-align: center; - color: ${COLOR.comment}; -`; - -export const FindLink = styled(Link)` - font: ${FONT_STYLE.xs.normal}; - color: ${COLOR.comment}; -`; - -export const SignUpLink = styled(Link)` - font: ${FONT_STYLE.xs.normal}; - color: ${COLOR.comment}; - text-decoration: underline; -`; - -export const SuggestionComment = styled.div` - display: grid; - grid-template-columns: 1fr 1fr; - text-align: center; - color: ${COLOR.comment}; -`; - -export const Divisor = styled.div` - position: relative; - font: ${FONT_STYLE.xs.normal}; - z-index: 0; - - &::after { - content: ''; - position: absolute; - bottom: 25%; - right: 0; - height: 50%; - border-right: 1px solid ${COLOR.comment}; -`; diff --git a/frontend/src/app/(client)/components/TeamBuildings/index.tsx b/frontend/src/app/(client)/components/TeamBuildings/index.tsx deleted file mode 100644 index d0d232ab..00000000 --- a/frontend/src/app/(client)/components/TeamBuildings/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import TeamBuilding from '@/components/TeamBuilding'; -import { teamBuildingInfos } from '@/mocks/teamBuilding'; - -import { AlertComment, AlertDescription, AlertLink, AlertTitle, TeamBuildingWrapper } from './styled'; -import GoPageIcon from '../GoPageIcon'; -import { Description, Title, TitleContent, TitleWrapper } from '../styled'; - -const TeamBuildings = () => ( -
- - - 팀 빌딩 - 프로젝트 팀원을 모집하고 있나요? 함께할 팀을 찾고 있나요? - - - - - {teamBuildingInfos.length === 0 && ( - - 아직 팀이 생성되지 않았습니다. - 지금 바로 새로운 팀을 생성해 보세요! - [팀 생성하기] - - )} - {teamBuildingInfos.map((team) => ( - - ))} - -
-); - -export default TeamBuildings; diff --git a/frontend/src/app/(client)/components/TeamBuildings/styled.ts b/frontend/src/app/(client)/components/TeamBuildings/styled.ts deleted file mode 100644 index 521a862a..00000000 --- a/frontend/src/app/(client)/components/TeamBuildings/styled.ts +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE, RESPONSIVE_WIDTH } from '@/constants'; - -export const TeamBuildingWrapper = styled.div` - margin-top: 20px; - display: grid; - grid-template-columns: repeat(3, 1fr); - row-gap: 16px; - justify-items: center; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - grid-template-columns: repeat(2, 1fr); - > :nth-child(3) { - display: none; - } - } - @media screen and (max-width: ${RESPONSIVE_WIDTH.tablet}) { - grid-template-columns: repeat(1, 1fr); - > :nth-child(2) { - display: none; - } - } -`; - -export const AlertComment = styled.div` - grid-column: 1 / 4; - width: 100%; - margin: 8px; - border-radius: ${BORDER_RADIUS.sm}; - background-color: ${COLOR.background.light}; - padding: 10px; - text-align: center; -`; - -export const AlertTitle = styled.p` - margin: 10px; - color: ${COLOR.black_text}; - font: ${FONT_STYLE.base.semibold}; -`; - -export const AlertDescription = styled.p` - margin: 10px; - color: ${COLOR.comment}; - font: ${FONT_STYLE.sm.normal}; -`; - -export const AlertLink = styled(Link)` - display: block; - margin: 10px; - color: ${COLOR.comment}; - font: ${FONT_STYLE.base.normal}; -`; diff --git a/frontend/src/app/(client)/components/styled.ts b/frontend/src/app/(client)/components/styled.ts deleted file mode 100644 index d50b1135..00000000 --- a/frontend/src/app/(client)/components/styled.ts +++ /dev/null @@ -1,25 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { COLOR, FONT_STYLE } from '@/constants'; - -export const TitleWrapper = styled.div` - display: flex; -`; - -export const TitleContent = styled.div` - display: flex; - flex-direction: column; -`; - -export const Title = styled.p` - font: ${FONT_STYLE.lg.semibold}; - cursor: default; -`; - -export const Description = styled.p` - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.comment}; - cursor: default; -`; diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/components/HackathonInformationTypeButtons/index.tsx b/frontend/src/app/(client)/hackathon/[slug]/components/HackathonInformationTypeButtons/index.tsx similarity index 100% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/components/HackathonInformationTypeButtons/index.tsx rename to frontend/src/app/(client)/hackathon/[slug]/components/HackathonInformationTypeButtons/index.tsx diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/layout.tsx b/frontend/src/app/(client)/hackathon/[slug]/layout.tsx similarity index 85% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/layout.tsx rename to frontend/src/app/(client)/hackathon/[slug]/layout.tsx index 7acbcada..b8029834 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/layout.tsx +++ b/frontend/src/app/(client)/hackathon/[slug]/layout.tsx @@ -1,4 +1,4 @@ -import Title from '@/components/Title'; +import PageTitle from '@/components/common/PageTitle'; import { getHackathonInformation } from '@/lib/api/server.api'; import HackathonInformationTypeButtons from './components/HackathonInformationTypeButtons'; @@ -9,7 +9,7 @@ const Layout = async ({ const hackathonInformation = await getHackathonInformation(slug); return (
- + <PageTitle title={hackathonInformation.name} /> <div className="h-0 w-full border border-border" /> <HackathonInformationTypeButtons slug={slug} /> <div>{children}</div> diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/page.tsx b/frontend/src/app/(client)/hackathon/[slug]/page.tsx similarity index 93% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/page.tsx rename to frontend/src/app/(client)/hackathon/[slug]/page.tsx index 508aa16c..c5cc9740 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/page.tsx +++ b/frontend/src/app/(client)/hackathon/[slug]/page.tsx @@ -1,4 +1,4 @@ -import MarkdownViewer from '@/components/MarkdownViewer'; +import MarkdownViewer from '@/components/ui/hackathon/MarkdownViewer'; import { getHackathonInformation } from '@/lib/api/server.api'; import Image from 'next/image'; diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/prize/page.tsx b/frontend/src/app/(client)/hackathon/[slug]/prize/page.tsx similarity index 100% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/prize/page.tsx rename to frontend/src/app/(client)/hackathon/[slug]/prize/page.tsx diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/HackathonTeamCreateModal/index.tsx b/frontend/src/app/(client)/hackathon/[slug]/vote/components/HackathonTeamCreateModal/index.tsx similarity index 98% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/HackathonTeamCreateModal/index.tsx rename to frontend/src/app/(client)/hackathon/[slug]/vote/components/HackathonTeamCreateModal/index.tsx index 8ed60093..ba78e6db 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/HackathonTeamCreateModal/index.tsx +++ b/frontend/src/app/(client)/hackathon/[slug]/vote/components/HackathonTeamCreateModal/index.tsx @@ -1,6 +1,6 @@ -import ImageUploader from '@/components/Formik/ImageUploader'; -import TextInput from '@/components/Formik/TextInput'; -import Title from '@/components/Title'; +import ImageUploader from '@/components/common/formik/ImageUploader'; +import TextInput from '@/components/common/formik/TextInput'; +import PageTitle from '@/components/common/PageTitle'; import { TeamMemberRole } from '@/data/hackathon'; import { useRegisterTeamMutation } from '@/lib/hooks/useApi'; import useBodyScrollLock from '@/lib/hooks/useBodyScrollLock'; @@ -103,7 +103,7 @@ const HackathonTeamCreateModal = ({ hackathonId, open, onClose }: HackathonTeamC <div className="fixed inset-0 z-[51] flex items-center justify-center bg-black bg-opacity-30"> <div ref={ref} className="flex w-full max-w-[900px] flex-col items-center gap-5 rounded bg-white p-5"> <div className="flex w-full justify-between"> - <Title title="팀 등록하기" /> + <PageTitle title="팀 등록하기" /> <button onClick={onClose} className="text-comment"> <VscClose className="h-8 w-8" /> </button> diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/HackathonTeamReadModal/index.tsx b/frontend/src/app/(client)/hackathon/[slug]/vote/components/HackathonTeamReadModal/index.tsx similarity index 97% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/HackathonTeamReadModal/index.tsx rename to frontend/src/app/(client)/hackathon/[slug]/vote/components/HackathonTeamReadModal/index.tsx index 76af2ee1..7089508a 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/HackathonTeamReadModal/index.tsx +++ b/frontend/src/app/(client)/hackathon/[slug]/vote/components/HackathonTeamReadModal/index.tsx @@ -1,4 +1,4 @@ -import Title from '@/components/Title'; +import PageTitle from '@/components/common/PageTitle'; import { TeamMemberRole, teamMemberRoleInfo } from '@/data/hackathon'; import useBodyScrollLock from '@/lib/hooks/useBodyScrollLock'; import useOnClickOutside from '@/lib/hooks/useOnClickOutside'; @@ -40,7 +40,7 @@ const HackathonTeamReadModal = ({ selectedTeam, onClose }: HackathonTeamReadModa className="flex h-[600px] w-full max-w-[900px] flex-col items-center gap-2 rounded bg-white p-5 sm:h-[700px]" > <div className="flex w-full flex-wrap items-center justify-between gap-y-2"> - <Title title={selectedTeam.name} /> + <PageTitle title={selectedTeam.name} /> <div className="flex flex-grow justify-end gap-2"> <span className="flex items-center gap-1 font-bold text-primary-main"> <VscHeart /> diff --git a/frontend/src/app/(client)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx b/frontend/src/app/(client)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx new file mode 100644 index 00000000..17178d6c --- /dev/null +++ b/frontend/src/app/(client)/hackathon/[slug]/vote/components/ReadmeViewer/index.tsx @@ -0,0 +1,25 @@ +import { useGithubReadme } from '@/lib/hooks/useGithubReadme'; +import MarkdownViewer from '@/components/ui/hackathon/MarkdownViewer'; + +interface ReadmeViewerProps { + repoUrl: string; +} + +const ReadmeViewer = ({ repoUrl }: ReadmeViewerProps) => { + const { data: readme, isPending, isError } = useGithubReadme(repoUrl); + if (isPending) + return ( + <div className="flex flex-grow items-center justify-center text-xl text-comment"> + README 문서를 불러오는 중입니다. + </div> + ); + if (isError || !readme) + return ( + <div className="flex flex-grow items-center justify-center text-xl text-comment"> + README 문서를 불러오는 데 실패했습니다. + </div> + ); + return <MarkdownViewer content={readme} />; +}; + +export default ReadmeViewer; diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/TeamCreateInputSection/index.tsx b/frontend/src/app/(client)/hackathon/[slug]/vote/components/TeamCreateInputSection/index.tsx similarity index 100% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/TeamCreateInputSection/index.tsx rename to frontend/src/app/(client)/hackathon/[slug]/vote/components/TeamCreateInputSection/index.tsx diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/TeamMemberInput/index.tsx b/frontend/src/app/(client)/hackathon/[slug]/vote/components/TeamMemberInput/index.tsx similarity index 94% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/TeamMemberInput/index.tsx rename to frontend/src/app/(client)/hackathon/[slug]/vote/components/TeamMemberInput/index.tsx index 6e8aaf48..b0270dfc 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/components/TeamMemberInput/index.tsx +++ b/frontend/src/app/(client)/hackathon/[slug]/vote/components/TeamMemberInput/index.tsx @@ -1,5 +1,5 @@ -import Dropdown from '@/components/Formik/Dropdown'; -import TextInput from '@/components/Formik/TextInput'; +import Dropdown from '@/components/common/formik/Dropdown'; +import TextInput from '@/components/common/formik/TextInput'; import { memberRoleOptions, teamMemberRoleInfo } from '@/data/hackathon'; import { useStudentMemberMutation } from '@/lib/hooks/useApi'; import useDebounce from '@/lib/hooks/useDebounce'; diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/page.tsx b/frontend/src/app/(client)/hackathon/[slug]/vote/page.tsx similarity index 97% rename from frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/page.tsx rename to frontend/src/app/(client)/hackathon/[slug]/vote/page.tsx index 506c0553..d1385433 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/[slug]/vote/page.tsx +++ b/frontend/src/app/(client)/hackathon/[slug]/vote/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import Pagination from '@/app/(client)/components/Pagination'; +import Pagination from '@/components/common/Pagination'; import { useHackathonTeamsQuery } from '@/lib/hooks/useApi'; import { HackathonTeamDto } from '@/types/common.dto'; import Image from 'next/image'; diff --git a/frontend/src/app/(client)/hackathon/layout.tsx b/frontend/src/app/(client)/hackathon/layout.tsx new file mode 100644 index 00000000..34b7f7e5 --- /dev/null +++ b/frontend/src/app/(client)/hackathon/layout.tsx @@ -0,0 +1,5 @@ +import SidebarLayout from '@/components/layout/SidebarLayout'; + +export default function Layout({ children }: Readonly<{ children: React.ReactNode }>) { + return <SidebarLayout>{children}</SidebarLayout>; +} diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/page.tsx b/frontend/src/app/(client)/hackathon/page.tsx similarity index 97% rename from frontend/src/app/(client)/(withSidebar)/hackathon/page.tsx rename to frontend/src/app/(client)/hackathon/page.tsx index ff85af86..30052f69 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/page.tsx +++ b/frontend/src/app/(client)/hackathon/page.tsx @@ -1,5 +1,5 @@ -import Pagination from '@/app/(client)/components/Pagination'; -import Title from '@/components/Title'; +import Pagination from '@/components/common/Pagination'; +import PageTitle from '@/components/common/PageTitle'; import { HackathonState } from '@/data/hackathon'; import { getHackathons } from '@/lib/api/server.api'; import classname from 'classnames'; @@ -44,7 +44,7 @@ const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | return ( <div className="flex w-full flex-col gap-4 rounded-sm bg-white p-5"> - <Title + <PageTitle title="창의융합SW해커톤" description="소프트웨어융합교육원에서는 2018년부터 매년 창의융합 SW 해커톤을 개최해오고 있습니다." /> diff --git a/frontend/src/app/(client)/(withSidebar)/hackathon/sw-contest/page.tsx b/frontend/src/app/(client)/hackathon/sw-contest/page.tsx similarity index 61% rename from frontend/src/app/(client)/(withSidebar)/hackathon/sw-contest/page.tsx rename to frontend/src/app/(client)/hackathon/sw-contest/page.tsx index 3db07350..dd5bdb0b 100644 --- a/frontend/src/app/(client)/(withSidebar)/hackathon/sw-contest/page.tsx +++ b/frontend/src/app/(client)/hackathon/sw-contest/page.tsx @@ -1,9 +1,9 @@ -import Title from '@/components/Title'; +import PageTitle from '@/components/common/PageTitle'; const Page = () => { return ( <div className="flex w-full flex-col gap-4 rounded-sm bg-white p-5"> - <Title title="SW문제 해결 경진대회" description="구글 사이트에 게시된 학생들의 작품을 감상해보세요!" /> + <PageTitle title="SW문제 해결 경진대회" description="구글 사이트에 게시된 학생들의 작품을 감상해보세요!" /> <div className="h-0 w-full border border-border" /> <div className="flex h-40 w-full items-center justify-center text-comment">개발 중인 기능입니다.</div> diff --git a/frontend/src/app/(client)/layout-styled.ts b/frontend/src/app/(client)/layout-styled.ts deleted file mode 100644 index 65881ee9..00000000 --- a/frontend/src/app/(client)/layout-styled.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { ADMIN_HEADER_HEIGHT, ADMIN_SIDEBAR_WIDTH, COLOR } from '@/adminConstants'; - -export const PageWrapper = styled.div` - width: 100vw; - min-height: calc(100vh - 200px); - background-color: ${COLOR.white}; -`; - -export const AdminPageWrapper = styled.div` - width: 100vw; - min-height: 100vh; - background-color: ${COLOR.secondary.light}; - padding-top: calc(${ADMIN_HEADER_HEIGHT} + 2px); - padding-left: ${ADMIN_SIDEBAR_WIDTH}; -`; diff --git a/frontend/src/app/(client)/layout.tsx b/frontend/src/app/(client)/layout.tsx index bd79c84e..6c832882 100644 --- a/frontend/src/app/(client)/layout.tsx +++ b/frontend/src/app/(client)/layout.tsx @@ -1,15 +1,12 @@ -/* eslint-disable @next/next/no-page-custom-font */ +import Footer from '@/components/layout/Footer'; +import Header from '@/components/layout/Header'; -import Footer from '@/components/Footer'; -import Header from '@/components/Header'; - -import { PageWrapper } from './layout-styled'; - -const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => ( - <> - <Header /> - <PageWrapper>{children}</PageWrapper> - <Footer /> - </> -); -export default RootLayout; +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + <> + <Header /> + <div className="min-h-[calc(100vh-200px)] w-[100vw] bg-white">{children}</div> + <Footer /> + </> + ); +} diff --git a/frontend/src/app/(client)/milestone/layout.tsx b/frontend/src/app/(client)/milestone/layout.tsx new file mode 100644 index 00000000..34b7f7e5 --- /dev/null +++ b/frontend/src/app/(client)/milestone/layout.tsx @@ -0,0 +1,5 @@ +import SidebarLayout from '@/components/layout/SidebarLayout'; + +export default function Layout({ children }: Readonly<{ children: React.ReactNode }>) { + return <SidebarLayout>{children}</SidebarLayout>; +} diff --git a/frontend/src/app/(client)/milestone/page.tsx b/frontend/src/app/(client)/milestone/page.tsx new file mode 100644 index 00000000..ddf39bef --- /dev/null +++ b/frontend/src/app/(client)/milestone/page.tsx @@ -0,0 +1,58 @@ +import Link from 'next/link'; + +export default function MilestonePage() { + return ( + <div className="w-full rounded-sm bg-white p-5 pt-16"> + <div className="flex h-[66px] justify-center bg-[url('/images/milestone/milestone_img01.png')] bg-center bg-no-repeat" /> + <div className="break-keep text-center md:px-20"> + <div className="mx-auto my-5 text-xl font-bold text-primary-main">마일스톤이란</div> + 마일스톤은 전공자들의 SW역량을 종합적으로 평가하기 위한 역량평가지수입니다. + <br /> + 학생들은 교내외 여러 활동들을 통하여 실전적 SW역량, 글로벌 역량, 커뮤니케이션 역량을 균형있게 함양하고 + SW중심대학사업단에서는 학생들의 적립된 마일스톤 점수에 따라 매년 장학생을 선발하고 있습니다. + </div> + <div className="mb-10 mt-6 flex flex-col items-center"> + <Link + href="/my-page/milestone-register" + className="mt-2 block w-fit rounded-sm bg-primary-main p-3 text-lg font-semibold tracking-wide text-white" + type="button" + > + 마일스톤 등록하러 가기 + </Link> + </div> + <div className="flex w-full justify-center bg-[url('/images/milestone/milestone_img02_bg.png')] bg-center bg-no-repeat"> + <img + alt="pattern" + className="!relative h-full w-full max-w-[400px]" + src="/images/milestone/milestone_img02.png" + /> + </div> + <ul className="mx-2.5 mt-[70px] md:mx-5"> + <li className="mb-10 md:flex"> + <div className="mb-2.5 w-[220px] text-lg font-semibold text-primary-main md:mb-0">마일스톤 획득 방법</div> + 각 영역별 활동 수행 시, 책정 기준에 따라 마일스톤을 획득할 수 있습니다. + <br /> + 상세 내용은 아래 표를 참고해주세요. + </li> + <li className="mb-10 md:flex"> + <div className="mb-2.5 w-[220px] text-lg font-semibold text-primary-main md:mb-0">마일스톤 평가기간</div> + 전년도 9월부터 당해년도 8월까지의 실적 + <br />※ SW 창업, 오픈소스 SW 컨트리뷰션의 경우 당해년도 1월부터 9월까지의 실적만을 반영함. + </li> + <li className="mb-10 md:flex"> + <div className="mb-2.5 w-[220px] text-lg font-semibold text-primary-main md:mb-0">마일스톤 확인 방법</div> + SW역량지원시스템에서는 나의 마일스톤 현황을 한 눈에 볼 수 있도록 제공하고 있습니다. + <br /> + 로그인 후, 메인 페이지와 마이페이지에서 확인하실 수 있습니다. + </li> + </ul> + <div className="flex w-full justify-center"> + <img + alt="마일스톤 역량 표" + className="!relative h-full w-full max-w-[800px]" + src="/images/milestone/milestone_img03.png" + /> + </div> + </div> + ); +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/edit/page.tsx b/frontend/src/app/(client)/my-page/info-edit/page.tsx similarity index 60% rename from frontend/src/app/(client)/(withSidebar)/my-page/edit/page.tsx rename to frontend/src/app/(client)/my-page/info-edit/page.tsx index 429d9ef8..68f5ef31 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/edit/page.tsx +++ b/frontend/src/app/(client)/my-page/info-edit/page.tsx @@ -1,13 +1,11 @@ -import Title from '@/components/Title'; +import PageTitle from '@/components/common/PageTitle'; -const Page = () => { +export default function EditMyInfoPage() { return ( <div className="flex w-full flex-col gap-4 rounded-sm bg-white p-5"> - <Title title="내 정보 수정" /> + <PageTitle title="내 정보 수정" /> <div className="flex h-40 w-full items-center justify-center text-comment">개발 중인 기능입니다.</div> </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/(client)/my-page/layout.tsx b/frontend/src/app/(client)/my-page/layout.tsx new file mode 100644 index 00000000..34b7f7e5 --- /dev/null +++ b/frontend/src/app/(client)/my-page/layout.tsx @@ -0,0 +1,5 @@ +import SidebarLayout from '@/components/layout/SidebarLayout'; + +export default function Layout({ children }: Readonly<{ children: React.ReactNode }>) { + return <SidebarLayout>{children}</SidebarLayout>; +} diff --git a/frontend/src/app/(client)/my-page/milestone-list/page.tsx b/frontend/src/app/(client)/my-page/milestone-list/page.tsx new file mode 100644 index 00000000..d2919704 --- /dev/null +++ b/frontend/src/app/(client)/my-page/milestone-list/page.tsx @@ -0,0 +1,23 @@ +import Link from 'next/link'; + +import PageTitle from '@/components/common/PageTitle'; +import MilestoneHistoryTable from '@/components/ui/milestone/MilestoneHistoryTable'; + +export interface MilestoneRegisterPageProps { + searchParams?: { [key: string]: string | undefined }; +} + +export default async function MilestoneHistoryListPage({ searchParams }: MilestoneRegisterPageProps) { + const pageNumber = searchParams?.page ? parseInt(searchParams.page, 10) : 1; + return ( + <div className="rounded-sm bg-white p-5"> + <div className="mb-10 flex items-center justify-between"> + <PageTitle title="실적 등록" description="나의 마일스톤 실적 결과 등록" /> + <Link href="/my-page/milestone-register" className="rounded-sm bg-primary-main px-5 py-1 text-white"> + 실적 등록 + </Link> + </div> + <MilestoneHistoryTable pageNumber={pageNumber} /> + </div> + ); +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/write/page.tsx b/frontend/src/app/(client)/my-page/milestone-register/page.tsx similarity index 90% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/write/page.tsx rename to frontend/src/app/(client)/my-page/milestone-register/page.tsx index 6584ae34..6ac54d60 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/write/page.tsx +++ b/frontend/src/app/(client)/my-page/milestone-register/page.tsx @@ -1,23 +1,20 @@ -/* eslint-disable implicit-arrow-linebreak */ - 'use client'; import { Form, Formik } from 'formik'; -import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import * as Yup from 'yup'; import { toast } from 'react-toastify'; -import { DatePicker } from '@/components/Formik/DatePicker'; -import { FileUploader } from '@/components/Formik/FileUploader'; -import { TextInput } from '@/app/(client)/components/Formik/TextInput'; -import PageTitle from '@/app/(client)/components/PageTitle'; +import { DatePicker } from '@/components/common/formik/DatePicker'; +import { FileUploader } from '@/components/common/formik/FileUploader'; +import TextInput from '@/components/common/formik/TextInput'; +import PageTitle from '@/components/common/PageTitle'; import { useMilestoneHistoryCreateMutation } from '@/lib/hooks/useApi'; import { MilestoneHistoryCreateDto } from '@/types/common.dto'; import { Milestone, MilestoneCategory } from '@/types/milestone'; -import MilestoneDropdown from './components/MilestoneDropdown'; +import MilestoneCategoryDropdown from '@/components/ui/milestone/MilestoneCategoryDropdown'; const validationSchema = Yup.object().shape({ milestoneId: Yup.number().min(1, '활동 구분을 선택해주세요.'), @@ -52,7 +49,7 @@ const initialValues: MilestoneHistoryInfo = { activatedAt: '', }; -const Page = () => { +export default function MilestoneRegisterPage() { const router = useRouter(); const { mutate: createMilestoneHistory } = useMilestoneHistoryCreateMutation(); const [selectedCategory, setSelectedCategory] = useState<MilestoneCategory>(); @@ -60,9 +57,10 @@ const Page = () => { const handleSubmitButtonClick = (values: MilestoneHistoryCreateDto) => { createMilestoneHistory(values, { - onSuccess: () => { + onSuccess: (res) => { + console.log(res); toast.info('실적 등록에 성공하였습니다.'); - router.push('/my-page/milestone/register'); + // router.push('/my-page/milestone-list'); }, onError: () => { toast.error('실적 등록에 실패하였습니다.'); @@ -72,7 +70,7 @@ const Page = () => { return ( <div className="rounded-sm bg-white p-5"> - <PageTitle title="실적 등록" description="" urlText="" url="" /> + <PageTitle title="실적 등록" /> <p className="mb-10 mt-6 flex items-center justify-between border-b border-black py-4 text-lg font-bold"> 실적 등록하기 </p> @@ -86,7 +84,7 @@ const Page = () => { > {({ isSubmitting, values, touched, handleChange, handleBlur, setFieldValue, errors }) => ( <Form className="flex flex-col gap-4"> - <MilestoneDropdown + <MilestoneCategoryDropdown label="" selectOptionText="선택" categoryId={values.categoryId} @@ -105,7 +103,6 @@ const Page = () => { name="unitScore" label="건당 점수" type="text" - defaultValue={0} value={selectedMilestone?.score || 0} disabled /> @@ -129,8 +126,7 @@ const Page = () => { name="totalScore" label="총 점수" type="text" - defaultValue={0} - value={(selectedMilestone?.score ?? 0) * values.count} + value={!selectedMilestone?.score ? 0 : selectedMilestone.score * values.count} disabled /> </div> @@ -200,6 +196,4 @@ const Page = () => { </Formik> </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/page.tsx b/frontend/src/app/(client)/my-page/milestone/page.tsx similarity index 50% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/page.tsx rename to frontend/src/app/(client)/my-page/milestone/page.tsx index 02206d48..864fa3b0 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/page.tsx +++ b/frontend/src/app/(client)/my-page/milestone/page.tsx @@ -1,18 +1,16 @@ 'use client'; -import { DateTime } from 'luxon'; import { useEffect, useState } from 'react'; +import { DateTime } from 'luxon'; +import { useSearchParams } from 'next/navigation'; -import { COLOR } from '@/constants'; -import { Period } from '@/types/common'; +import PeriodSearchBox from '@/components/common/PeriodSearchBox'; +import MyPageMilestoneOverview from '@/components/ui/my-page/MyPageMilestoneOverview'; +import MilestoneAcceptedTable from '@/components/ui/milestone/MilestoneAcceptedTable'; -import MilestoneHistoryTable from './components/MilestoneHistoryTable'; -import MilestoneOverview from './components/MilestoneOverview'; -import { Content, SubTitle } from './styled'; -import MilestonePeriodSearchForm from '@/components/MilestonePeriodSearchForm'; -import { useSearchParams } from 'next/navigation'; +import { Period } from '@/types/common'; -const Page = () => { +export default function MyPageMilestonePage() { const searchParams = useSearchParams(); const [pageNumber, setPageNumber] = useState<number>(1); const [filterPeriod, setFilterPeriod] = useState<Period>({ @@ -29,22 +27,16 @@ const Page = () => { }, [searchParams]); return ( - <Content> + <div className="w-full rounded-sm bg-white p-5"> <div className="mb-6 flex flex-wrap justify-between gap-4"> <p className="min-w-[10em] text-xl font-bold">마일스톤 획득 내역</p> - <MilestonePeriodSearchForm - setFilterPeriod={setFilterPeriod} - filterPeriod={filterPeriod} - setSearchFilterPeriod={setSearchFilterPeriod} - /> + <PeriodSearchBox setPeriod={setFilterPeriod} period={filterPeriod} setSearchPeriod={setSearchFilterPeriod} /> </div> - <SubTitle>전체 현황</SubTitle> - <MilestoneOverview searchFilterPeriod={searchFilterPeriod} /> - <div style={{ borderBottom: `1px dotted ${COLOR.border}`, margin: '30px 0px' }} /> - <SubTitle>획득 내역</SubTitle> - <MilestoneHistoryTable searchFilterPeriod={searchFilterPeriod} pageNumber={pageNumber} pageSize={5} /> - </Content> + <div className="mb-6 text-lg font-bold">전체 현황</div> + <MyPageMilestoneOverview searchFilterPeriod={searchFilterPeriod} /> + <div className="my-7 border-b border-dotted border-border" /> + <div className="mb-6 text-lg font-bold">획득 내역</div> + <MilestoneAcceptedTable searchFilterPeriod={searchFilterPeriod} pageNumber={pageNumber} pageSize={5} /> + </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/(client)/my-page/page.tsx b/frontend/src/app/(client)/my-page/page.tsx new file mode 100644 index 00000000..5afe2c5d --- /dev/null +++ b/frontend/src/app/(client)/my-page/page.tsx @@ -0,0 +1,19 @@ +import PageSubTitle from '@/components/common/PageSubTitle'; + +import MyPageMilestoneHistory from '@/components/ui/my-page/MyPageMilestoneHistory'; +import MyPageMilestone from '@/components/ui/my-page/MyPageMilestone'; +import MyPageStudentInfo from '@/components/ui/my-page/MyPageStudentInfo'; + +export default function MyPage() { + return ( + <div className="flex w-full flex-wrap gap-5"> + <MyPageStudentInfo /> + <MyPageMilestone /> + <MyPageMilestoneHistory /> + <div className="flex-grow rounded-sm bg-white p-5"> + <PageSubTitle title="내가 쓴 팀빌딩 글" urlText="전체보기" url="/my-page/team-building" /> + <div className="p-20 text-center text-comment">아직 준비중인 기능이에요.</div> + </div> + </div> + ); +} diff --git a/frontend/src/app/(client)/page.tsx b/frontend/src/app/(client)/page.tsx index f4d3cee3..c438140d 100644 --- a/frontend/src/app/(client)/page.tsx +++ b/frontend/src/app/(client)/page.tsx @@ -1,31 +1,30 @@ -import Announcement from './components/Announcement'; -import ExternalLink from './components/ExternalLink'; -import Milestone from './components/Milestone'; -import PnuLink from './components/PnuLink'; -import TeamBuildings from './components/TeamBuildings'; -import { AnnouncementContent, ContentWrapper, MilestoneWrapper, FlexWrapper, MainPageWrapper } from './styled'; +import HomeAnnouncement from '@/components/ui/home/HomeAnnouncement'; +import HomeExternalLink from '@/components/ui/home/HomeExternalLink'; +import HomeMilestone from '@/components/ui/home/HomeMilestone'; +import HomePnuLink from '@/components/ui/home/HomePnuLink'; +import HomeTeamBuilding from '@/components/ui/home/HomeTeamBuilding'; const Page = () => ( - <MainPageWrapper> - <FlexWrapper> - <MilestoneWrapper> - <Milestone /> - </MilestoneWrapper> - <AnnouncementContent> - <Announcement /> - </AnnouncementContent> - </FlexWrapper> - <ContentWrapper> - <ExternalLink /> - </ContentWrapper> - {/* TODO: 팀빌딩 구현 완료 되면 주석 풀기 - <ContentWrapper> - <TeamBuildings /> - </ContentWrapper> */} - <ContentWrapper> - <PnuLink /> - </ContentWrapper> - </MainPageWrapper> + <div className="mx-auto flex max-w-client-max flex-col gap-10 px-10 pb-12 pt-12 lg:pt-20"> + <div className="flex flex-col md:flex-row md:gap-3"> + <div className="mt-10 w-full shrink-0 p-3 md:w-[380px] lg:w-[500px]"> + <HomeMilestone /> + </div> + <div className="mt-10 w-full min-w-0 grow p-3"> + <HomeAnnouncement /> + </div> + </div> + <div className="w-full p-3"> + <HomeExternalLink /> + </div> + {/* TODO: 팀빌딩 구현 완료 되면 주석 풀기 */} + {/* <div className='w-full p-3'> + <HomeTeamBuilding /> + </div> */} + <div className="w-full p-3"> + <HomePnuLink /> + </div> + </div> ); export default Page; diff --git a/frontend/src/app/(client)/styled.ts b/frontend/src/app/(client)/styled.ts deleted file mode 100644 index a7c3a7d8..00000000 --- a/frontend/src/app/(client)/styled.ts +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { MAX_WIDTH, RESPONSIVE_WIDTH } from '@/constants'; - -export const MainPageWrapper = styled.div` - max-width: ${MAX_WIDTH}; - min-height: calc(100vh - 200px); - margin: auto; - padding: 77px 0 40px; - display: flex; - flex-direction: column; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - padding-top: 50px; - } -`; - -export const ContentWrapper = styled.div` - width: 100%; - margin-top: 40px; - padding: 10px; -`; - -export const FlexWrapper = styled.div` - display: flex; - gap: 10px; - @media screen and (max-width: ${RESPONSIVE_WIDTH.tablet}) { - flex-direction: column; - gap: 0; - } -`; - -export const MilestoneWrapper = styled(ContentWrapper)` - width: 500px; - flex-shrink: 0; - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - width: 380px; - } - - @media screen and (max-width: ${RESPONSIVE_WIDTH.tablet}) { - width: 100%; - } -`; - -export const AnnouncementContent = styled(ContentWrapper)` - flex-grow: 1; - min-width: 0; -`; diff --git a/frontend/src/app/(client)/team-building/layout.tsx b/frontend/src/app/(client)/team-building/layout.tsx new file mode 100644 index 00000000..34b7f7e5 --- /dev/null +++ b/frontend/src/app/(client)/team-building/layout.tsx @@ -0,0 +1,5 @@ +import SidebarLayout from '@/components/layout/SidebarLayout'; + +export default function Layout({ children }: Readonly<{ children: React.ReactNode }>) { + return <SidebarLayout>{children}</SidebarLayout>; +} diff --git a/frontend/src/app/(client)/(withSidebar)/team-building/page.tsx b/frontend/src/app/(client)/team-building/page.tsx similarity index 60% rename from frontend/src/app/(client)/(withSidebar)/team-building/page.tsx rename to frontend/src/app/(client)/team-building/page.tsx index ba587ac6..8b7aecbd 100644 --- a/frontend/src/app/(client)/(withSidebar)/team-building/page.tsx +++ b/frontend/src/app/(client)/team-building/page.tsx @@ -1,13 +1,11 @@ -import Title from '@/components/Title'; +import PageTitle from '@/components/common/PageTitle'; -const Page = () => { +export default function TeamBuildingPage() { return ( <div className="flex w-full flex-col gap-4 rounded-sm bg-white p-5"> - <Title title="팀 빌딩" /> + <PageTitle title="팀 빌딩" /> <div className="flex h-40 w-full items-center justify-center text-comment">개발 중인 기능입니다.</div> </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/contest/create/page.tsx b/frontend/src/app/admin/contest/create/page.tsx index bf4f4cdf..5435e9ab 100644 --- a/frontend/src/app/admin/contest/create/page.tsx +++ b/frontend/src/app/admin/contest/create/page.tsx @@ -1,9 +1,7 @@ -const Page = () => { +export default function ContestCreatePage() { return ( <div className="w-full"> <div className="flex h-40 w-full items-center justify-center text-comment">개발 중인 기능입니다.</div> </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/contest/list/page.tsx b/frontend/src/app/admin/contest/page.tsx similarity index 79% rename from frontend/src/app/admin/contest/list/page.tsx rename to frontend/src/app/admin/contest/page.tsx index bf4f4cdf..a42d4efe 100644 --- a/frontend/src/app/admin/contest/list/page.tsx +++ b/frontend/src/app/admin/contest/page.tsx @@ -1,9 +1,7 @@ -const Page = () => { +export default function ContestListPage() { return ( <div className="w-full"> <div className="flex h-40 w-full items-center justify-center text-comment">개발 중인 기능입니다.</div> </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/faculty/list/page.tsx b/frontend/src/app/admin/faculty/page.tsx similarity index 73% rename from frontend/src/app/admin/faculty/list/page.tsx rename to frontend/src/app/admin/faculty/page.tsx index d9d52c7e..6b9ee726 100644 --- a/frontend/src/app/admin/faculty/list/page.tsx +++ b/frontend/src/app/admin/faculty/page.tsx @@ -1,17 +1,19 @@ -/* eslint-disable max-len */ - import { headers } from 'next/headers'; -import Pagination from '@/adminComponents/Pagination'; -import SearchBox from '@/components/SearchBox'; +import AdminSearchBox from '@/components/common/admin/AdminSearchBox'; +import AdminPagination from '@/components/common/admin/AdminPagination'; +import FacultyMemberTable from '@/components/ui/admin/faculty/FacultyMemberTable'; import { facultyFieldCategories, members } from '@/mocks/adminMember'; -import MemberTable from './components/MemberTable'; import { getFacultyMembers } from '@/lib/api/server.api'; import { AuthSliceState } from '@/store/auth.slice'; import { getAuthFromCookie } from '@/lib/utils/auth'; -const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | undefined } }) => { +export interface FacultyListPageProps { + searchParams?: { [key: string]: string | undefined }; +} + +export default async function FacultyListPage({ searchParams }: FacultyListPageProps) { const headersList = headers(); const pathname = headersList.get('x-pathname') || ''; @@ -29,18 +31,18 @@ const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | <span className="mr-20"> 총 <span className="text-admin-primary-main">{members.length}</span>명의 회원이 있습니다. </span> - <SearchBox + <AdminSearchBox initialValues={{ field, keyword }} fieldCategories={facultyFieldCategories} - path="/admin/faculty/list" + path="/admin/faculty" /> </div> {facultyMembers.content.length === 0 ? ( <div className="p-20 text-center text-lg font-bold">조건에 부합하는 교직원이 없습니다.</div> ) : ( <> - <MemberTable members={facultyMembers.content} /> - <Pagination + <FacultyMemberTable members={facultyMembers.content} /> + <AdminPagination currentPage={page} totalItems={facultyMembers.totalElements} pathname={pathname} @@ -50,6 +52,4 @@ const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | )} </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/faculty/register/page.tsx b/frontend/src/app/admin/faculty/register/page.tsx index e8bf2bda..6cac65f5 100644 --- a/frontend/src/app/admin/faculty/register/page.tsx +++ b/frontend/src/app/admin/faculty/register/page.tsx @@ -1,23 +1,15 @@ -/* eslint-disable import/no-unresolved */ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable jsx-a11y/label-has-associated-control */ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable max-len */ - 'use client'; -import { Form, Formik } from 'formik'; +import { useState } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { toast } from 'react-toastify'; +import { Form, Formik } from 'formik'; import * as Yup from 'yup'; -import 'react-toastify/dist/ReactToastify.css'; -import EmailTextInput from '@/components/Formik/EmailTextInput'; -import TextInput from '@/components/Formik/TextInput'; +import EmailTextInput from '@/components/common/formik/EmailTextInput'; +import TextInput from '@/components/common/formik/TextInput'; import { useRegisterFacultiesByFileMutation, useRegisterFacultyMutation } from '@/lib/hooks/useAdminApi'; -import { useState } from 'react'; interface FormType { email: string; @@ -37,7 +29,7 @@ const validationSchema = Yup.object().shape({ name: Yup.string().required('필수 입력란입니다. 이름을 입력해주세요.'), }); -const Page = () => { +export default function FacultyRegisterPage() { const { mutate: registerFaculty } = useRegisterFacultyMutation(); const { mutate: registerFacultiesByFile } = useRegisterFacultiesByFileMutation(); const [errorMessage, setErrorMessage] = useState<String>(); @@ -91,7 +83,13 @@ const Page = () => { <p className="text-lg font-semibold">교직원 일괄 등록 방법</p> <ul className="flex flex-col gap-2 px-4 pb-8 pt-4"> <li className="flex text-base"> - <Image src="/images/admin/xlsx_icon.svg" alt="xlsx" width="16" height="16" /> + <Image + src="/images/admin/xlsx_icon.svg" + alt="xlsx" + width="16" + height="16" + style={{ width: 16, height: 16 }} + /> <button type="button" className="pl-[0.5px] text-green-500 underline underline-offset-4" @@ -197,6 +195,4 @@ const Page = () => { </div> </> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx index 9efb636e..162447d7 100644 --- a/frontend/src/app/admin/layout.tsx +++ b/frontend/src/app/admin/layout.tsx @@ -1,18 +1,18 @@ -/* eslint-disable @next/next/no-page-custom-font */ +import AdminFooter from '@/components/layout/AdminFooter'; +import AdminHeader from '@/components/layout/AdminHeader'; +import AdminSidebar from '@/components/layout/AdminSidebar'; -import AdminFooter from '@/adminComponents/Footer'; -import AdminHeader from '@/adminComponents/Header'; -import AdminSidebar from '@/adminComponents/Sidebar'; -import { ADMIN_HEADER_HEIGHT, ADMIN_SIDEBAR_WIDTH, COLOR } from '@/adminConstants'; - -const Layout = ({ children }: Readonly<{ children: React.ReactNode }>) => ( - <> - <AdminHeader /> - <AdminSidebar /> - <div className={`min-h-[100vh] w-[100vw] bg-[${COLOR.secondary.light}] pt-adminHeaderHeight pl-adminSidebarWidth`}> - <section className="relative m-5 min-h-[80vh] min-w-admin rounded-sm bg-white p-5">{children}</section> - <AdminFooter /> - </div> - </> -); -export default Layout; +export default function AdminLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + <> + <AdminHeader /> + <AdminSidebar /> + <div className={`min-h-[100vh] w-[100vw] pl-adminSidebarWidth pt-adminHeaderHeight`}> + <section className="relative m-5 min-h-[calc(100vh-theme(height.admin-header)-150px)] min-w-admin rounded-sm bg-white p-5"> + {children} + </section> + <AdminFooter /> + </div> + </> + ); +} diff --git a/frontend/src/app/admin/member/list/components/MemberTable/index.tsx b/frontend/src/app/admin/member/list/components/MemberTable/index.tsx deleted file mode 100644 index b6854d52..00000000 --- a/frontend/src/app/admin/member/list/components/MemberTable/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable prettier/prettier */ -import { StudentMemberDto } from '@/types/common.dto'; - -const MemberTable = ({ members }: { members: StudentMemberDto[] }) => ( - <table className="my-4 w-full table-fixed text-center text-sm [&_*]:cursor-default"> - <thead className="border-y-2 border-admin-border [&_th]:p-2"> - <th className="w-[100px]">이메일</th> - <th className="w-[60px]">이름</th> - <th className="w-[80px]">학번</th> - <th className="w-[100px]">주전공</th> - <th className="w-[100px]">부전공</th> - <th className="w-[100px]">복수전공</th> - <th className="w-[80px]">전화번호</th> - <th className="w-[100px]">진로</th> - </thead> - <tbody> - {members.map((member) => { - const emailWords = member.email.split('@'); - return ( - <tr key={member.id} className="h-[50px] border-b-[1px] border-admin-border [&_td]:break-keep [&_td]:p-2"> - <td> - {emailWords[0]} <br /> - <span className="text-xs text-admin-comment">@pusan.ac.kr</span> - </td> - <td className="font-semibold">{member.name}</td> - <td>{member.id}</td> - <td>{member.major}</td> - <td>{member.minor}</td> - <td>{member.doubleMajor}</td> - <td>{member.phoneNumber}</td> - <td className="overflow-hidden text-ellipsis whitespace-nowrap hover:whitespace-normal"> - {member.careerDetail} - </td> - </tr> - ); - })} - </tbody> - </table> -); - -export default MemberTable; diff --git a/frontend/src/app/admin/member/page.tsx b/frontend/src/app/admin/member/page.tsx deleted file mode 100644 index c703c9c7..00000000 --- a/frontend/src/app/admin/member/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { redirect } from 'next/navigation'; - -const Page = () => { - redirect('/admin/member/list'); - return null; -}; - -export default Page; diff --git a/frontend/src/app/admin/milestone/list/[slug]/components/FilePreview/index.tsx b/frontend/src/app/admin/milestone/[slug]/components/FilePreview/index.tsx similarity index 100% rename from frontend/src/app/admin/milestone/list/[slug]/components/FilePreview/index.tsx rename to frontend/src/app/admin/milestone/[slug]/components/FilePreview/index.tsx diff --git a/frontend/src/app/admin/milestone/list/[slug]/components/MilestoneHistoryStatusChangeButton/index.tsx b/frontend/src/app/admin/milestone/[slug]/components/MilestoneHistoryStatusChangeButton/index.tsx similarity index 94% rename from frontend/src/app/admin/milestone/list/[slug]/components/MilestoneHistoryStatusChangeButton/index.tsx rename to frontend/src/app/admin/milestone/[slug]/components/MilestoneHistoryStatusChangeButton/index.tsx index 2a71a13b..5ebc1b88 100644 --- a/frontend/src/app/admin/milestone/list/[slug]/components/MilestoneHistoryStatusChangeButton/index.tsx +++ b/frontend/src/app/admin/milestone/[slug]/components/MilestoneHistoryStatusChangeButton/index.tsx @@ -1,12 +1,10 @@ -/* eslint-disable implicit-arrow-linebreak */ - 'use client'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { toast } from 'react-toastify'; -import TextInput from '@/components/Formik/TextInput'; +import TextInput from '@/components/common/formik/TextInput'; import { MilestoneHistoryStatus } from '@/data/milestone'; import { useMilestoneHistoryStatusApproveMutation, @@ -83,7 +81,7 @@ const MilestoneHistoryStatusChangeButton = ({ historyId, status }: MilestoneHist <button type="button" onClick={handleRejectButtonClick} - className="hover:bg-admin-semantic-error-main bg-admin-semantic-error-light rounded-sm py-2 text-white" + className="rounded-sm bg-admin-semantic-error-light py-2 text-white hover:bg-admin-semantic-error-main" > 반려 </button> diff --git a/frontend/src/app/admin/milestone/list/[slug]/not-found.tsx b/frontend/src/app/admin/milestone/[slug]/not-found.tsx similarity index 93% rename from frontend/src/app/admin/milestone/list/[slug]/not-found.tsx rename to frontend/src/app/admin/milestone/[slug]/not-found.tsx index 713737a5..2d1684f4 100644 --- a/frontend/src/app/admin/milestone/list/[slug]/not-found.tsx +++ b/frontend/src/app/admin/milestone/[slug]/not-found.tsx @@ -7,7 +7,7 @@ export default function NotFound() { <h2 className="text-xl font-semibold">404 Not Found</h2> <p>요청하신 리소스를 찾을 수 없습니다.</p> <Link - href="/admin/milestone/list" + href="/admin/milestone" className="mt-4 rounded-sm bg-admin-primary-main px-4 py-2 text-sm text-white hover:bg-admin-primary-dark" > 목록으로 diff --git a/frontend/src/app/admin/milestone/list/[slug]/page.tsx b/frontend/src/app/admin/milestone/[slug]/page.tsx similarity index 90% rename from frontend/src/app/admin/milestone/list/[slug]/page.tsx rename to frontend/src/app/admin/milestone/[slug]/page.tsx index 92ea9b8e..ed7990cd 100644 --- a/frontend/src/app/admin/milestone/list/[slug]/page.tsx +++ b/frontend/src/app/admin/milestone/[slug]/page.tsx @@ -1,15 +1,15 @@ -/* eslint-disable max-len */ import Link from 'next/link'; +import { notFound } from 'next/navigation'; + +import MilestoneGroupLabel from '@/components/ui/milestone/MilestoneGroupLabel'; +import AdminMilestoneFilePreview from '@/components/ui/admin/milestone/AdminMilestoneFilePreview'; +import AdminMilestoneStatusChangeButton from '@/components/ui/admin/milestone/AdminMilestoneStatusChangeButton'; -import MilestoneGroupLabel from '@/components/MilestoneGroupLabel'; import { getMilestoneHistory } from '@/lib/api/server.api'; import { convertMilestoneHistoryStatus } from '@/lib/utils/utils'; +import { getAuthFromCookie } from '@/lib/utils/auth'; -import FilePreview from './components/FilePreview'; -import MilestoneHistoryStatusChangeButton from './components/MilestoneHistoryStatusChangeButton'; -import { notFound } from 'next/navigation'; import { AuthSliceState } from '@/store/auth.slice'; -import { getAuthFromCookie } from '@/lib/utils/auth'; interface MilestoneHistoryDetailPageProps { params: { @@ -103,20 +103,20 @@ const Page = async ({ params: { slug } }: MilestoneHistoryDetailPageProps) => { <span className="flex-grow">{history.rejectReason}</span> </p> )} - <MilestoneHistoryStatusChangeButton historyId={history.id} status={history.status} /> + <AdminMilestoneStatusChangeButton historyId={history.id} status={history.status} /> </div> </div> <div className="flex flex-1 flex-col"> <p className="text-lg font-bold">증빙자료 미리보기</p> <div className="my-4 flex flex-grow flex-col items-center justify-center gap-2 rounded-md border border-border p-2 px-4 text-center"> - <FilePreview fileName={history.fileUrl} /> + <AdminMilestoneFilePreview fileName={history.fileUrl} /> </div> </div> </div> <div className="flex justify-end"> <Link - href="/admin/milestone/list" + href="/admin/milestone" className="rounded-sm border-2 border-border px-4 py-2 hover:bg-admin-background-light" > 목록으로 diff --git a/frontend/src/app/admin/milestone/list/components/MilestoneHistoryTable/index.tsx b/frontend/src/app/admin/milestone/list/components/MilestoneHistoryTable/index.tsx deleted file mode 100644 index 728f0f2b..00000000 --- a/frontend/src/app/admin/milestone/list/components/MilestoneHistoryTable/index.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable max-len */ -/* eslint-disable jsx-a11y/control-has-associated-label */ -import Link from 'next/link'; - -import { MilestoneHistoryStatus } from '@/data/milestone'; -import { convertMilestoneHistoryStatus } from '@/lib/utils/utils'; -import { MilestoneHistoryDto } from '@/types/common.dto'; - -interface MilestoneHistoryTableProps { - histories: MilestoneHistoryDto[]; -} - -const MilestoneHistoryTable = ({ histories }: MilestoneHistoryTableProps) => { - const getHistoryStatus = (status: string) => { - switch (status) { - case MilestoneHistoryStatus.PENDING: - return <span className="text-lg font-bold text-admin-comment">{convertMilestoneHistoryStatus(status)}</span>; - case MilestoneHistoryStatus.APPROVED: - return ( - <span className="text-lg font-bold text-admin-primary-light">{convertMilestoneHistoryStatus(status)}</span> - ); - case MilestoneHistoryStatus.REJECTED: - return ( - <span className="text-admin-semantic-error-main text-lg font-bold"> - {convertMilestoneHistoryStatus(status)} - </span> - ); - default: - return convertMilestoneHistoryStatus(status); - } - }; - - return ( - <table className="my-4 w-full table-fixed text-center text-sm [&_*]:cursor-default"> - <thead className="border-y-2 border-admin-border [&_th]:p-2"> - <tr> - <th className="w-[60px]">No.</th> - <th className="w-[60px]">이름</th> - <th className="w-[80px]">학번</th> - <th className="w-[100px]">활동 코드</th> - <th className="w-full">활동명</th> - <th className="w-[80px]">건당 점수</th> - <th className="w-[80px]">활동 횟수(건)</th> - <th className="w-[100px]">활동일</th> - <th className="w-[100px]">등록일</th> - <th className="w-[100px]">승인 여부</th> - </tr> - </thead> - <tbody> - {histories?.map((history) => ( - <tr - key={history.id} - className="cursor-pointer border-b-[1px] border-admin-border hover:bg-admin-background-light [&_td]:h-[50px] [&_td]:break-keep" - > - <td> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.id} - </Link> - </td> - <td className="font-semibold"> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.student.name} - </Link> - </td> - <td className="font-semibold"> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.student.id} - </Link> - </td> - <td> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.milestone.id} - </Link> - </td> - <td className="text-left"> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-start" - > - {history.description} - </Link> - </td> - <td> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.milestone.score} - </Link> - </td> - <td> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.count} - </Link> - </td> - <td> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.activatedAt} - </Link> - </td> - <td> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {history.createdAt.slice(0, 10)} - </Link> - </td> - <td> - <Link - href={`/admin/milestone/list/${history.id}`} - className="flex h-full w-full items-center justify-center" - > - {getHistoryStatus(history.status)} - </Link> - </td> - </tr> - ))} - </tbody> - </table> - ); -}; - -export default MilestoneHistoryTable; diff --git a/frontend/src/app/admin/milestone/list/page.tsx b/frontend/src/app/admin/milestone/page.tsx similarity index 74% rename from frontend/src/app/admin/milestone/list/page.tsx rename to frontend/src/app/admin/milestone/page.tsx index 7120f81f..19dd6d24 100644 --- a/frontend/src/app/admin/milestone/list/page.tsx +++ b/frontend/src/app/admin/milestone/page.tsx @@ -1,15 +1,14 @@ -/* eslint-disable max-len */ import { headers } from 'next/headers'; -import Pagination from '@/adminComponents/Pagination'; -import SearchBox from '@/components/SearchBox'; +import AdminPagination from '@/components/common/admin/AdminPagination'; +import AdminSearchBox from '@/components/common/admin/AdminSearchBox'; +import AdminMilestoneTable from '@/components/ui/admin/milestone/AdminMilestoneTable'; +import AdminMilestoneDownloadButton from '@/components/ui/admin/milestone/AdminMilestoneDownloadButton'; + import { milestoneHistorySearchField } from '@/data/milestone'; import { getMilestoneHistories } from '@/lib/api/server.api'; - -import MilestoneHistoryTable from './components/MilestoneHistoryTable'; -import MilestoneHistoryExcelFileDownloadButton from './components/MilestoneHistoryTable/MilestoneHistoryExcelFileDownloadButton.tsx'; -import { AuthSliceState } from '@/store/auth.slice'; import { getAuthFromCookie } from '@/lib/utils/auth'; +import { AuthSliceState } from '@/store/auth.slice'; const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | undefined } }) => { const headersList = headers(); @@ -35,17 +34,17 @@ const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | 총 <span className="text-admin-primary-main">{milestoneHistories?.totalElements ?? 0}</span>건의 내역이 있습니다. </span> - <SearchBox + <AdminSearchBox initialValues={{ field, keyword }} fieldCategories={milestoneHistorySearchField} - path="/admin/milestone/list" + path="/admin/milestone" /> </div> - <MilestoneHistoryTable histories={milestoneHistories?.content || []} /> + <AdminMilestoneTable histories={milestoneHistories?.content || []} /> <div className="flex justify-end"> - <MilestoneHistoryExcelFileDownloadButton field={field} keyword={keyword} /> + <AdminMilestoneDownloadButton field={field} keyword={keyword} /> </div> - <Pagination + <AdminPagination currentPage={page} totalItems={milestoneHistories?.totalElements || 0} pathname={pathname} diff --git a/frontend/src/app/admin/milestone/rank/page.tsx b/frontend/src/app/admin/milestone/rank/page.tsx index a933de12..e1626c07 100644 --- a/frontend/src/app/admin/milestone/rank/page.tsx +++ b/frontend/src/app/admin/milestone/rank/page.tsx @@ -1,25 +1,20 @@ -/* eslint-disable prettier/prettier */ -/* eslint-disable operator-linebreak */ -/* eslint-disable function-paren-newline */ -/* eslint-disable implicit-arrow-linebreak */ -/* eslint-disable @typescript-eslint/no-unused-expressions */ - 'use client'; +import React from 'react'; +import { useMemo, useState } from 'react'; import { DateTime } from 'luxon'; import { usePathname, useSearchParams } from 'next/navigation'; -import { useMemo, useState } from 'react'; +import { toast } from 'react-toastify'; -import Pagination from '@/adminComponents/Pagination'; -import MilestonePeriodSearchForm from '@/components/MilestonePeriodSearchForm'; +import AdminPagination from '@/components/common/admin/AdminPagination'; +import PeriodSearchBox from '@/components/common/PeriodSearchBox'; import { MilestoneGroup } from '@/data/milestone'; import { useMilestoneHistoryScoreExcelFileQuery, useMilestoneScoresQuery } from '@/lib/hooks/useAdminApi'; import { useMilestoneQuery } from '@/lib/hooks/useApi'; import { convertMilestoneGroup } from '@/lib/utils/utils'; import { Period } from '@/types/common'; -import { toast } from 'react-toastify'; -const Page = () => { +export default function MilestoneRankPage() { const [filterPeriod, setFilterPeriod] = useState<Period>({ startDate: DateTime.now().minus({ years: 1 }).toFormat('yyyy-MM-dd'), endDate: DateTime.now().toFormat('yyyy-MM-dd'), @@ -28,7 +23,6 @@ const Page = () => { const searchParams = useSearchParams(); const pathname = usePathname(); const page = parseInt(searchParams.get('page') || '1', 10); - console.log(page); const { data: excelFile } = useMilestoneHistoryScoreExcelFileQuery( searchFilterPeriod.startDate, @@ -62,11 +56,7 @@ const Page = () => { return ( <div> <div className="mb-8 flex justify-end"> - <MilestonePeriodSearchForm - filterPeriod={filterPeriod} - setFilterPeriod={setFilterPeriod} - setSearchFilterPeriod={setSearchFilterPeriod} - /> + <PeriodSearchBox period={filterPeriod} setPeriod={setFilterPeriod} setSearchPeriod={setSearchFilterPeriod} /> </div> <div className="mb-8 min-h-[426px] w-full overflow-x-scroll text-xs"> <table className="border-collapse text-center"> @@ -77,9 +67,9 @@ const Page = () => { <th className="min-w-[10em] pb-2">학번</th> <th className="min-w-20 pb-2">총점</th> {Object.values(MilestoneGroup).map((group) => ( - <> + <React.Fragment key={`All-${group}`}> {milestones && - milestones[group]?.map((milestone) => ( + milestones[group].map((milestone) => ( <th key={milestone.id} className="min-w-20 break-keep p-2"> {milestone.name} </th> @@ -87,37 +77,38 @@ const Page = () => { <th key={group} className="min-w-20 break-keep p-2"> {convertMilestoneGroup(group)} SW역량 소계 </th> - </> + </React.Fragment> ))} </tr> </thead> <tbody> - {milestoneScores?.content?.map((milestoneScore, index) => ( - <tr key={milestoneScore.student.id} className="border-b border-border"> - <td>{milestoneScores!.size * milestoneScores!.number + index + 1}</td> - <td>{milestoneScore.student.name}</td> - <td className="border-r-2 border-border">{milestoneScore.student.id}</td> - <td className="min-w-20 bg-admin-primary-main p-2 font-bold text-admin-white"> - {Object.values(milestoneScore.milestoneScores).reduce( - (accumulator, currentValue) => - accumulator + currentValue.reduce((acc, curr) => acc + curr.score, 0), - 0, - )} - </td> - {Object.values(MilestoneGroup).map((group) => ( - <> - {milestoneScore.milestoneScores[group].map((score) => ( - <td key={score.id} className="min-w-20 border-r border-border p-2"> - {score.score} + {milestoneScores && + milestoneScores.content.map((milestoneScore, index) => ( + <tr key={milestoneScore.student.id} className="border-b border-border"> + <td>{milestoneScores!.size * milestoneScores!.number + index + 1}</td> + <td>{milestoneScore.student.name}</td> + <td className="border-r-2 border-border">{milestoneScore.student.id}</td> + <td className="min-w-20 bg-admin-primary-main p-2 font-bold text-admin-white"> + {Object.values(milestoneScore.milestoneScores).reduce( + (accumulator, currentValue) => + accumulator + currentValue.reduce((acc, curr) => acc + curr.score, 0), + 0, + )} + </td> + {Object.values(MilestoneGroup).map((group) => ( + <React.Fragment key={`All-${group}`}> + {milestoneScore.milestoneScores[group].map((score) => ( + <td key={score.id} className="min-w-20 border-r border-border p-2"> + {score.score} + </td> + ))} + <td key={group} className="min-w-20 bg-admin-background-point p-2 font-bold"> + {milestoneScore.milestoneScores[group].reduce((acc, curr) => acc + curr.score, 0)} </td> - ))} - <td key={group} className="min-w-20 bg-admin-background-point p-2 font-bold"> - {milestoneScore.milestoneScores[group].reduce((acc, curr) => acc + curr.score, 0)} - </td> - </> - ))} - </tr> - ))} + </React.Fragment> + ))} + </tr> + ))} </tbody> </table> </div> @@ -130,9 +121,7 @@ const Page = () => { Excel로 다운로드 </button> </div> - <Pagination currentPage={page} totalItems={milestoneScores?.totalElements ?? 0} pathname={pathname} /> + <AdminPagination currentPage={page} totalItems={milestoneScores?.totalElements ?? 0} pathname={pathname} /> </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/milestone/register/page.tsx b/frontend/src/app/admin/milestone/register/page.tsx index b933ea03..d7578463 100644 --- a/frontend/src/app/admin/milestone/register/page.tsx +++ b/frontend/src/app/admin/milestone/register/page.tsx @@ -1,16 +1,12 @@ -/* eslint-disable max-len */ -/* eslint-disable operator-linebreak */ -/* eslint-disable implicit-arrow-linebreak */ - 'use client'; -import { Form, Formik } from 'formik'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; +import { Form, Formik } from 'formik'; import { toast } from 'react-toastify'; import * as Yup from 'yup'; -import { FileUploader } from '@/components/Formik/FileUploader'; +import { FileUploader } from '@/components/common/formik/FileUploader'; import { useRegisterHistoryInBatchMutation } from '@/lib/hooks/useAdminApi'; const validationSchema = Yup.object().shape({ @@ -35,7 +31,7 @@ const initialValues: HistoryRegisterFormProps = { file: undefined, }; -const Page = () => { +export default function MilestoneRegisterPage() { const router = useRouter(); const { mutate: registerHistories } = useRegisterHistoryInBatchMutation(); @@ -43,7 +39,8 @@ const Page = () => { <> <div className="flex items-center rounded-sm border-[1px] border-admin-border bg-admin-background-light px-5 py-3 text-sm"> <p className="flex flex-1 justify-center gap-1"> - 점수 산정 기준 표 - <Image src="/images/admin/pdf_icon.svg" alt="pdf" width="16" height="16" /> + 점수 산정 기준 표 -{' '} + <Image src="/images/admin/pdf_icon.svg" alt="pdf" width="16" height="16" style={{ width: 16, height: 16 }} /> <a className="pl-[0.5px] text-red-500 underline underline-offset-4" href={process.env.NEXT_PUBLIC_FILE_URL + '/history_standard.pdf'} @@ -53,7 +50,14 @@ const Page = () => { </a> </p> <p className="flex flex-1 justify-center gap-1"> - 일괄등록 파일 예시 - <Image src="/images/admin/xlsx_icon.svg" alt="xlsx" width="16" height="16" /> + 일괄등록 파일 예시 -{' '} + <Image + src="/images/admin/xlsx_icon.svg" + alt="xlsx" + width="16" + height="16" + style={{ width: 16, height: 16 }} + /> <a className="pl-[0.5px] text-green-500 underline underline-offset-4" href={process.env.NEXT_PUBLIC_FILE_URL + '/history_register_sample.xlsx'} @@ -102,6 +106,4 @@ const Page = () => { </Formik> </> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/member/list/page.tsx b/frontend/src/app/admin/student/page.tsx similarity index 66% rename from frontend/src/app/admin/member/list/page.tsx rename to frontend/src/app/admin/student/page.tsx index e6723a65..68ddb6c9 100644 --- a/frontend/src/app/admin/member/list/page.tsx +++ b/frontend/src/app/admin/student/page.tsx @@ -1,15 +1,12 @@ -/* eslint-disable prettier/prettier */ -/* eslint-disable max-len */ - import { headers } from 'next/headers'; -import Pagination from '@/adminComponents/Pagination'; -import SearchBox from '@/components/SearchBox'; -import { fieldCategories, members } from '@/mocks/adminMember'; +import AdminPagination from '@/components/common/admin/AdminPagination'; +import AdminSearchBox from '@/components/common/admin/AdminSearchBox'; +import StudentMemberTable from '@/components/ui/admin/student/StudentMemberTable'; -import MemberTable from './components/MemberTable'; +import { fieldCategories, members } from '@/mocks/adminMember'; -const Page = ({ searchParams }: { searchParams?: { [key: string]: string | undefined } }) => { +export default function StudentListPage({ searchParams }: { searchParams?: { [key: string]: string | undefined } }) { const headersList = headers(); const pathname = headersList.get('x-pathname') || ''; @@ -27,10 +24,10 @@ const Page = ({ searchParams }: { searchParams?: { [key: string]: string | undef <span className="mr-20"> 총 <span className="text-admin-primary-main">{members.length}</span>명의 회원이 있습니다. </span> - <SearchBox initialValues={{ field, keyword }} fieldCategories={fieldCategories} path="/admin/member/list" /> + <AdminSearchBox initialValues={{ field, keyword }} fieldCategories={fieldCategories} path="/admin/student" /> </div> - <MemberTable members={members.slice((page - 1) * 10, page * 10)} /> - <Pagination + <StudentMemberTable members={members.slice((page - 1) * 10, page * 10)} /> + <AdminPagination currentPage={page} totalItems={members.length} pathname={pathname} @@ -38,6 +35,4 @@ const Page = ({ searchParams }: { searchParams?: { [key: string]: string | undef /> */} </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/admin/styled.ts b/frontend/src/app/admin/styled.ts deleted file mode 100644 index 9ef43657..00000000 --- a/frontend/src/app/admin/styled.ts +++ /dev/null @@ -1,64 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { COLOR, FONT_STYLE } from '@/adminConstants'; -import { BORDER_RADIUS } from '@/constants'; - -export const AdminButton = styled.button` - padding: 4px 8px; - border-radius: ${BORDER_RADIUS.sm}; - border: none; - font: ${FONT_STYLE.base.normal}; - cursor: pointer; -`; - -export const AdminBlueButton = styled(AdminButton)` - background-color: ${COLOR.primary.main}; - color: ${COLOR.white}; -`; - -export const AdminRedButton = styled(AdminButton)` - background-color: ${COLOR.semantic.error}; - color: ${COLOR.white}; -`; - -export const AdminBlackButton = styled(AdminButton)` - background-color: black; - color: ${COLOR.white}; -`; - -export const AdminGrayButton = styled(AdminButton)` - background-color: ${COLOR.secondary.light}; - border: 1px solid ${COLOR.secondary.main}; - color: ${COLOR.comment}; -`; - -export const AdminLink = styled.a` - padding: 4px 8px; - border-radius: ${BORDER_RADIUS.sm}; - border: none; - font: ${FONT_STYLE.base.normal}; - cursor: pointer; -`; - -export const AdminBlueLink = styled(AdminLink)` - background-color: ${COLOR.primary.main}; - color: ${COLOR.white}; -`; - -export const AdminRedLink = styled(AdminLink)` - background-color: ${COLOR.semantic.error}; - color: ${COLOR.white}; -`; - -export const AdminBlackLink = styled(AdminLink)` - background-color: black; - color: ${COLOR.white}; -`; - -export const AdminGrayLink = styled(AdminLink)` - background-color: ${COLOR.secondary.light}; - border: 1px solid ${COLOR.secondary.main}; - color: ${COLOR.comment}; -`; diff --git a/frontend/src/app/admin/team-building/page.tsx b/frontend/src/app/admin/team-building/page.tsx index bf4f4cdf..3ee4a72d 100644 --- a/frontend/src/app/admin/team-building/page.tsx +++ b/frontend/src/app/admin/team-building/page.tsx @@ -1,9 +1,7 @@ -const Page = () => { +export default function TeamBuildingPage() { return ( <div className="w-full"> <div className="flex h-40 w-full items-center justify-center text-comment">개발 중인 기능입니다.</div> </div> ); -}; - -export default Page; +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 063cc8b6..83655f57 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @next/next/no-page-custom-font */ import { Metadata } from 'next'; import { ToastContainer } from 'react-toastify'; @@ -7,6 +6,7 @@ import ReduxProvider from '@/lib/utils/reduxProvider'; import StyledComponentsRegistry from '@/theme/StyledComponentsRegistry'; import './globals.css'; +import 'react-toastify/ReactToastify.min.css'; export const metadata: Metadata = { title: '부산대학교 SW역량지원시스템', @@ -19,7 +19,6 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => ( <meta httpEquiv="Content-Security-Policy" content="upgrade-insecure-requests" /> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" /> - <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap" rel="stylesheet" /> </head> <body style={{ margin: 0 }}> <ReactQueryProvider> diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx deleted file mode 100644 index 58dd5967..00000000 --- a/frontend/src/components/Footer/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FooterWrapper, FooterLayout, FooterLogo, FooterDiv, FooterText, FooterLink } from './styled'; - -const Footer = () => ( - <FooterWrapper> - <FooterLayout> - <FooterLogo src="/images/logo/foot_logo.svg" alt="foot_logo" width="160" height="40" priority={false} /> - <FooterDiv> - <FooterText>(46241) 부산광역시 금정구 부산대학로 63번길 2 (장전동)</FooterText> - <FooterText>부산대학교 소프트웨어융합교육원</FooterText> - </FooterDiv> - <FooterLink href="/privacy">개인정보처리방침</FooterLink> - <FooterText>ⓒ 2021 PNUswedu. All Right Reserved.</FooterText> - <FooterText>TEL : 051-510-3737, 3738, 3624</FooterText> - </FooterLayout> - </FooterWrapper> -); - -export default Footer; diff --git a/frontend/src/components/Footer/styled.ts b/frontend/src/components/Footer/styled.ts deleted file mode 100644 index 64342039..00000000 --- a/frontend/src/components/Footer/styled.ts +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import Link from 'next/link'; -import styled from 'styled-components'; - -import { COLOR, FONT_STYLE, RESPONSIVE_WIDTH } from '@/constants'; - -export const FooterWrapper = styled.div` - width: 100vw; - min-height: 200px; - display: flex; - align-items: center; - justify-content: center; - background-color: ${COLOR.background.base}; -`; - -export const FooterLayout = styled.div` - width: 1200px; - display: grid; - grid-template-columns: 1fr 3fr 105px; - gap: 20px; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - grid-template-columns: 1fr; - justify-items: center; - } -`; - -export const FooterLogo = styled(Image)` - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - display: none; - } -`; - -export const FooterDiv = styled.div` - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - text-align: center; - } -`; - -export const FooterText = styled.p` - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.comment}; -`; - -export const FooterLink = styled(Link)` - width: fit-content; - height: fit-content; - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.comment}; - padding-bottom: 2px; - border-bottom: 1px solid ${COLOR.comment}; -`; diff --git a/frontend/src/components/GoPageIcon/index.tsx b/frontend/src/components/GoPageIcon/index.tsx deleted file mode 100644 index 15237611..00000000 --- a/frontend/src/components/GoPageIcon/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { VscAdd } from '@react-icons/all-files/vsc/VscAdd'; -import Link from 'next/link'; - -import { COLOR, FONT_STYLE } from '@/constants'; - -interface GoPageIconProps { - name: string; - url: string; -} - -const GoPageIcon = ({ name, url }: GoPageIconProps) => ( - <Link - href={url} - style={{ - display: 'flex', - color: COLOR.comment, - font: FONT_STYLE.sm.semibold, - alignItems: 'center', - gap: '4px', - }} - > - <VscAdd /> - {name} - </Link> -); - -export default GoPageIcon; diff --git a/frontend/src/components/Header/HeaderAccordion/index.tsx b/frontend/src/components/Header/HeaderAccordion/index.tsx deleted file mode 100644 index a9bd6ed6..00000000 --- a/frontend/src/components/Header/HeaderAccordion/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { CategoryDto } from '@/types/common.dto'; - -import { HeaderAccordionWrapper, Linker, Accordion, AccordionLink } from './styled'; - -const HeaderAccordion = ({ title, url, sub }: CategoryDto) => ( - <HeaderAccordionWrapper> - <Linker href={url}>{title}</Linker> - <Accordion> - {sub.map((item) => ( - <AccordionLink key={item.key} href={item.url}> - {item.title} - </AccordionLink> - ))} - </Accordion> - </HeaderAccordionWrapper> -); - -export default HeaderAccordion; diff --git a/frontend/src/components/Header/HeaderAccordion/styled.ts b/frontend/src/components/Header/HeaderAccordion/styled.ts deleted file mode 100644 index 36b98bdc..00000000 --- a/frontend/src/components/Header/HeaderAccordion/styled.ts +++ /dev/null @@ -1,51 +0,0 @@ -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const HeaderAccordionWrapper = styled.div` - position: relative; - height: 76px; - line-height: 76px; - align-items: center; - &:hover { - > div { - max-height: 200px; - } - } -`; - -export const Linker = styled(Link)` - padding: 0 40px; - font: ${FONT_STYLE.lg.semibold}; - &:hover { - color: ${COLOR.primary.dark}; - } -`; - -export const AccordionLink = styled(Link)` - display: block; - padding: 10px; - border-bottom: 1px solid ${COLOR.border}; - &:hover { - color: ${COLOR.primary.dark}; - } -`; - -export const Accordion = styled.div` - position: absolute; - width: 180px; - max-height: 0; - text-align: center; - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.comment}; - background-color: ${COLOR.white}; - border-bottom-left-radius: ${BORDER_RADIUS.sm}; - border-bottom-right-radius: ${BORDER_RADIUS.sm}; - box-shadow: 0px 2px 5px rgb(0, 0, 0, 0.25); - left: 50%; - top: 100%; - transform: translate(-50%, 0); - transition: max-height 0.4s ease-in-out; - overflow: hidden; -`; diff --git a/frontend/src/components/Header/Sidebar/index.tsx b/frontend/src/components/Header/Sidebar/index.tsx deleted file mode 100644 index 71900382..00000000 --- a/frontend/src/components/Header/Sidebar/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useState } from 'react'; - -import { COLOR } from '@/constants'; -import { CategoryDto } from '@/types/common.dto'; - -import * as S from './styled'; - -export interface SidebarProps { - open: boolean; - handleOpen: React.Dispatch<React.SetStateAction<boolean>>; - headerInfos: CategoryDto[]; -} - -const Sidebar = ({ open, handleOpen, headerInfos }: SidebarProps) => { - const [currTab, setCurrTab] = useState<string>(''); - - const handleClose = () => { - handleOpen((e) => !e); - setCurrTab(''); - }; - - return ( - <S.SidebarWrapper> - <S.HamburgerLogo onClick={handleClose} style={{ left: `${open ? '-200px' : 0}` }}> - <S.HamburgerLine - style={{ - transform: `${open ? 'translate(-50%, -50%) rotate(40deg)' : 'translate(-50%, calc(-50% - 12px))'}`, - }} - /> - <S.HamburgerLine style={{ transform: 'translate(-50%, -50%)', display: `${open ? 'none' : 'block'}` }} /> - <S.HamburgerLine - style={{ - transform: `${open ? 'translate(-50%, -50%) rotate(-40deg)' : 'translate(-50%, calc(-50% + 12px))'}`, - }} - /> - </S.HamburgerLogo> - <S.SidebarContent style={{ left: `${open ? '-150px' : '50px'}` }}> - {headerInfos.map((item) => ( - <S.SidebarContentLayout - key={item.title} - style={{ maxHeight: `${currTab === item.title ? '200px' : '42px'}` }} - > - <S.SidebarContentTitle - style={{ - color: `${currTab === item.title ? 'white' : 'black'}`, - backgroundColor: `${currTab === item.title ? COLOR.primary.dark : 'transparent'}`, - }} - onClick={() => setCurrTab(item.title)} - > - {item.title} - </S.SidebarContentTitle> - {item.sub.map((subItem) => ( - <S.SidebarContentSubTitle key={subItem.key} href={subItem.url} onClick={handleClose}> - {subItem.title} - </S.SidebarContentSubTitle> - ))} - </S.SidebarContentLayout> - ))} - </S.SidebarContent> - </S.SidebarWrapper> - ); -}; - -export default Sidebar; diff --git a/frontend/src/components/Header/Sidebar/styled.ts b/frontend/src/components/Header/Sidebar/styled.ts deleted file mode 100644 index 8719809e..00000000 --- a/frontend/src/components/Header/Sidebar/styled.ts +++ /dev/null @@ -1,63 +0,0 @@ -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, FONT_STYLE } from '@/constants'; - -export const SidebarWrapper = styled.div` - width: 50px; - height: 50px; - position: relative; -`; - -export const HamburgerLogo = styled.div` - background-color: ${COLOR.primary.main}; - width: 100%; - height: 100%; - position: absolute; - transition: left 0.4s ease-in-out; -`; - -export const HamburgerLine = styled.div` - width: 70%; - border-radius: ${BORDER_RADIUS.md}; - border: 2px solid ${COLOR.white}; - position: absolute; - left: 50%; - top: 50%; - transition: transform 0.4s ease-in-out; -`; - -export const SidebarContent = styled.div` - width: 200px; - height: 100vh; - font: ${FONT_STYLE.sm.normal}; - background-color: ${COLOR.white}; - position: absolute; - transition: left 0.4s ease-in-out; -`; - -export const SidebarContentLayout = styled.div` - overflow: hidden; - transition: max-height 0.4s ease-in-out; -`; - -export const SidebarContentTitle = styled.div` - padding: 10px 20px; - border-bottom: 1px solid ${COLOR.border}; - cursor: default; - &:hover { - color: ${COLOR.primary.main}; - } -`; - -export const SidebarContentSubTitle = styled(Link)` - display: block; - padding: 10px 30px; - border-bottom: 1px solid ${COLOR.border}; - background-color: ${COLOR.background.light}; - cursor: default; - color: ${COLOR.comment}; - &:hover { - color: ${COLOR.primary.main}; - } -`; diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx deleted file mode 100644 index 61fbdbcd..00000000 --- a/frontend/src/components/Header/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable implicit-arrow-linebreak */ - -'use client'; - -import { VscSettingsGear } from '@react-icons/all-files/vsc/VscSettingsGear'; -import { VscAccount } from '@react-icons/all-files/vsc/VscAccount'; -import { VscSignIn } from '@react-icons/all-files/vsc/VscSignIn'; -import { VscSignOut } from '@react-icons/all-files/vsc/VscSignOut'; -import Image from 'next/image'; -import Link from 'next/link'; -import { useState } from 'react'; - -import { headerInfos } from '@/data/clientCategory'; -import { useAppSelector } from '@/lib/hooks/redux'; - -import HeaderAccordion from './HeaderAccordion'; -import Sidebar from './Sidebar'; -import * as S from './styled'; -import IconButton from '../IconButton'; - -const Header = () => { - const [isSidebarOpen, setIsSideBarOpen] = useState<boolean>(false); - - const auth = useAppSelector((state) => state.auth.value); - - return ( - <S.HeaderWrapper> - <S.HeaderDesktopLayout> - <Link href="/" style={{ width: 'fit-content' }}> - <Image - src="/images/logo/SW_logo.svg" - alt="SW_logo" - width="160" - height="50" - style={{ width: 160, height: 50 }} - /> - </Link> - <div style={{ display: 'flex', justifyContent: 'center', flexGrow: 1 }}> - {headerInfos.map( - (item) => - item.inHeader && ( - <HeaderAccordion - key={item.title} - title={item.title} - url={item.url} - sub={item.sub} - description={item.description} - /> - ), - )} - </div> - {auth.isAuth && auth.isModerator && ( - <> - <IconButton icon={<VscSettingsGear />} title="관리" size="sm" link="/admin" /> - <IconButton icon={<VscSignOut />} title="로그아웃" size="sm" link="/sign-out" /> - </> - )} - {auth.isAuth && !auth.isModerator && ( - <> - <IconButton icon={<VscAccount />} title="마이페이지" size="sm" link="/my-page" /> - <IconButton icon={<VscSignOut />} title="로그아웃" size="sm" link="/sign-out" /> - </> - )} - {!auth.isAuth && ( - <S.SignButton> - <S.SignText> - <Link href="/sign-in">로그인</Link> /<Link href="/sign-up">회원가입</Link> - </S.SignText> - </S.SignButton> - )} - </S.HeaderDesktopLayout> - <S.SidebarBackground - style={{ display: `${isSidebarOpen ? 'block' : 'none'}` }} - onClick={() => setIsSideBarOpen(false)} - /> - - <S.HeaderTabletLayout> - <Link href="/" style={{ width: 'fit-content', height: '50px', padding: '5px 10px' }}> - <Image - src="/images/logo/SW_logo.svg" - alt="SW_logo" - width="125" - height="40" - style={{ width: 125, height: 40 }} - /> - </Link> - <div style={{ display: 'flex' }}> - {auth.isAuth && auth.isModerator && ( - <> - <IconButton icon={<VscSettingsGear />} title="관리" size="sm" link="/admin" /> - <IconButton icon={<VscSignOut />} title="로그아웃" size="sm" link="/sign-out" /> - </> - )} - {auth.isAuth && !auth.isModerator && ( - <> - <IconButton icon={<VscAccount />} title="마이페이지" size="sm" link="/my-page" /> - <IconButton icon={<VscSignOut />} title="로그아웃" size="sm" link="/sign-out" /> - </> - )} - {!auth.isAuth && <IconButton icon={<VscSignIn />} title="로그인" size="sm" link="/sign-in" />} - <Sidebar open={isSidebarOpen} handleOpen={setIsSideBarOpen} headerInfos={headerInfos} /> - </div> - </S.HeaderTabletLayout> - </S.HeaderWrapper> - ); -}; -export default Header; diff --git a/frontend/src/components/Header/styled.ts b/frontend/src/components/Header/styled.ts deleted file mode 100644 index 69df510a..00000000 --- a/frontend/src/components/Header/styled.ts +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { MAX_WIDTH, RESPONSIVE_WIDTH, FONT_STYLE, COLOR, BORDER_RADIUS } from '@/constants'; - -export const HeaderWrapper = styled.div` - position: fixed; - background-color: ${COLOR.white}; - width: 100vw; - border-bottom: 1px solid ${COLOR.border}; - z-index: 50; -`; - -export const HeaderDesktopLayout = styled.div` - max-width: ${MAX_WIDTH}; - margin: auto; - display: flex; - align-items: center; - @media screen and (max-width: ${RESPONSIVE_WIDTH.desktop}) { - display: none; - } -`; - -export const HeaderTabletLayout = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - @media screen and (min-width: ${RESPONSIVE_WIDTH.desktop}) { - display: none; - } -`; - -export const SignButton = styled.div` - background-color: ${COLOR.primary.main}; - padding: 10px 20px; - border-radius: ${BORDER_RADIUS.md}; -`; - -export const SignText = styled.span` - font: ${FONT_STYLE.sm.normal}; - color: ${COLOR.white}; -`; - -export const SidebarBackground = styled.div` - position: absolute; - width: 100vw; - height: 100vh; - left: 0; - top: 0; - background-color: rgba(0, 0, 0, 0.7); -`; diff --git a/frontend/src/components/IconButton/index.tsx b/frontend/src/components/IconButton/index.tsx deleted file mode 100644 index 511b6c99..00000000 --- a/frontend/src/components/IconButton/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import React from 'react'; - -import { FONT_STYLE } from '@/constants'; - -import { IconButtonWrapper } from './styled'; - -export interface IconButtonProps { - icon: React.ReactElement; - title: string; - size: 'sm' | 'md' | 'lg'; - link: string; -} - -const IconButton = ({ icon, title, size, link }: IconButtonProps) => { - const width = size === 'sm' ? '24px' : size === 'md' ? '32px' : '80px'; - const height = size === 'sm' ? '24px' : size === 'md' ? '32px' : '80px'; - - const Icon = React.createElement(icon.type, { - ...{ - ...icon.props, - style: { width, height }, - }, - }); - - return ( - <IconButtonWrapper href={link}> - {Icon} - <span - style={{ - font: `${ - size === 'sm' ? FONT_STYLE.xs.normal : size === 'md' ? FONT_STYLE.base.normal : FONT_STYLE.lg.normal - }`, - }} - > - {title} - </span> - </IconButtonWrapper> - ); -}; - -export default IconButton; diff --git a/frontend/src/components/IconButton/styled.ts b/frontend/src/components/IconButton/styled.ts deleted file mode 100644 index 8d06240b..00000000 --- a/frontend/src/components/IconButton/styled.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import Link from 'next/link'; -import styled from 'styled-components'; - -export const IconButtonWrapper = styled(Link)` - width: 60px; - height: 50px; - margin-right: 10px; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - background-color: transparent; - border: none; - cursor: pointer; - &:hover { - background-color: transparent; - } -`; diff --git a/frontend/src/components/MilestoneChart/index.tsx b/frontend/src/components/MilestoneChart/index.tsx deleted file mode 100644 index 65797d50..00000000 --- a/frontend/src/components/MilestoneChart/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { COLOR } from '@/constants'; -import { MilestoneOverviewScore } from '@/types/milestone'; - -import * as S from './styled'; - -export interface MilestoneChartProps { - chartSize: number; - fontSize: 'sm' | 'lg'; - milestoneOverviewScore: MilestoneOverviewScore; -} - -const MilestoneChart = ({ chartSize, fontSize, milestoneOverviewScore }: MilestoneChartProps) => { - const { activityScore, globalScore, communityScore, totalScore } = milestoneOverviewScore; - const scores = [ - { start: 0, score: activityScore, color: COLOR.milestone.blue.main, title: '실전적 SW역량' }, - { start: activityScore, score: globalScore, color: COLOR.milestone.green.main, title: '글로벌 SW역량' }, - { - start: activityScore + globalScore, - score: communityScore, - color: COLOR.milestone.purple.main, - title: '커뮤니티 SW역량', - }, - ]; - - return ( - <S.Chart size={chartSize}> - {scores.map((bar) => ( - <S.ChartBar key={bar.color} start={bar.start} score={bar.score} color={bar.color} /> - ))} - <S.ChartHole size={chartSize} /> - <S.ChartTextWrapper> - <S.ChartScoreText fontSize={fontSize}>{totalScore}</S.ChartScoreText> - <S.ChartText fontSize={fontSize}>/ 1000</S.ChartText> - </S.ChartTextWrapper> - </S.Chart> - ); -}; - -export default MilestoneChart; diff --git a/frontend/src/components/MilestoneChart/styled.ts b/frontend/src/components/MilestoneChart/styled.ts deleted file mode 100644 index 19f63bb4..00000000 --- a/frontend/src/components/MilestoneChart/styled.ts +++ /dev/null @@ -1,93 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { COLOR, FONT_STYLE } from '@/constants'; - -interface ChartProps { - size: number; -} - -interface ChartBarProps { - start: number; - score: number; - color: string; -} - -export const Chart = styled.div<ChartProps>` - margin: 0 auto; - position: relative; - width: ${(props) => props.size}px; - height: ${(props) => props.size}px; - border-radius: 100%; - background: ${COLOR.milestone.gray.light}; -`; - -export const ChartBar = styled.div<ChartBarProps>` - position: absolute; - width: inherit; - height: inherit; - border-radius: 50%; - background: conic-gradient( - transparent ${(props) => (props.start / 1000.0) * 360}deg, - ${(props) => props.color} ${(props) => (props.start / 1000.0) * 360}deg, - ${(props) => props.color} ${(props) => ((props.start + props.score) / 1000.0) * 360}deg, - transparent ${(props) => ((props.start + props.score) / 1000.0) * 360}deg - ); -`; - -export const ChartHole = styled.div<ChartProps>` - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: ${(props) => props.size / 1.7}px; - height: ${(props) => props.size / 1.7}px; - border-radius: 100%; - background: ${COLOR.white}; -`; - -export const ChartTextWrapper = styled.div` - position: absolute; - top: 45%; - left: 50%; - transform: translate(-50%, -50%); - width: 50%; - height: 50px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -`; - -interface ChartTextProps { - fontSize: 'sm' | 'lg'; -} - -export const ChartText = styled.p<ChartTextProps>` - color: ${COLOR.comment}; - font: ${({ fontSize }) => { - switch (fontSize) { - case 'sm': - return FONT_STYLE.xs.normal; - case 'lg': - return FONT_STYLE.base.normal; - default: - return FONT_STYLE.sm.normal; - } - }}; -`; - -export const ChartScoreText = styled(ChartText)<ChartTextProps>` - color: ${COLOR.black_text}; - font: ${({ fontSize }) => { - switch (fontSize) { - case 'sm': - return FONT_STYLE.base.bold; - case 'lg': - return FONT_STYLE.xl.bold; - default: - return FONT_STYLE.lg.bold; - } - }}; -`; diff --git a/frontend/src/components/MilestonePeriodSearchForm/index.tsx b/frontend/src/components/MilestonePeriodSearchForm/index.tsx deleted file mode 100644 index 581b6bcc..00000000 --- a/frontend/src/components/MilestonePeriodSearchForm/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ChangeEvent, Dispatch, SetStateAction } from 'react'; - -import { Period } from '@/types/common'; - -import { PeriodInput, SearchButton } from './styled'; - -interface MilestonePeriodSearchFormProps { - filterPeriod: Period; - setFilterPeriod: Dispatch<SetStateAction<Period>>; - setSearchFilterPeriod: Dispatch<SetStateAction<Period>>; -} - -const MilestonePeriodSearchForm = ({ - filterPeriod, - setFilterPeriod, - setSearchFilterPeriod, -}: MilestonePeriodSearchFormProps) => { - const handleSearchButtonClick = () => { - setSearchFilterPeriod(filterPeriod); - }; - - return ( - <div className="flex flex-grow flex-col items-center justify-center gap-x-4 sm:flex-row"> - <PeriodInput - type="date" - value={filterPeriod.startDate} - onChange={(e: ChangeEvent<HTMLInputElement>) => setFilterPeriod({ ...filterPeriod, startDate: e.target.value })} - /> - ~ - <PeriodInput - type="date" - value={filterPeriod.endDate} - onChange={(e: ChangeEvent<HTMLInputElement>) => setFilterPeriod({ ...filterPeriod, endDate: e.target.value })} - /> - <SearchButton onClick={handleSearchButtonClick}>검색</SearchButton> - </div> - ); -}; - -export default MilestonePeriodSearchForm; diff --git a/frontend/src/components/MilestonePeriodSearchForm/styled.ts b/frontend/src/components/MilestonePeriodSearchForm/styled.ts deleted file mode 100644 index 01950316..00000000 --- a/frontend/src/components/MilestonePeriodSearchForm/styled.ts +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { BORDER_RADIUS, COLOR, RESPONSIVE_WIDTH } from '@/constants'; - -export const PeriodInput = styled.input` - text-align: center; - padding: 8px; - border-radius: ${BORDER_RADIUS.md}; - border: none; - background-color: ${COLOR.border}; - - &:focus { - outline-color: ${COLOR.black_text}; - } -`; - -export const SearchButton = styled.button` - background-color: ${COLOR.black_text}; - color: white; - padding: 4px 16px; - border-radius: ${BORDER_RADIUS.sm}; - - @media screen and (max-width: ${RESPONSIVE_WIDTH.mobile}) { - margin-top: 16px; - } -`; diff --git a/frontend/src/components/MilestoneTable/index.tsx b/frontend/src/components/MilestoneTable/index.tsx deleted file mode 100644 index cb0d9c36..00000000 --- a/frontend/src/components/MilestoneTable/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { COLOR } from '@/constants'; -import { MilestoneOverviewScore } from '@/types/milestone'; - -import * as S from './styled'; - -interface MilestoneTableProps { - milestoneOverviewScore: MilestoneOverviewScore; -} - -const MilestoneTable = ({ milestoneOverviewScore }: MilestoneTableProps) => { - const { activityScore, globalScore, communityScore, totalScore } = milestoneOverviewScore; - const squareSize = 12; - const scores = [ - { score: activityScore, color: COLOR.milestone.blue.main, title: '실전적 SW역량' }, - { score: globalScore, color: COLOR.milestone.green.main, title: '글로벌 SW역량' }, - { score: communityScore, color: COLOR.milestone.purple.main, title: '커뮤니티 SW역량' }, - ]; - - return ( - <S.TableWrapper style={{ alignContent: 'center' }}> - <S.TableTitle>역량 구분</S.TableTitle> <S.TableTitle>획득</S.TableTitle> - {scores.map((bar) => ( - <> - <S.ScoreContent key={`1-${bar.color}`}> - <S.Square size={squareSize} color={bar.color} /> - {bar.title} - </S.ScoreContent> - <S.TableBottomBorderContent key={`2-${bar.color}`}>{bar.score}</S.TableBottomBorderContent> - </> - ))} - <S.TableTopBorderContent>합계</S.TableTopBorderContent> - <S.TableTopBorderContent>{totalScore}</S.TableTopBorderContent> - </S.TableWrapper> - ); -}; - -export default MilestoneTable; diff --git a/frontend/src/components/MilestoneTable/styled.ts b/frontend/src/components/MilestoneTable/styled.ts deleted file mode 100644 index 9d27b0da..00000000 --- a/frontend/src/components/MilestoneTable/styled.ts +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; - -import styled from 'styled-components'; - -import { COLOR, FONT_STYLE } from '@/constants'; - -interface SquareProps { - size: number; - color: string; -} - -export const TableWrapper = styled.div` - flex-grow: 1; - max-width: 300px; - display: grid; - grid-template-columns: 70% 30%; - color: ${COLOR.comment}; - text-align: center; - align-content: center; -`; - -export const TableTopBorderContent = styled.div` - padding: 8px 4px; - border-top: 1px solid ${COLOR.black_text}; - font: ${FONT_STYLE.sm.normal}; -`; - -export const TableBottomBorderContent = styled.div` - padding: 8px 4px; - border-bottom: 1px solid ${COLOR.border}; - font: ${FONT_STYLE.sm.normal}; -`; - -export const TableTitle = styled(TableBottomBorderContent)` - color: ${COLOR.black_text}; - font: ${FONT_STYLE.xs.semibold}; -`; - -export const ScoreContent = styled(TableBottomBorderContent)` - display: flex; - padding-left: 14px; - align-items: center; - gap: 4px; -`; - -export const Square = styled.div<SquareProps>` - width: ${(props) => props.size}px; - height: ${(props) => props.size}px; - background: ${(props) => props.color}; -`; diff --git a/frontend/src/components/SubTitle/index.tsx b/frontend/src/components/SubTitle/index.tsx deleted file mode 100644 index 9c588072..00000000 --- a/frontend/src/components/SubTitle/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import Link from 'next/link'; - -export interface SubTitleProps { - title: string; - urlText?: string; - url?: string; -} - -const SubTitle = ({ title, urlText, url }: SubTitleProps) => ( - <div className="flex flex-grow justify-between"> - <p className="cursor-default text-lg font-semibold">{title}</p> - {urlText && url && ( - <Link href={url} className="flex items-center gap-1 text-sm text-comment"> - {urlText} - </Link> - )} - </div> -); - -export default SubTitle; diff --git a/frontend/src/components/TabButton/index.tsx b/frontend/src/components/TabButton/index.tsx deleted file mode 100644 index 2069511f..00000000 --- a/frontend/src/components/TabButton/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-disable max-len */ - -'use client'; - -import { usePathname } from 'next/navigation'; - -export interface PageTabProps { - tabs: { name: string; url: string }[]; -} - -const PageTab = ({ tabs }: PageTabProps) => { - const pathname = usePathname(); - - return ( - <div className="flex w-full text-center"> - {tabs.map((tab) => ( - <a - key={tab.url} - href={tab.url} - className={`flex-grow pb-3 font-semibold ${pathname === tab.url ? 'border-b-4 border-primary-main text-primary-main' : 'border-b-[1px] border-comment text-comment'}`} - > - {tab.name} - </a> - ))} - </div> - ); -}; - -export default PageTab; diff --git a/frontend/src/components/TeamBuilding/index.tsx b/frontend/src/components/TeamBuilding/index.tsx deleted file mode 100644 index d6054e63..00000000 --- a/frontend/src/components/TeamBuilding/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { CgEye } from '@react-icons/all-files/cg/CgEye'; -import Image from 'next/image'; - -import { COLOR, TEAM_STATUS } from '@/constants'; -import { TeamBuildingDto } from '@/types/common.dto'; - -import * as S from './styled'; - -const TeamBuilding = ({ id, category, status, title, developer, designer, artist, other, views }: TeamBuildingDto) => { - const recruitment = [ - { img: '/images/teamBuilding/team_type_img_1.svg', text: '개발', count: developer }, - { img: '/images/teamBuilding/team_type_img_2.svg', text: '디자인', count: artist }, - { img: '/images/teamBuilding/team_type_img_3.svg', text: '기획', count: designer }, - { img: '/images/teamBuilding/team_type_img_4.svg', text: '기타', count: other }, - ]; - return ( - <S.TeamBuildingWrapper href={`/team-building/${id}`}> - <S.TeamHeaderWrapper> - <S.CategoryText>{category}</S.CategoryText> - <S.StatusText color={TEAM_STATUS[status].color}>{TEAM_STATUS[status].text}</S.StatusText> - </S.TeamHeaderWrapper> - <S.TeamBodyWrapper> - <S.TeamTitle>{title}</S.TeamTitle> - <S.RecruitmentWrapper> - {recruitment.map((item) => { - if (item.count !== 0) { - return ( - <S.RecruitmentItem key={`${id}-${item.text}`}> - <Image src={item.img} alt={item.text} width="30" height="30" priority={false} /> - <S.RecruitmentItemText> - {item.text} - <br /> - <span style={{ color: COLOR.black_text }}>{item.count}명</span> - </S.RecruitmentItemText> - </S.RecruitmentItem> - ); - } - return null; - })} - </S.RecruitmentWrapper> - <S.ViewDiv> - <CgEye color={COLOR.comment} /> - {views} - </S.ViewDiv> - </S.TeamBodyWrapper> - </S.TeamBuildingWrapper> - ); -}; - -export default TeamBuilding; diff --git a/frontend/src/components/TeamBuilding/styled.ts b/frontend/src/components/TeamBuilding/styled.ts deleted file mode 100644 index 23d62b36..00000000 --- a/frontend/src/components/TeamBuilding/styled.ts +++ /dev/null @@ -1,87 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import styled from 'styled-components'; - -import { BORDER_RADIUS, FONT_STYLE, COLOR } from '@/constants'; - -interface StatusTextProps { - color: string; -} - -export const TeamBuildingWrapper = styled(Link)` - width: 360px; - border: 2px solid ${COLOR.border}; - border-radius: ${BORDER_RADIUS.sm}; - overflow: hidden; -`; - -export const TeamHeaderWrapper = styled.div` - display: flex; - gap: 10px; - align-items: center; -`; - -export const CategoryText = styled.p` - border-bottom-right-radius: ${BORDER_RADIUS.sm}; - padding: 6px 12px; - background-color: ${COLOR.border}; - color: ${COLOR.black_text}; - font: ${FONT_STYLE.base.semibold}; -`; - -export const StatusText = styled.p<StatusTextProps>` - border: 1px solid ${(props) => props.color}; - border-radius: ${BORDER_RADIUS.sm}; - padding: 2px 8px; - color: ${(props) => props.color}; - font: ${FONT_STYLE.sm.normal}; -`; - -export const TeamBodyWrapper = styled.div` - margin: 8px 16px 16px; - display: flex; - flex-direction: column; - gap: 12px; - overflow: hidden; -`; - -export const TeamTitle = styled.p` - color: ${COLOR.black_text}; - font: ${FONT_STYLE.base.bold}; - - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - - &:hover { - white-space: wrap; - word-break: keep-all; - } -`; - -export const RecruitmentWrapper = styled.div` - display: flex; - justify-content: center; - gap: 16px; -`; - -export const RecruitmentItem = styled.div` - display: flex; - gap: 4px; - align-items: center; -`; - -export const RecruitmentItemText = styled.div` - font: ${FONT_STYLE.sm.semibold}; - color: ${COLOR.comment}; - text-align: center; -`; - -export const ViewDiv = styled.div` - display: flex; - align-items: center; - justify-content: end; - gap: 8px; - color: ${COLOR.comment}; -`; diff --git a/frontend/src/components/Title/index.tsx b/frontend/src/components/Title/index.tsx deleted file mode 100644 index 1c77647f..00000000 --- a/frontend/src/components/Title/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export interface TitleProps { - title: string; - description?: string; -} - -const Title = ({ title, description }: TitleProps) => ( - <div className="flex flex-col gap-1"> - <p className="cursor-default text-[28px] font-semibold">{title}</p> - {description && <div className="text-comment">{description}</div>} - </div> -); - -export default Title; diff --git a/frontend/src/components/common/IconButton.tsx b/frontend/src/components/common/IconButton.tsx new file mode 100644 index 00000000..0c27fcd9 --- /dev/null +++ b/frontend/src/components/common/IconButton.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import Link from 'next/link'; + +export interface IconButtonProps { + icon: React.ReactElement; + title: string; + link: string; +} + +export default function IconButton({ icon, title, link }: IconButtonProps) { + const width = '24px'; + const height = '24px'; + + const Icon = React.createElement(icon.type, { + ...{ + ...icon.props, + style: { width, height }, + }, + }); + + return ( + <Link + className="mr-2 flex h-[50px] w-[60px] cursor-pointer flex-col items-center justify-center border-none bg-transparent" + href={link} + > + {Icon} + <span className="text-xs">{title}</span> + </Link> + ); +} diff --git a/frontend/src/components/common/PageSubTitle.tsx b/frontend/src/components/common/PageSubTitle.tsx new file mode 100644 index 00000000..8442c3f6 --- /dev/null +++ b/frontend/src/components/common/PageSubTitle.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link'; + +export interface PageSubTitleProps { + title: string; + urlText?: string; + url?: string; +} + +export default function PageSubTitle({ title, urlText, url }: PageSubTitleProps) { + return ( + <div className="flex flex-grow justify-between"> + <p className="cursor-default text-lg font-semibold">{title}</p> + {urlText && url && ( + <Link href={url} className="flex items-center gap-1 text-sm text-comment"> + {urlText} + </Link> + )} + </div> + ); +} diff --git a/frontend/src/components/common/PageTitle.tsx b/frontend/src/components/common/PageTitle.tsx new file mode 100644 index 00000000..223cef84 --- /dev/null +++ b/frontend/src/components/common/PageTitle.tsx @@ -0,0 +1,13 @@ +export interface PageTitleProps { + title: string; + description?: string; +} + +export default function PageTitle({ title, description }: PageTitleProps) { + return ( + <div className="flex flex-col gap-1"> + <p className="cursor-default text-[28px] font-semibold">{title}</p> + {description && <div className="text-comment">{description}</div>} + </div> + ); +} diff --git a/frontend/src/app/(client)/components/Pagination/index.tsx b/frontend/src/components/common/Pagination.tsx similarity index 83% rename from frontend/src/app/(client)/components/Pagination/index.tsx rename to frontend/src/components/common/Pagination.tsx index 1d3d63a7..6a8818a8 100644 --- a/frontend/src/app/(client)/components/Pagination/index.tsx +++ b/frontend/src/components/common/Pagination.tsx @@ -1,6 +1,3 @@ -/* eslint-disable prettier/prettier */ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable max-len */ import { VscChevronLeft } from '@react-icons/all-files/vsc/VscChevronLeft'; import { VscChevronRight } from '@react-icons/all-files/vsc/VscChevronRight'; import Link from 'next/link'; @@ -13,7 +10,7 @@ export interface PaginationProps { query?: string; } -const Pagination = ({ currentPage, pageSize, totalItems, pathname, query }: PaginationProps) => { +export default function Pagination({ currentPage, pageSize, totalItems, pathname, query }: PaginationProps) { const buttonPerPage = 10; const totalPageCount = Math.ceil(totalItems / pageSize); @@ -25,7 +22,7 @@ const Pagination = ({ currentPage, pageSize, totalItems, pathname, query }: Pagi const queries = query ? JSON.parse(query) : null; const nextPageCalc = (showIdx + 1) * buttonPerPage + 1; - const prevPage = showIdx === 0 ? 1 : showIdx * buttonPerPage; + const prevPage = showIdx <= 1 ? 1 : showIdx * buttonPerPage; const nextPage = totalPageCount >= nextPageCalc ? nextPageCalc : totalPageCount; return ( @@ -47,6 +44,4 @@ const Pagination = ({ currentPage, pageSize, totalItems, pathname, query }: Pagi </Link> </div> ); -}; - -export default Pagination; +} diff --git a/frontend/src/components/common/PeriodSearchBox.tsx b/frontend/src/components/common/PeriodSearchBox.tsx new file mode 100644 index 00000000..75d05e60 --- /dev/null +++ b/frontend/src/components/common/PeriodSearchBox.tsx @@ -0,0 +1,39 @@ +import { ChangeEvent, Dispatch, SetStateAction } from 'react'; + +export interface Period { + startDate: string; + endDate: string; +} + +export interface PeriodSearchBoxProps { + period: Period; + setPeriod: Dispatch<SetStateAction<Period>>; + setSearchPeriod: Dispatch<SetStateAction<Period>>; +} + +export default function PeriodSearchBox({ period, setPeriod, setSearchPeriod }: PeriodSearchBoxProps) { + const handleSearchButtonClick = () => { + setSearchPeriod(period); + }; + + return ( + <div className="flex flex-grow flex-col items-center justify-center gap-x-4 sm:flex-row"> + <input + className="rounded-md border-none bg-border p-2 text-center focus:outline-black" + type="date" + value={period.startDate} + onChange={(e: ChangeEvent<HTMLInputElement>) => setPeriod({ ...period, startDate: e.target.value })} + /> + ~ + <input + className="rounded-md border-none bg-border p-2 text-center focus:outline-black" + type="date" + value={period.endDate} + onChange={(e: ChangeEvent<HTMLInputElement>) => setPeriod({ ...period, endDate: e.target.value })} + /> + <button className="mt-4 rounded-sm bg-black p-[4px_16px] text-white sm:mt-0" onClick={handleSearchButtonClick}> + 검색 + </button> + </div> + ); +} diff --git a/frontend/src/adminComponents/Pagination/index.tsx b/frontend/src/components/common/admin/AdminPagination.tsx similarity index 85% rename from frontend/src/adminComponents/Pagination/index.tsx rename to frontend/src/components/common/admin/AdminPagination.tsx index 8470dbd5..161b77c4 100644 --- a/frontend/src/adminComponents/Pagination/index.tsx +++ b/frontend/src/components/common/admin/AdminPagination.tsx @@ -1,19 +1,23 @@ -/* eslint-disable prettier/prettier */ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable max-len */ +import Link from 'next/link'; + import { VscChevronLeft } from '@react-icons/all-files/vsc/VscChevronLeft'; import { VscChevronRight } from '@react-icons/all-files/vsc/VscChevronRight'; -import Link from 'next/link'; -export interface PaginationProps { +export interface AdminPaginationProps { currentPage: number; totalItems: number; - itemPerPage?: number; pathname: string; + itemPerPage?: number; query?: string; } -const Pagination = ({ currentPage, totalItems, itemPerPage = 10, pathname, query }: PaginationProps) => { +export default function AdminPagination({ + currentPage, + pathname, + totalItems, + itemPerPage = 10, + query, +}: AdminPaginationProps) { const buttonPerPage = 10; const totalPageCount = Math.ceil(totalItems / itemPerPage); @@ -47,6 +51,4 @@ const Pagination = ({ currentPage, totalItems, itemPerPage = 10, pathname, query </Link> </div> ); -}; - -export default Pagination; +} diff --git a/frontend/src/components/SearchBox/index.tsx b/frontend/src/components/common/admin/AdminSearchBox.tsx similarity index 78% rename from frontend/src/components/SearchBox/index.tsx rename to frontend/src/components/common/admin/AdminSearchBox.tsx index 0ce535f0..e5c51661 100644 --- a/frontend/src/components/SearchBox/index.tsx +++ b/frontend/src/components/common/admin/AdminSearchBox.tsx @@ -1,29 +1,27 @@ -/* eslint-disable prettier/prettier */ - 'use client'; import { Form, Formik } from 'formik'; import { useRouter } from 'next/navigation'; -import Dropdown from '@/components/Formik/Dropdown'; -import TextInput from '@/components/Formik/TextInput'; +import Dropdown from '@/components/common/formik/Dropdown'; +import TextInput from '@/components/common/formik/TextInput'; import { FORM_SIZE } from '@/constants'; -interface SearchFormProps { +interface AdminSearchFormProps { field: number; keyword: string; } -export interface SearchBoxProps { - initialValues: SearchFormProps; +export interface AdminSearchBoxProps { + initialValues: AdminSearchFormProps; fieldCategories: { id: number; name: string }[]; path: string; } -const SearchBox = ({ initialValues, fieldCategories, path }: SearchBoxProps) => { +export default function AdminSearchBox({ initialValues, fieldCategories, path }: AdminSearchBoxProps) { const router = useRouter(); - const handleSearchButtonClick = (values: SearchFormProps) => { + const handleSearchButtonClick = (values: AdminSearchFormProps) => { router.push(`${path}?field=${values.field}&keyword=${values.keyword}`); }; @@ -67,6 +65,4 @@ const SearchBox = ({ initialValues, fieldCategories, path }: SearchBoxProps) => )} </Formik> ); -}; - -export default SearchBox; +} diff --git a/frontend/src/components/Formik/DatePicker/index.tsx b/frontend/src/components/common/formik/DatePicker.tsx similarity index 92% rename from frontend/src/components/Formik/DatePicker/index.tsx rename to frontend/src/components/common/formik/DatePicker.tsx index 02ac998a..0c258e23 100644 --- a/frontend/src/components/Formik/DatePicker/index.tsx +++ b/frontend/src/components/common/formik/DatePicker.tsx @@ -1,3 +1,5 @@ +'use client'; + import { VscInfo } from '@react-icons/all-files/vsc/VscInfo'; type BuiltInInputProps = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>; @@ -24,8 +26,8 @@ export const DatePicker = ({ isRequired = false, ...props }: DatePickerProps) => <div className="relative flex items-center gap-1 px-2 py-1 text-xs"> <VscInfo className="peer h-[14px] w-[14px]" /> <div className="absolute left-1/2 top-1 hidden -translate-x-1/2 -translate-y-[calc(100%+4px)] whitespace-nowrap break-keep rounded border bg-white p-2 peer-hover:block"> - {tooltip.split('\\n').map((data) => ( - <div>{data}</div> + {tooltip.split('\\n').map((data, index) => ( + <div key={`${index}-${data}`}>{data}</div> ))} </div> </div> diff --git a/frontend/src/components/Formik/Dropdown/index.tsx b/frontend/src/components/common/formik/Dropdown.tsx similarity index 95% rename from frontend/src/components/Formik/Dropdown/index.tsx rename to frontend/src/components/common/formik/Dropdown.tsx index 15eff666..f793b18d 100644 --- a/frontend/src/components/Formik/Dropdown/index.tsx +++ b/frontend/src/components/common/formik/Dropdown.tsx @@ -1,6 +1,3 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable max-len */ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { VscChevronDown } from '@react-icons/all-files/vsc/VscChevronDown'; import { VscChevronUp } from '@react-icons/all-files/vsc/VscChevronUp'; import { useState } from 'react'; diff --git a/frontend/src/app/(client)/components/Formik/Dropdown/index.tsx b/frontend/src/components/common/formik/DropdownDdang.tsx similarity index 97% rename from frontend/src/app/(client)/components/Formik/Dropdown/index.tsx rename to frontend/src/components/common/formik/DropdownDdang.tsx index 6a5b3632..fb31023d 100644 --- a/frontend/src/app/(client)/components/Formik/Dropdown/index.tsx +++ b/frontend/src/components/common/formik/DropdownDdang.tsx @@ -1,5 +1,3 @@ -/* eslint-disable max-len */ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { VscChevronDown } from '@react-icons/all-files/vsc/VscChevronDown'; import { VscChevronUp } from '@react-icons/all-files/vsc/VscChevronUp'; import React, { useState } from 'react'; diff --git a/frontend/src/components/Formik/EmailTextInput/index.tsx b/frontend/src/components/common/formik/EmailTextInput.tsx similarity index 97% rename from frontend/src/components/Formik/EmailTextInput/index.tsx rename to frontend/src/components/common/formik/EmailTextInput.tsx index fab027a6..805db493 100644 --- a/frontend/src/components/Formik/EmailTextInput/index.tsx +++ b/frontend/src/components/common/formik/EmailTextInput.tsx @@ -15,7 +15,7 @@ const EmailTextInput = ({ ...props }: TextInputProps) => { const hasError = errorText !== undefined; return ( - <div className="flex flex-col gap-1"> + <div className="flex grow flex-col gap-1"> <label htmlFor={inputProps.id || inputProps.name} className="text-sm font-semibold"> {label} <span className="text-sm font-semibold text-red-400">*</span>{' '} <span className="text-xs text-comment"> diff --git a/frontend/src/components/Formik/FileUploader/index.tsx b/frontend/src/components/common/formik/FileUploader.tsx similarity index 100% rename from frontend/src/components/Formik/FileUploader/index.tsx rename to frontend/src/components/common/formik/FileUploader.tsx diff --git a/frontend/src/components/Formik/ImageUploader/index.tsx b/frontend/src/components/common/formik/ImageUploader.tsx similarity index 100% rename from frontend/src/components/Formik/ImageUploader/index.tsx rename to frontend/src/components/common/formik/ImageUploader.tsx diff --git a/frontend/src/components/Formik/TextInput/index.tsx b/frontend/src/components/common/formik/TextInput.tsx similarity index 61% rename from frontend/src/components/Formik/TextInput/index.tsx rename to frontend/src/components/common/formik/TextInput.tsx index 9b37377b..ed4a20d1 100644 --- a/frontend/src/components/Formik/TextInput/index.tsx +++ b/frontend/src/components/common/formik/TextInput.tsx @@ -1,6 +1,7 @@ -/* eslint-disable @typescript-eslint/indent */ -/* eslint-disable max-len */ +'use client'; + import { FORM_SIZE } from '@/constants'; +import { VscInfo } from '@react-icons/all-files/vsc/VscInfo'; type BuiltInTextInputProps = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>; @@ -11,6 +12,7 @@ interface CustomTextInputProps { errorText?: string; size?: 'sm' | 'md' | 'lg'; isAdmin?: boolean; + tooltip?: string; onKeyDownEnter?(): void; onChangeText?(text: string): void; } @@ -18,13 +20,21 @@ interface CustomTextInputProps { export type TextInputProps = Omit<BuiltInTextInputProps, 'size'> & CustomTextInputProps; const TextInput = ({ isRequired = false, size = 'md', ...props }: TextInputProps) => { - const { label, errorText, onKeyDownEnter, onChangeText, isAdmin, ...inputProps } = props; + const { label, errorText, isAdmin, tooltip, onKeyDownEnter, onChangeText, ...inputProps } = props; const hasError = errorText !== undefined; return ( <div className="flex flex-grow flex-col gap-1"> {label && ( - <label htmlFor={inputProps.name} className={`${FORM_SIZE[size].subTextSize} font-semibold`}> + <label htmlFor={inputProps.name} className={`flex items-center ${FORM_SIZE[size].subTextSize} font-semibold`}> + {tooltip && ( + <div className="relative flex items-center gap-1 px-2 py-1 text-xs"> + <VscInfo className="peer h-[14px] w-[14px]" /> + <div className="absolute left-0 top-1 hidden -translate-y-[calc(100%+4px)] whitespace-nowrap break-keep rounded border bg-white p-2 peer-hover:block"> + {tooltip} + </div> + </div> + )} {label} {isRequired && <span className={`${FORM_SIZE[size].subTextSize} font-semibold text-red-400`}>*</span>} </label> )} @@ -40,7 +50,7 @@ const TextInput = ({ isRequired = false, size = 'md', ...props }: TextInputProps inputProps.onChange?.(e); onChangeText?.(e.target.value); }} - className={`rounded-sm border-[1px] outline-none ${isAdmin ? 'border-admin-border' : 'border-border'} ${FORM_SIZE[size].padding} ${FORM_SIZE[size].textSize} ${hasError && 'border-red-400'}`} + className={`rounded-sm border-[1px] ${isAdmin ? 'border-admin-border' : 'border-border'} outline-none ${FORM_SIZE[size].padding} ${FORM_SIZE[size].textSize} ${hasError && 'border-red-400'}`} /> {errorText && <span className="pl-1 text-xs text-red-400">{errorText}</span>} </div> diff --git a/frontend/src/app/(client)/components/Formik/TextInput/index.tsx b/frontend/src/components/common/formik/TextInputDdang.tsx similarity index 100% rename from frontend/src/app/(client)/components/Formik/TextInput/index.tsx rename to frontend/src/components/common/formik/TextInputDdang.tsx diff --git a/frontend/src/components/layout/AdminFooter.tsx b/frontend/src/components/layout/AdminFooter.tsx new file mode 100644 index 00000000..29a2cdfb --- /dev/null +++ b/frontend/src/components/layout/AdminFooter.tsx @@ -0,0 +1,7 @@ +export default function AdminFooter() { + return ( + <div className="min-w-admin p-2 pb-10 text-center text-sm text-admin-comment"> + copyright ⓒ KOREA LEARNING CONSULTING CENTER. All Right Reserved + </div> + ); +} diff --git a/frontend/src/components/layout/AdminHeader.tsx b/frontend/src/components/layout/AdminHeader.tsx new file mode 100644 index 00000000..44fc25ae --- /dev/null +++ b/frontend/src/components/layout/AdminHeader.tsx @@ -0,0 +1,77 @@ +'use client'; + +import Link from 'next/link'; +import Image from 'next/image'; +import { usePathname } from 'next/navigation'; + +import { adminCategories } from '@/data/adminCategory'; +import { useAppSelector } from '@/lib/hooks/redux'; + +export default function AdminHeader() { + return ( + <div className="fixed z-10 flex w-[100vw] min-w-admin-max justify-center border-b-2 border-primary-main bg-white"> + <div className="flex h-full w-full items-center justify-between pr-2"> + <div className="flex"> + <Link className="m-auto flex w-admin-sidebar items-center justify-center pl-2" href="/admin"> + <Image + src="/images/logo/SW_logo.svg" + alt="SW_logo" + width="120" + height="40" + style={{ width: 120, height: 40 }} + priority + /> + </Link> + <AdminNavigator /> + </div> + <div className="flex gap-2 pr-10"> + <AdminGreetingUser /> + <Link className="rounded-lg bg-admin-secondary-light px-2 py-1 text-xs text-comment" href="/"> + 사이트 메인으로 + </Link> + <Link className="rounded-lg bg-black px-2 py-1 text-xs text-white" href="/sign-out"> + 로그아웃 + </Link> + </div> + </div> + </div> + ); +} + +export function AdminNavigator() { + const pathname = usePathname(); + const linkStyle = + "h-admin-header text-admin-comment after:absolute relative flex items-center px-5 after:left-0 after:top-[15%] after:h-[70%] after:w-[1px] after:bg-border after:content-['']"; + return ( + <> + {adminCategories.map((item) => { + if (pathname.includes(item.url)) { + return ( + <Link + className={`bg-admin-primary-main text-white after:!h-0 ${linkStyle}`} + key={item.url} + href={item.sub[0].url} + > + {item.title} + </Link> + ); + } + return ( + <Link className={linkStyle} key={item.url} href={item.sub[0].url}> + {item.title} + </Link> + ); + })} + </> + ); +} + +export function AdminGreetingUser() { + const auth = useAppSelector((state) => state.auth).value; + + return ( + <span className="flex cursor-default items-center text-xs text-admin-comment"> + 반갑습니다! <span className="text-admin-primary-main">{auth.name}</span>님 + </span> + ); +} diff --git a/frontend/src/components/layout/AdminSidebar.tsx b/frontend/src/components/layout/AdminSidebar.tsx new file mode 100644 index 00000000..559a7a8c --- /dev/null +++ b/frontend/src/components/layout/AdminSidebar.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; + +import { adminCategories } from '@/data/adminCategory'; + +export default function AdminSidebar() { + const [currTab, setCurrTab] = useState<string>(''); + const pathname = usePathname(); + + const titleStyle = 'block max-h-[42px] cursor-default overflow-hidden border-b border-admin-border px-5 py-2.5'; + + const handleTabClick = (tab: string) => { + setCurrTab(tab); + }; + + useEffect(() => { + setCurrTab(pathname); + }, [pathname]); + + return ( + <div className="w-admin-sidebar fixed z-10 mt-[calc(theme(height.admin-header)+2px)] h-[calc(100vh-theme(height.admin-header))] bg-white shadow-sm"> + <div className="flex h-full w-full flex-col"> + {adminCategories.map((item) => ( + <div + className="overflow-hidden transition-[max-height] duration-500 ease-in-out" + key={item.title} + style={{ maxHeight: `${currTab.includes(item.url) || pathname.includes(item.url) ? '200px' : '42px'}` }} + > + {currTab.includes(item.url) ? ( + <div + className={`bg-admin-secondary-main text-white ${titleStyle}`} + onClick={() => handleTabClick(item.url)} + > + {item.title} + </div> + ) : ( + <div className={titleStyle} onClick={() => handleTabClick(item.url)}> + {item.title} + </div> + )} + {item.sub.map((subItem) => ( + <Link + className="block border-b border-admin-border bg-admin-background-light px-7 py-2.5 text-admin-comment" + key={subItem.key} + href={subItem.url} + style={{ fontWeight: `${pathname === subItem.url ? '700' : '400'}` }} + > + {subItem.title} + </Link> + ))} + </div> + ))} + </div> + </div> + ); +} diff --git a/frontend/src/components/layout/Footer.tsx b/frontend/src/components/layout/Footer.tsx new file mode 100644 index 00000000..2db78978 --- /dev/null +++ b/frontend/src/components/layout/Footer.tsx @@ -0,0 +1,29 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +export default function Footer() { + return ( + <footer className="flex min-h-[200px] w-[100vw] items-center justify-center bg-background-base"> + <div className="w-client-max grid grid-cols-1 justify-items-center gap-5 lg:grid-cols-[1fr_3fr_105px] lg:justify-items-start"> + <Image + className="hidden lg:block" + src="/images/logo/foot_logo.svg" + alt="foot_logo" + width="160" + height="40" + priority={false} + /> + {/* 400 14px "Noto Sans KR", sans-serif */} + <div className="text-center lg:text-left"> + <p className="text-sm font-normal">(46241) 부산광역시 금정구 부산대학로 63번길 2 (장전동)</p> + <p className="text-sm font-normal">부산대학교 소프트웨어융합교육원</p> + </div> + <Link className="h-fit w-fit border-b border-comment pb-[2px] text-sm font-normal text-comment" href="/"> + 개인정보처리방침 + </Link> + <p className="text-sm font-normal">ⓒ 2021 PNUswedu. All Right Reserved.</p> + <p className="text-sm font-normal">TEL : 051-510-3737, 3738, 3624</p> + </div> + </footer> + ); +} diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx new file mode 100644 index 00000000..88e67f77 --- /dev/null +++ b/frontend/src/components/layout/Header.tsx @@ -0,0 +1,214 @@ +'use client'; + +import { VscSettingsGear } from '@react-icons/all-files/vsc/VscSettingsGear'; +import { VscAccount } from '@react-icons/all-files/vsc/VscAccount'; +import { VscSignIn } from '@react-icons/all-files/vsc/VscSignIn'; +import { VscSignOut } from '@react-icons/all-files/vsc/VscSignOut'; + +import Image from 'next/image'; +import Link from 'next/link'; +import { useState } from 'react'; + +import { headerInfos } from '@/data/clientCategory'; +import { useAppSelector } from '@/lib/hooks/redux'; +import { CategoryDto } from '@/types/common.dto'; +import { AuthSliceState } from '@/store/auth.slice'; +import IconButton from '@/components/common/IconButton'; + +interface HeaderUIProps { + auth: AuthSliceState; +} + +interface SidebarProps { + open: boolean; + handleOpen: React.Dispatch<React.SetStateAction<boolean>>; +} + +export default function Header() { + const auth = useAppSelector((state) => state.auth.value); + + return ( + <header className="fixed z-50 h-client-header w-[100vw] border-b border-border bg-white pl-4 lg:h-client-lg-header lg:px-4"> + <DesktopLayoutHeader auth={auth} /> + <TabletLayoutHeader auth={auth} /> + </header> + ); +} + +export function TabletLayoutHeader({ auth }: HeaderUIProps) { + const [isSidebarOpen, setIsSideBarOpen] = useState<boolean>(false); + + return ( + <> + <div + className={`${isSidebarOpen ? 'block' : 'hidden'} absolute left-0 top-0 h-[100vh] w-[100vw] bg-[rgba(0,0,0,0.7)]`} + onClick={() => setIsSideBarOpen(false)} + /> + <div className="flex items-center justify-between lg:hidden"> + <Link href="/" style={{ width: 'fit-content', height: '50px', padding: '5px 10px' }}> + <Image + src="/images/logo/SW_logo.svg" + alt="SW_logo" + width="125" + height="40" + style={{ width: 125, height: 40 }} + /> + </Link> + <div className="flex"> + {auth.isAuth && auth.isModerator && ( + <> + <IconButton icon={<VscSettingsGear />} title="관리" link="/admin" /> + <IconButton icon={<VscSignOut />} title="로그아웃" link="/sign-out" /> + </> + )} + {auth.isAuth && !auth.isModerator && ( + <> + <IconButton icon={<VscAccount />} title="마이페이지" link="/my-page" /> + <IconButton icon={<VscSignOut />} title="로그아웃" link="/sign-out" /> + </> + )} + {!auth.isAuth && <IconButton icon={<VscSignIn />} title="로그인" link="/sign-in" />} + <Sidebar open={isSidebarOpen} handleOpen={setIsSideBarOpen} /> + </div> + </div> + </> + ); +} + +export function DesktopLayoutHeader({ auth }: HeaderUIProps) { + return ( + <div className="m-auto hidden max-w-client-max content-between items-center lg:flex"> + <Link href="/" style={{ width: 'fit-content' }}> + <Image + src="/images/logo/SW_logo.svg" + alt="SW_logo" + width="160" + height="50" + style={{ width: 160, height: 50 }} + /> + </Link> + <div style={{ display: 'flex', justifyContent: 'center', flexGrow: 1 }}> + {headerInfos.map( + (item) => + item.inHeader && ( + <HeaderAccordion + key={item.title} + title={item.title} + url={item.url} + sub={item.sub} + description={item.description} + /> + ), + )} + </div> + {auth.isAuth && auth.isModerator && ( + <> + <IconButton icon={<VscSettingsGear />} title="관리" link="/admin" /> + <IconButton icon={<VscSignOut />} title="로그아웃" link="/sign-out" /> + </> + )} + {auth.isAuth && !auth.isModerator && ( + <> + <IconButton icon={<VscAccount />} title="마이페이지" link="/my-page" /> + <IconButton icon={<VscSignOut />} title="로그아웃" link="/sign-out" /> + </> + )} + {!auth.isAuth && ( + <div className="rounded-md bg-primary-main p-[10px_20px]"> + <span className="text-sm font-normal text-white"> + <Link href="/sign-in">로그인</Link> /<Link href="/sign-up">회원가입</Link> + </span> + </div> + )} + </div> + ); +} + +export function HeaderAccordion({ title, url, sub }: CategoryDto) { + return ( + <div className="group relative h-client-lg-header items-center leading-client-lg-header"> + <Link className="p-[0_40px] text-lg font-semibold hover:text-primary-dark" href={url}> + {title} + </Link> + <div className="color-comment absolute left-1/2 top-full max-h-0 w-[180px] -translate-x-1/2 overflow-hidden rounded-b-sm bg-white text-center text-sm font-normal shadow transition-[max-height] duration-500 ease-in-out group-hover:max-h-[200px]"> + {sub.map((item) => ( + <Link + className="hover: block border-b border-border p-2 text-comment hover:text-primary-dark" + key={item.key} + href={item.url} + > + {item.title} + </Link> + ))} + </div> + </div> + ); +} + +export function Sidebar({ open, handleOpen }: SidebarProps) { + const [currTab, setCurrTab] = useState<string>(''); + + const handleClose = () => { + handleOpen((e) => !e); + setCurrTab(''); + }; + + return ( + <div className="relative h-client-header w-client-sidebar"> + <div + className="absolute h-full w-full bg-primary-main transition-[left] duration-500 ease-in-out" + onClick={handleClose} + style={{ left: `${open ? '-200px' : 0}` }} + > + <div + className="absolute left-1/2 top-1/2 w-2/3 rounded-md border-2 border-white transition-transform duration-500 ease-in-out" + style={{ + transform: `${open ? 'translate(-50%, -50%) rotate(40deg)' : 'translate(-50%, calc(-50% - 12px))'}`, + }} + /> + <div + className="absolute left-1/2 top-1/2 w-2/3 rounded-md border-2 border-white transition-transform duration-500 ease-in-out" + style={{ transform: 'translate(-50%, -50%)', display: `${open ? 'none' : 'block'}` }} + /> + <div + className="absolute left-1/2 top-1/2 w-2/3 rounded-md border-2 border-white transition-transform duration-500 ease-in-out" + style={{ + transform: `${open ? 'translate(-50%, -50%) rotate(-40deg)' : 'translate(-50%, calc(-50% + 12px))'}`, + }} + /> + </div> + <div + className="absolute h-[100vh] w-client-sidebar-open bg-white text-sm font-normal transition-[left] duration-500 ease-in-out" + style={{ left: `${open ? '-150px' : '50px'}` }} + > + {headerInfos.map((item) => ( + <div + className="overflow-hidden transition-[max-height] duration-500 ease-in-out" + key={item.title} + style={{ maxHeight: `${currTab === item.title ? '200px' : '37px'}` }} + > + <div + className={`${currTab === item.title ? 'bg-primary-dark' : 'transparent'} cursor-default border-b border-border p-[10px_20px] hover:text-primary-main`} + style={{ + color: `${currTab === item.title ? 'white' : 'black'}`, + }} + onClick={() => setCurrTab(item.title)} + > + {item.title} + </div> + {item.sub.map((subItem) => ( + <Link + className="block cursor-default border-b border-border bg-background-light p-[10px_30px] text-comment hover:text-primary-main" + key={subItem.key} + href={subItem.url} + onClick={handleClose} + > + {subItem.title} + </Link> + ))} + </div> + ))} + </div> + </div> + ); +} diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx new file mode 100644 index 00000000..ba387967 --- /dev/null +++ b/frontend/src/components/layout/Sidebar.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { usePathname } from 'next/navigation'; +import { useCallback, useEffect, useState } from 'react'; + +import { headerInfos } from '@/data/clientCategory'; +import { CategoryDto, SubCategoryDto } from '@/types/common.dto'; + +import Link from 'next/link'; + +export default function Sidebar() { + const pathname = usePathname(); + const [currentCategory, setCurrentCategory] = useState<CategoryDto | null>(); + const [currentSubCategory, setCurrentSubCategory] = useState<SubCategoryDto | null>(); + + const findMatchPath = useCallback(() => { + let maxOverlap = 0; + let bestMatch: SubCategoryDto | null = null; + currentCategory?.sub.forEach((category) => { + const { url } = category; + if (pathname.startsWith(url) && url.length > maxOverlap) { + maxOverlap = url.length; + bestMatch = category; + } + }); + return bestMatch; + }, [pathname, currentCategory]); + + useEffect(() => { + setCurrentCategory(headerInfos.filter((headerInfo) => pathname.startsWith(headerInfo.url))[0]); + }, [pathname]); + + useEffect(() => { + setCurrentSubCategory(findMatchPath()); + }, [findMatchPath]); + + return ( + <div + className="hidden w-[290px] border-r border-r-border bg-white px-5 pt-[120px] lg:block" + style={{ minHeight: 'calc(100vh - 200px)' }} + > + <p className="text-xl font-semibold">{currentCategory?.title}</p> + <p className="mt-8 break-keep text-sm text-comment">{currentCategory?.description}</p> + <div className="z-40 mt-8 flex h-full w-full flex-col text-comment"> + {currentCategory?.sub.map((sub) => ( + <Link + className={`mb-5 text-lg font-semibold leading-10 ${sub.title === currentSubCategory?.title && 'text-black underline underline-offset-8'}`} + key={sub.title} + href={sub.url} + > + {sub.title} + </Link> + ))} + </div> + </div> + ); +} diff --git a/frontend/src/components/layout/SidebarLayout.tsx b/frontend/src/components/layout/SidebarLayout.tsx new file mode 100644 index 00000000..414c7385 --- /dev/null +++ b/frontend/src/components/layout/SidebarLayout.tsx @@ -0,0 +1,12 @@ +import Sidebar from '@/components/layout/Sidebar'; + +export default function SidebarLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + <div className="block w-full bg-white lg:flex lg:bg-background-light" style={{ minHeight: 'calc(100vh-200px)' }}> + <Sidebar /> + <div className="grow"> + <div className="lg:w-client-content mt-[50px] w-full overflow-hidden p-5 lg:mt-[76px]">{children}</div> + </div> + </div> + ); +} diff --git a/frontend/src/app/admin/faculty/list/components/MemberTable/index.tsx b/frontend/src/components/ui/admin/faculty/FacultyMemberTable.tsx similarity index 90% rename from frontend/src/app/admin/faculty/list/components/MemberTable/index.tsx rename to frontend/src/components/ui/admin/faculty/FacultyMemberTable.tsx index b8603a09..54baea39 100644 --- a/frontend/src/app/admin/faculty/list/components/MemberTable/index.tsx +++ b/frontend/src/components/ui/admin/faculty/FacultyMemberTable.tsx @@ -1,14 +1,13 @@ -/* eslint-disable max-len */ - 'use client'; +import { useRouter } from 'next/navigation'; +import { toast } from 'react-toastify'; + import { useAppSelector } from '@/lib/hooks/redux'; import { useDeleteFacultyMutation } from '@/lib/hooks/useAdminApi'; import { FacultyMemberDto } from '@/types/common.dto'; -import { useRouter } from 'next/navigation'; -import { toast } from 'react-toastify'; -const MemberTable = ({ members }: { members: FacultyMemberDto[] }) => { +export default function FacultyMemberTable({ members }: { members: FacultyMemberDto[] }) { const { mutate: deleteFaculty } = useDeleteFacultyMutation(); const auth = useAppSelector((state) => state.auth).value; const router = useRouter(); @@ -16,18 +15,16 @@ const MemberTable = ({ members }: { members: FacultyMemberDto[] }) => { const handleDeleteButtonClick = (member: FacultyMemberDto) => { window.confirm(`${member.name}을(를) 삭제하시겠습니까?`); deleteFaculty(member.facultyId, { - onSuccess(data, variables, context) { + onSuccess() { toast.info('교직원 삭제에 성공했습니다.'); router.refresh(); }, - onError(error, variables, context) { + onError(error) { toast.error(error.message); }, }); }; - console.log(auth); - return ( <table className="my-4 w-full table-fixed text-center text-sm [&_*]:cursor-default"> <thead className="border-y-2 border-admin-border [&_th]:p-2"> @@ -64,6 +61,4 @@ const MemberTable = ({ members }: { members: FacultyMemberDto[] }) => { </tbody> </table> ); -}; - -export default MemberTable; +} diff --git a/frontend/src/app/admin/milestone/list/components/MilestoneHistoryTable/MilestoneHistoryExcelFileDownloadButton.tsx/index.tsx b/frontend/src/components/ui/admin/milestone/AdminMilestoneDownloadButton.tsx similarity index 80% rename from frontend/src/app/admin/milestone/list/components/MilestoneHistoryTable/MilestoneHistoryExcelFileDownloadButton.tsx/index.tsx rename to frontend/src/components/ui/admin/milestone/AdminMilestoneDownloadButton.tsx index a216f697..6273d66f 100644 --- a/frontend/src/app/admin/milestone/list/components/MilestoneHistoryTable/MilestoneHistoryExcelFileDownloadButton.tsx/index.tsx +++ b/frontend/src/components/ui/admin/milestone/AdminMilestoneDownloadButton.tsx @@ -4,12 +4,12 @@ import { useMilestoneHistoryExcelFileQuery } from '@/lib/hooks/useAdminApi'; import { useMemo } from 'react'; import { toast } from 'react-toastify'; -interface MilestoneHistoryExcelFileDownloadButtonProps { +interface AdminMilestoneDownloadButtonProps { field: number | null; keyword: string | null; } -const MilestoneHistoryExcelFileDownloadButton = ({ field, keyword }: MilestoneHistoryExcelFileDownloadButtonProps) => { +export default function AdminMilestoneDownloadButton({ field, keyword }: AdminMilestoneDownloadButtonProps) { const { data: excelFile } = useMilestoneHistoryExcelFileQuery(field, keyword); const excelFileUrl = useMemo(() => { if (excelFile) { @@ -38,6 +38,4 @@ const MilestoneHistoryExcelFileDownloadButton = ({ field, keyword }: MilestoneHi Excel로 다운로드 </button> ); -}; - -export default MilestoneHistoryExcelFileDownloadButton; +} diff --git a/frontend/src/components/ui/admin/milestone/AdminMilestoneFilePreview.tsx b/frontend/src/components/ui/admin/milestone/AdminMilestoneFilePreview.tsx new file mode 100644 index 00000000..68251016 --- /dev/null +++ b/frontend/src/components/ui/admin/milestone/AdminMilestoneFilePreview.tsx @@ -0,0 +1,55 @@ +import Image from 'next/image'; + +import { HistoryFileType } from '@/data/milestone'; +import { getFileType } from '@/lib/utils/utils'; + +interface AdminMilestoneFilePreviewProps { + fileName: string | null; +} + +export default function AdminMilestoneFilePreview({ fileName }: AdminMilestoneFilePreviewProps) { + switch (getFileType(fileName)) { + case HistoryFileType.PDF: + return ( + <> + <a + className="w-full rounded-sm bg-admin-primary-main text-white" + href={process.env.NEXT_PUBLIC_FILE_URL + '/' + fileName} + download + > + 다운로드 + </a> + <embed + src={process.env.NEXT_PUBLIC_FILE_URL + '/' + fileName} + type="application/pdf" + className="h-full w-full" + style={{ height: '100%', minHeight: '800px' }} + /> + </> + ); + case HistoryFileType.IMAGE: + return ( + <> + <a + className="w-full rounded-sm bg-admin-primary-main text-white" + href={process.env.NEXT_PUBLIC_FILE_URL + '/' + fileName} + download + > + 다운로드 + </a> + <Image + src={process.env.NEXT_PUBLIC_FILE_URL + '/' + fileName} + priority={false} + layout="responsive" + alt={fileName ?? ''} + width={532} + height={532} + /> + </> + ); + case HistoryFileType.EMPTY: + return <div>첨부된 파일이 없습니다.</div>; + default: + return <div>잘못된 유형의 파일이 첨부되어 있습니다.</div>; + } +} diff --git a/frontend/src/components/ui/admin/milestone/AdminMilestoneStatusChangeButton.tsx b/frontend/src/components/ui/admin/milestone/AdminMilestoneStatusChangeButton.tsx new file mode 100644 index 00000000..624e55e8 --- /dev/null +++ b/frontend/src/components/ui/admin/milestone/AdminMilestoneStatusChangeButton.tsx @@ -0,0 +1,105 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { toast } from 'react-toastify'; + +import TextInput from '@/components/common/formik/TextInput'; +import { MilestoneHistoryStatus } from '@/data/milestone'; +import { + useMilestoneHistoryStatusApproveMutation, + useMilestoneHistoryStatusCancelMutation, + useMilestoneHistoryStatusRejectMutation, +} from '@/lib/hooks/useAdminApi'; +import { convertMilestoneHistoryStatus } from '@/lib/utils/utils'; + +interface AdminMilestoneStatusChangeButtonProps { + historyId: number; + status: string; +} +export default function AdminMilestoneStatusChangeButton({ historyId, status }: AdminMilestoneStatusChangeButtonProps) { + const { mutate: approveMilestoneHistory } = useMilestoneHistoryStatusApproveMutation(); + const { mutate: rejectMilestoneHistory } = useMilestoneHistoryStatusRejectMutation(); + const { mutate: cancelMilestoneHistory } = useMilestoneHistoryStatusCancelMutation(); + const [rejectReason, setRejectReason] = useState<string>(''); + const router = useRouter(); + + const handleApproveButtonClick = () => + approveMilestoneHistory(historyId, { + onSuccess: () => { + toast.info('실적 내역을 승인하였습니다.'); + router.refresh(); + }, + onError: () => { + toast.error('실적 내역을 승인하는 데 실패했습니다.'); + }, + }); + const handleRejectButtonClick = () => + rejectMilestoneHistory( + { historyId, rejectReason }, + { + onSuccess: () => { + toast.info('실적 내역을 반려하였습니다.'); + router.refresh(); + }, + onError: () => { + toast.error('실적 내역을 반려하는 데 실패했습니다.'); + }, + }, + ); + + const handleCancelButtonClick = () => + cancelMilestoneHistory(historyId, { + onSuccess: () => { + toast.info(`${convertMilestoneHistoryStatus(status)}을(를) 취소했습니다.`); + router.refresh(); + }, + onError: () => { + toast.error(`${convertMilestoneHistoryStatus(status)} 취소에 실패하였습니다.`); + }, + }); + switch (status) { + case MilestoneHistoryStatus.PENDING: + return ( + <div className="flex items-end justify-center gap-4"> + <button + type="button" + onClick={handleApproveButtonClick} + className="h-full flex-1 rounded-sm bg-admin-primary-light text-white hover:bg-admin-primary-main" + > + 승인 + </button> + <div className="h-full w-0 border-l-2 border-border" /> + <div className="flex flex-1 flex-col gap-4"> + <TextInput + placeholder="반려 사유" + value={rejectReason} + onChange={(e) => setRejectReason(e.target.value)} + name="rejectReason" + className="w-full" + /> + <button + type="button" + onClick={handleRejectButtonClick} + className="rounded-sm bg-admin-semantic-error-light py-2 text-white hover:bg-admin-semantic-error-main" + > + 반려 + </button> + </div> + </div> + ); + case MilestoneHistoryStatus.APPROVED: + case MilestoneHistoryStatus.REJECTED: + return ( + <button + type="button" + onClick={handleCancelButtonClick} + className="rounded-sm bg-admin-secondary-main py-2 text-white" + > + {convertMilestoneHistoryStatus(status)}취소 + </button> + ); + default: + return <div className="text-center text-admin-comment">마일스톤 내역의 상태 값에 문제가 있습니다.</div>; + } +} diff --git a/frontend/src/components/ui/admin/milestone/AdminMilestoneTable.tsx b/frontend/src/components/ui/admin/milestone/AdminMilestoneTable.tsx new file mode 100644 index 00000000..982293f4 --- /dev/null +++ b/frontend/src/components/ui/admin/milestone/AdminMilestoneTable.tsx @@ -0,0 +1,111 @@ +import Link from 'next/link'; + +import { MilestoneHistoryStatus } from '@/data/milestone'; +import { convertMilestoneHistoryStatus } from '@/lib/utils/utils'; +import { MilestoneHistoryDto } from '@/types/common.dto'; + +interface AdminMilestoneTableProps { + histories: MilestoneHistoryDto[]; +} + +export default function AdminMilestoneTable({ histories }: AdminMilestoneTableProps) { + const getHistoryStatus = (status: string) => { + switch (status) { + case MilestoneHistoryStatus.PENDING: + return <span className="text-lg font-bold text-admin-comment">{convertMilestoneHistoryStatus(status)}</span>; + case MilestoneHistoryStatus.APPROVED: + return ( + <span className="text-lg font-bold text-admin-primary-light">{convertMilestoneHistoryStatus(status)}</span> + ); + case MilestoneHistoryStatus.REJECTED: + return ( + <span className="text-lg font-bold text-admin-semantic-error-main"> + {convertMilestoneHistoryStatus(status)} + </span> + ); + default: + return convertMilestoneHistoryStatus(status); + } + }; + + return ( + <table className="my-4 w-full table-fixed text-center text-sm [&_*]:cursor-default"> + <thead className="border-y-2 border-admin-border [&_th]:p-2"> + <tr> + <th className="w-[60px]">No.</th> + <th className="w-[60px]">이름</th> + <th className="w-[80px]">학번</th> + <th className="w-[100px]">활동 코드</th> + <th className="w-full">활동명</th> + <th className="w-[80px]">건당 점수</th> + <th className="w-[80px]">활동 횟수(건)</th> + <th className="w-[100px]">활동일</th> + <th className="w-[100px]">등록일</th> + <th className="w-[100px]">승인 여부</th> + </tr> + </thead> + <tbody> + {histories?.map((history) => { + const linkHref = `/admin/milestone/${history.id}`; + return ( + <tr + key={history.id} + className="cursor-pointer border-b-[1px] border-admin-border hover:bg-admin-background-light [&_td]:h-[50px] [&_td]:break-keep" + > + <td> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.id} + </Link> + </td> + <td className="font-semibold"> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.student.name} + </Link> + </td> + <td className="font-semibold"> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.student.id} + </Link> + </td> + <td> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.milestone.id} + </Link> + </td> + <td className="text-left"> + <Link href={linkHref} className="flex h-full w-full items-center justify-start"> + {history.description} + </Link> + </td> + <td> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.milestone.score} + </Link> + </td> + <td> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.count} + </Link> + </td> + <td> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.activatedAt} + </Link> + </td> + <td> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {history.createdAt.slice(0, 10)} + </Link> + </td> + <td> + <Link href={linkHref} className="flex h-full w-full items-center justify-center"> + {getHistoryStatus(history.status)} + </Link> + </td> + </tr> + ); + })} + </tbody> + </table> + ); +} diff --git a/frontend/src/components/ui/admin/student/StudentMemberTable.tsx b/frontend/src/components/ui/admin/student/StudentMemberTable.tsx new file mode 100644 index 00000000..632a6a32 --- /dev/null +++ b/frontend/src/components/ui/admin/student/StudentMemberTable.tsx @@ -0,0 +1,40 @@ +import { StudentMemberDto } from '@/types/common.dto'; + +export default function StudentMemberTable({ members }: { members: StudentMemberDto[] }) { + return ( + <table className="my-4 w-full table-fixed text-center text-sm [&_*]:cursor-default"> + <thead className="border-y-2 border-admin-border [&_th]:p-2"> + <th className="w-[100px]">이메일</th> + <th className="w-[60px]">이름</th> + <th className="w-[80px]">학번</th> + <th className="w-[100px]">주전공</th> + <th className="w-[100px]">부전공</th> + <th className="w-[100px]">복수전공</th> + <th className="w-[80px]">전화번호</th> + <th className="w-[100px]">진로</th> + </thead> + <tbody> + {members.map((member) => { + const emailWords = member.email.split('@'); + return ( + <tr key={member.id} className="h-[50px] border-b-[1px] border-admin-border [&_td]:break-keep [&_td]:p-2"> + <td> + {emailWords[0]} <br /> + <span className="text-xs text-admin-comment">@pusan.ac.kr</span> + </td> + <td className="font-semibold">{member.name}</td> + <td>{member.id}</td> + <td>{member.major}</td> + <td>{member.minor}</td> + <td>{member.doubleMajor}</td> + <td>{member.phoneNumber}</td> + <td className="overflow-hidden text-ellipsis whitespace-nowrap hover:whitespace-normal"> + {member.careerDetail} + </td> + </tr> + ); + })} + </tbody> + </table> + ); +} diff --git a/frontend/src/components/ui/auth/AuthFindPageFooter.tsx b/frontend/src/components/ui/auth/AuthFindPageFooter.tsx new file mode 100644 index 00000000..bf1e7023 --- /dev/null +++ b/frontend/src/components/ui/auth/AuthFindPageFooter.tsx @@ -0,0 +1,18 @@ +export default function AuthFindPageFooter() { + return ( + <div className="grid w-full grid-cols-2 text-center"> + <div className="cursor-default border-r-2 border-border text-sm font-semibold text-comment"> + 로그인 화면{' '} + <a href="/sign-in" className="underline-offset-3 font-semibold underline"> + 돌아가기 + </a> + </div> + <div className="cursor-default text-sm font-semibold text-comment"> + 처음오셨나요?{' '} + <a href="/sign-up" className="underline-offset-3 font-semibold underline"> + 회원가입 + </a> + </div> + </div> + ); +} diff --git a/frontend/src/components/ui/auth/AuthFindPageTabButton.tsx b/frontend/src/components/ui/auth/AuthFindPageTabButton.tsx new file mode 100644 index 00000000..c560561f --- /dev/null +++ b/frontend/src/components/ui/auth/AuthFindPageTabButton.tsx @@ -0,0 +1,28 @@ +import { headers } from 'next/headers'; + +const findTabs = [ + { name: '아이디 찾기', url: '/find-id' }, + { name: '비밀번호 찾기', url: '/find-password' }, +]; + +export default function AuthFindPageTabButton() { + const headersList = headers(); + const pathname = headersList.get('x-pathname') || ''; + + const matchedPathStyle = 'border-b-4 border-primary-main text-primary-main'; + const unmatchedPathStyle = 'border-b-[1px] border-comment text-comment'; + + return ( + <div className="flex w-full text-center"> + {findTabs.map((tab) => ( + <a + key={tab.url} + href={tab.url} + className={`flex-grow pb-3 font-semibold ${pathname === tab.url ? matchedPathStyle : unmatchedPathStyle}`} + > + {tab.name} + </a> + ))} + </div> + ); +} diff --git a/frontend/src/app/(client)/(auth)/find-password/components/FindForm/index.tsx b/frontend/src/components/ui/auth/AuthFindPasswordForm.tsx similarity index 92% rename from frontend/src/app/(client)/(auth)/find-password/components/FindForm/index.tsx rename to frontend/src/components/ui/auth/AuthFindPasswordForm.tsx index c909dfb0..e6284a61 100644 --- a/frontend/src/app/(client)/(auth)/find-password/components/FindForm/index.tsx +++ b/frontend/src/components/ui/auth/AuthFindPasswordForm.tsx @@ -1,12 +1,12 @@ 'use client'; +import { useRouter } from 'next/navigation'; import { Form, Formik } from 'formik'; +import { toast } from 'react-toastify'; -import EmailTextInput from '@/components/Formik/EmailTextInput'; -import TextInput from '@/components/Formik/TextInput'; +import EmailTextInput from '@/components/common/formik/EmailTextInput'; +import TextInput from '@/components/common/formik/TextInput'; import { useResetPasswordMutation } from '@/lib/hooks/useApi'; -import { toast } from 'react-toastify'; -import { useRouter } from 'next/navigation'; interface FormType { email: string; @@ -18,7 +18,7 @@ const initialValues: FormType = { name: '', }; -const FindForm = () => { +export default function AuthFindPasswordForm() { const { mutate: resetPasswordMutation } = useResetPasswordMutation(); const router = useRouter(); @@ -82,6 +82,4 @@ const FindForm = () => { )} </Formik> ); -}; - -export default FindForm; +} diff --git a/frontend/src/components/ui/auth/AuthSignInForm.tsx b/frontend/src/components/ui/auth/AuthSignInForm.tsx new file mode 100644 index 00000000..714a244c --- /dev/null +++ b/frontend/src/components/ui/auth/AuthSignInForm.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { toast } from 'react-toastify'; + +import { useAppDispatch, useAppSelector } from '@/lib/hooks/redux'; +import { useSignInMutation } from '@/lib/hooks/useApi'; +import { signIn } from '@/store/auth.slice'; + +export default function AuthSignInForm() { + const [userInfo, setUserInfo] = useState({ + email: '', + password: '', + }); + + const router = useRouter(); + + const { mutate: signInMutation } = useSignInMutation(); + + const dispatch = useAppDispatch(); + const auth = useAppSelector((state) => state.auth).value; + + const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { + setUserInfo((prev) => { + return { + ...prev, + [e.target.id]: e.target.value, + }; + }); + }; + + const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { + e.preventDefault(); + signInMutation(userInfo, { + onSuccess(data, variables, context) { + dispatch( + signIn({ + id: data.member_id, + token: `Bearer ${data.token}`, + name: data.name, + email: data.email, + isModerator: data.is_moderator, + }), + ); + router.push('/'); + }, + onError(error, variables, context) { + toast.error(error.message); + }, + }); + }; + + useEffect(() => { + if (auth.isAuth) router.push('/'); + }, [auth, router]); + + return ( + <form onSubmit={handleSubmit} className="flex flex-col gap-4"> + <div className="relative flex flex-col"> + <label className="text-sm font-semibold" htmlFor="email"> + 아이디(이메일) + </label> + <input + className="rounded-sm border border-border p-2 outline-none placeholder:italic placeholder:text-border" + id="email" + placeholder="아이디 입력" + value={userInfo.email} + onChange={handleInputChange} + /> + <div className="absolute right-2.5 translate-y-1/2 text-sm text-comment" style={{ bottom: 'calc(50% - 10px)' }}> + @pusan.ac.kr + </div> + </div> + <div className="relative flex flex-col"> + <label className="text-sm font-semibold" htmlFor="password"> + 비밀번호 + </label> + <input + className="rounded-sm border border-border p-2 outline-none placeholder:italic placeholder:text-border" + type="password" + id="password" + placeholder="비밀번호 입력" + value={userInfo.password} + onChange={handleInputChange} + /> + </div> + <button className="mt-4 w-full rounded-sm border-0 bg-primary-main p-2 text-white" type="submit"> + 로그인 + </button> + </form> + ); +} diff --git a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpFirstPage/index.tsx b/frontend/src/components/ui/auth/AuthSignUpFirst.tsx similarity index 94% rename from frontend/src/app/(client)/(auth)/sign-up/components/SignUpFirstPage/index.tsx rename to frontend/src/components/ui/auth/AuthSignUpFirst.tsx index 639db8b3..b837827f 100644 --- a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpFirstPage/index.tsx +++ b/frontend/src/components/ui/auth/AuthSignUpFirst.tsx @@ -1,17 +1,15 @@ -/* eslint-disable import/no-extraneous-dependencies */ - 'use client'; import { Formik, Form } from 'formik'; import * as Yup from 'yup'; -import { TextInput } from '@/app/(client)/components/Formik/TextInput'; +import { TextInput } from '@/components/common/formik/TextInputDdang'; -import EmailTextInput from './components/EmailTextInput'; import { useState } from 'react'; import { useSendAuthCodeMutation } from '@/lib/hooks/useApi'; import { toast } from 'react-toastify'; import { getValidationStudentId } from '@/lib/api/server.api'; +import EmailTextInput from '@/components/common/formik/EmailTextInput'; export interface FirstInfo { email: string; @@ -62,12 +60,12 @@ const validationSchema = Yup.object().shape({ .matches(/^([0-9]{10,11})$/, '띄어쓰기나 특수기호 없이 숫자로만 입력해주세요.'), }); -interface SignUpFirstPageProps { +interface AuthSignUpFirstProps { initialValues: FirstInfo; handleNextButtonClick: (value: FirstInfo) => void; } -const SignUpSecondPage = ({ initialValues, handleNextButtonClick }: SignUpFirstPageProps) => { +export default function AuthSignUpFirst({ initialValues, handleNextButtonClick }: AuthSignUpFirstProps) { const [isSendedMail, setIsSendedMail] = useState<boolean>(false); const { mutate: sendAuthCodeMutation } = useSendAuthCodeMutation(); @@ -75,10 +73,10 @@ const SignUpSecondPage = ({ initialValues, handleNextButtonClick }: SignUpFirstP setIsSendedMail(true); sendAuthCodeMutation(email + '@pusan.ac.kr', { - onSuccess(data, variables, context) { + onSuccess() { toast.info('메일이 정상 발송되었습니다.'); }, - onError(error, variables, context) { + onError(error) { toast.error(error.message); }, }); @@ -205,6 +203,4 @@ const SignUpSecondPage = ({ initialValues, handleNextButtonClick }: SignUpFirstP )} </Formik> ); -}; - -export default SignUpSecondPage; +} diff --git a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpSecondPage/components/MajorDropdown/index.tsx b/frontend/src/components/ui/auth/AuthSignUpMajorDropdown.tsx similarity index 80% rename from frontend/src/app/(client)/(auth)/sign-up/components/SignUpSecondPage/components/MajorDropdown/index.tsx rename to frontend/src/components/ui/auth/AuthSignUpMajorDropdown.tsx index 542cfeb9..d340d4e9 100644 --- a/frontend/src/app/(client)/(auth)/sign-up/components/SignUpSecondPage/components/MajorDropdown/index.tsx +++ b/frontend/src/components/ui/auth/AuthSignUpMajorDropdown.tsx @@ -1,20 +1,17 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { useEffect, useState } from 'react'; -import Dropdown, { DropdownProps } from '@/components/Formik/Dropdown'; -import { useCollegeQuery } from '@/lib/hooks/useApi'; +import Dropdown, { DropdownProps } from '@/components/common/formik/Dropdown'; import { getColleges } from '@/mocks/college'; import { CollegeDto, MajorDto } from '@/types/common.dto'; -export interface MajorDropdownProps extends Omit<DropdownProps, 'options' | 'selectedId' | 'name'> { +export interface AuthSignUpMajorDropdownProps extends Omit<DropdownProps, 'options' | 'selectedId' | 'name'> { collegeId: number; majorId: number; collegeName: string; majorName: string; } -const MajorDropdown = ({ ...props }: MajorDropdownProps) => { +export default function AuthSignUpMajorDropdown({ ...props }: AuthSignUpMajorDropdownProps) { const collegesData = getColleges; /* TODO: mocks 삭제 및 api 호출 const { data: collegesData } = useCollegeQuery(); @@ -65,6 +62,4 @@ const MajorDropdown = ({ ...props }: MajorDropdownProps) => { /> </div> ); -}; - -export default MajorDropdown; +} diff --git a/frontend/src/components/ui/auth/AuthSignUpSecond.tsx b/frontend/src/components/ui/auth/AuthSignUpSecond.tsx new file mode 100644 index 00000000..b8458ebd --- /dev/null +++ b/frontend/src/components/ui/auth/AuthSignUpSecond.tsx @@ -0,0 +1,133 @@ +'use client'; + +import { Formik, Form } from 'formik'; +import * as Yup from 'yup'; + +import { Dropdown } from '@/components/common/formik/DropdownDdang'; +import { TextInput } from '@/components/common/formik/TextInputDdang'; +import { careerCategory } from '@/data/signUp'; +import AuthSignUpMajorDropdown from '@/components/ui/auth/AuthSignUpMajorDropdown'; + +export interface SecondInfo { + majorCollegeId: number; + majorId: number; + minorCollegeId: number; + minorId: number; + doubleMajorCollegeId: number; + doubleMajorId: number; + career: number; + careerDetail: string; +} + +const validationSchema = Yup.object().shape({ + majorId: Yup.number().min(1, '필수 입력란입니다. 전공을 선택해주세요.'), + minorId: Yup.number().min(0, '필수 입력란입니다. 부전공 선택해주세요.'), + doubleMajorId: Yup.number().min(0, '필수 입력란입니다. 복수전공을 선택해주세요.'), + career: Yup.number().min(1, '필수 입력란입니다. 진로 분류를 선택해주세요.'), + careerDetail: Yup.string().required('필수 입력란입니다. 진로 상세 계획을 입력해주세요.'), +}); + +export interface SignUpSecondPageProps { + initialValues: SecondInfo; + handlePrevButtonClick: (value: SecondInfo) => void; + handleSubmitButtonClick: (value: SecondInfo) => void; +} + +export default function AuthSignUpSecond({ + initialValues, + handlePrevButtonClick, + handleSubmitButtonClick, +}: SignUpSecondPageProps) { + return ( + <Formik + initialValues={initialValues} + validationSchema={validationSchema} + onSubmit={(values, { setSubmitting }) => { + handleSubmitButtonClick(values); + setSubmitting(false); + }} + > + {({ isSubmitting, values, touched, handleChange, handleBlur, setFieldValue, errors }) => ( + <Form className="flex flex-col gap-6" autoComplete="off"> + <AuthSignUpMajorDropdown + collegeName="majorCollegeId" + majorName="majorId" + label="주전공" + selectOptionText="주전공을 선택해주세요." + collegeId={values.majorCollegeId} + majorId={values.majorId} + setFieldValue={setFieldValue} + isRequired + errorText={touched.majorId && errors.majorId ? errors.majorId : undefined} + /> + <AuthSignUpMajorDropdown + collegeName="minorCollegeId" + majorName="minorId" + label="부전공" + selectOptionText="부전공을 선택해주세요." + collegeId={values.minorCollegeId} + majorId={values.minorId} + setFieldValue={setFieldValue} + errorText={touched.minorId && errors.minorId ? errors.minorId : undefined} + /> + <AuthSignUpMajorDropdown + collegeName="doubleMajorCollegeId" + majorName="doubleMajorId" + label="복수전공" + selectOptionText="복수전공을 선택해주세요." + collegeId={values.doubleMajorCollegeId} + majorId={values.doubleMajorId} + setFieldValue={setFieldValue} + errorText={touched.doubleMajorId && errors.doubleMajorId ? errors.doubleMajorId : undefined} + /> + <div> + <Dropdown + name="career" + label="진로계획" + options={careerCategory} + selectOptionText="진로 분류 선택" + selectedId={values.career} + setFieldValue={setFieldValue} + isRequired + errorText={ + (touched.career && errors.career) || (touched.careerDetail && errors.careerDetail) ? '' : undefined + } + /> + <TextInput + name="careerDetail" + label="" + type="text" + placeholder="진로 상세 계획을 작성해주세요." + value={values.careerDetail} + onChange={handleChange} + onBlur={handleBlur} + errorText={ + (touched.career && errors.career) || (touched.careerDetail && errors.careerDetail) + ? errors.career || errors.careerDetail + : undefined + } + /> + </div> + <div className="flex w-full gap-2"> + <button + className="mt-2 flex-grow rounded-sm border-[1px] border-primary-main p-3 text-base text-primary-main" + type="reset" + onClick={() => { + handlePrevButtonClick(values); + }} + > + 이전으로 + </button> + <button + className="mt-2 flex-grow rounded-sm bg-primary-main p-3 text-base text-white" + type="submit" + disabled={isSubmitting} + > + 회원가입 + </button> + </div> + </Form> + )} + </Formik> + ); +} diff --git a/frontend/src/components/MarkdownViewer/markdown.css b/frontend/src/components/ui/hackathon/MarkdownViewer.css similarity index 100% rename from frontend/src/components/MarkdownViewer/markdown.css rename to frontend/src/components/ui/hackathon/MarkdownViewer.css diff --git a/frontend/src/components/MarkdownViewer/index.tsx b/frontend/src/components/ui/hackathon/MarkdownViewer.tsx similarity index 94% rename from frontend/src/components/MarkdownViewer/index.tsx rename to frontend/src/components/ui/hackathon/MarkdownViewer.tsx index 22bc5710..0ca0c7e3 100644 --- a/frontend/src/components/MarkdownViewer/index.tsx +++ b/frontend/src/components/ui/hackathon/MarkdownViewer.tsx @@ -17,13 +17,13 @@ import { EditorContent, useEditor } from '@tiptap/react'; import { StarterKit } from '@tiptap/starter-kit'; import { common, createLowlight } from 'lowlight'; import { Markdown } from 'tiptap-markdown'; -import './markdown.css'; +import './MarkdownViewer.css'; interface MarkdownViewerProps { content: string; } -const MarkdownViewer = ({ content }: MarkdownViewerProps) => { +export default function MarkdownViewer({ content }: MarkdownViewerProps) { const editor = useEditor({ extensions: [ StarterKit.configure({ @@ -69,6 +69,4 @@ const MarkdownViewer = ({ content }: MarkdownViewerProps) => { <EditorContent editor={editor} /> </div> ); -}; - -export default MarkdownViewer; +} diff --git a/frontend/src/components/ui/home/GoPageIcon.tsx b/frontend/src/components/ui/home/GoPageIcon.tsx new file mode 100644 index 00000000..7e6b5151 --- /dev/null +++ b/frontend/src/components/ui/home/GoPageIcon.tsx @@ -0,0 +1,16 @@ +import { VscAdd } from '@react-icons/all-files/vsc/VscAdd'; +import Link from 'next/link'; + +interface GoPageIconProps { + name: string; + url: string; +} + +export default function GoPageIcon({ name, url }: GoPageIconProps) { + return ( + <Link href={url} className="flex items-center gap-1 text-sm font-semibold text-comment"> + <VscAdd /> + {name} + </Link> + ); +} diff --git a/frontend/src/components/ui/home/HomeAnnouncement.tsx b/frontend/src/components/ui/home/HomeAnnouncement.tsx new file mode 100644 index 00000000..c9a2a057 --- /dev/null +++ b/frontend/src/components/ui/home/HomeAnnouncement.tsx @@ -0,0 +1,40 @@ +import RSSParser from 'rss-parser'; + +import GoPageIcon from '@/components/ui/home/GoPageIcon'; + +import Link from 'next/link'; + +export default async function HomeAnnouncement() { + const ANNOUNCEMENT_URL = 'https://swedu.pusan.ac.kr/swedu/31630/subview.do'; + const parser = new RSSParser(); + const announcements = await parser.parseURL('https://swedu.pusan.ac.kr/bbs/swedu/6906/rssList.do?row=50'); + + return ( + <div style={{ display: 'flex', flexDirection: 'column' }}> + <div className="flex justify-between"> + <div className="flex flex-col"> + <p className="cursor-default text-lg font-semibold">공지사항</p> + <p className="cursor-default text-sm text-comment">소프트웨어융합교육원의 공지사항을 알려드려요.</p> + </div> + <GoPageIcon name="더보기" url={ANNOUNCEMENT_URL} /> + </div> + <div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginTop: '20px' }}> + {announcements.items.slice(0, 4).map((item) => ( + <Link + className="flex justify-between gap-5 rounded-sm border border-border p-[10px] hover:text-primary-main" + key={item.link} + href={item.link || ''} + target="_blank" + > + <span className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-semibold text-black"> + {item.title} + </span> + <span className="w-[90px] flex-shrink text-right text-sm text-comment"> + {item.pubDate?.slice(0, 10).replaceAll('-', '.')} + </span> + </Link> + ))} + </div> + </div> + ); +} diff --git a/frontend/src/components/ui/home/HomeExternalLink.tsx b/frontend/src/components/ui/home/HomeExternalLink.tsx new file mode 100644 index 00000000..463d3057 --- /dev/null +++ b/frontend/src/components/ui/home/HomeExternalLink.tsx @@ -0,0 +1,28 @@ +import Image from 'next/image'; + +import { externalLinkInfos } from '@/data/externalLink'; + +import Link from 'next/link'; + +export default function HomeExternalLink() { + return ( + <div className="grid grid-cols-3 gap-y-[20px] rounded-sm bg-primary-light p-[10px] md:grid-cols-6"> + {externalLinkInfos.map((link) => { + const markup = { __html: link.title }; + return ( + <Link + className="flex flex-col items-center justify-center gap-3" + key={link.url} + href={link.url} + target="_blank" + > + <div className="flex h-20 w-20 items-center justify-center rounded-full bg-white"> + <Image src={link.img} alt={link.title} width="50" height="50" style={{ width: 50, height: 50 }} /> + </div> + <div className="h-10 text-center text-xs font-semibold text-black" dangerouslySetInnerHTML={markup} /> + </Link> + ); + })} + </div> + ); +} diff --git a/frontend/src/app/(client)/components/Milestone/index.tsx b/frontend/src/components/ui/home/HomeMilestone.tsx similarity index 55% rename from frontend/src/app/(client)/components/Milestone/index.tsx rename to frontend/src/components/ui/home/HomeMilestone.tsx index 85345ff5..0dfb1e22 100644 --- a/frontend/src/app/(client)/components/Milestone/index.tsx +++ b/frontend/src/components/ui/home/HomeMilestone.tsx @@ -1,18 +1,41 @@ -import MilestoneChart from '@/components/MilestoneChart'; -import MilestoneTable from '@/components/MilestoneTable'; +import MilestoneCircleChart from '@/components/ui/milestone/MilestoneCircleChart'; +import MilestoneOverviewTable from '@/components/ui/milestone/MilestoneOverviewTable'; +import GoPageIcon from '@/components/ui/home/GoPageIcon'; +import HomeSignIn from '@/components/ui/home/HomeSignIn'; import { getAuthFromCookie } from '@/lib/utils/auth'; import { AuthSliceState } from '@/store/auth.slice'; -import { MilestoneChartWrapper } from './styled'; -import GoPageIcon from '../GoPageIcon'; -import SignIn from '../SignIn'; -import { Description, Title, TitleContent, TitleWrapper } from '../styled'; import { getMyMilestoneHistory } from '@/lib/api/server.api'; import { DateTime } from 'luxon'; import { MilestoneOverviewScore } from '@/types/milestone'; import { initialMilestoneOverview } from '@/data/milestone'; -const getMilestoneScores = async (studentId: number) => { +export default async function HomeMilestone() { + const auth: AuthSliceState = getAuthFromCookie(); + + const milestoneOverviewScore = await getMilestoneOverviewScore(auth.id); + + return ( + <div className="flex flex-col"> + <div className="flex justify-between"> + <div className="flex flex-col"> + <p className="text-lg font-semibold">나의 마일스톤</p> + <p className="cursor-default text-sm text-comment">나의 마일스톤 내역을 확인할 수 있어요.</p> + </div> + {auth.isAuth && <GoPageIcon name="전체보기" url="/my-page/milestone" />} + </div> + {auth.isAuth && ( + <div className="m-3 flex items-center justify-around gap-5"> + <MilestoneCircleChart chartSize={120} fontSize="sm" milestoneOverviewScore={milestoneOverviewScore} /> + <MilestoneOverviewTable milestoneOverviewScore={milestoneOverviewScore} /> + </div> + )} + {!auth.isAuth && <HomeSignIn />} + </div> + ); +} + +async function getMilestoneScores(studentId: number) { const auth = getAuthFromCookie(); const startDate = DateTime.now().minus({ years: 1 }).toFormat('yyyy-MM-dd'); const endDate = DateTime.now().toFormat('yyyy-MM-dd'); @@ -23,9 +46,9 @@ const getMilestoneScores = async (studentId: number) => { // TODO: server api error handling } return null; -}; +} -const getMilestoneOverviewScore = async (studentId: number) => { +async function getMilestoneOverviewScore(studentId: number) { const milestoneScores = await getMilestoneScores(studentId); const milestoneOverviewScore = milestoneScores?.reduce<MilestoneOverviewScore>( (acc, cur) => { @@ -37,31 +60,4 @@ const getMilestoneOverviewScore = async (studentId: number) => { { ...initialMilestoneOverview }, ); return milestoneOverviewScore || initialMilestoneOverview; -}; - -const Milestone = async () => { - const auth: AuthSliceState = getAuthFromCookie(); - - const milestoneOverviewScore = await getMilestoneOverviewScore(auth.id); - - return ( - <div style={{ display: 'flex', flexDirection: 'column' }}> - <TitleWrapper style={{ justifyContent: 'space-between' }}> - <TitleContent> - <Title>나의 마일스톤 - 나의 마일스톤 내역을 확인할 수 있어요. - - {auth.isAuth && } - - {auth.isAuth && ( - - - - - )} - {!auth.isAuth && } -
- ); -}; - -export default Milestone; +} diff --git a/frontend/src/app/(client)/components/PnuLink/index.tsx b/frontend/src/components/ui/home/HomePnuLink.tsx similarity index 61% rename from frontend/src/app/(client)/components/PnuLink/index.tsx rename to frontend/src/components/ui/home/HomePnuLink.tsx index 8d2ee2e1..90b72603 100644 --- a/frontend/src/app/(client)/components/PnuLink/index.tsx +++ b/frontend/src/components/ui/home/HomePnuLink.tsx @@ -5,14 +5,15 @@ import { useEffect, useState } from 'react'; import { Autoplay, Scrollbar } from 'swiper/modules'; import { Swiper, SwiperClass, SwiperSlide } from 'swiper/react'; +import { VscChevronLeft } from '@react-icons/all-files/vsc/VscChevronLeft'; +import { VscChevronRight } from '@react-icons/all-files/vsc/VscChevronRight'; import { RESPONSIVE_WIDTH } from '@/constants'; import { pnuLinkInfos } from '@/data/externalLink'; -import { ButtonWrapper, NextButton, PnuLinker, PrevButton } from './styled'; - import 'swiper/css'; +import Link from 'next/link'; -const PnuLink = () => { +export default function HomePnuLink() { const [displayCount, setDisplayCount] = useState(2); const [swiper, setSwiper] = useState(); const [innerWidth, setInnerWidth] = useState(window.innerWidth); @@ -35,16 +36,21 @@ const PnuLink = () => { useEffect(() => { if (innerWidth > parseInt(RESPONSIVE_WIDTH.desktop, 10)) setDisplayCount(6); else if (innerWidth > parseInt(RESPONSIVE_WIDTH.tablet, 10)) setDisplayCount(4); - else if (innerWidth > parseInt(RESPONSIVE_WIDTH.mobile, 10)) setDisplayCount(3); else setDisplayCount(2); }, [innerWidth]); return (
- - - - +
+ + +
{ > {pnuLinkInfos.map((link) => ( - - {link.title} - + + {link.title} + ))}
); -}; - -export default PnuLink; +} diff --git a/frontend/src/components/ui/home/HomeSignIn.tsx b/frontend/src/components/ui/home/HomeSignIn.tsx new file mode 100644 index 00000000..ccb50c18 --- /dev/null +++ b/frontend/src/components/ui/home/HomeSignIn.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; + +import { useAppDispatch } from '@/lib/hooks/redux'; +import { signIn } from '@/store/auth.slice'; + +import { useSignInMutation } from '@/lib/hooks/useApi'; +import { toast } from 'react-toastify'; +import Link from 'next/link'; + +export default function HomeSignIn() { + const [userInfo, setUserInfo] = useState({ + email: '', + password: '', + }); + + const { mutate: signInMutation } = useSignInMutation(); + + const router = useRouter(); + const dispatch = useAppDispatch(); + + const handleInputChange = (e: React.ChangeEvent) => { + setUserInfo((prev) => { + return { + ...prev, + [e.target.id]: e.target.value, + }; + }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + signInMutation(userInfo, { + onSuccess(data, variables, context) { + dispatch( + signIn({ + id: data.member_id, + token: `Bearer ${data.token}`, + name: data.name, + email: data.email, + isModerator: data.is_moderator, + }), + ); + router.refresh(); + }, + onError(error, variables, context) { + toast.error(error.message); + }, + }); + }; + + return ( +
+
로그인이 필요한 서비스입니다.
+
+
+ + +
@pusan.ac.kr
+
+ +
+
+
+ + 아이디 + + / + + 비밀번호 + + 찾기 +
+
+ 처음오셨나요?{' '} + + 회원가입 + +
+
+
+ ); +} diff --git a/frontend/src/components/ui/home/HomeTeamBuilding.tsx b/frontend/src/components/ui/home/HomeTeamBuilding.tsx new file mode 100644 index 00000000..1c328bdb --- /dev/null +++ b/frontend/src/components/ui/home/HomeTeamBuilding.tsx @@ -0,0 +1,46 @@ +import TeamBuildingCard from '@/components/ui/team-building/TeamBuildingCard'; +import GoPageIcon from '@/components/ui/home/GoPageIcon'; +import { teamBuildingInfos } from '@/mocks/teamBuilding'; + +import Link from 'next/link'; + +export default function HomeTeamBuilding() { + return ( +
+
+
+

팀 빌딩

+

+ 프로젝트 팀원을 모집하고 있나요? 함께할 팀을 찾고 있나요? +

+
+ +
+
+ {teamBuildingInfos.length === 0 && ( +
+

아직 팀이 생성되지 않았습니다.

+

지금 바로 새로운 팀을 생성해 보세요!

+ + [팀 생성하기] + +
+ )} + {teamBuildingInfos.map((team) => ( + + ))} +
+
+ ); +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx b/frontend/src/components/ui/milestone/MilestoneAcceptedTable.tsx similarity index 86% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx rename to frontend/src/components/ui/milestone/MilestoneAcceptedTable.tsx index 438e09b5..f4e5381c 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx +++ b/frontend/src/components/ui/milestone/MilestoneAcceptedTable.tsx @@ -1,21 +1,24 @@ -/* eslint-disable jsx-a11y/control-has-associated-label */ -/* eslint-disable max-len */ -import Pagination from '@/app/(client)/components/Pagination'; -import MilestoneGroupLabel from '@/components/MilestoneGroupLabel'; +import { usePathname } from 'next/navigation'; + +import Pagination from '@/components/common/Pagination'; +import MilestoneGroupLabel from '@/components/ui/milestone/MilestoneGroupLabel'; import { MilestoneHistoryStatus } from '@/data/milestone'; import { useAppSelector } from '@/lib/hooks/redux'; import { useMilestoneHistoriesOfStudentQuery } from '@/lib/hooks/useApi'; import { Period } from '@/types/common'; import { MilestoneHistorySortCriteria, SortDirection } from '@/types/milestone'; -import { usePathname } from 'next/navigation'; -interface MilestoneHistoryTableProps { +export interface MilestoneAcceptedTableProps { searchFilterPeriod: Period; pageNumber: number; pageSize: number; } -const MilestoneHistoryTable = ({ searchFilterPeriod, pageNumber, pageSize }: MilestoneHistoryTableProps) => { +export default function MilestoneAcceptedTable({ + searchFilterPeriod, + pageNumber, + pageSize, +}: MilestoneAcceptedTableProps) { const pathname = usePathname(); const auth = useAppSelector((state) => state.auth).value; const { data: milestoneHistoriesOfStudent } = useMilestoneHistoriesOfStudentQuery( @@ -64,6 +67,4 @@ const MilestoneHistoryTable = ({ searchFilterPeriod, pageNumber, pageSize }: Mil />
); -}; - -export default MilestoneHistoryTable; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/write/components/MilestoneDropdown/index.tsx b/frontend/src/components/ui/milestone/MilestoneCategoryDropdown.tsx similarity index 88% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/write/components/MilestoneDropdown/index.tsx rename to frontend/src/components/ui/milestone/MilestoneCategoryDropdown.tsx index f7298846..febf882e 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/write/components/MilestoneDropdown/index.tsx +++ b/frontend/src/components/ui/milestone/MilestoneCategoryDropdown.tsx @@ -1,13 +1,11 @@ -/* eslint-disable operator-linebreak */ -/* eslint-disable implicit-arrow-linebreak */ import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'; -import { Dropdown, DropdownOption, DropdownProps } from '@/app/(client)/components/Formik/Dropdown'; +import { Dropdown, DropdownOption, DropdownProps } from '@/components/common/formik/DropdownDdang'; import { MilestoneGroup } from '@/data/milestone'; import { useMilestoneQuery } from '@/lib/hooks/useApi'; import { Milestone, MilestoneCategory } from '@/types/milestone'; -export interface MilestoneDropdownProps extends Omit { +export interface MilestoneCategoryDropdownProps extends Omit { categoryId: number; milestoneId: number; categoryName: string; @@ -17,7 +15,7 @@ export interface MilestoneDropdownProps extends Omit>; } -const MilestoneDropdown = ({ ...props }: MilestoneDropdownProps) => { +export default function MilestoneCategoryDropdown({ ...props }: MilestoneCategoryDropdownProps) { const { data: milestoneOverviews } = useMilestoneQuery(); const { categoryId, @@ -93,6 +91,4 @@ const MilestoneDropdown = ({ ...props }: MilestoneDropdownProps) => {
); -}; - -export default MilestoneDropdown; +} diff --git a/frontend/src/components/ui/milestone/MilestoneCircleChart.tsx b/frontend/src/components/ui/milestone/MilestoneCircleChart.tsx new file mode 100644 index 00000000..643b94d8 --- /dev/null +++ b/frontend/src/components/ui/milestone/MilestoneCircleChart.tsx @@ -0,0 +1,53 @@ +import { MilestoneOverviewScore } from '@/types/milestone'; + +export interface MilestoneCircleChartProps { + chartSize: number; + fontSize: 'sm' | 'lg'; + milestoneOverviewScore: MilestoneOverviewScore; +} + +export default function MilestoneCircleChart({ + chartSize, + fontSize, + milestoneOverviewScore, +}: MilestoneCircleChartProps) { + const { activityScore, globalScore, communityScore, totalScore } = milestoneOverviewScore; + const scores = [ + { start: 0, score: activityScore, color: '#8FA3F8', title: '실전적 SW역량' }, + { start: activityScore, score: globalScore, color: '#9DE6BC', title: '글로벌 SW역량' }, + { + start: activityScore + globalScore, + score: communityScore, + color: '#AA8CF8', + title: '커뮤니티 SW역량', + }, + ]; + + return ( +
+ {scores.map((bar) => ( +
+ ))} +
+
+

{totalScore}

+

/ 1000

+
+
+ ); +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryDeleteButton/index.tsx b/frontend/src/components/ui/milestone/MilestoneDeleteButton.tsx similarity index 76% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryDeleteButton/index.tsx rename to frontend/src/components/ui/milestone/MilestoneDeleteButton.tsx index e536e30a..b7a262f0 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryDeleteButton/index.tsx +++ b/frontend/src/components/ui/milestone/MilestoneDeleteButton.tsx @@ -5,11 +5,11 @@ import { useRouter } from 'next/navigation'; import { toast } from 'react-toastify'; import { useMilestoneHistoryDeleteMutation } from '@/lib/hooks/useApi'; -interface MilestoneHistoryDeleteButtonProps { +export interface MilestoneDeleteButtonProps { historyId: number; } -const MilestoneHistoryDeleteButton = ({ historyId }: MilestoneHistoryDeleteButtonProps) => { +export default function MilestoneDeleteButton({ historyId }: MilestoneDeleteButtonProps) { const router = useRouter(); const { mutate: deleteMilestoneHistory } = useMilestoneHistoryDeleteMutation(); @@ -30,11 +30,9 @@ const MilestoneHistoryDeleteButton = ({ historyId }: MilestoneHistoryDeleteButto ); -}; - -export default MilestoneHistoryDeleteButton; +} diff --git a/frontend/src/components/ui/milestone/MilestoneDetailTable.tsx b/frontend/src/components/ui/milestone/MilestoneDetailTable.tsx new file mode 100644 index 00000000..10d97b8a --- /dev/null +++ b/frontend/src/components/ui/milestone/MilestoneDetailTable.tsx @@ -0,0 +1,34 @@ +import { MilestoneScoreDto } from '@/types/common.dto'; + +interface MilestoneDetailTableProps { + milestoneScores: MilestoneScoreDto[]; +} + +export default function MilestoneDetailTable({ milestoneScores }: MilestoneDetailTableProps) { + return ( + + + {milestoneScores.map((milestoneScore) => ( + + + + + + ))} + +
+ + {milestoneScore.name} + + +
+
+
+
+ {milestoneScore.score}/{milestoneScore.limitScore} +
+ ); +} diff --git a/frontend/src/components/MilestoneGroupLabel/index.tsx b/frontend/src/components/ui/milestone/MilestoneGroupLabel.tsx similarity index 86% rename from frontend/src/components/MilestoneGroupLabel/index.tsx rename to frontend/src/components/ui/milestone/MilestoneGroupLabel.tsx index bd7ecec1..a7f1f6d8 100644 --- a/frontend/src/components/MilestoneGroupLabel/index.tsx +++ b/frontend/src/components/ui/milestone/MilestoneGroupLabel.tsx @@ -1,12 +1,13 @@ import { MilestoneGroup } from '@/data/milestone'; import { convertMilestoneGroup } from '@/lib/utils/utils'; -interface MilestoneGroupLabelProps { +export interface MilestoneGroupLabelProps { group: string; } -const MilestoneGroupLabel = ({ group }: MilestoneGroupLabelProps) => { +export default function MilestoneGroupLabel({ group }: MilestoneGroupLabelProps) { const labelText = convertMilestoneGroup(group); + switch (group) { case MilestoneGroup.ACTIVITY: return ( @@ -29,6 +30,4 @@ const MilestoneGroupLabel = ({ group }: MilestoneGroupLabelProps) => { default: return {labelText}; } -}; - -export default MilestoneGroupLabel; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx b/frontend/src/components/ui/milestone/MilestoneHistoryTable.tsx similarity index 53% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx rename to frontend/src/components/ui/milestone/MilestoneHistoryTable.tsx index 5273acbe..dea789bf 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx +++ b/frontend/src/components/ui/milestone/MilestoneHistoryTable.tsx @@ -1,20 +1,18 @@ -/* eslint-disable jsx-a11y/control-has-associated-label */ -/* eslint-disable max-len */ +import { headers } from 'next/headers'; -import MilestoneHistoryStatusLabel from '@/app/(client)/(withSidebar)/my-page/components/MilestoneHistoryStatusLabel'; import { getMilestoneHistoriesOfStudent } from '@/lib/api/server.api'; import { getAuthFromCookie } from '@/lib/utils/auth'; import { MilestoneHistorySortCriteria, SortDirection } from '@/types/milestone'; -import MilestoneHistoryDeleteButton from '../MilestoneHistoryDeleteButton'; -import Pagination from '@/app/(client)/components/Pagination'; -import { headers } from 'next/headers'; +import Pagination from '@/components/common/Pagination'; +import MilestoneStatusLabel from '@/components/ui/milestone/MilestoneStatusLabel'; +import MilestoneDeleteButton from '@/components/ui/milestone/MilestoneDeleteButton'; -interface MilestoneHistoryTableProp { +export interface MilestoneHistoryTableProps { pageNumber: number; } -const MilestoneHistoryTable = async ({ pageNumber }: MilestoneHistoryTableProp) => { +export default async function MilestoneHistoryTable({ pageNumber }: MilestoneHistoryTableProps) { const headersList = headers(); const pathname = headersList.get('x-pathname') || ''; @@ -37,51 +35,45 @@ const MilestoneHistoryTable = async ({ pageNumber }: MilestoneHistoryTableProp) return (
- +
- + - - - - - + + + + + {milestoneHistories?.content.map((milestoneHistory, index) => ( - + - + - - + - - - ))} @@ -95,6 +87,4 @@ const MilestoneHistoryTable = async ({ pageNumber }: MilestoneHistoryTableProp) /> ); -}; - -export default MilestoneHistoryTable; +} diff --git a/frontend/src/components/ui/milestone/MilestoneOverviewTable.tsx b/frontend/src/components/ui/milestone/MilestoneOverviewTable.tsx new file mode 100644 index 00000000..dfc3e072 --- /dev/null +++ b/frontend/src/components/ui/milestone/MilestoneOverviewTable.tsx @@ -0,0 +1,43 @@ +import { MilestoneOverviewScore } from '@/types/milestone'; + +export interface MilestoneOverviewTableProps { + milestoneOverviewScore: MilestoneOverviewScore; +} + +export default function MilestoneOverviewTable({ milestoneOverviewScore }: MilestoneOverviewTableProps) { + const { activityScore, globalScore, communityScore, totalScore } = milestoneOverviewScore; + + const scores = [ + { score: activityScore, color: '#8FA3F8', title: '실전적 SW역량' }, + { score: globalScore, color: '#9DE6BC', title: '글로벌 SW역량' }, + { score: communityScore, color: '#AA8CF8', title: '커뮤니티 SW역량' }, + ]; + + return ( +
No제목제목 점수활동일등록일진행 상황처리실적 내역활동일등록일진행 상황처리실적 내역
{index + 1} {milestoneHistory.description}{milestoneHistory.description} {milestoneHistory.milestone.score * milestoneHistory.count}{milestoneHistory.activatedAt.replaceAll('-', '.')} + {milestoneHistory.activatedAt.replaceAll('-', '.')} {milestoneHistory.createdAt.slice(0, 10).replaceAll('-', '.')} - + + - + + +
- +
{milestoneHistory.description}
-
+
활동: {milestoneHistory.activatedAt.replaceAll('-', '.')}
등록: {milestoneHistory.createdAt.slice(0, 10).replaceAll('-', '.')}
- +
+ + + + + + + + {scores.map((score) => ( + + + + + ))} + + + + + + + +
역량 구분획득
+
+ {score.title} +
{score.score}
합계{totalScore}
+ ); +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneHistoryStatusLabel/index.tsx b/frontend/src/components/ui/milestone/MilestoneStatusLabel.tsx similarity index 82% rename from frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneHistoryStatusLabel/index.tsx rename to frontend/src/components/ui/milestone/MilestoneStatusLabel.tsx index 764ec6e3..a2004003 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneHistoryStatusLabel/index.tsx +++ b/frontend/src/components/ui/milestone/MilestoneStatusLabel.tsx @@ -1,14 +1,13 @@ -/* eslint-disable max-len */ import { VscInfo } from '@react-icons/all-files/vsc/VscInfo'; import { MilestoneHistoryStatus } from '@/data/milestone'; -interface MilestoneHistoryStatusLabelProps { +export interface MilestoneStatusLabelProps { status: string; rejectReason: string | null; } -const MilestoneHistoryStatusLabel = ({ status, rejectReason }: MilestoneHistoryStatusLabelProps) => { +export default function MilestoneStatusLabel({ status, rejectReason }: MilestoneStatusLabelProps) { switch (status) { case MilestoneHistoryStatus.APPROVED: return 승인; @@ -27,6 +26,4 @@ const MilestoneHistoryStatusLabel = ({ status, rejectReason }: MilestoneHistoryS default: return '잘못된 상태'; } -}; - -export default MilestoneHistoryStatusLabel; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneSection/index.tsx b/frontend/src/components/ui/my-page/MyPageMilestone.tsx similarity index 75% rename from frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneSection/index.tsx rename to frontend/src/components/ui/my-page/MyPageMilestone.tsx index 5af631a8..4d8eb1b5 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneSection/index.tsx +++ b/frontend/src/components/ui/my-page/MyPageMilestone.tsx @@ -1,16 +1,11 @@ -/* eslint-disable implicit-arrow-linebreak */ -/* eslint-disable max-len */ -/* eslint-disable operator-linebreak */ -/* eslint-disable react/jsx-wrap-multilines */ - 'use client'; import { DateTime } from 'luxon'; import { useMemo, useState } from 'react'; -import MilestoneChart from '@/components/MilestoneChart'; -import MilestoneTable from '@/components/MilestoneTable'; -import SubTitle from '@/components/SubTitle'; +import MilestoneCircleChart from '@/components/ui/milestone/MilestoneCircleChart'; +import MilestoneOverviewTable from '@/components/ui/milestone/MilestoneOverviewTable'; +import PageSubTitle from '@/components/common/PageSubTitle'; import { MilestoneInfoType, initialMilestoneOverview, milestoneInfoTypes } from '@/data/milestone'; import { useAppSelector } from '@/lib/hooks/redux'; import { useMilestoneScoresOfStudentQuery } from '@/lib/hooks/useApi'; @@ -18,10 +13,10 @@ import { compareByIdAsc } from '@/lib/utils/utils'; import { Period } from '@/types/common'; import { MilestoneOverviewScore } from '@/types/milestone'; -import MilestoneHistoryTable from '../../milestone/components/MilestoneHistoryTable'; -import MilestoneRowBarTable from '../MilestoneRowBarTable'; +import MilestoneDetailTable from '@/components/ui/milestone/MilestoneDetailTable'; +import MilestoneAcceptedTable from '@/components/ui/milestone/MilestoneAcceptedTable'; -const MilestoneSection = () => { +export default function MyPageMilestone() { const auth = useAppSelector((state) => state.auth).value; const searchFilterPeriod: Period = { startDate: DateTime.now().minus({ years: 1 }).toFormat('yyyy-MM-dd'), @@ -49,7 +44,7 @@ const MilestoneSection = () => { return (
- +
{searchFilterPeriod.startDate}~ {searchFilterPeriod.endDate} @@ -69,14 +64,14 @@ const MilestoneSection = () => {
{selectedInfoType === MilestoneInfoType.TOTAL && (
- - + +
)} {(selectedInfoType === MilestoneInfoType.ACTIVITY || selectedInfoType === MilestoneInfoType.GLOBAL || selectedInfoType === MilestoneInfoType.COMMUNITY) && ( - milestoneScore.group === selectedInfoType) @@ -85,10 +80,9 @@ const MilestoneSection = () => { /> )} {selectedInfoType === MilestoneInfoType.HISTORY && ( - + )}
); -}; -export default MilestoneSection; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx b/frontend/src/components/ui/my-page/MyPageMilestoneDetail.tsx similarity index 58% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx rename to frontend/src/components/ui/my-page/MyPageMilestoneDetail.tsx index 4699d258..9503456a 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx +++ b/frontend/src/components/ui/my-page/MyPageMilestoneDetail.tsx @@ -8,34 +8,31 @@ import { useMilestoneScoresOfStudentQuery } from '@/lib/hooks/useApi'; import { compareByIdAsc } from '@/lib/utils/utils'; import { Period } from '@/types/common'; -import { GroupButton } from './styled'; -import MilestoneRowBarTable from '../../../components/MilestoneRowBarTable'; +import MilestoneDetailTable from '@/components/ui/milestone/MilestoneDetailTable'; -const MilestoneDetail = ({ startDate, endDate }: Period) => { +export default function MyPageMilestoneDetail({ startDate, endDate }: Period) { const auth = useAppSelector((state) => state.auth).value; const [selectedGroup, setSelectedGroup] = useState(MilestoneGroup.ACTIVITY); const { data: milestoneScores } = useMilestoneScoresOfStudentQuery(auth.id, startDate, endDate); return ( -
+
{milestoneGroups.map((group) => ( - setSelectedGroup(group.id)} > {group.text} - + ))}
- milestoneScore.group === selectedGroup) - .sort(compareByIdAsc)} + milestoneScore.group === selectedGroup).sort(compareByIdAsc) || [] + } />
); -}; - -export default MilestoneDetail; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneHistorySection/index.tsx b/frontend/src/components/ui/my-page/MyPageMilestoneHistory.tsx similarity index 65% rename from frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneHistorySection/index.tsx rename to frontend/src/components/ui/my-page/MyPageMilestoneHistory.tsx index b2931673..395af0d5 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/components/MilestoneHistorySection/index.tsx +++ b/frontend/src/components/ui/my-page/MyPageMilestoneHistory.tsx @@ -1,16 +1,11 @@ -/* eslint-disable implicit-arrow-linebreak */ -/* eslint-disable max-len */ -/* eslint-disable operator-linebreak */ -/* eslint-disable react/jsx-wrap-multilines */ - -import SubTitle from '@/components/SubTitle'; +import PageSubTitle from '@/components/common/PageSubTitle'; import { getMilestoneHistoriesOfStudent } from '@/lib/api/server.api'; import { getAuthFromCookie } from '@/lib/utils/auth'; import { MilestoneHistorySortCriteria, SortDirection } from '@/types/milestone'; -import MilestoneHistoryStatusLabel from '../MilestoneHistoryStatusLabel'; +import MilestoneStatusLabel from '@/components/ui/milestone/MilestoneStatusLabel'; -const MilestoneHistorySection = async () => { +export default async function MyPageMilestoneHistory() { const auth = getAuthFromCookie(); let milestoneHistoriesOfStudent; @@ -24,7 +19,7 @@ const MilestoneHistorySection = async () => { MilestoneHistorySortCriteria.ACTIVATED_AT, SortDirection.DESC, 0, - 5, + 6, ); } catch (err) { // TODO: server api error handling... @@ -32,18 +27,15 @@ const MilestoneHistorySection = async () => { return (
- +
{milestoneHistoriesOfStudent ? ( milestoneHistoriesOfStudent.content.map((milestoneHistory) => (
-

{milestoneHistory.description}

+

{milestoneHistory.description}

활동일: {milestoneHistory.activatedAt} - +

)) @@ -53,5 +45,4 @@ const MilestoneHistorySection = async () => {
); -}; -export default MilestoneHistorySection; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx b/frontend/src/components/ui/my-page/MyPageMilestoneOverview.tsx similarity index 60% rename from frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx rename to frontend/src/components/ui/my-page/MyPageMilestoneOverview.tsx index 992366a1..063ebf9d 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx +++ b/frontend/src/components/ui/my-page/MyPageMilestoneOverview.tsx @@ -1,22 +1,20 @@ -/* eslint-disable implicit-arrow-linebreak */ import { useMemo } from 'react'; -import MilestoneChart from '@/components/MilestoneChart'; -import MilestoneTable from '@/components/MilestoneTable'; +import MilestoneCircleChart from '@/components/ui/milestone/MilestoneCircleChart'; +import MilestoneOverviewTable from '@/components/ui/milestone/MilestoneOverviewTable'; import { initialMilestoneOverview } from '@/data/milestone'; import { useAppSelector } from '@/lib/hooks/redux'; import { useMilestoneScoresOfStudentQuery } from '@/lib/hooks/useApi'; import { Period } from '@/types/common'; import { MilestoneOverviewScore } from '@/types/milestone'; -import { MilestoneWrapper } from './styled'; -import MilestoneDetail from '../MilestoneDetail'; +import MyPageMilestoneDetail from './MyPageMilestoneDetail'; -interface MilestoneOverviewProps { +export interface MyPageMilestoneOverviewProps { searchFilterPeriod: Period; } -const MilestoneOverview = ({ searchFilterPeriod }: MilestoneOverviewProps) => { +export default function MyPageMilestoneOverview({ searchFilterPeriod }: MyPageMilestoneOverviewProps) { const auth = useAppSelector((state) => state.auth).value; const { data: milestoneScoresOfStudent } = useMilestoneScoresOfStudentQuery( auth.id, @@ -38,13 +36,11 @@ const MilestoneOverview = ({ searchFilterPeriod }: MilestoneOverviewProps) => { ); return (
- - - - - +
+ + +
+
); -}; - -export default MilestoneOverview; +} diff --git a/frontend/src/app/(client)/(withSidebar)/my-page/components/StudentInfoSection/index.tsx b/frontend/src/components/ui/my-page/MyPageStudentInfo.tsx similarity index 76% rename from frontend/src/app/(client)/(withSidebar)/my-page/components/StudentInfoSection/index.tsx rename to frontend/src/components/ui/my-page/MyPageStudentInfo.tsx index 5ba8760d..4d79dfee 100644 --- a/frontend/src/app/(client)/(withSidebar)/my-page/components/StudentInfoSection/index.tsx +++ b/frontend/src/components/ui/my-page/MyPageStudentInfo.tsx @@ -1,27 +1,20 @@ -/* eslint-disable react/jsx-wrap-multilines */ - 'use client'; -import SubTitle from '@/components/SubTitle'; +import { ReactNode } from 'react'; + +import PageSubTitle from '@/components/common/PageSubTitle'; import { useAppSelector } from '@/lib/hooks/redux'; import { useStudentMemberQuery } from '@/lib/hooks/useApi'; import { appendDashPhoneNumber, convertCareerToStr } from '@/lib/utils/utils'; import { VscWarning } from '@react-icons/all-files/vsc/VscWarning'; -import StudentInfoLabel from './StudentInfoLabel'; - -const StudentInfoSection = () => { - // TODO - 관리자가 로그인한 경우에 대한 처린 +export default function MyPageStudentInfo() { const auth = useAppSelector((state) => state.auth).value; - let member; - try { - member = useStudentMemberQuery(auth.id).data; - } catch (err) { - // TODO: server api error handling - } + const member = useStudentMemberQuery(auth.id).data; + return (
- + {member ? (
@@ -59,5 +52,18 @@ const StudentInfoSection = () => { )}
); -}; -export default StudentInfoSection; +} + +export interface StudentInfoLabelProps { + label: string; + value: ReactNode | string; +} + +export function StudentInfoLabel({ label, value }: StudentInfoLabelProps) { + return ( +

+ {label} + {value} +

+ ); +} diff --git a/frontend/src/components/ui/team-building/TeamBuildingCard.tsx b/frontend/src/components/ui/team-building/TeamBuildingCard.tsx new file mode 100644 index 00000000..3acd8870 --- /dev/null +++ b/frontend/src/components/ui/team-building/TeamBuildingCard.tsx @@ -0,0 +1,75 @@ +import { CgEye } from '@react-icons/all-files/cg/CgEye'; +import Image from 'next/image'; + +import { TeamBuildingDto } from '@/types/common.dto'; + +import Link from 'next/link'; + +export const TEAM_STATUS = { + RECRUITING: { + color: '#FAD3CA', + text: '모집 중', + }, + RECRUITMENT_END: { + color: '#DEE3EB', + text: '모집마감', + }, +}; + +export default function TeamBuildingCard({ + id, + category, + status, + title, + developer, + designer, + artist, + other, + views, +}: TeamBuildingDto) { + const recruitment = [ + { img: '/images/teamBuilding/team_type_img_1.svg', text: '개발', count: developer }, + { img: '/images/teamBuilding/team_type_img_2.svg', text: '디자인', count: artist }, + { img: '/images/teamBuilding/team_type_img_3.svg', text: '기획', count: designer }, + { img: '/images/teamBuilding/team_type_img_4.svg', text: '기타', count: other }, + ]; + return ( + +
+

{category}

+

+ {TEAM_STATUS[status].text} +

+
+
+

+ {title} +

+
+ {recruitment.map((item) => { + if (item.count !== 0) { + return ( +
+ {item.text} +
+ {item.text} +
+ {item.count}명 +
+
+ ); + } + return null; + })} +
+
+ + {views} +
+
+ + ); +} diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 512aae4e..0fa95e66 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -1,91 +1,9 @@ -export const MAX_WIDTH = '1200px'; - -export const CONTENT_WIDTH = '970px'; - -export const SIGN_WIDTH = '500px'; - export const RESPONSIVE_WIDTH = { mobile: '480px', tablet: '768px', desktop: '1200px', }; -export const COLOR = { - white: '#FFFFFF', - black_text: '#333333', - comment: '#898C96', - border: '#EEEEF0', - background: { - light: '#F0F1F7', - base: '#D2D4DC', - }, - primary: { - light: '#E6EBFD', - main: '#5773F1', - dark: '#1A40EB', - }, - secondary: { - light: '#64709B', - main: '#26325C', - dark: '#050A1C', - }, - milestone: { - blue: { - light: '#CCD7FF', - dark: '#5379FF', - main: '#8FA3F8', - }, - green: { - light: '#CEFFD9', - dark: '#11BA69', - main: '#9DE6BC', - }, - purple: { - light: '#D7C5FF', - dark: '#7b61ff', - main: '#AA8CF8', - }, - gray: { - light: '#F2F2F2', - }, - }, -}; - -export const FONT_STYLE = { - xs: { - normal: '400 12px "Noto Sans KR", sans-serif', - semibold: '600 12px "Noto Sans KR", sans-serif', - bold: '700 12px "Noto Sans KR", sans-serif', - }, - sm: { - normal: '400 14px "Noto Sans KR", sans-serif', - semibold: '600 14px "Noto Sans KR", sans-serif', - bold: '700 14px "Noto Sans KR", sans-serif', - }, - base: { - normal: '400 16px "Noto Sans KR", sans-serif', - semibold: '600 16px "Noto Sans KR", sans-serif', - bold: '700 16px "Noto Sans KR", sans-serif', - }, - lg: { - normal: '400 20px "Noto Sans KR", sans-serif', - semibold: '600 20px "Noto Sans KR", sans-serif', - bold: '700 20px "Noto Sans KR", sans-serif', - }, - xl: { - normal: '400 32px "Noto Sans KR", sans-serif', - semibold: '600 32px "Noto Sans KR", sans-serif', - bold: '700 32px "Noto Sans KR", sans-serif', - }, -}; - -export const BORDER_RADIUS = { - sm: '10px', - md: '20px', - lg: '30px', - full: '100%', -}; - export const TEAM_STATUS = { RECRUITING: { color: '#FAD3CA', diff --git a/frontend/src/data/adminCategory.ts b/frontend/src/data/adminCategory.ts index 001e53bf..20f128cd 100644 --- a/frontend/src/data/adminCategory.ts +++ b/frontend/src/data/adminCategory.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ import { CategoryDto } from '@/types/common.dto'; export const adminCategories: CategoryDto[] = [ @@ -6,7 +5,7 @@ export const adminCategories: CategoryDto[] = [ title: '마일스톤 관리', url: '/admin/milestone', sub: [ - { title: '마일스톤 목록', url: '/admin/milestone/list', key: 'milestone-list' }, + { title: '마일스톤 목록', url: '/admin/milestone', key: 'milestone-list' }, { title: '마일스톤 일괄 등록', url: '/admin/milestone/register', key: 'milestone-register' }, { title: '마일스톤 점수 현황', url: '/admin/milestone/rank', key: 'milestone-rank' }, ], @@ -15,14 +14,14 @@ export const adminCategories: CategoryDto[] = [ title: '교직원 관리', url: '/admin/faculty', sub: [ - { title: '교직원 목록', url: '/admin/faculty/list', key: 'faculty-list' }, + { title: '교직원 목록', url: '/admin/faculty', key: 'faculty-list' }, { title: '교직원 등록', url: '/admin/faculty/register', key: 'faculty-register' }, ], }, { title: '학생 관리', - url: '/admin/member', - sub: [{ title: '학생 목록', url: '/admin/member/list', key: 'member-list' }], + url: '/admin/student', + sub: [{ title: '학생 목록', url: '/admin/student', key: 'student-list' }], }, { title: '팀빌딩 관리', @@ -33,7 +32,7 @@ export const adminCategories: CategoryDto[] = [ title: '대회 관리', url: '/admin/contest', sub: [ - { title: '대회 목록', url: '/admin/contest/list', key: 'contest-list' }, + { title: '대회 목록', url: '/admin/contest', key: 'contest-list' }, { title: '대회 생성', url: '/admin/contest/create', key: 'contest-create' }, ], }, diff --git a/frontend/src/data/clientCategory.ts b/frontend/src/data/clientCategory.ts index 5482b8c9..8bfebb47 100644 --- a/frontend/src/data/clientCategory.ts +++ b/frontend/src/data/clientCategory.ts @@ -6,14 +6,14 @@ export const headerInfos: CategoryDto[] = [ url: '/milestone', description: '마일스톤이란?', inHeader: true, - sub: [{ title: '마일스톤이란?', url: '/milestone', key: '1_milestone' }], + sub: [{ title: '마일스톤이란?', url: '/milestone', key: 'milestone' }], }, { title: '팀빌딩', url: '/team-building', description: '팀원을 모집하는 공간입니다.', inHeader: true, - sub: [{ title: '팀빌딩', url: '/', key: '2_teamBuilding' }], + sub: [{ title: '팀빌딩', url: '/team-building', key: 'teamBuilding' }], }, { title: 'PNU 해커톤', @@ -32,8 +32,9 @@ export const headerInfos: CategoryDto[] = [ inHeader: false, sub: [ { title: '전체보기', url: '/my-page', key: 'dashboard' }, + { title: '마일스톤 등록', url: '/my-page/milestone-register', key: 'register' }, { title: '마일스톤 획득 내역', url: '/my-page/milestone', key: 'milestone' }, - { title: '실적 등록', url: '/my-page/milestone/register', key: 'result' }, + { title: '마일스톤 등록 내역', url: '/my-page/milestone-list', key: 'result' }, ], }, ]; diff --git a/frontend/src/lib/hooks/useAdminApi.ts b/frontend/src/lib/hooks/useAdminApi.ts index a3e4068c..e800ca95 100644 --- a/frontend/src/lib/hooks/useAdminApi.ts +++ b/frontend/src/lib/hooks/useAdminApi.ts @@ -1,4 +1,3 @@ -/* eslint-disable implicit-arrow-linebreak */ import { QueryKeys } from '@/data/queryKey'; import { client } from '@/lib/api/client.axios'; import { useAxiosMutation, useAxiosQuery } from '@/lib/hooks/useAxios'; diff --git a/frontend/src/lib/hooks/useApi.ts b/frontend/src/lib/hooks/useApi.ts index a70931ec..b74c07aa 100644 --- a/frontend/src/lib/hooks/useApi.ts +++ b/frontend/src/lib/hooks/useApi.ts @@ -1,6 +1,6 @@ -/* eslint-disable implicit-arrow-linebreak */ -import { FirstInfo } from '@/app/(client)/(auth)/sign-up/components/SignUpFirstPage'; -import { SecondInfo } from '@/app/(client)/(auth)/sign-up/components/SignUpSecondPage'; +import { FirstInfo } from '@/components/ui/auth/AuthSignUpFirst'; +import { SecondInfo } from '@/components/ui/auth/AuthSignUpSecond'; + import { MilestoneHistoryStatus } from '@/data/milestone'; import { QueryKeys } from '@/data/queryKey'; import { client } from '@/lib/api/client.axios'; diff --git a/frontend/src/lib/hooks/useAxios.ts b/frontend/src/lib/hooks/useAxios.ts index e84a066d..9eb74785 100644 --- a/frontend/src/lib/hooks/useAxios.ts +++ b/frontend/src/lib/hooks/useAxios.ts @@ -4,8 +4,6 @@ import { redirect, RedirectType, usePathname } from 'next/navigation'; import { useEffect } from 'react'; import { toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; - import { AccessDeniedError, BusinessError, UnauthorizedError } from '@/types/error'; const handleError = (error: Error, pathname: string) => { diff --git a/frontend/src/lib/utils/utils.tsx b/frontend/src/lib/utils/utils.tsx index 760c2efa..32c98b9a 100644 --- a/frontend/src/lib/utils/utils.tsx +++ b/frontend/src/lib/utils/utils.tsx @@ -1,6 +1,3 @@ -/* eslint-disable max-len */ -/* eslint-disable implicit-arrow-linebreak */ - import { HistoryFileType, MilestoneGroup, MilestoneHistoryStatus } from '@/data/milestone'; // 빈 파라미터를 제거하는 유틸함수 diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts index 176e03cc..7736a44e 100644 --- a/frontend/src/middleware.ts +++ b/frontend/src/middleware.ts @@ -15,7 +15,7 @@ export const middleware = (request: NextRequest) => { return Response.redirect(new URL('/', request.url)); } if (request.nextUrl.pathname === '/admin') { - return Response.redirect(new URL('/admin/milestone/list', request.url)); + return Response.redirect(new URL('/admin/milestone', request.url)); } const requestHeaders = new Headers(request.headers); diff --git a/frontend/src/store/auth.slice.ts b/frontend/src/store/auth.slice.ts index 235d4f6a..0f0fdbec 100644 --- a/frontend/src/store/auth.slice.ts +++ b/frontend/src/store/auth.slice.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-extraneous-dependencies */ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import cookie from 'react-cookies'; diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index 034fe9c2..8e52c2be 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -1,4 +1,3 @@ -/* eslint-disable implicit-arrow-linebreak */ import { combineReducers, configureStore } from '@reduxjs/toolkit'; import { persistReducer, persistStore } from 'redux-persist'; diff --git a/frontend/src/store/store.tsx b/frontend/src/store/store.tsx index 0d18f582..56f02fd1 100644 --- a/frontend/src/store/store.tsx +++ b/frontend/src/store/store.tsx @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/no-explicit-any */ import createWebStorage from 'redux-persist/lib/storage/createWebStorage'; const createNoopStorage = () => ({ diff --git a/frontend/src/theme/StyledComponentsRegistry/index.tsx b/frontend/src/theme/StyledComponentsRegistry/index.tsx index 889ea616..5d1cfd4f 100644 --- a/frontend/src/theme/StyledComponentsRegistry/index.tsx +++ b/frontend/src/theme/StyledComponentsRegistry/index.tsx @@ -1,6 +1,3 @@ -/* eslint-disable react/jsx-no-useless-fragment */ -/* eslint-disable max-len */ - 'use client'; import { useServerInsertedHTML } from 'next/navigation'; diff --git a/frontend/src/types/error.ts b/frontend/src/types/error.ts index 20798dae..66081681 100644 --- a/frontend/src/types/error.ts +++ b/frontend/src/types/error.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable max-classes-per-file */ import { AxiosError } from 'axios'; export class ApplicationError extends Error { diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 8b7063aa..1e1af4ae 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -101,9 +101,28 @@ module.exports = { }, width: { sign: '500px', + 'client-max': '1200px', + 'client-content': '970px', + 'client-sidebar': '50px', + 'client-sidebar-open': '200px', + 'admin-max': '1200px', + 'admin-sidebar': '220px', + }, + maxWidth: { + 'client-max': '1200px', + 'sign-max': '500px', }, minWidth: { admin: '1150px', + 'admin-max': '1200px', + }, + height: { + 'client-header': '50px', + 'client-lg-header': '76px', + 'admin-header': '55px', + }, + lineHeight: { + 'client-lg-header': '76px', }, }, },