├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .eslintrc.json ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── lint.yaml │ └── playwright.yaml ├── .gitignore ├── .husky ├── commit-msg └── prepare-commit-msg ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── .releaserc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── README_INTRODUCE.md ├── appspec.yml ├── commitlint.config.js ├── docs └── REFACTOR_CONVENTION.md ├── gtag.d.ts ├── jest.config.js ├── next.config.js ├── package.json ├── playwright.config.ts ├── public ├── logo.svg └── mockServiceWorker.js ├── radar.d.ts ├── renovate.json ├── scripts ├── after_install.sh ├── application_start.sh └── before_install.sh ├── sentry.client.config.ts ├── sentry.edge.config.ts ├── sentry.server.config.ts ├── setupTests.ts ├── src ├── @modal │ ├── context │ │ ├── index.ts │ │ └── modal.context.ts │ ├── hooks │ │ ├── index.ts │ │ ├── useModal.spec.tsx │ │ └── useModal.ts │ ├── layouts │ │ ├── LoginModal.tsx │ │ ├── ModalView.tsx │ │ └── index.tsx │ └── types │ │ ├── ModalState.type.ts │ │ └── index.ts ├── @user │ ├── assets │ │ └── data │ │ │ ├── defaultUser.data.ts │ │ │ └── index.ts │ ├── constants │ │ ├── index.ts │ │ └── role.constant.ts │ ├── context │ │ ├── index.ts │ │ └── user.context.ts │ ├── hooks │ │ ├── index.ts │ │ └── useUser.tsx │ ├── services │ │ ├── api.service.ts │ │ └── mutation.service.ts │ └── types │ │ ├── User.ts │ │ └── index.ts ├── apis │ ├── constants │ │ ├── error.constant.ts │ │ └── index.ts │ ├── httpClient │ │ ├── httpClient.ts │ │ └── index.ts │ ├── interceptor │ │ └── index.ts │ └── token │ │ ├── authorization.ts │ │ ├── index.ts │ │ └── refresh.ts ├── app │ ├── applications │ │ └── page.tsx │ ├── bamboo │ │ └── page.tsx │ ├── ber │ │ └── page.tsx │ ├── calendar │ │ └── page.tsx │ ├── layout.tsx │ ├── meal │ │ └── page.tsx │ ├── meister │ │ └── page.tsx │ ├── not-found.tsx │ ├── oauth │ │ └── page.tsx │ ├── page.tsx │ ├── post │ │ ├── [id] │ │ │ ├── edit │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── write │ │ │ └── page.tsx │ └── timetable │ │ └── page.tsx ├── assets │ ├── icons │ │ ├── ArrowIcon.tsx │ │ ├── BambooIcon.tsx │ │ ├── BerIcon.tsx │ │ ├── CalendarIcon.tsx │ │ ├── ChatIcon.tsx │ │ ├── Check.tsx │ │ ├── CheckIcon.tsx │ │ ├── Curve.tsx │ │ ├── DesktopIcon.tsx │ │ ├── DistributionIcon.tsx │ │ ├── GraphIcon.tsx │ │ ├── HistorySeparator.tsx │ │ ├── JoinCheckIcon.tsx │ │ ├── Like.tsx │ │ ├── LinkArrow.tsx │ │ ├── Logo.tsx │ │ ├── MealIcon.tsx │ │ ├── NoticeIcon.tsx │ │ ├── ProgressIcon.tsx │ │ ├── Setting.tsx │ │ ├── TimetableIcon.tsx │ │ ├── UploadIcon.tsx │ │ ├── View.tsx │ │ ├── XIcon.tsx │ │ └── index.ts │ └── images │ │ ├── Back.png │ │ ├── QR.png │ │ ├── banner │ │ ├── banner1.png │ │ ├── banner2.png │ │ ├── banner3.png │ │ └── banner4.svg │ │ ├── emoticon.png │ │ ├── hugging_face.png │ │ ├── index.ts │ │ ├── loading.gif │ │ ├── page_not_found.png │ │ ├── profile_default.png │ │ ├── shushing_face.png │ │ ├── test_banner.png │ │ └── thinking_face.png ├── components │ ├── Flex │ │ ├── Column.tsx │ │ ├── Row.tsx │ │ ├── index.ts │ │ └── type.ts │ ├── atoms │ │ ├── Button.tsx │ │ ├── Category.tsx │ │ ├── DropDown.tsx │ │ ├── FallbackImage.tsx │ │ ├── FallbackImgImage.tsx │ │ ├── Input.tsx │ │ ├── Loading.tsx │ │ ├── Switch.tsx │ │ └── index.ts │ └── common │ │ ├── Aside │ │ ├── CheckInBox.tsx │ │ ├── MeisterInfoBox.tsx │ │ ├── StudentInfoBox.tsx │ │ ├── assets │ │ │ └── data │ │ │ │ ├── defaultAside.data.ts │ │ │ │ └── index.ts │ │ ├── context │ │ │ ├── aside.context.ts │ │ │ └── index.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useAside.ts │ │ └── index.tsx │ │ ├── ContentEditor │ │ └── index.tsx │ │ ├── ContentViewer │ │ └── index.tsx │ │ ├── DragDrop │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useDragDrop.ts │ │ └── index.tsx │ │ ├── Footer │ │ ├── Info.tsx │ │ └── index.tsx │ │ ├── Header │ │ ├── @setting │ │ │ └── layouts │ │ │ │ └── setting.tsx │ │ ├── Navigation.tsx │ │ ├── assets │ │ │ └── data │ │ │ │ ├── index.ts │ │ │ │ └── navigationList.data.ts │ │ └── index.tsx │ │ └── index.ts ├── config │ └── index.ts ├── constants │ ├── date.constant.ts │ ├── direction.constant.ts │ ├── index.ts │ ├── key.constant.ts │ └── router.constant.ts ├── graphql │ └── index.ts ├── hooks │ ├── index.ts │ ├── useDidMountEffect.ts │ ├── useImageUpload.ts │ ├── useInfiniteScroll.spec.tsx │ ├── useInfiniteScroll.ts │ └── useWindow.ts ├── provider │ ├── LayoutProvider.tsx │ ├── MainProvider.tsx │ ├── ReactQueryProvider.tsx │ └── StyledComponentsProvider.tsx ├── storage │ ├── constants │ │ ├── index.ts │ │ ├── setting.constant.ts │ │ └── token.constant.ts │ ├── index.ts │ └── types │ │ ├── StorageKey.type.ts │ │ └── index.ts ├── styles │ ├── flex.ts │ ├── font.ts │ ├── globalStyle.ts │ ├── index.ts │ └── theme.ts └── templates │ ├── applications │ ├── assets │ │ └── data │ │ │ ├── applicationList.data.ts │ │ │ └── index.ts │ └── layouts │ │ ├── ApplicationListItem.tsx │ │ └── index.tsx │ ├── bamboo │ ├── hooks │ │ ├── index.ts │ │ └── useBamboo.tsx │ ├── layouts │ │ ├── BambooCreateModal.tsx │ │ ├── BambooManageModal.tsx │ │ ├── BambooPostListItem.tsx │ │ └── index.tsx │ ├── services │ │ ├── api.service.ts │ │ ├── mutation.service.ts │ │ └── query.service.ts │ └── types │ │ ├── @props │ │ ├── BambooPostListItem.type.ts │ │ └── index.ts │ │ ├── BambooPendingPost.type.ts │ │ ├── BambooPost.type.ts │ │ └── index.ts │ ├── ber │ ├── assets │ │ └── data │ │ │ ├── defaultBerReserve.data.ts │ │ │ ├── index.ts │ │ │ └── noticeRuleList.data.ts │ ├── constants │ │ ├── ber.constant.ts │ │ └── index.ts │ ├── helpers │ │ ├── getIfReservedRoomCSS.helper.ts │ │ ├── getIfSelectedRoomCSS.helper.ts │ │ └── index.ts │ ├── hooks │ │ ├── index.ts │ │ ├── useBer.ts │ │ └── useBerReserve.ts │ ├── layouts │ │ ├── ReserveCheckBox.tsx │ │ ├── ReserveJoinBox.tsx │ │ ├── ReserveList.tsx │ │ ├── ReserveMap.tsx │ │ ├── ReserveNoticeRuleList.tsx │ │ ├── ReserveStudentList.tsx │ │ └── index.tsx │ ├── services │ │ ├── api.service.ts │ │ ├── mutation.service.ts │ │ └── query.service.ts │ └── types │ │ ├── @props │ │ ├── BerReserveCheckBoxProps.type.ts │ │ ├── BerReserveJoinBoxProps.type.ts │ │ ├── BerReserveJoinHookprops.type.ts │ │ ├── BerReserveListProps.type.ts │ │ ├── BerReserveMapProps.type.ts │ │ ├── BerReserveStudentListProps.type.ts │ │ └── index.ts │ │ ├── BerCreateReserveQuery.type.ts │ │ ├── BerReserve.type.ts │ │ ├── BerReserveQuery.type.ts │ │ └── index.ts │ ├── calendar │ ├── constants │ │ ├── index.ts │ │ └── plan.constant.ts │ ├── helpers │ │ ├── getColorByDayName.helper.ts │ │ ├── getColorByPlanType.helper.ts │ │ ├── getPaddingDayOfWeek.helper.ts │ │ ├── getPlanNameByPlanType.helper.ts │ │ ├── getPlanTypeByPlanName.helper.ts │ │ └── index.ts │ ├── hooks │ │ ├── index.ts │ │ └── useCalendar.tsx │ ├── layouts │ │ ├── CalendarListItem.tsx │ │ ├── CalendarPlanAddModal.tsx │ │ ├── CalendarPlanWriterInformationModal.tsx │ │ ├── WeekDayHeaderBox.tsx │ │ └── index.tsx │ ├── services │ │ ├── api.service.ts │ │ ├── mutation.service.ts │ │ └── query.service.ts │ └── types │ │ ├── @props │ │ ├── CalendarArrowIconProps.type.ts │ │ ├── CalendarListItemProps.type.ts │ │ ├── CalendarPlanAddModalProps.type.ts │ │ ├── CalendarPlanAddQuery.type.ts │ │ └── index.ts │ │ ├── CalendarArrowDirection.type.ts │ │ ├── CalendarItem.type.ts │ │ ├── CalendarPlan.type.ts │ │ ├── Calender.type.ts │ │ └── index.ts │ ├── home │ ├── assets │ │ └── data │ │ │ ├── defaultBanner.data.ts │ │ │ └── index.ts │ ├── helpers │ │ ├── getCurrentMeal.helper.ts │ │ ├── get식사명ByMealName.helper.ts │ │ └── index.ts │ ├── index.tsx │ ├── layouts │ │ ├── BasicMode │ │ │ ├── HomeBamboo.tsx │ │ │ ├── HomeCalendar.tsx │ │ │ ├── HomeHead.tsx │ │ │ ├── HomeMainBanner.tsx │ │ │ ├── HomeMeal.tsx │ │ │ ├── HomeMiniBanner.tsx │ │ │ ├── HomePost.tsx │ │ │ ├── HomePostList.tsx │ │ │ ├── HomeRadarChart.tsx │ │ │ ├── HomeReserve.tsx │ │ │ ├── HomeReserveMap.tsx │ │ │ └── index.tsx │ │ └── RemoconMode │ │ │ └── index.tsx │ └── services │ │ ├── api.service.ts │ │ └── query.service.ts │ ├── meal │ ├── assets │ │ └── data │ │ │ ├── defaultMealList.data.ts │ │ │ └── index.ts │ ├── constants │ │ ├── index.ts │ │ └── meal.constant.ts │ ├── hooks │ │ ├── index.ts │ │ └── useMeal.ts │ ├── layouts │ │ ├── BlinkerBox.tsx │ │ ├── MealListItem.tsx │ │ ├── MealPageTitleBox.tsx │ │ └── index.tsx │ ├── services │ │ ├── api.service.ts │ │ └── query.service.ts │ └── types │ │ └── @props │ │ ├── MealListItemProps.type.ts │ │ └── index.ts │ ├── meister │ ├── assets │ │ └── data │ │ │ ├── defaultMeister.data.ts │ │ │ ├── defaultMeisterDetail.data.ts │ │ │ ├── graphColor.data.ts │ │ │ ├── index.ts │ │ │ ├── meisterChart.data.ts │ │ │ ├── meisterList.data.ts │ │ │ ├── meisterVariable.data.ts │ │ │ └── radarChartVariable.data.ts │ ├── chart │ │ ├── MeisterChart.tsx │ │ └── RadarChart.tsx │ ├── constants │ │ ├── index.ts │ │ ├── meister.constant.ts │ │ └── score.constant.ts │ ├── context │ │ ├── buttonSwitch.context.ts │ │ ├── index.ts │ │ └── studentNumber.context.ts │ ├── helpers │ │ ├── getMeisterChapter.helper.ts │ │ ├── getStatusColor.helper.ts │ │ ├── getStudentId.helper.ts │ │ ├── getStudentInformationHTML.helper.ts │ │ ├── get요일ByWeekday.helper.ts │ │ ├── index.ts │ │ └── setMeisterPointNaming.helper.ts │ ├── hooks │ │ ├── index.ts │ │ ├── useMeister.tsx │ │ └── useMeisterHTML.ts │ ├── layouts │ │ ├── CircularProgressBox.tsx │ │ ├── Distribution.tsx │ │ ├── MeisterProfileBox.tsx │ │ ├── PointHTMLContent.tsx │ │ ├── Ranking.tsx │ │ ├── RankingListItem.tsx │ │ ├── ScoreHTMLContent.tsx │ │ ├── YearlyMeisterScore.tsx │ │ └── index.tsx │ ├── services │ │ ├── api.service.ts │ │ └── query.service.ts │ └── types │ │ ├── @props │ │ ├── CircularProgressBoxProps.type.ts │ │ └── index.ts │ │ ├── Meister.type.ts │ │ ├── MeisterChartData.type.ts │ │ ├── MeisterDetail.type.ts │ │ ├── MeisterRankingItem.type.ts │ │ ├── index.ts │ │ ├── meisterKey.type.ts │ │ └── meisterScore.type.ts │ ├── oauth │ ├── hooks │ │ ├── index.ts │ │ └── useOAuth.ts │ ├── layouts │ │ └── index.tsx │ └── services │ │ ├── api.service.ts │ │ └── mutation.service.ts │ ├── post │ ├── assets │ │ ├── data │ │ │ ├── categoryList.data.ts │ │ │ ├── dateTime.data.ts │ │ │ ├── defaultPost.data.ts │ │ │ └── index.ts │ │ ├── icons │ │ │ ├── AddCommentIcon.tsx │ │ │ ├── CategoryArrow.tsx │ │ │ ├── CommentIcon.tsx │ │ │ ├── LikeIcon.tsx │ │ │ ├── ReactiveLikeIcon.tsx │ │ │ ├── TimeIcon.tsx │ │ │ └── index.ts │ │ └── images │ │ │ ├── empty_image.png │ │ │ └── index.ts │ ├── constants │ │ ├── category.constant.ts │ │ ├── index.ts │ │ ├── like.constant.ts │ │ └── postInput.constant.ts │ ├── context │ │ ├── currentCategory.context.ts │ │ └── index.ts │ ├── helpers │ │ ├── getFilteredPostDataByCategory.helper.ts │ │ ├── getPostIsValid.helper.ts │ │ ├── getTextDepthCount.helper.ts │ │ ├── getTextIsOverflow.helper.ts │ │ ├── get카테고리명ByCategory.helper.ts │ │ ├── get회당불러올게시글개수.helper.ts │ │ └── index.ts │ ├── hooks │ │ ├── index.ts │ │ ├── useComment.ts │ │ ├── useLike.ts │ │ ├── usePost.ts │ │ ├── usePostWritable.ts │ │ ├── useRecomment.ts │ │ └── useTextarea.ts │ ├── layouts │ │ ├── components │ │ │ ├── CodeReviewInputBox.tsx │ │ │ ├── ContentInputBox.tsx │ │ │ ├── FoundInputBox.tsx │ │ │ ├── LostFoundInputBox.tsx │ │ │ ├── ProjectInputBox.tsx │ │ │ ├── TitleInputBox.tsx │ │ │ └── index.ts │ │ ├── detail │ │ │ ├── CommentStylesheet.tsx │ │ │ ├── PostCategoryInformationBox.tsx │ │ │ ├── PostLikeCountBox.tsx │ │ │ ├── PostMainBox.tsx │ │ │ ├── PostMainlyInformationBox.tsx │ │ │ ├── SectionBox.tsx │ │ │ ├── comment │ │ │ │ ├── CommentContentBox.tsx │ │ │ │ ├── CommentLikeInformationBox.tsx │ │ │ │ ├── CommentList.tsx │ │ │ │ ├── CommentListItem.tsx │ │ │ │ ├── CommentWritableBox.tsx │ │ │ │ ├── CreateCommentBox.tsx │ │ │ │ └── RecommentViewButton.tsx │ │ │ ├── index.tsx │ │ │ └── recomment │ │ │ │ ├── CreateRecommentBox.tsx │ │ │ │ ├── RecommentContentBox.tsx │ │ │ │ ├── RecommentList.tsx │ │ │ │ ├── RecommentListItem.tsx │ │ │ │ └── RecommentWritableBox.tsx │ │ ├── edit │ │ │ └── index.tsx │ │ ├── list │ │ │ ├── PostListItem.tsx │ │ │ ├── PostListItemInformationBar.tsx │ │ │ └── index.tsx │ │ └── write │ │ │ ├── CategoryBox.tsx │ │ │ └── index.tsx │ ├── services │ │ ├── comment │ │ │ ├── api.service.ts │ │ │ ├── mutation.service.ts │ │ │ └── query.service.ts │ │ ├── graphql │ │ │ └── data.graphql.ts │ │ ├── like │ │ │ ├── api.service.ts │ │ │ └── mutation.service.ts │ │ ├── post │ │ │ ├── api.service.ts │ │ │ ├── graphql.service.ts │ │ │ ├── mutation.service.ts │ │ │ └── query.service.ts │ │ └── recomment │ │ │ ├── api.service.ts │ │ │ ├── mutation.service.ts │ │ │ └── query.service.ts │ └── types │ │ ├── @props │ │ ├── CommentContentBoxProps.type.ts │ │ ├── CommentLikeInformationBoxProps.type.ts │ │ ├── CommentListItemProps.type.ts │ │ ├── CommentQueryProps.type.ts │ │ ├── CreateRecommentBoxProps.type.ts │ │ ├── GetCommentListProps.type.ts │ │ ├── GetPostListProps.type.ts │ │ ├── LikeIconProps.type.ts │ │ ├── PostCategoryBoxProps.type.ts │ │ ├── PostCategoryInputBoxProps.type.ts │ │ ├── PostCommentProps.type.ts │ │ ├── PostCountBoxProps.type.ts │ │ ├── PostDetailParamsProps.type.ts │ │ ├── PostListItemInformationBarProps.type.ts │ │ ├── PostProps.type.ts │ │ ├── PostSectionBoxProps.type.ts │ │ ├── RecommentViewButtonProps.type.ts │ │ ├── UseLikeProps.type.ts │ │ └── index.ts │ │ ├── Comment.type.ts │ │ ├── Post.type.ts │ │ ├── PostCategory.type.ts │ │ ├── PostCreateQuery.type.ts │ │ ├── PostData.type.ts │ │ ├── PostListProperty.type.ts │ │ ├── PostListQuery.type.ts │ │ ├── PostListQueryProperty.type.ts │ │ ├── PostQuery.type.ts │ │ ├── PostUpdateQuery.type.ts │ │ ├── Recomment.type.ts │ │ └── index.ts │ └── timetable │ ├── assets │ └── data │ │ ├── defaultTimeTable.data.ts │ │ └── index.ts │ ├── hooks │ ├── index.ts │ └── useTimeTable.ts │ ├── layouts │ └── index.tsx │ └── services │ ├── api.service.ts │ └── query.service.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": ["babel-plugin-styled-components"] 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.tsx] 4 | indent_style = space -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | jest.config.js 2 | jest.setup.js 3 | next.config.js 4 | commitlint.config.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # BSM에 기여하기 2 | 3 | 우리는 커뮤니티의 모든 사람의 기여를 환영합니다.
4 | 이 레포지토리는 다양한 나라의 언어로 이루어지며, 한국어를 중심으로 이루어집니다. 5 | 6 | > BSM의 모든 기여자는 우리의 행동 강령을 준수해야 합니다. 7 | >
어떤 행동이 허용되고 허용되지 않는지 이해하려면 [전체 내용](./CODE_OF_CONDUCT.md)를 읽어보시기 바랍니다 . 8 | 9 | ## 1. 이슈 10 | 11 | 다음을 통해 BSM에 기여할 수 있습니다 : 12 | 13 | - [버그 신고하기](https://github.com/Team-INSERT/bssm-frontend/issues/new/choose) 14 | - [새로운 기능 요청](https://github.com/Team-INSERT/bssm-frontend/issues/issues/new/choose) 15 | - [이미 있는 이슈를 보고](https://github.com/Team-INSERT/bssm-frontend/issues/issues) 수정해야 할 사항을 확인하세요. 16 | 17 | ## 2. Pull Requests 18 | 19 | > [PR 시작하기](https://github.com/Team-INSERT/bssm-frontend/compare)
20 | 21 | 나만의 PR을 올릴 수 있습니다. PR 제목은 다음 형식과 일치해야 합니다. 22 | 23 | ``` 24 | (template scope): 25 | 26 | ex) feat(calendar): 바 형식으로 조회하는 기능 추가 27 | ``` 28 | 29 | > 우리는 모든 PR을 메인에 병합하기 때문에 기록의 커밋 수나 스타일에 신경 쓰지 않습니다.
30 | > 편안하다고 느끼는 스타일로 자유롭게 커밋해주세요. 31 | 32 | ### commit keyword 33 | 34 | **유형은 다음 중 하나여야 합니다:** 35 | 36 | src 내 메인 로직을 포함하는 코드를 수정한 경우 : 37 | 38 | - feat - 새로운 기능 추가 39 | - fix - 새로운 기능을 추가하지 않은 수정사항 40 | 41 | src 내 메인 로직을 포함하는 코드를 수정하지 않은 경우 : 42 | 43 | - test - 테스트 코드를 작성하거나 변경한 경우 44 | 45 | 그 외 : 46 | 47 | - chore - 기타 모든 것 ( 주석 추가 등등... ) 48 | 49 | ### 3. etc 50 | 51 | 코드를 작성할 때 BSM이 추구하는 구조 자체를 건드리는 것은 유쾌하지 않습니다. 52 | 만약 더욱 가독성이 좋은 디렉터리 구조를 발견하셨다면 이슈로 제보해주세요. 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: "Bug" 2 | description: "🤬" 3 | labels: 버그 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Describe 8 | description: | 9 | 버그에 대해서 설명해주세요! 10 | placeholder: | 11 | 헤더가 깨지는 버그가 발생해요 12 | 13 | - type: textarea 14 | attributes: 15 | label: Additional 16 | description: | 17 | 추가로 해주실 말씀이 있나요? 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature" 2 | description: "추가할 일이 있으신가요? 📗" 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Describe 7 | description: | 8 | 추가할 일에 관한 설명 9 | placeholder: | 10 | 요구사항을 작성해주세요 11 | 12 | - type: textarea 13 | attributes: 14 | label: Work 15 | description: | 16 | [작업내용] 무슨 작업을 하셨나요? 17 | placeholder: | 18 | ~~이런 작업을 했습니다. 19 | 20 | - type: textarea 21 | attributes: 22 | label: Additional 23 | description: | 24 | 추가로 해주실 말씀이 있나요? 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 작업사항 2 | 3 | ## 변경한 점 4 | 5 | ## 스크린샷 6 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yaml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: [18.x] 16 | - name: Install dependencies 17 | run: yarn install --immutable --immutable-cache --check-cache 18 | - name: Install Playwright Browsers 19 | run: npx playwright install --with-deps 20 | - name: Run Playwright tests 21 | run: npx playwright test 22 | - uses: actions/upload-artifact@v3 23 | if: always() 24 | with: 25 | name: playwright-report 26 | path: playwright-report/ 27 | retention-days: 30 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | .env 3 | /node_modules 4 | /.pnp 5 | .pnp.js 6 | 7 | playwright-report/ 8 | test-results 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | .pnpm-debug.log* 29 | 30 | # local env files 31 | .env*.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | 40 | # Sentry Config File 41 | .sentryclirc 42 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo "🚦 COMMIT-MSG | commitlint check..." 5 | 6 | yarn commitlint --edit ${1} -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo "🚦 PREPARE-COMMIT-MSG | Start cz with cz-customizable..." 5 | exec < /dev/tty && yarn lint && yarn cz --hook || true -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.13.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | semi: true, 4 | singleQuote: false, 5 | trailingComma: "all", 6 | tabWidth: 2, 7 | bracketSpacing: true, 8 | endOfLine: "auto", 9 | useTabs: false, 10 | arrowParens: "always", 11 | }; 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/.yarn": true, 4 | "**/.pnp.*": true 5 | }, 6 | "typescript.enablePromptUseWorkspaceTsdk": true, 7 | "git.ignoreLimitWarning": true 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Insert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 학생 스마트 정보 관리 시스템 [BSM](https://newbsm.team-insert.com) · [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./.github/CONTRIBUTING.md) 2 | 3 | BSM은 [인서트](https://team-insert.com)에서 개발한 부산소마고 교내 학생 정보 관리 시스템입니다. 4 | 학생들은 한 서비스 내에서 마이스터 역량 인증제와 급식, 학사 일정 등 여러가지 학교와 관련된 정보들을 확인할 수 있습니다. 5 | 6 | [사이트 소개 보기](./README_INTRODUCE.md) 7 | 8 | ## 기여하기 9 | 10 | BSM에는 인서트 팀의 멤버, 혹은 부산소마고 학생이 아니더라도 누구나 기여할 수 있습니다. 11 | BSM에 기여하고 싶다고 생각하셨다면 아래 문서를 참고해주세요. 12 | 13 | [CONTRIBUTING](./.github/CONTRIBUTING.md) 14 | 15 | ## 라이선스 16 | 17 | MIT © Team. INSERT. [LICENSE](./LICENSE) 파일을 참고하세요. 18 | 19 | 20 | 21 | 22 | 인서트 23 | 24 | 25 | -------------------------------------------------------------------------------- /appspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | os: linux 3 | 4 | files: 5 | - source: / 6 | destination: /home/ubuntu/FE-auto 7 | overwrite: yes 8 | permissions: 9 | - object: /home/ubuntu/FE-auto 10 | owner: ubuntu 11 | group: ubuntu 12 | mode: 755 13 | 14 | hooks: 15 | BeforeInstall: 16 | - location: scripts/before_install.sh 17 | timeout: 300 18 | runas: ubuntu 19 | ignoreFailures: true 20 | 21 | AfterInstall: 22 | - location: scripts/after_install.sh 23 | timeout: 300 24 | runas: ubuntu 25 | 26 | ApplicationStart: 27 | - location: scripts/application_start.sh 28 | timeout: 300 29 | runas: ubuntu 30 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "header-max-length": [2, "always", 72], 5 | "body-leading-blank": [2, "always"], 6 | "body-max-length": [2, "always", 400], 7 | "footer-leading-blank": [2, "always"], 8 | "type-enum": [ 9 | 2, 10 | "always", 11 | [ 12 | "feat", 13 | "fix", 14 | "style", 15 | "chore", 16 | "ci", 17 | "refactor", 18 | "revert", 19 | "test", 20 | "remove", 21 | "move", 22 | "docs", 23 | "perf", 24 | ], 25 | ], 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /gtag.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/gtag.d.ts -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const nextJest = require("next/jest"); 2 | const createJestConfig = nextJest({ 3 | dir: "./", 4 | }); 5 | const customJestConfig = { 6 | setupFilesAfterEnv: ["/setupTests.ts"], 7 | moduleDirectories: ["node_modules", "/"], 8 | testEnvironment: "jest-environment-jsdom", 9 | }; 10 | module.exports = createJestConfig(customJestConfig); 11 | -------------------------------------------------------------------------------- /radar.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-d3-radar"; 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [":timezone(Asia/Seoul)", ":label(renovate)", "config:base"], 3 | "npm": { 4 | "separateMinorPatch": true, 5 | "packageRules": [ 6 | { 7 | "packagePatterns": ["^@types/"], 8 | "automerge": true, 9 | "major": { 10 | "automerge": false 11 | } 12 | }, 13 | { 14 | "groupName": "EPS", 15 | "packageNames": ["eslint", "prettier", "stylelint"], 16 | "packagePatterns": ["^eslint-", "^prettier-", "stylelint-"] 17 | } 18 | ] 19 | }, 20 | "enabledManagers": ["npm"], 21 | "ignorePaths": [] 22 | } 23 | -------------------------------------------------------------------------------- /scripts/after_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /home/ubuntu/FE-auto 3 | yarn install 4 | 5 | -------------------------------------------------------------------------------- /scripts/application_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /home/ubuntu/FE-auto 3 | source ~/.zshrc 4 | pm2 start yarn --name newbsm --watch -- start 5 | -------------------------------------------------------------------------------- /scripts/before_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pm2 kill 4 | -------------------------------------------------------------------------------- /sentry.client.config.ts: -------------------------------------------------------------------------------- 1 | // This file configures the initialization of Sentry on the client. 2 | // The config you add here will be used whenever a users loads a page in their browser. 3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 4 | 5 | import * as Sentry from "@sentry/nextjs"; 6 | 7 | Sentry.init({ 8 | dsn: "https://d9b95ec831a783292206973ebf392318@o4506432587563008.ingest.sentry.io/4506432597262336", 9 | 10 | // Adjust this value in production, or use tracesSampler for greater control 11 | tracesSampleRate: 1, 12 | 13 | // Setting this option to true will print useful information to the console while you're setting up Sentry. 14 | debug: false, 15 | 16 | replaysOnErrorSampleRate: 1.0, 17 | 18 | // This sets the sample rate to be 10%. You may want this to be 100% while 19 | // in development and sample at a lower rate in production 20 | replaysSessionSampleRate: 0.1, 21 | 22 | // You can remove this option if you're not planning to use the Sentry Session Replay feature: 23 | integrations: [ 24 | new Sentry.Replay({ 25 | // Additional Replay configuration goes in here, for example: 26 | maskAllText: true, 27 | blockAllMedia: true, 28 | }), 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /sentry.edge.config.ts: -------------------------------------------------------------------------------- 1 | // This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on). 2 | // The config you add here will be used whenever one of the edge features is loaded. 3 | // Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally. 4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 5 | 6 | import * as Sentry from "@sentry/nextjs"; 7 | 8 | Sentry.init({ 9 | dsn: "https://d9b95ec831a783292206973ebf392318@o4506432587563008.ingest.sentry.io/4506432597262336", 10 | 11 | // Adjust this value in production, or use tracesSampler for greater control 12 | tracesSampleRate: 1, 13 | 14 | // Setting this option to true will print useful information to the console while you're setting up Sentry. 15 | debug: false, 16 | }); 17 | -------------------------------------------------------------------------------- /sentry.server.config.ts: -------------------------------------------------------------------------------- 1 | // This file configures the initialization of Sentry on the server. 2 | // The config you add here will be used whenever the server handles a request. 3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 4 | 5 | import * as Sentry from "@sentry/nextjs"; 6 | 7 | Sentry.init({ 8 | dsn: "https://d9b95ec831a783292206973ebf392318@o4506432587563008.ingest.sentry.io/4506432597262336", 9 | 10 | // Adjust this value in production, or use tracesSampler for greater control 11 | tracesSampleRate: 1, 12 | 13 | // Setting this option to true will print useful information to the console while you're setting up Sentry. 14 | debug: false, 15 | }); 16 | -------------------------------------------------------------------------------- /setupTests.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | -------------------------------------------------------------------------------- /src/@modal/context/index.ts: -------------------------------------------------------------------------------- 1 | export { default as modalContext } from "./modal.context"; 2 | -------------------------------------------------------------------------------- /src/@modal/context/modal.context.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai"; 2 | import { ModalState } from "../types"; 3 | 4 | const modalContext = atom({ 5 | component: null, 6 | visible: false, 7 | }); 8 | 9 | export default modalContext; 10 | -------------------------------------------------------------------------------- /src/@modal/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useModal } from "./useModal"; 2 | -------------------------------------------------------------------------------- /src/@modal/hooks/useModal.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen, fireEvent } from "@testing-library/react"; 2 | import useModal from "./useModal"; 3 | 4 | jest.mock("./useModal"); 5 | 6 | const TestComponent = () => { 7 | const { openModal, closeModal } = useModal(); 8 | return ( 9 |
10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | describe("useModal", () => { 17 | const mockUseModal = useModal as jest.MockedFunction; 18 | mockUseModal.mockReturnValue({ 19 | openModal: jest.fn(), 20 | closeModal: jest.fn(), 21 | visible: false, 22 | }); 23 | 24 | it("openModal이 호출되면 화면에 모달이 render 되어야 한다", async () => { 25 | render(); 26 | 27 | fireEvent.click(screen.getByRole("button", { name: "Open" })); 28 | expect(mockUseModal().openModal).toHaveBeenCalled(); 29 | expect(document.activeElement).toBeInstanceOf(HTMLElement); 30 | }); 31 | 32 | it("closeModal이 호출되면 화면에 render된 모달이 unmount 되어야 한다.", () => { 33 | render(); 34 | 35 | fireEvent.click(screen.getByRole("button", { name: "Close" })); 36 | expect(mockUseModal().closeModal).toHaveBeenCalled(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/@modal/hooks/useModal.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useAtom } from "jotai"; 3 | import { ModalState } from "../types"; 4 | import { modalContext } from "../context"; 5 | 6 | const useModal = () => { 7 | const [modal, setModal] = useAtom(modalContext); 8 | 9 | const openModal = React.useCallback( 10 | ({ component }: ModalState) => { 11 | setModal({ component, visible: true }); 12 | }, 13 | [setModal], 14 | ); 15 | 16 | const closeModal = React.useCallback(() => { 17 | setModal({ component: null, visible: false }); 18 | }, [setModal]); 19 | 20 | return { openModal, closeModal, visible: !!modal.visible }; 21 | }; 22 | 23 | export default useModal; 24 | -------------------------------------------------------------------------------- /src/@modal/layouts/LoginModal.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/navigation"; 2 | import styled from "styled-components"; 3 | import { Logo } from "@/assets/icons"; 4 | import { ROUTER } from "@/constants"; 5 | import useWindow from "@/hooks/useWindow"; 6 | import { theme, flex, font } from "@/styles"; 7 | 8 | const LoginModal = () => { 9 | const router = useRouter(); 10 | const { isWindow } = useWindow(); 11 | 12 | const handleLoginButtonClick = () => { 13 | if (isWindow) { 14 | window.open(process.env.NEXT_PUBLIC_OAUTH_URL || ROUTER.HOME); 15 | // router.push(process.env.NEXT_PUBLIC_OAUTH_URL || ROUTER.HOME); 16 | } 17 | }; 18 | 19 | return ( 20 | 21 | 22 | 23 | BSM 계정으로 로그인 24 | 25 | 26 | ); 27 | }; 28 | 29 | const Container = styled.div` 30 | width: fit-content; 31 | height: fit-content; 32 | padding: 40px 30px; 33 | background-color: ${theme.white}; 34 | border-radius: 6px; 35 | ${flex.COLUMN_CENTER}; 36 | gap: 5vh; 37 | `; 38 | 39 | const LoginButton = styled.button` 40 | width: fit-content; 41 | border-radius: 4px; 42 | padding: 8px 14px; 43 | background-color: ${theme.primary_blue}; 44 | color: ${theme.white}; 45 | ${font.btn3}; 46 | `; 47 | 48 | export default LoginModal; 49 | -------------------------------------------------------------------------------- /src/@modal/layouts/ModalView.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | import { flex } from "@/styles"; 3 | import { ModalState } from "../types"; 4 | 5 | interface ModalViewProps extends ModalState { 6 | onClose?: () => void; 7 | } 8 | 9 | const ModalView = ({ component, visible, onClose }: ModalViewProps) => { 10 | return ( 11 | 12 | 15 | ); 16 | }; 17 | 18 | const Container = styled.div``; 19 | 20 | const Background = styled.div<{ hidden: boolean }>` 21 | position: fixed; 22 | top: 0; 23 | left: 0; 24 | width: 100%; 25 | height: 100%; 26 | background-color: rgba(0, 0, 0, 0.3); 27 | z-index: 10; 28 | ${({ hidden }) => 29 | hidden && 30 | css` 31 | display: none; 32 | `} 33 | `; 34 | 35 | const ModalContainer = styled.div` 36 | ${flex.COLUMN_CENTER}; 37 | position: fixed; 38 | top: 50%; 39 | left: 50%; 40 | width: fit-content; 41 | height: fit-content; 42 | z-index: 20; 43 | transform: translate(-50%, -50%); 44 | 45 | ${({ hidden }) => 46 | hidden && 47 | css` 48 | display: none; 49 | `} 50 | `; 51 | 52 | export default ModalView; 53 | -------------------------------------------------------------------------------- /src/@modal/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import { useAtomValue } from "jotai"; 2 | import { modalContext } from "../context"; 3 | import { useModal } from "../hooks"; 4 | import ModalView from "./ModalView"; 5 | 6 | const Modal = () => { 7 | const modal = useAtomValue(modalContext); 8 | const { closeModal } = useModal(); 9 | 10 | const handleModalClose = () => { 11 | modal.onClose?.(); 12 | if (!modal.manualClose) closeModal(); 13 | }; 14 | 15 | return ; 16 | }; 17 | 18 | export default Modal; 19 | -------------------------------------------------------------------------------- /src/@modal/types/ModalState.type.ts: -------------------------------------------------------------------------------- 1 | export default interface ModalState { 2 | component: React.ReactNode; 3 | visible?: boolean; 4 | manualClose?: boolean; 5 | onClose?: () => void; 6 | } 7 | -------------------------------------------------------------------------------- /src/@modal/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as ModalState } from "./ModalState.type"; 2 | -------------------------------------------------------------------------------- /src/@user/assets/data/defaultUser.data.ts: -------------------------------------------------------------------------------- 1 | const defaultUserData = { 2 | isLogin: false, 3 | id: 0, 4 | nickname: "", 5 | email: "", 6 | name: "", 7 | profile_url: "", 8 | profile_image: "", 9 | authority: "", 10 | role: "", 11 | enroll: 0, 12 | grade: 0, 13 | classNum: 0, 14 | studentNumber: 0, 15 | }; 16 | 17 | export default defaultUserData; 18 | -------------------------------------------------------------------------------- /src/@user/assets/data/index.ts: -------------------------------------------------------------------------------- 1 | export { default as defaultUserData } from "./defaultUser.data"; 2 | -------------------------------------------------------------------------------- /src/@user/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ROLE } from "./role.constant"; 2 | -------------------------------------------------------------------------------- /src/@user/constants/role.constant.ts: -------------------------------------------------------------------------------- 1 | const ROLE = { 2 | ADMIN: "ADMIN", 3 | USER: "USER", 4 | } as const; 5 | 6 | export default ROLE; 7 | -------------------------------------------------------------------------------- /src/@user/context/index.ts: -------------------------------------------------------------------------------- 1 | export { default as userContext } from "./user.context"; 2 | -------------------------------------------------------------------------------- /src/@user/context/user.context.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai"; 2 | import { User } from "../types"; 3 | import { defaultUserData } from "../assets/data"; 4 | 5 | const userContext = atom(defaultUserData); 6 | 7 | export default userContext; 8 | -------------------------------------------------------------------------------- /src/@user/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useUser } from "./useUser"; 2 | -------------------------------------------------------------------------------- /src/@user/services/api.service.ts: -------------------------------------------------------------------------------- 1 | import httpClient from "@/apis/httpClient"; 2 | import Storage from "@/storage"; 3 | 4 | export const logout = async () => { 5 | const { data } = await httpClient.auth.logout({ 6 | headers: { refresh_token: Storage.getItem("TOKEN:REFRESH") }, 7 | }); 8 | return data; 9 | }; 10 | -------------------------------------------------------------------------------- /src/@user/services/mutation.service.ts: -------------------------------------------------------------------------------- 1 | import { KEY } from "@/constants"; 2 | import Storage from "@/storage"; 3 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 4 | import { logout } from "./api.service"; 5 | 6 | export const useLogoutMutation = () => { 7 | const queryClient = useQueryClient(); 8 | return useMutation(logout, { 9 | onSettled: () => { 10 | queryClient.invalidateQueries([KEY.USER]); 11 | Storage.delItem("TOKEN:ACCESS"); 12 | Storage.delItem("TOKEN:REFRESH"); 13 | }, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/@user/types/User.ts: -------------------------------------------------------------------------------- 1 | export default interface UserType { 2 | isLogin: boolean; 3 | id: number; 4 | nickname: string; 5 | email: string; 6 | name: string; 7 | profile_url: string; 8 | profile_image: string; 9 | authority: string; 10 | role: string; 11 | enroll: number; 12 | grade: number; 13 | classNum: number; 14 | studentNumber: number; 15 | } 16 | -------------------------------------------------------------------------------- /src/@user/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as User } from "./User"; 2 | -------------------------------------------------------------------------------- /src/apis/constants/error.constant.ts: -------------------------------------------------------------------------------- 1 | const ERROR = { 2 | CODE: { 3 | IMG_400_1: "IMG-400-1", 4 | DOCS_404_1: "DOCS-404-1", 5 | DOCS_404_2: "DOCS-404-2", 6 | DOCS_403_1: "DOCS-403-1", 7 | COMMON_403_1: "COMMON-403-1", 8 | USER_403_1: "USER-403-1", 9 | DOCS_403_2: "DOCS-403-2", 10 | TOKEN_403_1: "TOKEN-403-1", 11 | TOKEN_403_2: "TOKEN-403-2", 12 | TOKEN_403_3: "TOKEN-403-3", 13 | USER_404_1: "USER-404-1", 14 | }, 15 | STATUS: { 16 | SUCCESS: 200, 17 | NOT_FOUND: 404, 18 | FORBIDDEN: 403, 19 | BAD_REQUEST: 400, 20 | SERVER_ERROR: 500, 21 | }, 22 | } as const; 23 | 24 | export default ERROR; 25 | -------------------------------------------------------------------------------- /src/apis/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ERROR } from "./error.constant"; 2 | -------------------------------------------------------------------------------- /src/apis/httpClient/index.ts: -------------------------------------------------------------------------------- 1 | import httpClient from "./httpClient"; 2 | 3 | export default httpClient; 4 | -------------------------------------------------------------------------------- /src/apis/interceptor/index.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig, AxiosResponse } from "axios"; 2 | import { TOKEN } from "@/storage/constants"; 3 | import Storage from "../../storage"; 4 | 5 | export const requestInterceptors = (requestConfig: AxiosRequestConfig) => { 6 | const urlParams = requestConfig.url?.split("/:") || []; 7 | const accessToken = Storage.getItem(TOKEN.ACCESS); 8 | 9 | if (accessToken && requestConfig.headers) 10 | requestConfig.headers.Authorization = accessToken; 11 | if (urlParams.length < 2) return requestConfig; 12 | 13 | const paramParsedUrl = urlParams 14 | ?.map((paramKey) => { 15 | return requestConfig.params[paramKey]; 16 | }) 17 | .join("/"); 18 | 19 | urlParams?.forEach((paramKey: string) => { 20 | delete requestConfig.params[paramKey]; 21 | }, {}); 22 | 23 | return { 24 | ...requestConfig, 25 | url: paramParsedUrl, 26 | }; 27 | }; 28 | 29 | export const responseInterceptors = (originalResponse: AxiosResponse) => { 30 | return { 31 | ...originalResponse, 32 | data: originalResponse.data, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/apis/token/authorization.ts: -------------------------------------------------------------------------------- 1 | import Storage from "@/storage"; 2 | import { TOKEN } from "@/storage/constants"; 3 | 4 | const authorization = () => ({ 5 | headers: { 6 | Authorization: `Bearer ${Storage.getItem(TOKEN.ACCESS)}`, 7 | }, 8 | }); 9 | 10 | export default authorization; 11 | -------------------------------------------------------------------------------- /src/apis/token/index.ts: -------------------------------------------------------------------------------- 1 | export { default as refresh } from "./refresh"; 2 | export { default as authorization } from "./authorization"; 3 | -------------------------------------------------------------------------------- /src/apis/token/refresh.ts: -------------------------------------------------------------------------------- 1 | import { TOKEN } from "@/storage/constants"; 2 | import Storage from "@/storage"; 3 | import axios from "axios"; 4 | 5 | const instance = axios.create({ 6 | baseURL: process.env.NEXT_PUBLIC_BASE_URL, 7 | }); 8 | 9 | const refresh = async () => { 10 | try { 11 | const { data } = await instance.put("/api/auth/refresh/access", { 12 | refreshToken: `${Storage.getItem(TOKEN.REFRESH)}`, 13 | }); 14 | Storage.setItem(TOKEN.ACCESS, data.accessToken); 15 | } catch (err) { 16 | Storage.clear(); 17 | } 18 | }; 19 | 20 | export default refresh; 21 | -------------------------------------------------------------------------------- /src/app/applications/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import ApplicationsPage from "@/templates/applications/layouts"; 4 | 5 | const Applications = () => { 6 | return ; 7 | }; 8 | 9 | export default Applications; 10 | -------------------------------------------------------------------------------- /src/app/bamboo/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import BambooPage from "@/templates/bamboo/layouts"; 4 | 5 | const Bamboo = () => { 6 | return ; 7 | }; 8 | 9 | export default Bamboo; 10 | -------------------------------------------------------------------------------- /src/app/ber/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import BerPage from "@/templates/ber/layouts"; 4 | 5 | const Ber = () => { 6 | return ; 7 | }; 8 | 9 | export default Ber; 10 | -------------------------------------------------------------------------------- /src/app/calendar/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import CalendarPage from "@/templates/calendar/layouts"; 4 | 5 | const Calendar = () => { 6 | return ; 7 | }; 8 | 9 | export default Calendar; 10 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Provider from "@/provider/MainProvider"; 2 | import React from "react"; 3 | 4 | export const metadata = { 5 | title: "BSM", 6 | description: "부산소마고 스마트 학생 생활 플랫폼입니다.", 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: { 12 | children: React.ReactNode; 13 | }) { 14 | return ( 15 | 16 | 17 | {children} 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/meal/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import MealPage from "@/templates/meal/layouts"; 4 | 5 | const Meal = () => { 6 | return ; 7 | }; 8 | 9 | export default Meal; 10 | -------------------------------------------------------------------------------- /src/app/meister/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import MeisterPage from "@/templates/meister/layouts"; 4 | 5 | const Meister = () => { 6 | return ; 7 | }; 8 | 9 | export default Meister; 10 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { PageNotFound } from "@/assets/images"; 4 | import { Button } from "@/components/atoms"; 5 | import ROUTER from "@/constants/router.constant"; 6 | import { theme, flex } from "@/styles"; 7 | import Image from "next/image"; 8 | import { useRouter } from "next/navigation"; 9 | import React from "react"; 10 | import styled from "styled-components"; 11 | 12 | const NotFound = () => { 13 | const router = useRouter(); 14 | return ( 15 | 16 | 17 | 23 | 24 | ); 25 | }; 26 | 27 | const Container = styled.div` 28 | width: 100%; 29 | height: 70vh; 30 | ${flex.COLUMN_CENTER}; 31 | `; 32 | 33 | const StyledImage = styled(Image)` 34 | width: 50%; 35 | height: fit-content; 36 | `; 37 | 38 | export default NotFound; 39 | -------------------------------------------------------------------------------- /src/app/oauth/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import OAuthPage from "@/templates/oauth/layouts"; 4 | 5 | const OAuth = () => { 6 | return ; 7 | }; 8 | 9 | export default OAuth; 10 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import HomePage from "@/templates/home"; 4 | 5 | const Home = () => { 6 | return ; 7 | }; 8 | 9 | export default Home; 10 | -------------------------------------------------------------------------------- /src/app/post/[id]/edit/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import PostEditPage from "@/templates/post/layouts/edit"; 4 | 5 | interface PostUpdatePageParams { 6 | params: { 7 | id: number; 8 | }; 9 | } 10 | 11 | const Update = ({ params }: PostUpdatePageParams) => { 12 | return ; 13 | }; 14 | 15 | export default Update; 16 | -------------------------------------------------------------------------------- /src/app/post/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import PostDetailPage from "@/templates/post/layouts/detail"; 4 | 5 | interface PostDetailPageParams { 6 | params: { 7 | id: number; 8 | }; 9 | } 10 | 11 | const PostDetail = ({ params }: PostDetailPageParams) => { 12 | return ; 13 | }; 14 | 15 | export default PostDetail; 16 | -------------------------------------------------------------------------------- /src/app/post/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import PostListPage from "@/templates/post/layouts/list"; 4 | 5 | const Post = () => { 6 | return ; 7 | }; 8 | 9 | export default Post; 10 | -------------------------------------------------------------------------------- /src/app/post/write/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import PostWritePage from "@/templates/post/layouts/write"; 4 | 5 | const Write = () => { 6 | return ; 7 | }; 8 | 9 | export default Write; 10 | -------------------------------------------------------------------------------- /src/app/timetable/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import TimeTablePage from "@/templates/timetable/layouts"; 4 | 5 | const TimeTable = () => { 6 | return ; 7 | }; 8 | 9 | export default TimeTable; 10 | -------------------------------------------------------------------------------- /src/assets/icons/BerIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const BerIcon = ({ ...props }: React.SVGProps) => { 4 | return ( 5 | 13 | 17 | 18 | ); 19 | }; 20 | 21 | export default BerIcon; 22 | -------------------------------------------------------------------------------- /src/assets/icons/ChatIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ChatIcon = () => { 4 | return ( 5 | 12 | 16 | 17 | ); 18 | }; 19 | 20 | export default ChatIcon; 21 | -------------------------------------------------------------------------------- /src/assets/icons/CheckIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const CheckIcon = () => { 4 | return ( 5 | 12 | 18 | 19 | ); 20 | }; 21 | 22 | export default CheckIcon; 23 | -------------------------------------------------------------------------------- /src/assets/icons/Curve.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Curve = () => { 4 | return ( 5 | 12 | 19 | 28 | 35 | 42 | 43 | 44 | 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default Curve; 54 | -------------------------------------------------------------------------------- /src/assets/icons/DesktopIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DesktopIcon = () => { 4 | return ( 5 | 12 | 16 | 17 | ); 18 | }; 19 | 20 | export default DesktopIcon; 21 | -------------------------------------------------------------------------------- /src/assets/icons/DistributionIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DistributionIcon = () => { 4 | return ( 5 | 12 | 19 | 26 | 31 | 37 | 38 | ); 39 | }; 40 | 41 | export default DistributionIcon; 42 | -------------------------------------------------------------------------------- /src/assets/icons/GraphIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const GraphIcon = () => { 4 | return ( 5 | 12 | 19 | 27 | 35 | 36 | ); 37 | }; 38 | 39 | export default GraphIcon; 40 | -------------------------------------------------------------------------------- /src/assets/icons/HistorySeparator.tsx: -------------------------------------------------------------------------------- 1 | import { theme } from "@/styles"; 2 | import React from "react"; 3 | 4 | const HistorySeparator = () => { 5 | return ( 6 | 13 | 17 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default HistorySeparator; 27 | -------------------------------------------------------------------------------- /src/assets/icons/JoinCheckIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const JoinCheckIcon = ({ ...props }: React.SVGProps) => { 4 | return ( 5 | 13 | 17 | 18 | ); 19 | }; 20 | 21 | export default JoinCheckIcon; 22 | -------------------------------------------------------------------------------- /src/assets/icons/LinkArrow.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const LinkArrow = ({ ...props }: React.SVGProps) => { 4 | return ( 5 | 11 | 15 | 16 | ); 17 | }; 18 | 19 | export default LinkArrow; 20 | -------------------------------------------------------------------------------- /src/assets/icons/NoticeIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NoticeIcon = ({ ...props }: React.SVGProps) => { 4 | return ( 5 | 13 | 17 | 18 | ); 19 | }; 20 | 21 | export default NoticeIcon; 22 | -------------------------------------------------------------------------------- /src/assets/icons/ProgressIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ProgressIcon = () => { 4 | return ( 5 | 12 | 16 | 17 | ); 18 | }; 19 | 20 | export default ProgressIcon; 21 | -------------------------------------------------------------------------------- /src/assets/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Check } from "./Check"; 2 | export { default as Like } from "./Like"; 3 | export { default as LinkArrow } from "./LinkArrow"; 4 | export { default as Logo } from "./Logo"; 5 | export { default as Setting } from "./Setting"; 6 | export { default as View } from "./View"; 7 | export { default as Curve } from "./Curve"; 8 | export { default as HistorySeparator } from "./HistorySeparator"; 9 | export { default as UploadIcon } from "./UploadIcon"; 10 | export { default as DesktopIcon } from "./DesktopIcon"; 11 | export { default as MealIcon } from "./MealIcon"; 12 | export { default as BerIcon } from "./BerIcon"; 13 | export { default as CalendarIcon } from "./CalendarIcon"; 14 | export { default as NoticeIcon } from "./NoticeIcon"; 15 | export { default as ChatIcon } from "./ChatIcon"; 16 | export { default as BambooIcon } from "./BambooIcon"; 17 | export { default as ArrowIcon } from "./ArrowIcon"; 18 | export { default as XIcon } from "./XIcon"; 19 | export { default as CheckIcon } from "./CheckIcon"; 20 | export { default as DistributionIcon } from "./DistributionIcon"; 21 | export { default as GraphIcon } from "./GraphIcon"; 22 | -------------------------------------------------------------------------------- /src/assets/images/Back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/Back.png -------------------------------------------------------------------------------- /src/assets/images/QR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/QR.png -------------------------------------------------------------------------------- /src/assets/images/banner/banner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/banner/banner1.png -------------------------------------------------------------------------------- /src/assets/images/banner/banner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/banner/banner2.png -------------------------------------------------------------------------------- /src/assets/images/banner/banner3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/banner/banner3.png -------------------------------------------------------------------------------- /src/assets/images/emoticon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/emoticon.png -------------------------------------------------------------------------------- /src/assets/images/hugging_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/hugging_face.png -------------------------------------------------------------------------------- /src/assets/images/index.ts: -------------------------------------------------------------------------------- 1 | export { default as emoticon } from "./emoticon.png"; 2 | export { default as loading } from "./loading.gif"; 3 | export { default as defaultProfile } from "./profile_default.png"; 4 | export { default as QR } from "./QR.png"; 5 | export { default as TestBanner } from "./test_banner.png"; 6 | export { default as PageNotFound } from "./page_not_found.png"; 7 | export { default as Back } from "./Back.png"; 8 | 9 | export { default as Banner1Image } from "./banner/banner1.png"; 10 | export { default as Banner2Image } from "./banner/banner2.png"; 11 | export { default as Banner3Image } from "./banner/banner3.png"; 12 | export { default as Banner4Image } from "./banner/banner4.svg"; 13 | export { default as Banner5Image } from "./banner/banner2.png"; 14 | export { default as Banner6Image } from "./banner/banner3.png"; 15 | -------------------------------------------------------------------------------- /src/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/loading.gif -------------------------------------------------------------------------------- /src/assets/images/page_not_found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/page_not_found.png -------------------------------------------------------------------------------- /src/assets/images/profile_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/profile_default.png -------------------------------------------------------------------------------- /src/assets/images/shushing_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/shushing_face.png -------------------------------------------------------------------------------- /src/assets/images/test_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/test_banner.png -------------------------------------------------------------------------------- /src/assets/images/thinking_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/assets/images/thinking_face.png -------------------------------------------------------------------------------- /src/components/Flex/Column.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import FlexPropsType from "./type"; 3 | 4 | const Column = ({ 5 | children, 6 | gap, 7 | justifyContent = "none", 8 | alignItems = "none", 9 | width, 10 | height, 11 | }: FlexPropsType) => { 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | }; 18 | 19 | export default Column; 20 | 21 | const StyledColumn = styled.div` 22 | display: flex; 23 | flex-direction: column; 24 | `; 25 | -------------------------------------------------------------------------------- /src/components/Flex/Row.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import FlexPropsType from "./type"; 3 | 4 | const Row = ({ 5 | children, 6 | gap, 7 | justifyContent = "none", 8 | alignItems = "none", 9 | width, 10 | as, 11 | ...props 12 | }: FlexPropsType) => { 13 | return ( 14 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | export default Row; 25 | 26 | const StyledRow = styled.div` 27 | display: flex; 28 | `; 29 | -------------------------------------------------------------------------------- /src/components/Flex/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Column } from "./Column"; 2 | export { default as Row } from "./Row"; 3 | -------------------------------------------------------------------------------- /src/components/Flex/type.ts: -------------------------------------------------------------------------------- 1 | import { HTMLAttributes, ReactNode } from "react"; 2 | 3 | interface FlexPropsType extends HTMLAttributes { 4 | children: ReactNode; 5 | gap?: string; 6 | justifyContent?: 7 | | "none" 8 | | "center" 9 | | "flex-end" 10 | | "flex-start" 11 | | "space-between"; 12 | alignItems?: "none" | "center" | "flex-end" | "flex-start" | "space-between"; 13 | width?: string; 14 | height?: string; 15 | as?: string; 16 | } 17 | 18 | export default FlexPropsType; 19 | -------------------------------------------------------------------------------- /src/components/atoms/Button.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | import { theme, font } from "@/styles"; 3 | 4 | interface ButtonProps extends React.ButtonHTMLAttributes { 5 | color: string; 6 | align?: "TOP" | "RIGHT" | "BOTTOM" | "LEFT"; 7 | isSmall?: boolean; 8 | } 9 | 10 | const Button = ({ ...props }: ButtonProps) => { 11 | return ; 12 | }; 13 | 14 | const StyledButton = styled.button<{ 15 | color: string; 16 | align?: string; 17 | isSmall?: boolean; 18 | }>` 19 | width: fit-content; 20 | border-radius: 4px; 21 | background-color: ${(props) => props.color}; 22 | color: ${theme.white}; 23 | ${({ isSmall }) => 24 | isSmall 25 | ? css` 26 | padding: 3px 10px; 27 | ${font.caption}; 28 | ` 29 | : css` 30 | padding: 4px 16px; 31 | ${font.btn3}; 32 | `}; 33 | ${({ align }) => { 34 | switch (align) { 35 | case "TOP": 36 | return css` 37 | margin-bottom: auto; 38 | `; 39 | case "RIGHT": 40 | return css` 41 | margin-left: auto; 42 | `; 43 | case "BOTTOM": 44 | return css` 45 | margin-top: auto; 46 | `; 47 | case "LEFT": 48 | return css` 49 | margin-right: auto; 50 | `; 51 | default: 52 | return ""; 53 | } 54 | }} 55 | `; 56 | 57 | export default Button; 58 | -------------------------------------------------------------------------------- /src/components/atoms/Category.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | import { theme, font } from "@/styles"; 3 | import { Row } from "@/components/Flex"; 4 | 5 | interface CategoryProps extends React.InputHTMLAttributes { 6 | id?: string; 7 | label?: string; 8 | selected: boolean; 9 | } 10 | 11 | const Category = ({ id, label, selected, ...props }: CategoryProps) => { 12 | return ( 13 | 14 | 15 | 16 | {label} 17 | 18 | 19 | ); 20 | }; 21 | 22 | const StyledCategory = styled.input` 23 | display: none; 24 | `; 25 | 26 | const StyledLabel = styled.label<{ selected: boolean }>` 27 | border: none; 28 | padding: 6px 16px; 29 | border-radius: 999px; 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | box-shadow: 0 0 10px 0 rgba(144, 144, 144, 0.1); 34 | ${font.btn3}; 35 | ${({ selected }) => 36 | selected 37 | ? css` 38 | background-color: ${theme.primary_blue}; 39 | color: ${theme.white}; 40 | ` 41 | : css` 42 | background-color: ${theme.white}; 43 | color: ${theme.gray}; 44 | 45 | &:hover { 46 | background-color: ${theme.on_tertiary}; 47 | } 48 | `} 49 | `; 50 | 51 | export default Category; 52 | -------------------------------------------------------------------------------- /src/components/atoms/FallbackImage.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image, { ImageProps, StaticImageData } from "next/image"; 3 | import styled, { css } from "styled-components"; 4 | import { StaticImport } from "next/dist/shared/lib/get-img-props"; 5 | 6 | interface FallbackImageProps extends ImageProps { 7 | fallbackSrc: StaticImageData | string; 8 | alt: string; 9 | sizes?: string; 10 | rounded?: boolean; 11 | isShouldHide?: boolean; 12 | } 13 | 14 | const FallbackImage = ({ 15 | src, 16 | fallbackSrc, 17 | alt, 18 | sizes, 19 | isShouldHide, 20 | rounded, 21 | ...props 22 | }: FallbackImageProps) => { 23 | const [imgSrc, setImgSrc] = React.useState< 24 | StaticImageData | StaticImport | string 25 | >(""); 26 | 27 | React.useEffect(() => { 28 | setImgSrc(src ?? "/"); 29 | }, [src]); 30 | 31 | return ( 32 | setImgSrc(fallbackSrc)} 38 | isrounded={rounded?.toString()} 39 | /> 40 | ); 41 | }; 42 | 43 | const StyledImage = styled(Image)<{ 44 | isshouldhide?: boolean; 45 | isrounded?: string; 46 | }>` 47 | height: auto; 48 | ${({ isshouldhide }) => 49 | isshouldhide && 50 | css` 51 | display: none; 52 | `} 53 | ${({ isrounded }) => 54 | isrounded === "true" && 55 | css` 56 | border-radius: 50%; 57 | `} 58 | `; 59 | 60 | export default FallbackImage; 61 | -------------------------------------------------------------------------------- /src/components/atoms/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { theme, flex } from "@/styles"; 2 | import { PuffLoader } from "react-spinners"; 3 | import styled from "styled-components"; 4 | 5 | const Loading = () => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | const Container = styled.div` 14 | margin: 20px 0; 15 | width: 100%; 16 | ${flex.CENTER}; 17 | `; 18 | 19 | export default Loading; 20 | -------------------------------------------------------------------------------- /src/components/atoms/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from "./Button"; 2 | export { default as Category } from "./Category"; 3 | export { default as DropDown } from "./DropDown"; 4 | export { default as FallbackImage } from "./FallbackImage"; 5 | export { default as Input } from "./Input"; 6 | export { default as Loading } from "./Loading"; 7 | export { default as Switch } from "./Switch"; 8 | -------------------------------------------------------------------------------- /src/components/common/Aside/assets/data/defaultAside.data.ts: -------------------------------------------------------------------------------- 1 | const defaultAsideData = { 2 | score: 0, 3 | positivePoint: 0, 4 | negativePoint: 0, 5 | ranking: 0, 6 | room: { 7 | roomNumber: 0, 8 | yearSemester: { 9 | year: 0, 10 | semester: 0, 11 | }, 12 | dormitoryType: "", 13 | }, 14 | isCheckedIn: false, 15 | }; 16 | 17 | export default defaultAsideData; 18 | -------------------------------------------------------------------------------- /src/components/common/Aside/assets/data/index.ts: -------------------------------------------------------------------------------- 1 | export { default as defaultAsideData } from "./defaultAside.data"; 2 | -------------------------------------------------------------------------------- /src/components/common/Aside/context/aside.context.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai"; 2 | import { defaultAsideData } from "../assets/data"; 3 | 4 | const asideContext = atom(defaultAsideData); 5 | 6 | export default asideContext; 7 | -------------------------------------------------------------------------------- /src/components/common/Aside/context/index.ts: -------------------------------------------------------------------------------- 1 | export { default as asideContext } from "./aside.context"; 2 | -------------------------------------------------------------------------------- /src/components/common/Aside/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useAside } from "./useAside"; 2 | -------------------------------------------------------------------------------- /src/components/common/Aside/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Row } from "@/components/Flex"; 3 | import { flex } from "@/styles"; 4 | import { useUser } from "@/@user/hooks"; 5 | import InformationBox from "./StudentInfoBox"; 6 | import MeisterBox from "./MeisterInfoBox"; 7 | import JoinCheckBox from "./CheckInBox"; 8 | 9 | const Aside = () => { 10 | const { isLoggedIn } = useUser(); 11 | 12 | return ( 13 | 14 | {isLoggedIn && ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | )} 23 | 24 | ); 25 | }; 26 | 27 | const Container = styled.aside` 28 | ${flex.COLUMN_FLEX}; 29 | width: 22vw; 30 | height: 30vh; 31 | gap: 6px; 32 | margin-left: auto; 33 | 34 | @media screen and (max-width: 900px) { 35 | width: 28vw; 36 | } 37 | 38 | @media screen and (max-width: 800px) { 39 | width: 24vw; 40 | } 41 | 42 | @media screen and (max-width: 768px) { 43 | display: none; 44 | } 45 | `; 46 | 47 | export default Aside; 48 | -------------------------------------------------------------------------------- /src/components/common/ContentViewer/index.tsx: -------------------------------------------------------------------------------- 1 | import MDViewer from "@uiw/react-markdown-preview"; 2 | import rehypeSanitize from "rehype-sanitize"; 3 | 4 | interface ContentViewerProps { 5 | content?: string; 6 | } 7 | 8 | const ContentViewer = ({ content }: ContentViewerProps) => { 9 | return ( 10 | 15 | ); 16 | }; 17 | 18 | export default ContentViewer; 19 | -------------------------------------------------------------------------------- /src/components/common/DragDrop/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useDragDrop } from "./useDragDrop"; 2 | -------------------------------------------------------------------------------- /src/components/common/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { flex, theme } from "@/styles"; 3 | import Info from "./Info"; 4 | 5 | const Footer = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | const Layout = styled.div` 16 | ${flex.CENTER}; 17 | width: 100%; 18 | height: 400px; 19 | color: ${theme.content}; 20 | background-color: ${theme.tertiary}; 21 | `; 22 | 23 | const Container = styled.div` 24 | width: 76%; 25 | height: 60%; 26 | `; 27 | 28 | export default Footer; 29 | -------------------------------------------------------------------------------- /src/components/common/Header/@setting/layouts/setting.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { XIcon } from "@/assets/icons"; 4 | import { theme, flex, font } from "@/styles"; 5 | import { useModal } from "@/@modal/hooks"; 6 | import { useUser } from "@/@user/hooks"; 7 | import { Button } from "@/components/atoms"; 8 | 9 | const SettingModal = () => { 10 | const { closeModal } = useModal(); 11 | const { logout } = useUser(); 12 | 13 | return ( 14 | 15 | 16 | 17 | 설정 18 | 19 | 20 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | const Modal = styled.div` 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | ${flex.CENTER}; 33 | width: 100%; 34 | height: 100%; 35 | background-color: rgba(0, 0, 0, 0.5); 36 | `; 37 | 38 | const ModalContent = styled.div` 39 | background-color: ${theme.white}; 40 | border-radius: 3%; 41 | `; 42 | 43 | const SettingBox = styled.div` 44 | ${flex.VERTICAL} 45 | `; 46 | 47 | const SettingText = styled.p` 48 | ${flex.CENTER}; 49 | ${font.H4}; 50 | `; 51 | 52 | export default SettingModal; 53 | -------------------------------------------------------------------------------- /src/components/common/Header/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import styled from "styled-components"; 3 | import { theme, flex, font } from "@/styles"; 4 | import { navigationListData } from "./assets/data"; 5 | 6 | const Navigation = () => { 7 | return ( 8 | 9 | {navigationListData.map(({ id, href, name }) => ( 10 | 11 | {name} 12 | 13 | ))} 14 | 15 | ); 16 | }; 17 | 18 | const NavigationList = styled.ul` 19 | ${flex.VERTICAL}; 20 | width: 100%; 21 | gap: 6.2%; 22 | `; 23 | 24 | const NavigationListItem = styled(Link)` 25 | font-size: ${font.p2}; 26 | font-weight: 600; 27 | color: ${theme.black}; 28 | cursor: pointer; 29 | white-space: pre-wrap; 30 | `; 31 | 32 | export default Navigation; 33 | -------------------------------------------------------------------------------- /src/components/common/Header/assets/data/index.ts: -------------------------------------------------------------------------------- 1 | export { default as navigationListData } from "./navigationList.data"; 2 | -------------------------------------------------------------------------------- /src/components/common/Header/assets/data/navigationList.data.ts: -------------------------------------------------------------------------------- 1 | const navigationListData = [ 2 | { 3 | id: 1, 4 | name: "👩🏻‍🎓 인증제", 5 | href: "/meister", 6 | }, 7 | { 8 | id: 2, 9 | name: "📬 게시판", 10 | href: "/post", 11 | }, 12 | { 13 | id: 3, 14 | name: "🎋 대나무숲", 15 | href: "/bamboo", 16 | }, 17 | { 18 | id: 4, 19 | name: "🕐 시간표", 20 | href: "/timetable", 21 | }, 22 | { 23 | id: 5, 24 | name: "🍚 급식", 25 | href: "/meal", 26 | }, 27 | { 28 | id: 6, 29 | name: "🗓️ 캘린더", 30 | href: "/calendar", 31 | }, 32 | { 33 | id: 7, 34 | name: "☕️ 베르실", 35 | href: "/ber", 36 | }, 37 | ]; 38 | 39 | export default navigationListData; 40 | -------------------------------------------------------------------------------- /src/components/common/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { theme, flex } from "@/styles"; 4 | import { Logo, Setting } from "@/assets/icons"; 5 | import { useModal } from "@/@modal/hooks"; 6 | import Navigation from "./Navigation"; 7 | import SettingModal from "./@setting/layouts/setting"; 8 | 9 | const Header = () => { 10 | const { openModal } = useModal(); 11 | 12 | const handleOpenSettingModalClick = () => { 13 | openModal({ component: }); 14 | }; 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | const Layout = styled.div` 26 | ${flex.VERTICAL}; 27 | width: 100%; 28 | height: 70px; 29 | gap: 6%; 30 | background-color: ${theme.white}; 31 | border-bottom: 0.5px solid ${theme.on_tertiary}; 32 | padding: 0 8vw; 33 | `; 34 | 35 | export default Header; 36 | -------------------------------------------------------------------------------- /src/components/common/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Aside } from "./Aside"; 2 | export { default as ContentEditor } from "./ContentEditor"; 3 | export { default as ContentViewer } from "./ContentViewer"; 4 | export { default as DragDrop } from "./DragDrop"; 5 | export { default as Footer } from "./Footer"; 6 | export { default as Header } from "./Header"; 7 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | const createConfig = () => { 2 | return { 3 | clientUrl: typeof window !== "undefined" ? window.location.origin : "", 4 | serviceName: "BSM", 5 | }; 6 | }; 7 | 8 | export default createConfig(); 9 | -------------------------------------------------------------------------------- /src/constants/date.constant.ts: -------------------------------------------------------------------------------- 1 | const DATE = { 2 | DAY: "day", 3 | DETAIL: "YYYY년 M월 D일 dddd", 4 | FRI: "FRI", 5 | MON: "MON", 6 | WEEKDAY: "ddd", 7 | YYMMDD: "YYMMDD", 8 | YYYYMMDD: "YYYYMMDD", 9 | YYYYMMDDAHHMM: "YYYY. MM. DD A hh:mm", 10 | INCREASE: "INCREASE", 11 | DECREASE: "DECREASE", 12 | } as const; 13 | 14 | export default DATE; 15 | -------------------------------------------------------------------------------- /src/constants/direction.constant.ts: -------------------------------------------------------------------------------- 1 | const DIRECTION = { 2 | TOP: "TOP", 3 | RIGHT: "RIGHT", 4 | BOTTOM: "BOTTOM", 5 | LEFT: "LEFT", 6 | } as const; 7 | 8 | export default DIRECTION; 9 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DATE } from "./date.constant"; 2 | export { default as DIRECTION } from "./direction.constant"; 3 | export { default as KEY } from "./key.constant"; 4 | export { default as ROUTER } from "./router.constant"; 5 | -------------------------------------------------------------------------------- /src/constants/key.constant.ts: -------------------------------------------------------------------------------- 1 | const KEY = { 2 | USER: "useUser", 3 | TIMETABLE: "useTimetable", 4 | POST: "usePost", 5 | COMMENT: "useComment", 6 | RECOMMENT: "useRecomment", 7 | BAMBOO: "useBamboo", 8 | BAMBOO_ADMIN: "useBambooAdmin", 9 | MEAL: "useMeal", 10 | CALENDER: "useCalendar", 11 | RESERVE: "useReserve", 12 | MEISTER: "useMeister", 13 | MEISTER_DETAIL: "useMeisterDetail", 14 | MEISTER_ME: "useMeisterMe", 15 | RANKING: "useRanking", 16 | MAIN: "useMain", 17 | ASIDE: "useAside", 18 | } as const; 19 | 20 | export default KEY; 21 | -------------------------------------------------------------------------------- /src/constants/router.constant.ts: -------------------------------------------------------------------------------- 1 | const ROUTER = { 2 | HOME: "/", 3 | MEISTER: "/meister", 4 | APPLICATIONS: "/applications", 5 | LOGIN: "/oauth", 6 | POST: { 7 | DETAIL: "/post", 8 | LIST: "/post", 9 | WRITE: "/post/write", 10 | UPDATE: "/edit", 11 | }, 12 | TIMETABLE: "/timetable", 13 | MYPAGE: "/mypage", 14 | NOTFOUND: "/404", 15 | MEAL: "/meal", 16 | RESERVE: "/ber", 17 | JOIN_CHECK: "/join", 18 | CALENDER: "/calendar", 19 | } as const; 20 | 21 | export default ROUTER; 22 | -------------------------------------------------------------------------------- /src/graphql/index.ts: -------------------------------------------------------------------------------- 1 | import Storage from "@/storage"; 2 | import { TOKEN } from "@/storage/constants"; 3 | import { GraphQLClient } from "graphql-request"; 4 | 5 | export const graphQLClient = new GraphQLClient( 6 | `${process.env.NEXT_PUBLIC_BASE_URL}/api/graphql`, 7 | { 8 | headers: { 9 | Authorization: Storage.getItem(TOKEN.ACCESS) || "", 10 | }, 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useDidMountEffect } from "./useDidMountEffect"; 2 | export { default as useImageUpload } from "./useImageUpload"; 3 | export { default as useWindow } from "./useWindow"; 4 | export { default as useInfiniteScroll } from "./useInfiniteScroll"; 5 | -------------------------------------------------------------------------------- /src/hooks/useDidMountEffect.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const useDidMountEffect = (func: () => void, deps: React.DependencyList) => { 4 | const didMount = React.useRef(false); 5 | 6 | React.useEffect(() => { 7 | if (didMount.current) func(); 8 | else didMount.current = true; 9 | }, deps); 10 | }; 11 | 12 | export default useDidMountEffect; 13 | -------------------------------------------------------------------------------- /src/hooks/useImageUpload.ts: -------------------------------------------------------------------------------- 1 | import httpClient from "@/apis/httpClient"; 2 | 3 | const useImageUpload = () => { 4 | const uploadImage = async (file?: File) => { 5 | if (file) { 6 | const formData = new FormData(); 7 | formData.append("image", file, file.name); 8 | const { data } = await httpClient.image.post(formData); 9 | return data; 10 | } 11 | }; 12 | return { uploadImage }; 13 | }; 14 | 15 | export default useImageUpload; 16 | -------------------------------------------------------------------------------- /src/hooks/useInfiniteScroll.spec.tsx: -------------------------------------------------------------------------------- 1 | import { fireEvent, renderHook } from "@testing-library/react"; 2 | import useInfiniteScroll from "./useInfiniteScroll"; 3 | 4 | describe("useInfiniteScroll", () => { 5 | it("스크롤을 끝까지 내렸을 때 fetchNextPage 호출 확인", () => { 6 | const fetchNextPage = jest.fn(); 7 | renderHook(() => useInfiniteScroll(fetchNextPage)); 8 | 9 | Object.defineProperty(window, "scrollY", { 10 | value: 0, 11 | }); 12 | Object.defineProperty(document.documentElement, "offsetHeight", { 13 | value: 3000, 14 | }); 15 | // 스크롤을 끝까지 내리지 않음 16 | fireEvent.scroll(window); 17 | // 스크롤을 끝까지 내림 18 | window.scrollY = 3000; 19 | fireEvent.scroll(window); 20 | 21 | expect(fetchNextPage).toHaveBeenCalledTimes(1); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/hooks/useInfiniteScroll.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FetchNextPageOptions, 3 | InfiniteQueryObserverResult, 4 | } from "@tanstack/react-query"; 5 | import React from "react"; 6 | 7 | const useInfiniteScroll = ( 8 | fetchNextPage: ( 9 | options?: FetchNextPageOptions, 10 | ) => Promise, 11 | ) => { 12 | const isScroll = () => { 13 | const { scrollY } = window; 14 | const screenHeight = window.innerHeight; 15 | const bodyHeight = document.documentElement.offsetHeight; 16 | const scrollEnd = scrollY + screenHeight; 17 | const pos = scrollEnd + 2000; 18 | const isEnd = pos >= bodyHeight; 19 | if (isEnd) fetchNextPage(); 20 | }; 21 | 22 | React.useEffect(() => { 23 | window.addEventListener("scroll", isScroll); 24 | return () => window.removeEventListener("scroll", isScroll); 25 | }, []); 26 | }; 27 | 28 | export default useInfiniteScroll; 29 | -------------------------------------------------------------------------------- /src/hooks/useWindow.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | 5 | const useWindow = () => { 6 | const [isWindow, setIsWindow] = React.useState(false); 7 | 8 | React.useEffect(() => { 9 | if (typeof window !== "undefined") setIsWindow(true); 10 | }, []); 11 | 12 | return { isWindow }; 13 | }; 14 | 15 | export default useWindow; 16 | -------------------------------------------------------------------------------- /src/provider/LayoutProvider.tsx: -------------------------------------------------------------------------------- 1 | import "react-toastify/dist/ReactToastify.css"; 2 | import { ToastContainer, toast } from "react-toastify"; 3 | import styled from "styled-components"; 4 | import { Footer, Header } from "@/components/common"; 5 | import { GlobalStyle, flex } from "@/styles"; 6 | import Modal from "@/@modal/layouts"; 7 | 8 | const LayoutProvider = ({ children }: React.PropsWithChildren) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 |
16 |
{children}
17 |