├── .babelrc ├── .eslintrc.json ├── .github ├── ISSUE_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.cjs ├── .stylelintrc.json ├── .vscode └── settings.json ├── @types └── index.d.ts ├── LICENCE ├── README.md ├── changelog.config.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── public ├── 36th_og.png ├── android-icon-144x144.png ├── android-icon-192x192.png ├── android-icon-36x36.png ├── android-icon-48x48.png ├── android-icon-72x72.png ├── android-icon-96x96.png ├── apple-icon-114x114.png ├── apple-icon-120x120.png ├── apple-icon-144x144.png ├── apple-icon-152x152.png ├── apple-icon-180x180.png ├── apple-icon-57x57.png ├── apple-icon-60x60.png ├── apple-icon-72x72.png ├── apple-icon-76x76.png ├── apple-icon-precomposed.png ├── apple-icon.png ├── browserconfig.xml ├── down_arrow.svg ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── fonts │ ├── SUIT-Bold.woff2 │ ├── SUIT-ExtraBold.woff2 │ ├── SUIT-ExtraLight.woff2 │ ├── SUIT-Heavy.woff2 │ ├── SUIT-Light.woff2 │ ├── SUIT-Medium.woff2 │ ├── SUIT-Regular.woff2 │ ├── SUIT-SemiBold.woff2 │ └── SUIT-Thin.woff2 ├── images │ ├── members │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 152.png │ │ ├── 174.png │ │ ├── 2.png │ │ ├── 209.png │ │ ├── 246.png │ │ ├── 291.png │ │ ├── 3.png │ │ ├── 354.png │ │ ├── 387.png │ │ ├── 4.png │ │ ├── 408.png │ │ ├── 49.png │ │ ├── 5.png │ │ ├── 57.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 80.png │ │ └── 9.png │ ├── networking1.png │ ├── networking2.jpg │ ├── networking3.png │ ├── networking4.png │ ├── sopt_discord_seo.png │ └── sopt_twitter_seo.png ├── manifest.json ├── ms-icon-144x144.png ├── ms-icon-150x150.png ├── ms-icon-310x310.png ├── ms-icon-70x70.png └── vercel.svg ├── src ├── assets │ ├── corperates │ │ ├── codeit.png │ │ ├── doodlin.png │ │ ├── greenlabs.png │ │ ├── index.ts │ │ ├── miridih.png │ │ ├── ringle.png │ │ ├── teamSparta.png │ │ ├── toss.jpeg │ │ └── voyagerx.png │ ├── icons │ │ ├── appstore_icon.svg │ │ ├── arrow_down.svg │ │ ├── arrow_left.svg │ │ ├── arrow_left_28x28.svg │ │ ├── arrow_right.svg │ │ ├── arrow_right_16x16.svg │ │ ├── arrow_right_24x24.svg │ │ ├── arrow_right_28x28.svg │ │ ├── arrow_right_grey.svg │ │ ├── arrow_right_white.svg │ │ ├── arrow_up_24x24.svg │ │ ├── btn-mobile-filter.svg │ │ ├── chevronDown.svg │ │ ├── circle.svg │ │ ├── diagonal.svg │ │ ├── dosopt_symbol.png │ │ ├── facebook.svg │ │ ├── github_icon.svg │ │ ├── googleplay_icon.svg │ │ ├── ic_arrow_comment.svg │ │ ├── ic_arrow_left.svg │ │ ├── ic_arrow_right.svg │ │ ├── ic_arrow_right_white.svg │ │ ├── ic_arrow_stick_right.svg │ │ ├── ic_behance.svg │ │ ├── ic_chevron-down.svg │ │ ├── ic_downScroll.svg │ │ ├── ic_ellipse_blue.svg │ │ ├── ic_ellipse_green.svg │ │ ├── ic_email.svg │ │ ├── ic_facebook.svg │ │ ├── ic_github.svg │ │ ├── ic_heart_filled.svg │ │ ├── ic_heart_unfilled.svg │ │ ├── ic_heartfilled.svg │ │ ├── ic_heartunfilled.svg │ │ ├── ic_instagram.svg │ │ ├── ic_kakao.svg │ │ ├── ic_linkedin.svg │ │ ├── ic_logo_sopt_white.svg │ │ ├── ic_media.svg │ │ ├── ic_pagination_left.svg │ │ ├── ic_pagination_right.svg │ │ ├── ic_polygon.svg │ │ ├── ic_profile_default.svg │ │ ├── ic_school.svg │ │ ├── ic_sort.svg │ │ ├── ic_timer.svg │ │ ├── ic_toggle_arrow.svg │ │ ├── ic_up_arrow.svg │ │ ├── ic_view.svg │ │ ├── ic_website.svg │ │ ├── img_blog_default.svg │ │ ├── img_instagram.png │ │ ├── instagram.svg │ │ ├── kakao.svg │ │ ├── mail.svg │ │ ├── menuBar.svg │ │ ├── triangle.svg │ │ ├── value_challenge.svg │ │ ├── value_link.svg │ │ ├── value_share.svg │ │ ├── xButton.png │ │ └── youtube.svg │ ├── images │ │ ├── img_appjam.jpg │ │ ├── img_event.jpg │ │ ├── img_intro_card1.png │ │ ├── img_intro_card2.png │ │ ├── img_intro_card3.png │ │ ├── img_mainBanner.png │ │ ├── img_main_makers_card_big_pc.png │ │ ├── img_main_makers_card_mo.png │ │ ├── img_main_makers_card_pc.png │ │ ├── img_main_makers_card_ta.png │ │ ├── img_main_manage_card_big_pc.png │ │ ├── img_main_manage_card_mo.png │ │ ├── img_main_manage_card_pc.png │ │ ├── img_main_manage_card_ta.png │ │ ├── img_main_media_card_big_pc.png │ │ ├── img_main_media_card_mo.png │ │ ├── img_main_media_card_pc.png │ │ ├── img_main_media_card_ta.png │ │ ├── img_main_mind_card_big_pc.png │ │ ├── img_main_mind_card_mo.png │ │ ├── img_main_mind_card_pc.png │ │ ├── img_main_mind_card_ta.png │ │ ├── img_mainvalue_logos.png │ │ ├── img_recent_news_1.png │ │ ├── img_recent_news_2.jpg │ │ ├── img_recent_news_3.jpg │ │ ├── img_recent_news_4.webp │ │ ├── img_recent_news_5.png │ │ ├── img_recruit_banner.png │ │ ├── img_recruit_bg.png │ │ ├── img_review_banner.png │ │ ├── img_review_banner_mobile.png │ │ ├── img_review_banner_responsive.png │ │ ├── img_review_banner_tablet.png │ │ ├── img_seminar.jpg │ │ ├── img_soptkaton.jpg │ │ ├── img_soptterm.jpg │ │ ├── img_story_banner.png │ │ ├── img_story_banner_mobile.png │ │ ├── img_story_banner_responsive.png │ │ ├── img_story_banner_tablet.png │ │ ├── img_study.jpg │ │ ├── img_video.png │ │ ├── main-page_banner.png │ │ ├── null_image.png │ │ ├── recent-release-project │ │ │ ├── img_project_logo1.avif │ │ │ └── img_project_logo2.avif │ │ ├── sopt-corporate-activity │ │ │ ├── aws-seminar.png │ │ │ ├── foundation-attention.png │ │ │ ├── kb-dna.png │ │ │ ├── open-insight.png │ │ │ ├── scon-it.png │ │ │ ├── seeso-remote-work.png │ │ │ ├── spark-lab.png │ │ │ ├── unithon.png │ │ │ └── we-make-price-campus.png │ │ ├── sopt-corporate-partner │ │ │ ├── asan.png │ │ │ ├── aws-educate.png │ │ │ ├── dcamp.png │ │ │ ├── dream-plus.png │ │ │ ├── ict-coc.png │ │ │ ├── kb.png │ │ │ ├── maru.png │ │ │ ├── my-real-trip.png │ │ │ ├── nation-of-delivery.png │ │ │ ├── naver-d2.png │ │ │ ├── orange-farm.png │ │ │ ├── tom-toc.png │ │ │ ├── venture.png │ │ │ ├── vingle.png │ │ │ ├── we-make-price.png │ │ │ └── yonsei.png │ │ └── toggle.svg │ └── mainLogo │ │ ├── base64_logo.ts │ │ ├── img_logo_sopt.png │ │ ├── index.ts │ │ ├── logo_11.svg │ │ ├── logo_12.svg │ │ ├── logo_14.svg │ │ ├── logo_15.svg │ │ ├── logo_16.svg │ │ ├── logo_17.svg │ │ ├── logo_18.svg │ │ ├── logo_19.svg │ │ ├── logo_20.svg │ │ ├── logo_21.svg │ │ ├── logo_22.svg │ │ ├── logo_23.svg │ │ ├── logo_24.svg │ │ ├── logo_25.svg │ │ ├── logo_26.svg │ │ ├── logo_27.svg │ │ ├── logo_28.svg │ │ ├── logo_29.svg │ │ ├── logo_30.svg │ │ ├── logo_31.svg │ │ ├── logo_andsopt_dark.png │ │ ├── logo_dosopt.png │ │ ├── logo_dosopt@2x.png │ │ ├── logo_dosopt@3x.png │ │ ├── logo_nowsopt.png │ │ └── logo_sopt_nowsopt.svg ├── components │ ├── Footer │ │ ├── Channels │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── MakersNForm │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── OriginFooter │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ └── index.tsx │ ├── Header │ │ ├── Desktop │ │ │ └── index.tsx │ │ ├── Mobile │ │ │ ├── HeaderMenu │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── index.tsx │ │ ├── constants │ │ │ └── menuTapList.ts │ │ ├── index.tsx │ │ ├── style.ts │ │ └── types.ts │ ├── Layout │ │ └── index.tsx │ ├── common │ │ ├── Carousel │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Condition │ │ │ └── index.tsx │ │ ├── DummyDiv │ │ │ └── index.tsx │ │ ├── Flex │ │ │ └── index.tsx │ │ ├── FlippableCard │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── NumberRoller │ │ │ └── index.tsx │ │ ├── OvalSpinner │ │ │ └── index.tsx │ │ ├── PageLayout │ │ │ └── index.tsx │ │ ├── Pagination │ │ │ └── index.tsx │ │ ├── RoundButton │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── SEO │ │ │ └── index.tsx │ │ ├── ScrollToTopButton │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Select │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ └── Timer │ │ │ └── index.tsx │ ├── googleTagManager │ │ ├── Noscript.tsx │ │ ├── Script.tsx │ │ └── gtm.ts │ ├── index.ts │ └── kakao │ │ └── Script.tsx ├── hooks │ ├── useBooleanState.ts │ ├── useDevice.ts │ ├── useDrag.ts │ ├── useHeader.ts │ ├── useHorizontalScroll.ts │ ├── useInView.ts │ ├── useInfiniteCarousel.ts │ ├── useIntersectionObserver.ts │ ├── useInterval.ts │ ├── useMounted.ts │ ├── useNoScroll.ts │ ├── useOutsideClickListener.ts │ ├── useScrollPosition.ts │ ├── useStackedFetchBase │ │ ├── index.ts │ │ ├── reducer.ts │ │ └── types.ts │ ├── useStorage.ts │ └── useTabs.ts ├── lib │ ├── api │ │ ├── index.ts │ │ └── remote │ │ │ ├── about.ts │ │ │ ├── admin.ts │ │ │ ├── project.ts │ │ │ ├── review.ts │ │ │ └── sopticle.ts │ ├── constants │ │ ├── about.ts │ │ ├── client.ts │ │ ├── faq.ts │ │ ├── gtmClass.ts │ │ ├── main.ts │ │ ├── project.ts │ │ ├── rules.ts │ │ └── tabs.ts │ ├── styles │ │ ├── animation.ts │ │ ├── font.ts │ │ ├── global.ts │ │ ├── scrollbar.ts │ │ └── textEllipsis.ts │ ├── types │ │ ├── about.ts │ │ ├── admin.ts │ │ ├── blog.ts │ │ ├── dto.ts │ │ ├── faq.ts │ │ ├── main.ts │ │ ├── project.ts │ │ ├── review.ts │ │ ├── sopticle.ts │ │ └── universal.ts │ └── utils │ │ ├── array.ts │ │ ├── convertMsIntoDate.ts │ │ ├── date.ts │ │ ├── dateFormat.ts │ │ ├── debounce.ts │ │ ├── getLinkNameAndSrcWithType.ts │ │ ├── pageViewTrackingEnrichment.ts │ │ ├── parsePartToKorean.ts │ │ ├── parseStringToPart.ts │ │ ├── platform.ts │ │ ├── sanitize.ts │ │ └── storageHandler.ts ├── pages │ ├── 404.tsx │ ├── 500.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── about.tsx │ ├── blog.tsx │ ├── index.tsx │ ├── project │ │ ├── [id] │ │ │ └── index.tsx │ │ └── index.tsx │ ├── recruit.tsx │ ├── rules.tsx │ └── sponsor.tsx └── views │ ├── AboutPage │ ├── components │ │ ├── @common │ │ │ ├── SectionDescription │ │ │ │ └── index.tsx │ │ │ ├── SectionTitle │ │ │ │ └── index.tsx │ │ │ ├── SectionTop │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── TabBar │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ ├── Banner │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── CoreValue │ │ │ ├── Item │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── List │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── Section │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ ├── Curriculum │ │ │ ├── Content │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── Section │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ ├── Member │ │ │ ├── Card │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── Section │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ ├── Record │ │ │ ├── Item │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── List │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── Section │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ └── index.tsx │ └── index.tsx │ ├── BlogPage │ ├── components │ │ ├── Banner │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── BlogPost │ │ │ ├── DefaultProfileImage │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── Header │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── Like │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── BlogPostList │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── BlogPostSkeletonUI │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── BlogTab │ │ │ ├── index.tsx │ │ │ ├── style.ts │ │ │ └── types.ts │ │ ├── EmptyBlogPostList │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ └── OfficialVideo │ │ │ ├── index.tsx │ │ │ └── style.ts │ ├── hooks │ │ ├── useGetResponse.ts │ │ └── useInfiniteScroll.ts │ └── index.tsx │ ├── ErrorPage │ ├── assets │ │ ├── ic_404_back.svg │ │ ├── ic_404_front.svg │ │ ├── ic_404_ghost.svg │ │ ├── ic_404_ghost_dark.svg │ │ ├── ic_500_back.svg │ │ ├── ic_500_cone.svg │ │ ├── ic_500_cone_dark.svg │ │ ├── ic_500_front.svg │ │ └── index.ts │ ├── components │ │ └── ErrorCode │ │ │ ├── index.tsx │ │ │ └── style.ts │ ├── constants │ │ ├── errorButton.ts │ │ └── errorMessage.ts │ ├── index.tsx │ └── styles.ts │ ├── MainPage │ ├── components │ │ ├── Activity │ │ │ ├── Card │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── MobileCard │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── ActivitySection │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Banner │ │ │ ├── RecruitButton │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── BottomLayout │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Comment │ │ │ ├── Card │ │ │ │ ├── Desktop │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.ts │ │ │ │ └── Mobile │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.ts │ │ │ ├── Cards │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── IntroSection │ │ │ ├── IntroContent │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Introduce │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── OwnOrganization │ │ │ ├── Card │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── PartConfig │ │ │ ├── PartButton.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── PartSlide │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── RecentNews │ │ │ ├── Card │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── RecruitMessage │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── ScrollInteractiveLogo │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Tab │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ └── TopBanner │ │ │ ├── index.tsx │ │ │ └── style.ts │ ├── hooks │ │ ├── useGetVisitor.ts │ │ └── usePostVisitor.ts │ └── index.tsx │ ├── ProjectDetailPage │ ├── style.ts │ └── types.ts │ ├── ProjectPage │ ├── assets │ │ ├── app-store-40x40.svg │ │ ├── github-40x40.svg │ │ ├── instagram-30x30.svg │ │ ├── play-store-40x40.svg │ │ ├── website-40x40.svg │ │ └── youtube-30x30.svg │ ├── components │ │ ├── RecentProjectList │ │ │ ├── Carousel │ │ │ │ └── index.tsx │ │ │ ├── Item │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── RecentProjectListSkeletonUI │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── index.tsx │ │ └── project │ │ │ ├── ProjectCard │ │ │ ├── ServiceInfo │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ │ ├── ProjectCardList │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ │ ├── ProjectCategoryDescription │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ │ ├── ProjectList │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ │ ├── ProjectListCount │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ │ └── ProjectListFallback │ │ │ ├── ProjectCardSkeletonUI │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ ├── hooks │ │ └── useGetProjectList.ts │ ├── index.tsx │ └── styles.ts │ ├── RecruitPage │ ├── components │ │ ├── ActivityReview │ │ │ ├── hooks │ │ │ │ └── queries │ │ │ │ │ └── useGetSampleReviews.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── ApplySection │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── BottomLogo │ │ │ └── index.tsx │ │ ├── ChapterInfo │ │ │ ├── constants.ts │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Contact │ │ │ ├── constant.ts │ │ │ └── index.tsx │ │ ├── FAQ │ │ │ ├── QuestionBox │ │ │ │ └── index.tsx │ │ │ ├── constant.ts │ │ │ └── index.tsx │ │ ├── NotificationSection │ │ │ └── index.tsx │ │ ├── RecruteeInfo │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── Schedule │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ └── common │ │ │ ├── Tabs │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ │ └── style.ts │ └── index.tsx │ ├── RulesPage │ ├── components │ │ ├── CollapseLi │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── RulesHead │ │ │ └── index.tsx │ │ ├── RulesList │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ ├── UnderlinedText │ │ │ ├── index.tsx │ │ │ └── style.ts │ │ └── index.tsx │ └── index.tsx │ └── SponsorPage │ ├── components │ ├── CorporatePartner │ │ ├── constants.ts │ │ ├── index.tsx │ │ └── style.ts │ └── index.tsx │ └── index.tsx └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": ["@emotion"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/ISSUE_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 요약 2 | 3 | ## 해결 방법 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## ✨ 작업 내용 11 | - [ ] 작업 1 12 | - [ ] 작업 2 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | ## Screenshot 4 | 5 | ## Comment 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: [main, develop] 6 | 7 | jobs: 8 | continuous-integration: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Use Node.js 18.x 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: '18' 16 | 17 | - name: Install pnpm 18 | run: npm install -g pnpm@7.28.0 19 | 20 | - name: Install dependencies 21 | run: pnpm install --no-frozen-lockfile 22 | 23 | - name: Check code style 24 | run: pnpm run lint 25 | 26 | - name: Build project 27 | run: pnpm run build 28 | env: 29 | NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL }} 30 | NEXT_PUBLIC_AMPLITUDE_API_KEY: ${{ secrets.NEXT_PUBLIC_AMPLITUDE_API_KEY }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | 38 | .idea/ 39 | 40 | # env files 41 | .env.* 42 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | npx lint-staged 4 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require.resolve('@trivago/prettier-plugin-sort-imports')], 3 | printWidth: 100, 4 | semi: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | tabWidth: 2, 8 | bracketSpacing: true, 9 | endOfLine: 'auto', 10 | useTabs: false, 11 | importOrderSortSpecifiers: true, 12 | importOrderSeparation: false, 13 | importOrder: ['next', 'react', '^@src/(.*)$', '^(?!react)\\w+$', '^[./]'], 14 | }; 15 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-prettier", 4 | "stylelint-config-concentric-order", 5 | "stylelint-config-styled-components", 6 | "postcss-syntax", 7 | "postcss-html" 8 | ], 9 | "overrides": [ 10 | { 11 | "files": ["**/*.@(ts|tsx)"], 12 | "customSyntax": "@stylelint/postcss-css-in-js" 13 | }, 14 | { 15 | "files": ["*.html", "**/*.html"], 16 | "customSyntax": "postcss-html" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit", 5 | "source.fixAll.stylelint": "explicit" 6 | }, 7 | 8 | "editor.formatOnSave" : true, 9 | } 10 | -------------------------------------------------------------------------------- /@types/index.d.ts: -------------------------------------------------------------------------------- 1 | type Nullable = T | null; 2 | 3 | type NonNullableObj = { 4 | [K in keyof T]-?: T[K]; 5 | }; 6 | 7 | type DataMap = { 8 | data: T; 9 | }; 10 | 11 | interface IParentComponentProps { 12 | className?: string; 13 | children: ReactChild; 14 | } 15 | 16 | declare module 'shortid'; 17 | 18 | declare module '*.svg' { 19 | export const ReactComponent: React.FC>; 20 | const src: string; 21 | export default src; 22 | } 23 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sopt 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. 22 | -------------------------------------------------------------------------------- /changelog.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | disableEmoji: true, 3 | questions: ['type', 'scope', 'subject', 'issues'], 4 | }; 5 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /public/36th_og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/36th_og.png -------------------------------------------------------------------------------- /public/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/android-icon-144x144.png -------------------------------------------------------------------------------- /public/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/android-icon-192x192.png -------------------------------------------------------------------------------- /public/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/android-icon-36x36.png -------------------------------------------------------------------------------- /public/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/android-icon-48x48.png -------------------------------------------------------------------------------- /public/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/android-icon-72x72.png -------------------------------------------------------------------------------- /public/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/android-icon-96x96.png -------------------------------------------------------------------------------- /public/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/apple-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /public/down_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/SUIT-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-Bold.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-ExtraBold.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-ExtraLight.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-Heavy.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-Heavy.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-Medium.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-SemiBold.woff2 -------------------------------------------------------------------------------- /public/fonts/SUIT-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/fonts/SUIT-Thin.woff2 -------------------------------------------------------------------------------- /public/images/members/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/1.png -------------------------------------------------------------------------------- /public/images/members/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/10.png -------------------------------------------------------------------------------- /public/images/members/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/11.png -------------------------------------------------------------------------------- /public/images/members/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/12.png -------------------------------------------------------------------------------- /public/images/members/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/152.png -------------------------------------------------------------------------------- /public/images/members/174.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/174.png -------------------------------------------------------------------------------- /public/images/members/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/2.png -------------------------------------------------------------------------------- /public/images/members/209.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/209.png -------------------------------------------------------------------------------- /public/images/members/246.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/246.png -------------------------------------------------------------------------------- /public/images/members/291.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/291.png -------------------------------------------------------------------------------- /public/images/members/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/3.png -------------------------------------------------------------------------------- /public/images/members/354.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/354.png -------------------------------------------------------------------------------- /public/images/members/387.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/387.png -------------------------------------------------------------------------------- /public/images/members/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/4.png -------------------------------------------------------------------------------- /public/images/members/408.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/408.png -------------------------------------------------------------------------------- /public/images/members/49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/49.png -------------------------------------------------------------------------------- /public/images/members/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/5.png -------------------------------------------------------------------------------- /public/images/members/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/57.png -------------------------------------------------------------------------------- /public/images/members/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/6.png -------------------------------------------------------------------------------- /public/images/members/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/7.png -------------------------------------------------------------------------------- /public/images/members/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/8.png -------------------------------------------------------------------------------- /public/images/members/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/80.png -------------------------------------------------------------------------------- /public/images/members/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/members/9.png -------------------------------------------------------------------------------- /public/images/networking1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/networking1.png -------------------------------------------------------------------------------- /public/images/networking2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/networking2.jpg -------------------------------------------------------------------------------- /public/images/networking3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/networking3.png -------------------------------------------------------------------------------- /public/images/networking4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/networking4.png -------------------------------------------------------------------------------- /public/images/sopt_discord_seo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/sopt_discord_seo.png -------------------------------------------------------------------------------- /public/images/sopt_twitter_seo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/images/sopt_twitter_seo.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /public/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/public/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/corperates/codeit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/codeit.png -------------------------------------------------------------------------------- /src/assets/corperates/doodlin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/doodlin.png -------------------------------------------------------------------------------- /src/assets/corperates/greenlabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/greenlabs.png -------------------------------------------------------------------------------- /src/assets/corperates/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImgDoodlinLogo } from './doodlin.png'; 2 | export { default as ImgGreenlabsLogo } from './greenlabs.png'; 3 | export { default as ImgMiridihLogo } from './miridih.png'; 4 | export { default as ImgRingleLogo } from './ringle.png'; 5 | export { default as ImgTeamSpartaLogo } from './teamSparta.png'; 6 | export { default as ImgTossLogo } from './toss.jpeg'; 7 | export { default as ImgVoyagerxLogo } from './voyagerx.png'; 8 | export { default as ImgCodeitLogo } from './codeit.png'; 9 | -------------------------------------------------------------------------------- /src/assets/corperates/miridih.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/miridih.png -------------------------------------------------------------------------------- /src/assets/corperates/ringle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/ringle.png -------------------------------------------------------------------------------- /src/assets/corperates/teamSparta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/teamSparta.png -------------------------------------------------------------------------------- /src/assets/corperates/toss.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/toss.jpeg -------------------------------------------------------------------------------- /src/assets/corperates/voyagerx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/corperates/voyagerx.png -------------------------------------------------------------------------------- /src/assets/icons/appstore_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_left_28x28.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_right_16x16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_right_24x24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_right_28x28.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_right_grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_right_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/arrow_up_24x24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/btn-mobile-filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/chevronDown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/diagonal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/dosopt_symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/icons/dosopt_symbol.png -------------------------------------------------------------------------------- /src/assets/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_arrow_comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/ic_arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_arrow_right_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_arrow_stick_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/ic_downScroll.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/ic_ellipse_blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/icons/ic_ellipse_green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/ic_heart_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_heart_unfilled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_heartfilled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_heartunfilled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/ic_pagination_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/ic_pagination_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/ic_polygon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_profile_default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/icons/ic_school.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/ic_sort.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/ic_timer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/ic_toggle_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_up_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/ic_view.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/img_instagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/icons/img_instagram.png -------------------------------------------------------------------------------- /src/assets/icons/mail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/menuBar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/xButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/icons/xButton.png -------------------------------------------------------------------------------- /src/assets/icons/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/img_appjam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_appjam.jpg -------------------------------------------------------------------------------- /src/assets/images/img_event.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_event.jpg -------------------------------------------------------------------------------- /src/assets/images/img_intro_card1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_intro_card1.png -------------------------------------------------------------------------------- /src/assets/images/img_intro_card2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_intro_card2.png -------------------------------------------------------------------------------- /src/assets/images/img_intro_card3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_intro_card3.png -------------------------------------------------------------------------------- /src/assets/images/img_mainBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_mainBanner.png -------------------------------------------------------------------------------- /src/assets/images/img_main_makers_card_big_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_makers_card_big_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_makers_card_mo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_makers_card_mo.png -------------------------------------------------------------------------------- /src/assets/images/img_main_makers_card_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_makers_card_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_makers_card_ta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_makers_card_ta.png -------------------------------------------------------------------------------- /src/assets/images/img_main_manage_card_big_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_manage_card_big_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_manage_card_mo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_manage_card_mo.png -------------------------------------------------------------------------------- /src/assets/images/img_main_manage_card_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_manage_card_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_manage_card_ta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_manage_card_ta.png -------------------------------------------------------------------------------- /src/assets/images/img_main_media_card_big_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_media_card_big_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_media_card_mo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_media_card_mo.png -------------------------------------------------------------------------------- /src/assets/images/img_main_media_card_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_media_card_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_media_card_ta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_media_card_ta.png -------------------------------------------------------------------------------- /src/assets/images/img_main_mind_card_big_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_mind_card_big_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_mind_card_mo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_mind_card_mo.png -------------------------------------------------------------------------------- /src/assets/images/img_main_mind_card_pc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_mind_card_pc.png -------------------------------------------------------------------------------- /src/assets/images/img_main_mind_card_ta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_main_mind_card_ta.png -------------------------------------------------------------------------------- /src/assets/images/img_mainvalue_logos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_mainvalue_logos.png -------------------------------------------------------------------------------- /src/assets/images/img_recent_news_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_recent_news_1.png -------------------------------------------------------------------------------- /src/assets/images/img_recent_news_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_recent_news_2.jpg -------------------------------------------------------------------------------- /src/assets/images/img_recent_news_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_recent_news_3.jpg -------------------------------------------------------------------------------- /src/assets/images/img_recent_news_4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_recent_news_4.webp -------------------------------------------------------------------------------- /src/assets/images/img_recent_news_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_recent_news_5.png -------------------------------------------------------------------------------- /src/assets/images/img_recruit_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_recruit_banner.png -------------------------------------------------------------------------------- /src/assets/images/img_recruit_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_recruit_bg.png -------------------------------------------------------------------------------- /src/assets/images/img_review_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_review_banner.png -------------------------------------------------------------------------------- /src/assets/images/img_review_banner_mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_review_banner_mobile.png -------------------------------------------------------------------------------- /src/assets/images/img_review_banner_responsive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_review_banner_responsive.png -------------------------------------------------------------------------------- /src/assets/images/img_review_banner_tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_review_banner_tablet.png -------------------------------------------------------------------------------- /src/assets/images/img_seminar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_seminar.jpg -------------------------------------------------------------------------------- /src/assets/images/img_soptkaton.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_soptkaton.jpg -------------------------------------------------------------------------------- /src/assets/images/img_soptterm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_soptterm.jpg -------------------------------------------------------------------------------- /src/assets/images/img_story_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_story_banner.png -------------------------------------------------------------------------------- /src/assets/images/img_story_banner_mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_story_banner_mobile.png -------------------------------------------------------------------------------- /src/assets/images/img_story_banner_responsive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_story_banner_responsive.png -------------------------------------------------------------------------------- /src/assets/images/img_story_banner_tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_story_banner_tablet.png -------------------------------------------------------------------------------- /src/assets/images/img_study.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_study.jpg -------------------------------------------------------------------------------- /src/assets/images/img_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/img_video.png -------------------------------------------------------------------------------- /src/assets/images/main-page_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/main-page_banner.png -------------------------------------------------------------------------------- /src/assets/images/null_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/null_image.png -------------------------------------------------------------------------------- /src/assets/images/recent-release-project/img_project_logo1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/recent-release-project/img_project_logo1.avif -------------------------------------------------------------------------------- /src/assets/images/recent-release-project/img_project_logo2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/recent-release-project/img_project_logo2.avif -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/aws-seminar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/aws-seminar.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/foundation-attention.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/foundation-attention.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/kb-dna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/kb-dna.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/open-insight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/open-insight.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/scon-it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/scon-it.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/seeso-remote-work.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/seeso-remote-work.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/spark-lab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/spark-lab.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/unithon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/unithon.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-activity/we-make-price-campus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-activity/we-make-price-campus.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/asan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/asan.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/aws-educate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/aws-educate.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/dcamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/dcamp.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/dream-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/dream-plus.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/ict-coc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/ict-coc.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/kb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/kb.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/maru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/maru.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/my-real-trip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/my-real-trip.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/nation-of-delivery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/nation-of-delivery.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/naver-d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/naver-d2.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/orange-farm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/orange-farm.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/tom-toc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/tom-toc.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/venture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/venture.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/vingle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/vingle.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/we-make-price.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/we-make-price.png -------------------------------------------------------------------------------- /src/assets/images/sopt-corporate-partner/yonsei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/images/sopt-corporate-partner/yonsei.png -------------------------------------------------------------------------------- /src/assets/images/toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/mainLogo/img_logo_sopt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/mainLogo/img_logo_sopt.png -------------------------------------------------------------------------------- /src/assets/mainLogo/logo_andsopt_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/mainLogo/logo_andsopt_dark.png -------------------------------------------------------------------------------- /src/assets/mainLogo/logo_dosopt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/mainLogo/logo_dosopt.png -------------------------------------------------------------------------------- /src/assets/mainLogo/logo_dosopt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/mainLogo/logo_dosopt@2x.png -------------------------------------------------------------------------------- /src/assets/mainLogo/logo_dosopt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/mainLogo/logo_dosopt@3x.png -------------------------------------------------------------------------------- /src/assets/mainLogo/logo_nowsopt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sopt-makers/sopt.org-frontend/b9c0f94932a5b064a1e6dc6fdbd4933dd435d3aa/src/assets/mainLogo/logo_nowsopt.png -------------------------------------------------------------------------------- /src/components/Footer/Channels/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import Image from 'next/image'; 3 | 4 | interface WrapProps { 5 | isFooter: boolean; 6 | } 7 | 8 | export const ChannelButtonsWrap = styled.div` 9 | display: grid; 10 | grid-template-columns: ${(props: WrapProps) => 11 | props.isFooter ? 'repeat(5, minmax(30px, auto))' : 'repeat(3, minmax(30px, auto))'}; 12 | column-gap: ${(props: WrapProps) => (props.isFooter ? '10px' : '16px')}; 13 | row-gap: 16px; 14 | 15 | width: ${(props: WrapProps) => (props.isFooter ? '190px' : '152px')}; 16 | height: ${(props: WrapProps) => (props.isFooter ? '30px' : '96px')}; 17 | margin-top: 15px; 18 | `; 19 | 20 | export const ClickableChannelButton = styled(Image)` 21 | cursor: pointer; 22 | `; 23 | -------------------------------------------------------------------------------- /src/components/Footer/MakersNForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import useScrollPosition from '@src/hooks/useScrollPosition'; 3 | import St from './style'; 4 | 5 | const MakersNForm: FC = () => { 6 | const { isScrollingDown, isScrollTop } = useScrollPosition(); 7 | 8 | const handleClickKakao = () => { 9 | window.Kakao.Channel.chat({ 10 | channelPublicId: '_sxaIWG', 11 | }); 12 | }; 13 | 14 | return ( 15 | 16 | 21 | 만든 사람들 22 | 23 | 24 | 의견 제안하기 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default MakersNForm; 31 | -------------------------------------------------------------------------------- /src/components/Footer/MakersNForm/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import Link from 'next/link'; 3 | import { css } from '@emotion/react'; 4 | 5 | const FooterForm = styled.div<{ hide: boolean }>` 6 | display: flex; 7 | position: fixed; 8 | bottom: 0; 9 | 10 | width: 100%; 11 | 12 | padding: 0 0 0 38px; 13 | 14 | border-top: 1px solid #3c3d40; 15 | background-color: #1c1d1e; 16 | 17 | transition: transform 0.3s; 18 | z-index: 100; 19 | 20 | ${({ hide }) => 21 | hide 22 | ? css` 23 | transform: translateY(100%); 24 | ` 25 | : ''} 26 | 27 | /* 모바일 뷰 */ 28 | @media (max-width: 47.8125rem) { 29 | padding: 0 0 0 10px; 30 | } 31 | `; 32 | 33 | const FooterLink = styled(Link)` 34 | padding: 17px 10px; 35 | 36 | color: #c0c5c9; 37 | line-height: normal; 38 | font-size: 16rem; 39 | 40 | &:hover { 41 | cursor: pointer; 42 | color: #ffffff; 43 | } 44 | `; 45 | const FooterButton = styled.button` 46 | padding: 17px 10px; 47 | 48 | color: #c0c5c9; 49 | font-size: 16rem; 50 | 51 | &:hover { 52 | cursor: pointer; 53 | color: #ffffff; 54 | } 55 | `; 56 | 57 | const St = { 58 | FooterForm, 59 | FooterLink, 60 | FooterButton, 61 | }; 62 | 63 | export default St; 64 | -------------------------------------------------------------------------------- /src/components/Footer/OriginFooter/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { FC } from 'react'; 3 | import { ReactComponent as ArrowRight } from '@src/assets/icons/arrow_right_16x16.svg'; 4 | import Channels from '@src/components/Footer/Channels'; 5 | import * as St from './style'; 6 | 7 | const OriginFooter: FC = () => { 8 | const router = useRouter(); 9 | 10 | const handleClick = () => { 11 | router.push('/rules'); 12 | }; 13 | 14 | return ( 15 | 16 | 17 |
18 | 19 | SOPT 회칙 20 | 21 | 22 | 23 | SOPT (솝트, 대학생연합 IT벤처창업 동아리) 24 |
25 | Copyrightⓒ2022.SOPT. All rights reserved. 26 |
27 |
28 | 29 | SOPT 채널 바로가기 30 | 31 | 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default OriginFooter; 38 | -------------------------------------------------------------------------------- /src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import DummyDiv from '../common/DummyDiv'; 3 | import MakersNForm from './MakersNForm'; 4 | 5 | const DynamicOriginFooter = dynamic(() => import('./OriginFooter'), { 6 | loading: () => , 7 | }); 8 | 9 | export default function Footer() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Header/constants/menuTapList.ts: -------------------------------------------------------------------------------- 1 | import { MenuTapList, MenuTapType } from '../types'; 2 | 3 | export const menuTapList: MenuTapList = [ 4 | { 5 | type: MenuTapType.DEFAULT, 6 | title: '소개', 7 | href: '/about', 8 | }, 9 | { 10 | type: MenuTapType.DEFAULT, 11 | title: '프로젝트', 12 | href: '/project', 13 | }, 14 | { 15 | type: MenuTapType.DEFAULT, 16 | title: '블로그', 17 | href: '/blog', 18 | }, 19 | { 20 | type: MenuTapType.DEFAULT, 21 | title: '후원', 22 | href: '/sponsor', 23 | }, 24 | { 25 | type: MenuTapType.SPECIAL, 26 | title: '지원하기', 27 | href: '/recruit', 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useIsDesktop, useIsMobile, useIsTablet } from '@src/hooks/useDevice'; 3 | import DesktopHeader from './Desktop'; 4 | import MobileHeader from './Mobile'; 5 | import * as S from './style'; 6 | 7 | export function Header() { 8 | const isDesktop = useIsDesktop('58.75rem'); 9 | const isTablet = useIsTablet('48rem', '58.6875rem'); 10 | const isMobile = useIsMobile(); 11 | 12 | const [isTransparent, setIsTransparent] = useState(false); 13 | 14 | useEffect(() => { 15 | const handleScroll = () => { 16 | const scrollPosition = window.scrollY; 17 | 18 | scrollPosition <= 0 ? setIsTransparent(false) : setIsTransparent(true); 19 | }; 20 | 21 | window.addEventListener('scroll', handleScroll); 22 | 23 | return () => { 24 | window.removeEventListener('scroll', handleScroll); 25 | }; 26 | }); 27 | 28 | return ( 29 | 30 | {isDesktop && } 31 | {isTablet && } 32 | {isMobile && } 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Header/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { css } from '@emotion/react'; 3 | 4 | export const Wrapper = styled.header<{ isTransparent: boolean }>` 5 | width: 100%; 6 | min-height: 80px; 7 | 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | position: fixed; 12 | z-index: 100; 13 | top: 0; 14 | 15 | ${({ isTransparent }) => 16 | isTransparent && 17 | css` 18 | background-color: rgba(0, 0, 0, 0.2); 19 | backdrop-filter: blur(30px); 20 | color: white; 21 | `} 22 | 23 | padding: 0 20px; 24 | 25 | /* 태블릿 + 데스크탑 뷰 */ 26 | @media (max-width: 58.75rem) and (min-width: 48rem) { 27 | height: 48px; 28 | padding: 0; 29 | } 30 | 31 | /* 모바일 뷰 */ 32 | @media (max-width: 47.9375rem) { 33 | height: 48px; 34 | min-height: 48px; 35 | padding: 0; 36 | 37 | justify-content: space-between; 38 | } 39 | `; 40 | -------------------------------------------------------------------------------- /src/components/Header/types.ts: -------------------------------------------------------------------------------- 1 | export type MenuState = 'idle' | 'open' | 'close'; 2 | 3 | export const enum MenuTapType { 4 | DEFAULT = 'DEFAULT', 5 | SPECIAL = 'SPECIAL', 6 | } 7 | 8 | export type SingleMenuTap = { 9 | type: MenuTapType.DEFAULT | MenuTapType.SPECIAL; 10 | title: string; 11 | href: string; 12 | }; 13 | 14 | export type MenuTapList = SingleMenuTap[]; 15 | -------------------------------------------------------------------------------- /src/components/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { SerializedStyles } from '@emotion/react'; 3 | import { PropsWithChildren } from 'react'; 4 | 5 | export function Layout({ 6 | children, 7 | moreStyle, 8 | }: PropsWithChildren<{ moreStyle?: SerializedStyles }>) { 9 | return
{children}
; 10 | } 11 | 12 | const Main = styled.div<{ moreStyle?: SerializedStyles }>` 13 | display: flex; 14 | flex-direction: column; 15 | `; 16 | -------------------------------------------------------------------------------- /src/components/common/Condition/index.tsx: -------------------------------------------------------------------------------- 1 | export const Condition: React.FunctionComponent< 2 | React.PropsWithChildren<{ statement?: boolean }> 3 | > = ({ statement, children }) => { 4 | if (!statement) { 5 | return null; 6 | } 7 | 8 | return <>{children}; 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/common/DummyDiv/index.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'react'; 2 | 3 | type DummyDivProps = { 4 | width?: CSSProperties['width']; 5 | height?: CSSProperties['height']; 6 | backgroundColor?: CSSProperties['backgroundColor']; 7 | }; 8 | 9 | function DummyDiv({ width, height, backgroundColor }: DummyDivProps) { 10 | return
; 11 | } 12 | 13 | export default DummyDiv; 14 | -------------------------------------------------------------------------------- /src/components/common/FlippableCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useBooleanState from '@src/hooks/useBooleanState'; 3 | import { useDeviceType } from '@src/hooks/useDevice'; 4 | import * as S from './style'; 5 | 6 | type FlippableCardProps = { 7 | frontContent: React.ReactNode; 8 | backContent: React.ReactNode; 9 | }; 10 | 11 | const useFlippableCard = () => { 12 | const [isFlipped, setIsFlipped, setIsUnflipped, toggleFlipped] = useBooleanState(false); 13 | const deviceType = useDeviceType(); 14 | 15 | if (deviceType === 'desktop') { 16 | return { isFlipped, onMouseEnter: setIsFlipped, onMouseLeave: setIsUnflipped }; 17 | } 18 | return { isFlipped, onClick: () => toggleFlipped() }; 19 | }; 20 | 21 | export default function FlippableCard({ frontContent, backContent }: FlippableCardProps) { 22 | const { isFlipped, ...eventListeners } = useFlippableCard(); 23 | 24 | const variants = { 25 | front: { rotateY: 0 }, 26 | back: { rotateY: 180 }, 27 | }; 28 | 29 | return ( 30 |
31 | 36 | {frontContent} 37 | {backContent} 38 | 39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/common/FlippableCard/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { motion } from 'framer-motion'; 3 | 4 | export const CardWrapper = styled(motion.div)` 5 | transition: 0.2s; 6 | display: inline-grid; 7 | transform: perspective(800px) rotateY(0deg); 8 | transform-style: preserve-3d; 9 | `; 10 | 11 | export const SideCardWrapper = styled.div` 12 | grid-area: 1 / 1 / 1 / 1; 13 | backface-visibility: hidden; 14 | `; 15 | 16 | export const FrontSideCardWrapper = styled(SideCardWrapper)``; 17 | 18 | export const BackSideCardWrapper = styled(SideCardWrapper)` 19 | transform: rotateY(180deg); 20 | `; 21 | -------------------------------------------------------------------------------- /src/components/common/NumberRoller/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import useInView from '@src/hooks/useInView'; 3 | 4 | type NumberRollerProps = { 5 | goalNumber: number; 6 | rollRange?: number; 7 | }; 8 | 9 | const NumberRoller = ({ goalNumber, rollRange = 50 }: NumberRollerProps) => { 10 | const [number, setNumber] = useState(Math.max(goalNumber - rollRange, 0)); 11 | const { isInView, ref: wrapperRef } = useInView(); 12 | 13 | useEffect(() => { 14 | if (isInView && number < goalNumber) { 15 | setTimeout(() => { 16 | setNumber((n) => n + 1); 17 | }, 100); 18 | } 19 | }, [goalNumber, isInView, number]); 20 | 21 | return {number}; 22 | }; 23 | 24 | export default NumberRoller; 25 | -------------------------------------------------------------------------------- /src/components/common/OvalSpinner/index.tsx: -------------------------------------------------------------------------------- 1 | export default function OvalSpinner() { 2 | return ( 3 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/common/PageLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import { SerializedStyles } from '@emotion/react'; 3 | import { Header, Layout } from '@src/components'; 4 | 5 | type PageLayoutOwnProps = { 6 | showScrollTopButton?: boolean; 7 | moreStyle?: SerializedStyles; 8 | }; 9 | 10 | const DynamicFooter = dynamic(() => import('@src/components/Footer')); 11 | 12 | const DynamicScrollToTopButton = dynamic(() => import('@src/components/common/ScrollToTopButton')); 13 | 14 | export default function PageLayout({ 15 | children, 16 | moreStyle, 17 | showScrollTopButton, 18 | }: React.PropsWithChildren) { 19 | return ( 20 | 21 |
22 | {showScrollTopButton && } 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/common/RoundButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { DetailedHTMLProps, HTMLAttributes, ReactElement } from 'react'; 2 | import * as S from './style'; 3 | 4 | interface ButtonProps 5 | extends DetailedHTMLProps, HTMLButtonElement> { 6 | children: string | ReactElement; 7 | } 8 | 9 | function RoundButton({ children, ...props }: ButtonProps) { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | 17 | export default RoundButton; 18 | -------------------------------------------------------------------------------- /src/components/common/RoundButton/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | 4 | export const Root = styled.button` 5 | display: flex; 6 | align-items: center; 7 | 8 | padding: 12px 28px; 9 | border-radius: 99px; 10 | background: ${colors.gray10}; 11 | 12 | color: ${colors.gray950}; 13 | font-size: 22rem; 14 | font-weight: 600; 15 | line-height: 150%; /* 36px */ 16 | letter-spacing: -0.48px; 17 | cursor: pointer; 18 | 19 | @media (max-width: 26.75rem) { 20 | padding: 8px 22px; 21 | font-size: 18rem; 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /src/components/common/ScrollToTopButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { ReactComponent as UpArrow } from '@src/assets/icons/ic_up_arrow.svg'; 3 | import { debounce } from '@src/lib/utils/debounce'; 4 | import * as S from './style'; 5 | 6 | const SCROLL_MINIMUM_VALUE = 120; 7 | 8 | export default function ScrollToTopButton() { 9 | const [isScrolled, setIsScrolled] = useState(false); 10 | 11 | const checkScroll = debounce(() => { 12 | window.scrollY > SCROLL_MINIMUM_VALUE ? setIsScrolled(true) : setIsScrolled(false); 13 | }); 14 | 15 | const handleUpBtnClick = () => { 16 | window.scrollTo({ top: 0, behavior: 'smooth' }); 17 | }; 18 | 19 | useEffect(() => { 20 | window.addEventListener('scroll', checkScroll); 21 | return () => { 22 | window.removeEventListener('scroll', checkScroll); 23 | }; 24 | }, [checkScroll]); 25 | 26 | return ( 27 | <> 28 | {isScrolled && ( 29 | 30 | UP 31 | 32 | 33 | )} 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/common/ScrollToTopButton/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { FadeIn } from '@src/lib/styles/animation'; 3 | 4 | export const Wrapper = styled.button` 5 | position: fixed; 6 | z-index: 9999; 7 | cursor: pointer; 8 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.25); 9 | background: #242424; 10 | 11 | // 모바일 12 | @media (max-width: 47.8125rem) { 13 | width: 56px; 14 | height: 56px; 15 | right: 20px; 16 | bottom: 105px; 17 | border-radius: 50%; 18 | } 19 | 20 | // 태블릿 21 | @media (min-width: 47.875rem) and (max-width: 119.9375rem) { 22 | width: 70px; 23 | height: 70px; 24 | border-radius: 50%; 25 | right: 40px; 26 | bottom: 120px; 27 | } 28 | 29 | // 데스크탑 30 | @media (min-width: 120rem) { 31 | width: 123px; 32 | height: 80px; 33 | border-radius: 52px; 34 | right: 100px; 35 | top: 50%; 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | padding: 16px 32px; 40 | } 41 | 42 | ${FadeIn} 43 | animation: fadein 0.4s; 44 | `; 45 | 46 | export const Text = styled.span` 47 | font-weight: 600; 48 | font-size: 24rem; 49 | line-height: 48px; 50 | color: #ffffff; 51 | // 모바일 태블릿 52 | @media (min-width: 22.5rem) and (max-width: 119.9375rem) { 53 | display: none; 54 | } 55 | `; 56 | -------------------------------------------------------------------------------- /src/components/common/Timer/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from 'react'; 2 | import useInterval from '@src/hooks/useInterval'; 3 | import { convertMsIntoDate } from '@src/lib/utils/convertMsIntoDate'; 4 | 5 | interface TimerProps { 6 | targetDate: Date; 7 | prefix?: string; 8 | suffix?: string; 9 | endMessage: string; 10 | } 11 | 12 | type TimeObj = ReturnType; 13 | 14 | const Timer: FC = (props) => { 15 | const [timeDiff, setTimeDiff] = useState('prepare'); 16 | 17 | useInterval(() => { 18 | const diff = props.targetDate.getTime() - Date.now(); 19 | if (diff >= 0) { 20 | setTimeDiff(convertMsIntoDate(diff)); 21 | } else { 22 | setTimeDiff('timeEnd'); 23 | } 24 | }, 100); 25 | 26 | if (timeDiff === 'prepare') { 27 | return <> ; 28 | } 29 | if (timeDiff === 'timeEnd') { 30 | return <>{props.endMessage}; 31 | } 32 | 33 | return ( 34 | <> 35 | {props.prefix} 36 | {timeDiff.days}일 {padZero(timeDiff.hours)}:{padZero(timeDiff.minutes)}: 37 | {padZero(timeDiff.seconds)} 38 | {props.suffix} 39 | 40 | ); 41 | }; 42 | 43 | export default Timer; 44 | 45 | const padZero = (num: number) => { 46 | return `${num}`.padStart(2, '0'); 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/googleTagManager/Noscript.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/iframe-has-title */ 2 | export default function GoogleTagManagerNoscript() { 3 | return ( 4 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/googleTagManager/Script.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script'; 2 | 3 | export default function GoogleTagManagerScript() { 4 | return ( 5 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/useBooleanState.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | 3 | export default function useBooleanState(defaultValue = false) { 4 | const [bool, setBool] = useState(defaultValue); 5 | 6 | const setTrue = useCallback(() => { 7 | setBool(true); 8 | }, []); 9 | 10 | const setFalse = useCallback(() => { 11 | setBool(false); 12 | }, []); 13 | 14 | const toggle = useCallback(() => { 15 | setBool((b) => !b); 16 | }, []); 17 | 18 | return [bool, setTrue, setFalse, toggle] as const; 19 | } 20 | -------------------------------------------------------------------------------- /src/hooks/useDrag.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | 3 | function useDrag() { 4 | const dragRef = useRef(null); 5 | const [dragging, setDragging] = useState(false); 6 | const [clickPoint, setClickPoint] = useState(0); 7 | const [scrollLeft, setScrollLeft] = useState(0); 8 | 9 | const handleMouseDown = (e: React.MouseEvent) => { 10 | setDragging(true); 11 | if (dragRef.current) { 12 | setClickPoint(e.pageX); 13 | setScrollLeft(dragRef.current.scrollLeft); 14 | } 15 | }; 16 | 17 | const handleMouseMove = (e: React.MouseEvent) => { 18 | if (!dragging) return; 19 | e.preventDefault(); 20 | if (dragRef.current) { 21 | const draggedAmount = e.pageX - clickPoint; 22 | dragRef.current.scrollLeft = scrollLeft - draggedAmount; 23 | } 24 | }; 25 | 26 | const initDragging = () => { 27 | setDragging(false); 28 | }; 29 | 30 | return { dragRef, handleMouseDown, handleMouseMove, initDragging }; 31 | } 32 | 33 | export default useDrag; 34 | -------------------------------------------------------------------------------- /src/hooks/useHeader.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | function useHeader() { 4 | const router = useRouter(); 5 | 6 | const handleClickLogo = () => router.push('/'); 7 | 8 | const handleIsSelected = (path: string | string[]) => { 9 | if (typeof path === 'string') return router.pathname.startsWith(path); 10 | return path.some((p) => router.pathname.startsWith(p)); 11 | }; 12 | 13 | return { handleClickLogo, handleIsSelected }; 14 | } 15 | 16 | export default useHeader; 17 | -------------------------------------------------------------------------------- /src/hooks/useHorizontalScroll.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | 3 | export function useHorizontalScroll(scrollValue: number, maxMoveValue: number) { 4 | const [horizontalMoveCount, setHorizontalMoveCount] = useState(0); 5 | const isLeftScrollable = horizontalMoveCount > 0; 6 | const isRightScrollable = horizontalMoveCount < maxMoveValue; 7 | const scrollableRef = useRef(null); 8 | const onClickLeftButton = (element: HTMLDivElement | null) => { 9 | if (element === null || !isLeftScrollable) return; 10 | element.scrollBy({ left: -scrollValue, behavior: 'smooth' }); 11 | setHorizontalMoveCount((prevCount) => prevCount - 1); 12 | }; 13 | 14 | const onClickRightButton = (element: HTMLDivElement | null) => { 15 | if (element === null || !isRightScrollable) return; 16 | element.scrollBy({ left: scrollValue, behavior: 'smooth' }); 17 | setHorizontalMoveCount((prevCount) => prevCount + 1); 18 | }; 19 | 20 | return { 21 | scrollableRef, 22 | onClickLeftButton, 23 | onClickRightButton, 24 | isLeftScrollable, 25 | isRightScrollable, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/hooks/useInView.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import useIntersectionObserver from './useIntersectionObserver'; 3 | 4 | interface useInViewProps { 5 | options?: IntersectionObserverInit; 6 | } 7 | 8 | function useInView({ options }: useInViewProps = {}) { 9 | const ref = useIntersectionObserver((entry) => { 10 | setIsInView(entry.isIntersecting); 11 | }, options); 12 | const [isInView, setIsInView] = useState(false); 13 | 14 | return { isInView, ref }; 15 | } 16 | 17 | export default useInView; 18 | -------------------------------------------------------------------------------- /src/hooks/useIntersectionObserver.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from 'react'; 2 | 3 | type IntersectHandler = (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void; 4 | 5 | const useIntersectionObserver = ( 6 | onIntersect: IntersectHandler, 7 | options?: IntersectionObserverInit, 8 | ) => { 9 | const ref = useRef(null); 10 | const callback = useCallback( 11 | (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { 12 | entries.forEach((entry) => { 13 | onIntersect(entry, observer); 14 | }); 15 | }, 16 | [onIntersect], 17 | ); 18 | 19 | useEffect(() => { 20 | if (!ref.current) return; 21 | const observer = new IntersectionObserver(callback, options); 22 | observer.observe(ref.current); 23 | return () => observer.disconnect(); 24 | }, [ref, options, callback]); 25 | 26 | return ref; 27 | }; 28 | 29 | export default useIntersectionObserver; 30 | -------------------------------------------------------------------------------- /src/hooks/useInterval.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | type IntervalId = ReturnType; 4 | 5 | export default function useInterval(callback: () => void, delay: number) { 6 | const intervalId = useRef(null); 7 | 8 | const clear = () => { 9 | if (intervalId.current !== null) { 10 | clearInterval(intervalId.current); 11 | } 12 | }; 13 | 14 | useEffect(() => { 15 | intervalId.current = setInterval(callback, delay); 16 | return () => { 17 | clear(); 18 | }; 19 | }, [callback, delay]); 20 | 21 | return { 22 | clear, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/hooks/useMounted.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | function useMounted() { 4 | const [mounted, setMounted] = useState(false); 5 | 6 | useEffect(() => { 7 | setMounted(true); 8 | }, []); 9 | 10 | return mounted; 11 | } 12 | 13 | export default useMounted; 14 | -------------------------------------------------------------------------------- /src/hooks/useNoScroll.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { MenuState } from '@src/components/Header/types'; 3 | 4 | export default function useNoScroll(isMenuShown: MenuState) { 5 | useEffect(() => { 6 | if (isMenuShown === 'open') { 7 | document.body.style.overflow = 'hidden'; 8 | } else { 9 | return () => { 10 | document.body.style.overflow = 'auto'; 11 | }; 12 | } 13 | 14 | return () => { 15 | document.body.style.overflow = 'auto'; 16 | }; 17 | }, [isMenuShown]); 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/useOutsideClickListener.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const useOutsideClickListener = (targetRefs: React.RefObject[], callback: () => void) => { 4 | useEffect(() => { 5 | const handleClickOutside = (event: MouseEvent) => { 6 | const target = event.target; 7 | if (target instanceof Node) { 8 | const isOutsideClick = targetRefs.every( 9 | (targetRef: React.RefObject) => 10 | targetRef.current === null || !targetRef.current.contains(target), 11 | ); 12 | if (isOutsideClick) { 13 | callback(); 14 | } 15 | } 16 | }; 17 | 18 | document.addEventListener('mousedown', handleClickOutside); 19 | 20 | return () => { 21 | document.removeEventListener('mousedown', handleClickOutside); 22 | }; 23 | }, [targetRefs, callback]); 24 | }; 25 | 26 | export default useOutsideClickListener; 27 | -------------------------------------------------------------------------------- /src/hooks/useScrollPosition.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | 3 | const useScrollPosition = () => { 4 | const lastScrollPositionRef = useRef(0); 5 | const [isScrollingDown, setIsScrollingDown] = useState(true); 6 | const [isScrollTop, setIsScrollTop] = useState(true); 7 | 8 | useEffect(() => { 9 | const scrollHandler = () => { 10 | const currentScrollPosition = window.scrollY; 11 | 12 | setIsScrollTop(currentScrollPosition < 20); 13 | setIsScrollingDown(lastScrollPositionRef.current < currentScrollPosition); 14 | 15 | lastScrollPositionRef.current = currentScrollPosition; 16 | }; 17 | 18 | window.addEventListener('scroll', scrollHandler); 19 | 20 | return () => { 21 | window.removeEventListener('scroll', scrollHandler); 22 | }; 23 | }, []); 24 | 25 | return { 26 | isScrollingDown, 27 | isScrollTop, 28 | }; 29 | }; 30 | 31 | export default useScrollPosition; 32 | -------------------------------------------------------------------------------- /src/hooks/useStackedFetchBase/index.ts: -------------------------------------------------------------------------------- 1 | import { to } from 'await-to-js'; 2 | import { useEffect, useReducer } from 'react'; 3 | import { debounce } from '@src/lib/utils/debounce'; 4 | import { reducer } from './reducer'; 5 | import { Action, State } from './types'; 6 | 7 | function useStackedFetchBase( 8 | willFetch: () => Promise, 9 | isInitialFetching: boolean, 10 | ): State { 11 | const [state, dispatch] = useReducer, Action>>(reducer, { 12 | _TAG: 'IDLE', 13 | data: [], 14 | }); 15 | 16 | useEffect(() => { 17 | const fetchList = debounce(async () => { 18 | dispatch({ 19 | _TAG: 'FETCH', 20 | isInitialFetching, 21 | }); 22 | 23 | const [error, response] = await to(willFetch()); 24 | 25 | if (error) { 26 | return dispatch({ 27 | _TAG: 'FAILED', 28 | error, 29 | }); 30 | } 31 | 32 | if (response) { 33 | dispatch({ _TAG: 'SUCCESS', isInitialFetching, data: response }); 34 | } 35 | }); 36 | 37 | fetchList(); 38 | }, [willFetch, isInitialFetching]); 39 | 40 | return state; 41 | } 42 | 43 | export default useStackedFetchBase; 44 | -------------------------------------------------------------------------------- /src/hooks/useStackedFetchBase/reducer.ts: -------------------------------------------------------------------------------- 1 | import type { Action, State } from './types'; 2 | 3 | export function reducer(prevState: State, action: Action): State { 4 | switch (action._TAG) { 5 | case 'FETCH': 6 | if (prevState._TAG === 'ERROR') throw new Error('Invalid action during error'); 7 | return { 8 | _TAG: 'LOADING', 9 | data: action.isInitialFetching ? [] : prevState.data, 10 | }; 11 | case 'FAILED': 12 | return { 13 | _TAG: 'ERROR', 14 | error: action.error, 15 | }; 16 | case 'SUCCESS': 17 | if (prevState._TAG === 'ERROR') throw new Error('Invalid action during error'); 18 | return { 19 | _TAG: 'OK', 20 | data: action.isInitialFetching ? action.data : [...prevState.data, ...action.data], 21 | }; 22 | default: 23 | throw new Error('Unknown action type'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/hooks/useStackedFetchBase/types.ts: -------------------------------------------------------------------------------- 1 | export type State = 2 | | { 3 | _TAG: 'IDLE'; 4 | data: T[]; 5 | } 6 | | { 7 | _TAG: 'LOADING'; 8 | data: T[]; 9 | } 10 | | { 11 | _TAG: 'ERROR'; 12 | error: Error; 13 | } 14 | | { 15 | _TAG: 'OK'; 16 | data: T[]; 17 | }; 18 | 19 | export type Action = 20 | | { 21 | _TAG: 'FETCH'; 22 | isInitialFetching: boolean; 23 | } 24 | | { 25 | _TAG: 'FAILED'; 26 | error: Error; 27 | } 28 | | { 29 | _TAG: 'SUCCESS'; 30 | isInitialFetching: boolean; 31 | data: T[]; 32 | }; 33 | -------------------------------------------------------------------------------- /src/hooks/useStorage.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | import { getStorageHandler } from '@src/lib/utils/storageHandler'; 3 | 4 | export default function useStorage( 5 | key: string, 6 | storageType: 'localStorage' | 'sessionStorage', 7 | defaultValue: T, 8 | ): [value: T, setValue: (newValue: T) => void] { 9 | const handler = useCallback(() => getStorageHandler(storageType), [storageType]); 10 | const [valueInState, setValueInState] = useState(defaultValue); 11 | 12 | useEffect(() => { 13 | const storageItem = handler().getItem(key); 14 | if (storageItem !== null) { 15 | setValueInState(storageItem); 16 | } else if (defaultValue !== undefined) { 17 | handler().setItem(key, defaultValue); 18 | setValueInState(defaultValue); 19 | } 20 | }, [handler, key, defaultValue]); 21 | 22 | const setValue = (newValue: T) => { 23 | handler().setItem(key, newValue); 24 | setValueInState(newValue); 25 | }; 26 | 27 | return [valueInState, setValue]; 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useTabs.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | interface ExtraTabType { 4 | type: string; 5 | } 6 | 7 | export function useTabs(initialTab: T, allTabs: T[]) { 8 | const [currentTab, setCurrentTab] = useState(initialTab); 9 | 10 | const changeTab = (tabType: string) => { 11 | const shouldChangeTab = allTabs.find(({ type }) => type === tabType); 12 | if (shouldChangeTab === undefined) { 13 | throw new Error(`Cannot Change Tab ${tabType}`); 14 | } 15 | 16 | setCurrentTab(shouldChangeTab); 17 | }; 18 | 19 | return { 20 | currentTab: allTabs.find(({ type }) => type === currentTab.type) || initialTab, 21 | changeTab, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/api/index.ts: -------------------------------------------------------------------------------- 1 | import { API } from '../types/universal'; 2 | import { remoteAboutAPI } from './remote/about'; 3 | // import { remoteAdminAPI } from './remote/admin'; 4 | import { remoteProjectAPI } from './remote/project'; 5 | import { remoteReviewAPI } from './remote/review'; 6 | import { remoteSopticleAPI } from './remote/sopticle'; 7 | 8 | export const api: API = { 9 | projectAPI: remoteProjectAPI, 10 | reviewAPI: remoteReviewAPI, 11 | aboutAPI: remoteAboutAPI, 12 | sopticleAPI: remoteSopticleAPI, 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/api/remote/admin.ts: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from '@src/lib/constants/client'; 2 | import axios from 'axios'; 3 | 4 | const adminClient = axios.create({ baseURL: `${BASE_URL}` }); 5 | 6 | const getHomepage = async () => { 7 | const { data } = await adminClient.get('/homepage'); 8 | return data; 9 | }; 10 | const getAboutpage = async () => { 11 | const { data } = await adminClient.get('/homepage/about'); 12 | return data; 13 | }; 14 | const getRecruitpage = async () => { 15 | const { data } = await adminClient.get('/homepage/recruit'); 16 | return data; 17 | }; 18 | 19 | export const remoteAdminAPI = { 20 | getHomepage, 21 | getAboutpage, 22 | getRecruitpage, 23 | }; 24 | -------------------------------------------------------------------------------- /src/lib/api/remote/project.ts: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from '@src/lib/constants/client'; 2 | import axios from 'axios'; 3 | import qs from 'qs'; 4 | import { 5 | GetProjectDetailResponse, 6 | ProjectAPI, 7 | ProjectCategoryType, 8 | ProjectPlatformType, 9 | ProjectResponse, 10 | } from '../../types/project'; 11 | 12 | const client = axios.create({ baseURL: BASE_URL }); 13 | 14 | const getProjectDetail = async (projectId: number): Promise => { 15 | const { data } = await client.get(`/projects/${projectId}`); 16 | const dataServiceType = data.serviceType; 17 | 18 | const serviceType = Array.isArray(dataServiceType) ? dataServiceType : [dataServiceType]; 19 | 20 | return { project: { ...data, serviceType } }; 21 | }; 22 | 23 | const getProjectList = async ( 24 | category: ProjectCategoryType, 25 | platform: ProjectPlatformType, 26 | pageNo: number, 27 | ): Promise => { 28 | const categoryParameter = category === ProjectCategoryType.ALL ? {} : { filter: category }; 29 | const platformParameter = platform === ProjectPlatformType.ALL ? {} : { platform }; 30 | const parameter = qs.stringify({ ...categoryParameter, ...platformParameter, pageNo }); 31 | const { data } = await client.get(`/projects?${parameter}`); 32 | return data; 33 | }; 34 | 35 | export const remoteProjectAPI: ProjectAPI = { 36 | getProjectDetail, 37 | getProjectList, 38 | }; 39 | -------------------------------------------------------------------------------- /src/lib/constants/about.ts: -------------------------------------------------------------------------------- 1 | import { RecordTitleType } from '@src/lib/types/about'; 2 | 3 | export const RECORD_TITLE: RecordTitleType = { 4 | '활동 멤버': 'member', 5 | 프로젝트: 'project', 6 | 스터디: 'study', 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/constants/client.ts: -------------------------------------------------------------------------------- 1 | export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; 2 | export const DEFAULT_TIMEOUT = 3000; 3 | export const AMPLITUDE_API_KEY = process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY ?? ''; 4 | -------------------------------------------------------------------------------- /src/lib/constants/gtmClass.ts: -------------------------------------------------------------------------------- 1 | type GtmClass = { 2 | [key: string]: string; 3 | projectCard: string; 4 | informationCard프로젝트: string; 5 | informationCardFAQ: string; 6 | informationCardYoutube: string; 7 | }; 8 | 9 | export const GTM_CLASS: GtmClass = { 10 | projectCard: 'project-card', 11 | informationCard프로젝트: 'information_card_project', 12 | informationCardFAQ: 'information_card_faq', 13 | informationCardYoutube: 'information_card_youtube', 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/styles/scrollbar.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const HideScrollbar = styled.div` 4 | /* Chrome, Safari and Opera */ 5 | &::-webkit-scrollbar { 6 | display: none; 7 | } 8 | 9 | /* Firefox */ 10 | scrollbar-width: none; 11 | 12 | /* IE and Edge */ 13 | -ms-overflow-style: none; 14 | `; 15 | -------------------------------------------------------------------------------- /src/lib/styles/textEllipsis.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | 3 | export const textSingularLineEllipsis = css` 4 | overflow: hidden; 5 | text-overflow: ellipsis; 6 | white-space: nowrap; 7 | `; 8 | 9 | export const textpluralLinesEllipsis = (lines: number) => css` 10 | overflow: hidden; 11 | text-overflow: ellipsis; 12 | -webkit-box-orient: vertical; 13 | -webkit-line-clamp: ${lines}; 14 | word-break: break-all; 15 | `; 16 | -------------------------------------------------------------------------------- /src/lib/types/about.ts: -------------------------------------------------------------------------------- 1 | import { MemberType, PartCurriculumType } from './admin'; 2 | 3 | export interface CoreValueType { 4 | title: string; 5 | description: string; 6 | src: string; 7 | } 8 | 9 | export type PositionType = 10 | | '회장' 11 | | '부회장' 12 | | '총무' 13 | | '미디어 팀장' 14 | | '운영 팀장' 15 | | '기획 파트장' 16 | | '디자인 파트장' 17 | | '안드로이드 파트장' 18 | | 'iOS 파트장' 19 | | '웹 파트장' 20 | | '서버 파트장' 21 | | '메이커스 팀장' 22 | | ''; 23 | 24 | export interface AboutInfoType { 25 | generation: number; 26 | title: string; 27 | brandingColor: { 28 | main: string; 29 | low: string; 30 | high: string; 31 | point: string; 32 | }; 33 | bannerImage: string; 34 | coreValue: { 35 | mainDescription: string; 36 | eachValues: CoreValueType[]; 37 | }; 38 | curriculums: Array; 39 | records: { 40 | memberCount: number; 41 | projectCount: number; 42 | studyCount: number; 43 | }; 44 | members: MemberType[]; 45 | } 46 | 47 | export interface GetAboutInfoResponse { 48 | aboutInfo: AboutInfoType; 49 | } 50 | 51 | export interface AboutAPI { 52 | getAboutInfo(): Promise; 53 | } 54 | 55 | export type RecordTitle = '활동 멤버' | '프로젝트' | '스터디'; 56 | export type RecordTitleType = { 57 | [key in RecordTitle]: 'member' | 'project' | 'study'; 58 | }; 59 | -------------------------------------------------------------------------------- /src/lib/types/blog.ts: -------------------------------------------------------------------------------- 1 | export type BlogPostListType = { 2 | limit: number; 3 | totalCount: number; 4 | totalPage: number; 5 | currentPage: number; 6 | data: BlogPostType[]; 7 | hasNextPage: boolean; 8 | hasPrevPage: boolean; 9 | }; 10 | 11 | export type BlogPostType = { 12 | id: number; 13 | part: string; 14 | thumbnailUrl: string; 15 | title: string; 16 | description: string; 17 | url: string; 18 | uploadedAt: string; 19 | 20 | /* article */ 21 | likeCount?: number; 22 | liked?: boolean; 23 | isLikedByUser: boolean; 24 | 25 | /* review */ 26 | generation?: number; 27 | subject?: string[]; 28 | author?: string; 29 | authorProfileImageUrl?: string | null; 30 | }; 31 | 32 | export enum PartCategoryType { 33 | ALL = 'ALL', 34 | PLAN = 'PLAN', 35 | DESIGN = 'DESIGN', 36 | ANDROID = 'ANDROID', 37 | IOS = 'iOS', 38 | WEB = 'WEB', 39 | SERVER = 'SERVER', 40 | } 41 | 42 | export type BlogResponse = { 43 | hasNextPage: boolean; 44 | hasPrevPage: boolean; 45 | response: BlogPostType[]; 46 | currentPage: number; 47 | totalPage: number; 48 | isLikedByUser: boolean; 49 | totalCount: number; 50 | }; 51 | 52 | export enum SortType { 53 | LATEST = 'latest', 54 | LIKES = 'likes', 55 | } 56 | 57 | export enum BlogCategoryType { 58 | DOCUMENT_INTERVIEW = '서류/면접', 59 | ALL_ACTIVITIES = '전체 활동', 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/types/dto.ts: -------------------------------------------------------------------------------- 1 | export type CoreValueResponseDto = { 2 | id: number; 3 | title: string; 4 | subTitle: string; 5 | imageUrl: string; 6 | }; 7 | 8 | export type MemberResponseDto = { 9 | id: number; 10 | name: string; 11 | profileImage: string; 12 | introduction: string; 13 | part: string; 14 | generation: number; 15 | }; 16 | 17 | export type StudyResponseDto = { 18 | id: number; 19 | generation: number; 20 | parts: string[]; 21 | title: string; 22 | imageUrl: string; 23 | startDate: Date; 24 | endDate: Date; 25 | memberCount: number; 26 | }; 27 | -------------------------------------------------------------------------------- /src/lib/types/faq.ts: -------------------------------------------------------------------------------- 1 | export interface ContactInfoType { 2 | id: string; 3 | text: string; 4 | content: string; 5 | } 6 | 7 | export interface FAQType { 8 | question: string; 9 | answer: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/types/review.ts: -------------------------------------------------------------------------------- 1 | import { BlogCategoryType, PartCategoryType } from '@src/lib/types/blog'; 2 | import { ActivitySelectType } from '@src/lib/types/main'; 3 | import { BlogPostType, BlogResponse } from './blog'; 4 | 5 | export type GetSampleReviewsResponse = { 6 | reviews: BlogPostType[]; 7 | }; 8 | 9 | export interface ReviewAPI { 10 | getResponse( 11 | category: BlogCategoryType, 12 | activity: ActivitySelectType | null, 13 | generation: number | null, 14 | part: PartCategoryType | null, 15 | pageNo?: number, 16 | limit?: number, 17 | ): Promise; 18 | getSampleReviews(): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/types/sopticle.ts: -------------------------------------------------------------------------------- 1 | import { BlogResponse, SortType } from './blog'; 2 | 3 | export interface SopticleAPI { 4 | getResponse(sort: SortType, page: number): Promise; 5 | postSopticleLike(sopticleId: number): Promise; 6 | postSopticleUnlike(sopticleId: number): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/types/universal.ts: -------------------------------------------------------------------------------- 1 | import { AboutAPI } from './about'; 2 | import { ProjectAPI } from './project'; 3 | import { ReviewAPI } from './review'; 4 | import { SopticleAPI } from './sopticle'; 5 | 6 | export interface API { 7 | projectAPI: ProjectAPI; 8 | reviewAPI: ReviewAPI; 9 | aboutAPI: AboutAPI; 10 | sopticleAPI: SopticleAPI; 11 | } 12 | 13 | export enum Part { 14 | PLAN = 'PLAN', 15 | DESIGN = 'DESIGN', 16 | ANDROID = 'ANDROID', 17 | IOS = 'iOS', 18 | WEB = 'WEB', 19 | SERVER = 'SERVER', 20 | } 21 | 22 | export enum PartExtraType { 23 | ALL = 'ALL', 24 | } 25 | 26 | export type ExtraPart = PartExtraType | Part; 27 | 28 | type TabTypeOption = { 29 | value: T; 30 | label: string; 31 | }; 32 | 33 | export type TabType = TabTypeOption; 34 | export type ExtraTabType = TabTypeOption; 35 | 36 | export type LabelKeyType = string | number | symbol; 37 | 38 | export enum CarouselArrowType { 39 | External = 'external', 40 | None = 'none', 41 | // Internal = 'internal', 42 | // Overlay = 'overlay', 43 | } 44 | 45 | export enum CarouselOverflowType { 46 | Blur = 'blur', 47 | Visible = 'visible', 48 | } 49 | 50 | export enum PageType { 51 | BLOG = 'BLOG', 52 | PROJECT = 'PROJECT', 53 | } 54 | 55 | export type TextWeightType = { 56 | content: string; 57 | weight: 'normal' | 'bold'; 58 | }; 59 | -------------------------------------------------------------------------------- /src/lib/utils/array.ts: -------------------------------------------------------------------------------- 1 | export function sortBy(array: T[], key: keyof T): T[] { 2 | return [...array].sort((a, b) => { 3 | const aValue = a[key]; 4 | const bValue = b[key]; 5 | if (typeof aValue === 'number' && typeof bValue === 'number') { 6 | return aValue - bValue; 7 | } 8 | if (typeof aValue === 'string' && typeof bValue === 'string') { 9 | return (aValue as string).localeCompare(bValue as string); 10 | } 11 | return 0; 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/utils/convertMsIntoDate.ts: -------------------------------------------------------------------------------- 1 | export const convertMsIntoDate = (milliseconds: number) => { 2 | const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24)); 3 | const hours = Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); 4 | const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); 5 | const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); 6 | return { days, hours, minutes, seconds }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/utils/date.ts: -------------------------------------------------------------------------------- 1 | export const checkIsTimeInRange = (startDate: string, endDate: string) => { 2 | return Date.now() >= new Date(startDate).getTime() && new Date(endDate).getTime() >= Date.now(); 3 | }; 4 | -------------------------------------------------------------------------------- /src/lib/utils/dateFormat.ts: -------------------------------------------------------------------------------- 1 | export const dateFormat = (date: string) => { 2 | if (!date) return null; 3 | const splitedDate = date.split('-'); 4 | 5 | return splitedDate[0] + '.' + splitedDate[1]; 6 | }; 7 | 8 | export const formatDate = ( 9 | date: Date, 10 | format: 'yyyymmdd' | 'mmdd', 11 | separator: '/' | '.' = '/', 12 | ): string => { 13 | switch (format) { 14 | case 'yyyymmdd': 15 | return date 16 | .toLocaleDateString('ja-JP', { 17 | year: 'numeric', 18 | month: '2-digit', 19 | day: '2-digit', 20 | }) 21 | .replaceAll('/', separator); 22 | case 'mmdd': 23 | return date 24 | .toLocaleDateString('ja-JP', { month: '2-digit', day: '2-digit' }) 25 | .replaceAll('/', separator); 26 | default: 27 | return date.toLocaleDateString().replaceAll('/', separator); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/lib/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | export function debounce(func: () => void, delay = 100) { 2 | let timerId: NodeJS.Timeout; 3 | 4 | return function () { 5 | if (timerId) { 6 | clearTimeout(timerId); 7 | } 8 | 9 | timerId = setTimeout(() => { 10 | func(); 11 | }, delay); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/utils/getLinkNameAndSrcWithType.ts: -------------------------------------------------------------------------------- 1 | import appstore from '@src/assets/icons/appstore_icon.svg'; 2 | import github from '@src/assets/icons/github_icon.svg'; 3 | import googleplay from '@src/assets/icons/googleplay_icon.svg'; 4 | import media from '@src/assets/icons/ic_media.svg'; 5 | import website from '@src/assets/icons/ic_website.svg'; 6 | import instagram from '@src/assets/icons/instagram.svg'; 7 | import { LinkDetailType } from '../../views/ProjectDetailPage/types'; 8 | 9 | const LinkMap: Record = { 10 | website: { name: '웹사이트', src: website }, 11 | googlePlay: { name: '플레이스토어', src: googleplay }, 12 | appStore: { name: '앱스토어', src: appstore }, 13 | github: { name: 'Github', src: github }, 14 | instagram: { name: '인스타그램', src: instagram }, 15 | media: { name: '발표영상', src: media }, 16 | }; 17 | 18 | export const getLinkNameAndSrcWithType = (title: LinkDetailType) => { 19 | return LinkMap[title]; 20 | }; 21 | -------------------------------------------------------------------------------- /src/lib/utils/pageViewTrackingEnrichment.ts: -------------------------------------------------------------------------------- 1 | import { Types } from '@amplitude/analytics-browser'; 2 | 3 | export const pageViewTrackingEnrichment = (): Types.EnrichmentPlugin => { 4 | return { 5 | name: 'page-view-tracking-enrichment', 6 | type: 'enrichment', 7 | setup: async () => undefined, 8 | execute: async (event) => { 9 | if (event.event_type !== '[Amplitude] Page Viewed') { 10 | return event; 11 | } 12 | const pageName = window.location.pathname.slice(1) || 'main'; 13 | event.event_type = `pageview_${pageName}`; 14 | return event; 15 | }, 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/utils/parsePartToKorean.ts: -------------------------------------------------------------------------------- 1 | import { Part } from '@src/lib/types/universal'; 2 | 3 | // TODO :: ReviewType의 part 타입을 string -> TAB 변경 4 | export function parsePartToKorean(part: string) { 5 | switch (part) { 6 | case Part.ANDROID: 7 | return '안드로이드'; 8 | case Part.WEB: 9 | return '웹'; 10 | case Part.DESIGN: 11 | return '디자인'; 12 | case Part.PLAN: 13 | return '기획'; 14 | case Part.SERVER: 15 | return '서버'; 16 | default: 17 | return part; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/utils/parseStringToPart.ts: -------------------------------------------------------------------------------- 1 | import { Part } from '@src/lib/types/universal'; 2 | 3 | export const parseStringToPart = (_part: string): Part => { 4 | switch (_part.toUpperCase()) { 5 | case '기획': 6 | case 'PM': 7 | case 'PLAN': 8 | return Part.PLAN; 9 | case '디자인': 10 | case 'DESIGN': 11 | return Part.DESIGN; 12 | case '웹': 13 | case 'WEB': 14 | return Part.WEB; 15 | case '서버': 16 | case 'SERVER': 17 | return Part.SERVER; 18 | case '안드로이드': 19 | case 'ANDROID': 20 | return Part.ANDROID; 21 | case 'IOS': 22 | return Part.IOS; 23 | default: 24 | throw Error('올바른 파트명이 아님'); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/lib/utils/platform.ts: -------------------------------------------------------------------------------- 1 | export const getWindowHeight = () => { 2 | if (typeof window !== 'undefined') { 3 | if (window.innerHeight) { 4 | return window.innerHeight; 5 | } else if (document.documentElement && document.documentElement.clientHeight) { 6 | return document.documentElement.clientHeight; 7 | } else if (document.body && document.body.clientHeight) { 8 | return document.body.clientHeight; 9 | } 10 | } 11 | return 0; 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/utils/sanitize.ts: -------------------------------------------------------------------------------- 1 | export const sanitizeImageUrl = (url: string): string => { 2 | if (/^(https?:\/\/)/.test(url)) return url; 3 | if (/^(\/\/)/.test(url)) return 'https:' + url; 4 | else return 'https://' + url; 5 | }; 6 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { Page404 } from '@src/views/ErrorPage'; 2 | 3 | export default Page404; 4 | -------------------------------------------------------------------------------- /src/pages/500.tsx: -------------------------------------------------------------------------------- 1 | import { Page500 } from '@src/views/ErrorPage'; 2 | 3 | export default Page500; 4 | -------------------------------------------------------------------------------- /src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '@src/views/AboutPage'; 2 | -------------------------------------------------------------------------------- /src/pages/blog.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '@src/views/BlogPage'; 2 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '@src/views/MainPage'; 2 | -------------------------------------------------------------------------------- /src/pages/project/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '@src/views/ProjectPage'; 2 | -------------------------------------------------------------------------------- /src/pages/recruit.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '@src/views/RecruitPage'; 2 | -------------------------------------------------------------------------------- /src/pages/rules.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '@src/views/RulesPage'; 2 | -------------------------------------------------------------------------------- /src/pages/sponsor.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '@src/views/SponsorPage'; 2 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/@common/SectionDescription/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { ReactNode } from 'react'; 3 | 4 | const SectionDescription = ({ children }: { children: ReactNode }) => { 5 | return
{children}
; 6 | }; 7 | 8 | const Div = styled.div` 9 | font-size: 28rem; 10 | line-height: 150%; 11 | letter-spacing: 0%; 12 | color: #ffffff; 13 | white-space: pre-line; 14 | word-break: keep-all; 15 | /* 태블릿 뷰 */ 16 | @media (max-width: 74.9375rem) and (min-width: 47.875rem) { 17 | font-size: 18rem; 18 | line-height: 26px; 19 | text-align: center; 20 | } 21 | /* 모바일 뷰 */ 22 | @media (max-width: 47.86875rem) { 23 | font-size: 16rem; 24 | line-height: 150%; 25 | text-align: center; 26 | } 27 | `; 28 | 29 | export default SectionDescription; 30 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/@common/SectionTitle/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { ReactNode } from 'react'; 3 | 4 | const SectionTitle = ({ children }: { children: ReactNode }) => { 5 | return

{children}

; 6 | }; 7 | 8 | const H1 = styled.h1` 9 | font-size: 45rem; 10 | line-height: 60px; 11 | letter-spacing: -1%; 12 | font-weight: 700; 13 | /* 태블릿 뷰 */ 14 | @media (max-width: 74.9375rem) and (min-width: 47.875rem) { 15 | font-size: 28rem; 16 | line-height: 100%; 17 | text-align: center; 18 | } 19 | /* 모바일 뷰 */ 20 | @media (max-width: 47.86875rem) { 21 | font-size: 18rem; 22 | line-height: 150%; 23 | text-align: center; 24 | } 25 | `; 26 | 27 | export default SectionTitle; 28 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/@common/SectionTop/index.tsx: -------------------------------------------------------------------------------- 1 | import { useIsMobile } from '@src/hooks/useDevice'; 2 | import * as S from './style'; 3 | 4 | interface SectionTopProps { 5 | engTitle: string; 6 | korTitle: string; 7 | description?: string; 8 | } 9 | 10 | export default function SectionTop({ engTitle, korTitle, description }: SectionTopProps) { 11 | const isMobile = useIsMobile(); 12 | return ( 13 | 14 | 15 | {engTitle} 16 | {korTitle} 17 | 18 | {description && {isMobile ? description.replace(',', ',\n') : description}} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Banner/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import * as S from './style'; 3 | 4 | interface BannerProps { 5 | imageSrc: string; 6 | } 7 | 8 | const Banner = (props: BannerProps) => { 9 | const { imageSrc } = props; 10 | 11 | return ( 12 | 13 | SOPT banner 23 | 24 | ); 25 | }; 26 | 27 | export default Banner; 28 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Banner/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Banner = styled.div` 4 | width: 100vw; 5 | max-height: 630px; 6 | height: calc(376px + 13vw); 7 | position: relative; 8 | overflow: hidden; 9 | 10 | /* 태블릿 뷰 */ 11 | @media (max-width: 48rem) { 12 | max-height: 376px; 13 | height: calc(150px + 29vw); 14 | } 15 | 16 | /* 모바일 뷰 */ 17 | @media (max-width: 26.75rem) { 18 | height: 230px; 19 | } 20 | `; 21 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/CoreValue/Item/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from 'react'; 2 | import useInView from '@src/hooks/useInView'; 3 | import { CoreValueType } from '@src/lib/types/about'; 4 | import { BrandingColorContext } from '@src/views/AboutPage'; 5 | import * as S from './style'; 6 | 7 | type CoreValueProps = { 8 | coreValue: CoreValueType; 9 | order: number; 10 | }; 11 | 12 | const CoreValueItem = ({ coreValue, order }: CoreValueProps) => { 13 | const [isHovered, setIsHovered] = useState(false); 14 | const { isInView, ref: wrapperRef } = useInView(); 15 | const { point } = useContext(BrandingColorContext); 16 | return ( 17 | setIsHovered(true)} 20 | onMouseLeave={() => setIsHovered(false)} 21 | order={order} 22 | isInView={isInView} 23 | ref={wrapperRef} 24 | > 25 | 26 | 27 | 28 | {order + 1} 29 | {coreValue.title} 30 | 31 | {coreValue.description} 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default CoreValueItem; 38 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/CoreValue/List/index.tsx: -------------------------------------------------------------------------------- 1 | import { CoreValueType } from '@src/lib/types/about'; 2 | import CoreValueItem from '../Item'; 3 | import * as S from './style'; 4 | 5 | type CoreValueListProps = { 6 | coreValues: CoreValueType[]; 7 | }; 8 | 9 | const CoreValueList = ({ coreValues }: CoreValueListProps) => { 10 | return ( 11 | 12 | {coreValues.map((coreValue, idx) => ( 13 | 14 | ))} 15 | 16 | ); 17 | }; 18 | 19 | export default CoreValueList; 20 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/CoreValue/List/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const CoreValueList = styled.div` 4 | display: flex; 5 | justify-content: center; 6 | gap: min(30px, calc(15px + 0.78vw)); 7 | 8 | @media (max-width: 48rem) { 9 | flex-direction: column; 10 | align-items: center; 11 | gap: 30px; 12 | } 13 | 14 | @media (max-width: 26.75rem) { 15 | gap: 21.22px; 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/CoreValue/Section/index.tsx: -------------------------------------------------------------------------------- 1 | import { CoreValueType } from '@src/lib/types/about'; 2 | import SectionTop from '@src/views/AboutPage/components/@common/SectionTop'; 3 | import CoreValueList from '../List'; 4 | import * as S from './style'; 5 | 6 | type CoreValueSectionProps = { 7 | mainDescription: string; 8 | coreValues: CoreValueType[]; 9 | }; 10 | 11 | const CoreValueSection = ({ mainDescription, coreValues }: CoreValueSectionProps) => { 12 | return ( 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default CoreValueSection; 21 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/CoreValue/Section/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const CoreValueSection = styled.section` 4 | display: flex; 5 | flex-direction: column; 6 | gap: 48px; 7 | 8 | margin-top: 83px; 9 | 10 | /* 태블릿 뷰 */ 11 | @media (max-width: 48rem) { 12 | gap: 44px; 13 | margin-top: 72.19px; 14 | } 15 | 16 | /* 모바일 뷰 */ 17 | @media (max-width: 26.75rem) { 18 | gap: 27px; 19 | margin-top: 69.48px; 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Curriculum/Section/index.tsx: -------------------------------------------------------------------------------- 1 | import { PartCurriculumType } from '@src/lib/types/admin'; 2 | import SectionTop from '@src/views/AboutPage/components/@common/SectionTop'; 3 | import CurriculumContent from '../Content'; 4 | import * as S from './style'; 5 | 6 | type CurriculumSectionProps = { 7 | curriculums: PartCurriculumType[]; 8 | }; 9 | 10 | const CurriculumSection = ({ curriculums }: CurriculumSectionProps) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default CurriculumSection; 20 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Curriculum/Section/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const CurriculumSection = styled.section` 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | gap: 48px; 8 | margin-top: 275px; 9 | 10 | /* 태블릿 뷰 */ 11 | @media (max-width: 48rem) { 12 | gap: 19px; 13 | margin-top: 190px; 14 | } 15 | 16 | /* 모바일 뷰 */ 17 | @media (max-width: 26.75rem) { 18 | gap: 13.44px; 19 | margin-top: 120px; 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Member/Section/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const MarginTop = styled.div` 4 | height: 275px; 5 | @media (max-width: 90rem) and (min-width: 47.875rem) { 6 | height: 190px; 7 | } 8 | /* 모바일 뷰 */ 9 | @media (max-width: 47.86875rem) { 10 | height: 120px; 11 | } 12 | `; 13 | 14 | export const CardContainer = styled.div` 15 | display: grid; 16 | 17 | grid-template-columns: repeat(3, 1fr); 18 | gap: 34px; 19 | width: 1200px; 20 | 21 | @media (max-width: 80rem) and (min-width: 73.125rem) { 22 | width: calc(100% - 40px); 23 | } 24 | 25 | @media (max-width: 73.125rem) and (min-width: 48rem) { 26 | grid-template-columns: repeat(2, 1fr); 27 | width: 752px; 28 | } 29 | 30 | @media (max-width: 48rem) and (min-width: 36.5rem) { 31 | grid-template-columns: repeat(2, 1fr); 32 | gap: 24px; 33 | width: 576px; 34 | } 35 | /* 모바일 뷰 */ 36 | @media (max-width: 36.5rem) { 37 | grid-template-columns: repeat(2, 1fr); 38 | gap: 15px; 39 | width: max(350px, 100% - 40px); 40 | } 41 | `; 42 | 43 | export const OvalSpinnerWrapper = styled.div` 44 | width: 100%; 45 | height: 100vh; 46 | 47 | padding-top: 200px; 48 | 49 | display: flex; 50 | flex-direction: column; 51 | align-items: center; 52 | `; 53 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Record/List/index.tsx: -------------------------------------------------------------------------------- 1 | import RecordItem from '../Item'; 2 | import * as St from './style'; 3 | 4 | const RecordList = ({ 5 | activitiesRecords: { activitiesMemberCount, projectCounts, studyCounts }, 6 | }: { 7 | activitiesRecords: { 8 | activitiesMemberCount: number; 9 | projectCounts: number; 10 | studyCounts: number; 11 | }; 12 | }) => { 13 | return ( 14 | 15 | 21 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default RecordList; 34 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Record/List/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Wrapper = styled.div` 4 | display: flex; 5 | gap: 30px; 6 | justify-content: center; 7 | align-items: center; 8 | padding: 0 20px; 9 | 10 | @media (max-width: 62.25rem) and (min-width: 47.875rem) { 11 | gap: 24px; 12 | } 13 | 14 | @media (max-width: 47.875rem) and (min-width: 26.75rem) { 15 | gap: 20px; 16 | } 17 | 18 | @media (max-width: 32.5rem) { 19 | flex-direction: column; 20 | } 21 | 22 | /* 모바일 뷰 */ 23 | @media (max-width: 26.75rem) { 24 | flex-direction: column; 25 | gap: 16px; 26 | } 27 | 28 | & * { 29 | font-size: 100%; 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Record/Section/index.tsx: -------------------------------------------------------------------------------- 1 | import { GetAboutpageResponse } from '@src/lib/types/admin'; 2 | import SectionTop from '../../@common/SectionTop'; 3 | import RecordList from '../List'; 4 | import * as St from './style'; 5 | 6 | type RecordSectionProps = Pick; 7 | 8 | const RecordSection = ({ generation, activitiesRecords }: RecordSectionProps) => { 9 | return ( 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default RecordSection; 18 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/Record/Section/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Wrapper = styled.section` 4 | margin-top: 275px; 5 | margin-bottom: 340px; 6 | gap: 48px; 7 | display: flex; 8 | flex-direction: column; 9 | 10 | @media (max-width: 47.875rem) and (min-width: 26.75rem) { 11 | margin-top: 190px; 12 | margin-bottom: 218px; 13 | gap: 28px; 14 | } 15 | 16 | /* 모바일 뷰 */ 17 | @media (max-width: 26.75rem) { 18 | margin-top: 120px; 19 | margin-bottom: 136px; 20 | gap: 26px; 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /src/views/AboutPage/components/index.tsx: -------------------------------------------------------------------------------- 1 | import Banner from './Banner'; 2 | import CoreValueSection from './CoreValue/Section'; 3 | import CurriculumSection from './Curriculum/Section'; 4 | import MemberSection from './Member/Section'; 5 | import RecordSection from './Record/Section'; 6 | 7 | export { Banner, CurriculumSection, CoreValueSection, MemberSection, RecordSection }; 8 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/Banner/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const BannerWrapper = styled.div` 4 | position: relative; 5 | width: 100%; 6 | height: 288px; 7 | 8 | @media (max-width: 767px) { 9 | height: 200px; 10 | } 11 | 12 | @media (max-width: 375px) { 13 | height: 200px; 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/BlogPost/DefaultProfileImage/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import icProfileDefault from '@src/assets/icons/ic_profile_default.svg'; 3 | import * as S from './style'; 4 | 5 | export default function DefaultProfileImage() { 6 | return ( 7 | 8 | 작성자 프로필 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/BlogPost/DefaultProfileImage/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | 4 | export const DefaultProfileImage = styled.div` 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | 9 | width: 18px; 10 | height: 18px; 11 | border-radius: 18px; 12 | background-color: ${colors.gray700}; 13 | `; 14 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/BlogPost/Header/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | import Image from 'next/image'; 4 | 5 | export const Header = styled.div` 6 | display: flex; 7 | height: 23px; 8 | margin-bottom: 4px; 9 | 10 | color: ${colors.gray200}; 11 | font-size: 14rem; 12 | font-weight: 400; 13 | line-height: 160%; 14 | letter-spacing: -0.21px; 15 | 16 | & > * { 17 | font-size: 100%; 18 | } 19 | 20 | /* 모바일 뷰 */ 21 | @media (max-width: 47.9375rem) { 22 | height: 16px; 23 | margin-bottom: 0; 24 | 25 | font-size: 12rem; 26 | font-weight: 500; 27 | line-height: 135%; /* 16.2px */ 28 | letter-spacing: -0.18px; 29 | } 30 | `; 31 | 32 | export const Profile = styled.div` 33 | display: flex; 34 | align-items: center; 35 | gap: 6px; 36 | & > * { 37 | font-size: 100%; 38 | } 39 | `; 40 | 41 | export const ProfileImage = styled(Image)` 42 | border-radius: 18px; 43 | 44 | /* 모바일 뷰 */ 45 | @media (max-width: 47.9375rem) { 46 | width: 15px; 47 | height: 15px; 48 | } 49 | `; 50 | 51 | export const Divider = styled.div` 52 | padding: 0 2px 0 2px; 53 | `; 54 | 55 | export const Part = styled.div` 56 | display: flex; 57 | align-items: center; 58 | `; 59 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/BlogPost/Like/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import { useState } from 'react'; 3 | import icHeartFilled from '@src/assets/icons/ic_heart_filled.svg'; 4 | import icHeartUnfilled from '@src/assets/icons/ic_heart_unfilled.svg'; 5 | import { api } from '@src/lib/api'; 6 | import { BlogPostType } from '@src/lib/types/blog'; 7 | import * as S from './style'; 8 | 9 | interface LikeProps { 10 | blogPost: BlogPostType; 11 | } 12 | 13 | export default function Like({ blogPost }: LikeProps) { 14 | const [likesCount, setLikesCount] = useState(blogPost.likeCount || 0); 15 | const [isLiked, setIsLiked] = useState(blogPost.isLikedByUser); 16 | 17 | const clickLike = async (e: React.MouseEvent) => { 18 | e.stopPropagation(); 19 | e.preventDefault(); 20 | 21 | if (isLiked) { 22 | await api.sopticleAPI.postSopticleUnlike(blogPost.id); 23 | setIsLiked(false); 24 | } else { 25 | await api.sopticleAPI.postSopticleLike(blogPost.id); 26 | setIsLiked(true); 27 | } 28 | 29 | setLikesCount((prevLikeCount) => (isLiked ? prevLikeCount - 1 : prevLikeCount + 1)); 30 | }; 31 | 32 | return ( 33 | 34 | 좋아요 35 | {likesCount} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/BlogPost/Like/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | 4 | export const Like = styled.div` 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | gap: 3px; 9 | 10 | position: absolute; 11 | top: 10px; 12 | right: 10px; 13 | 14 | height: 28px; 15 | padding: 2px 8px; 16 | border-radius: 6px; 17 | background: ${colors.grayAlpha100}; 18 | 19 | color: ${colors.gray100}; 20 | font-size: 14rem; 21 | font-weight: 400; 22 | line-height: 160%; /* 22.4px */ 23 | letter-spacing: -0.21px; 24 | 25 | transition: opacity 0.1s ease-out; 26 | 27 | z-index: 99; 28 | &:hover { 29 | opacity: 0.8; 30 | } 31 | 32 | /* 모바일 뷰 */ 33 | @media (max-width: 47.9375rem) { 34 | display: none; 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/BlogPostSkeletonUI/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from './style'; 2 | 3 | export default function BlogPostSkeletonUI() { 4 | const BlogPostSkeletonUIList = [0, 1, 2]; 5 | 6 | return ( 7 | 8 | 9 | {BlogPostSkeletonUIList.map((value) => ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ))} 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/BlogTab/types.ts: -------------------------------------------------------------------------------- 1 | import type { PartCategoryType } from '@src/lib/types/blog'; 2 | import { ActivitySelectType } from '@src/lib/types/main'; 3 | 4 | export enum BlogTabType { 5 | REVIEW = 'review', 6 | ARTICLE = 'article', 7 | } 8 | 9 | export type BlogTabMap = Record; 10 | 11 | export type SelectedType = { 12 | selectedTab: BlogTabType; 13 | selectedMajorCategory: number; 14 | selectedSubCategory: PartCategoryType; 15 | selectedActivity: ActivitySelectType; 16 | tag: 'recruit' | 'activity'; 17 | }; 18 | -------------------------------------------------------------------------------- /src/views/BlogPage/components/OfficialVideo/index.tsx: -------------------------------------------------------------------------------- 1 | import { track } from '@amplitude/analytics-browser'; 2 | import * as S from './style'; 3 | import YouTube from 'react-youtube'; 4 | 5 | const VIDEO_URL = 'https://www.youtube.com/watch?v=f4-QwhPIlLI'; 6 | const VIDEO_ID = 'f4-QwhPIlLI'; 7 | 8 | const videoOpts = { 9 | width: '100%', 10 | height: '224px', 11 | playerVars: { 12 | autoplay: 1, 13 | }, 14 | }; 15 | 16 | const VIDEO_INFO = { 17 | title: '35기 AND SOPT 데모데이: \n\'Connecting Dots\'', 18 | description: 19 | '작년 9월 작은 점으로 모인 우리가 8번의 세미나와 5주간의 앱잼을 지나 비로소 연결되었던 데모데이의 현장 사진을 공개합니다. \n작은 열정들이 모여 엄청난 결과물이 보여졌듯 앞으로 모든 팀의 여정을 응원합니다 ❕', 20 | }; 21 | 22 | const OfficialVideo = () => { 23 | return ( 24 | { 26 | track('click_sopt_official_video'); 27 | window.open(VIDEO_URL, '_target'); 28 | }} 29 | > 30 | 🎥 공식 영상을 통해 SOPT를 만나보세요 31 | 32 | 33 | 34 | 35 | 36 | {VIDEO_INFO.title} 37 | {VIDEO_INFO.description} 38 | 39 | 40 | 41 | ); 42 | }; 43 | 44 | export default OfficialVideo; 45 | -------------------------------------------------------------------------------- /src/views/BlogPage/hooks/useInfiniteScroll.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FetchNextPageOptions, 3 | InfiniteData, 4 | InfiniteQueryObserverResult, 5 | } from '@tanstack/react-query'; 6 | import { useEffect, useState } from 'react'; 7 | import useIntersectionObserver from '@src/hooks/useIntersectionObserver'; 8 | 9 | export default function useInfiniteScroll(fetchNextPage: { 10 | (options?: FetchNextPageOptions): Promise< 11 | InfiniteQueryObserverResult, Error> 12 | >; 13 | (): void; 14 | }) { 15 | const [hasObserved, setHasObserved] = useState(false); 16 | 17 | const ref = useIntersectionObserver( 18 | async (entry) => { 19 | if (!hasObserved && entry.isIntersecting) { 20 | fetchNextPage(); 21 | setHasObserved(true); 22 | } 23 | 24 | setHasObserved(false); 25 | }, 26 | { rootMargin: '80px' }, 27 | ); 28 | 29 | useEffect(() => { 30 | setHasObserved(false); 31 | }, []); 32 | 33 | return { ref }; 34 | } 35 | -------------------------------------------------------------------------------- /src/views/ErrorPage/assets/ic_404_back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/views/ErrorPage/assets/ic_404_front.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/views/ErrorPage/assets/ic_404_ghost.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/ErrorPage/assets/ic_404_ghost_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/ErrorPage/assets/ic_500_cone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/ErrorPage/assets/ic_500_cone_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/ErrorPage/assets/index.ts: -------------------------------------------------------------------------------- 1 | import { ReactComponent as Ic404Front } from './ic_404_front.svg'; 2 | import { ReactComponent as Ic404Back } from './ic_404_back.svg'; 3 | import { ReactComponent as Ic404Ghost } from './ic_404_ghost.svg'; 4 | import { ReactComponent as Ic404GhostDark } from './ic_404_ghost_dark.svg'; 5 | import { ReactComponent as Ic500Front } from './ic_500_front.svg'; 6 | import { ReactComponent as Ic500Back } from './ic_500_back.svg'; 7 | import { ReactComponent as Ic500Cone } from './ic_500_cone.svg'; 8 | import { ReactComponent as Ic500ConeDark } from './ic_500_cone_dark.svg'; 9 | 10 | export { Ic404Front, Ic404Back, Ic404Ghost, Ic404GhostDark, Ic500Cone, Ic500Back, Ic500Front, Ic500ConeDark }; 11 | -------------------------------------------------------------------------------- /src/views/ErrorPage/components/ErrorCode/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { motion } from 'framer-motion'; 3 | 4 | export const ErrorCode = styled(motion.div)` 5 | display: flex; 6 | align-items: center; 7 | gap: 6px; 8 | 9 | position: relative; 10 | `; 11 | export const ErrorIcon = styled(motion.div)` 12 | display: flex; 13 | justify-content: center; 14 | position: absolute; 15 | width: 100%; 16 | `; 17 | -------------------------------------------------------------------------------- /src/views/ErrorPage/constants/errorButton.ts: -------------------------------------------------------------------------------- 1 | const ERROR_BUTTON = { 2 | CODE404 : '홈으로 가기', 3 | CODE500 : '이전 페이지로 가기', 4 | }; 5 | 6 | export default ERROR_BUTTON; 7 | -------------------------------------------------------------------------------- /src/views/ErrorPage/constants/errorMessage.ts: -------------------------------------------------------------------------------- 1 | const ERROR_MESSAGE = { 2 | CODE404 : '존재하지 않는 페이지예요', 3 | CODE500 : '알 수 없는 오류가 발생했어요', 4 | }; 5 | 6 | export default ERROR_MESSAGE; 7 | -------------------------------------------------------------------------------- /src/views/ErrorPage/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Root = styled.main` 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | 9 | position: relative; 10 | 11 | width: 100%; 12 | height: 100dvh; 13 | min-height: 400px; 14 | padding-bottom: 20dvh; 15 | 16 | overflow: scroll; 17 | `; 18 | export const TopSection = styled.section` 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | gap: 34px; 23 | 24 | width: 100%; 25 | `; 26 | export const ErrorText = styled.p` 27 | color: #fcfcfc; 28 | 29 | font-size: 28rem; 30 | font-weight: 600; 31 | line-height: 150%; 32 | letter-spacing: -0.96px; 33 | 34 | @media (max-width: 26.75rem) { 35 | font-size: 24rem; 36 | } 37 | `; 38 | export const ContactButton = styled.button` 39 | position: absolute; 40 | bottom: 17dvh; 41 | 42 | color: #fff; 43 | font-size: 24rem; 44 | font-weight: 600; 45 | line-height: 150%; /* 36px */ 46 | letter-spacing: -0.48px; 47 | text-decoration-line: underline; 48 | 49 | cursor: pointer; 50 | 51 | @media (max-width: 26.75rem) { 52 | font-size: 18rem; 53 | } 54 | `; 55 | -------------------------------------------------------------------------------- /src/views/MainPage/components/Activity/index.tsx: -------------------------------------------------------------------------------- 1 | import { Ref, forwardRef } from 'react'; 2 | import { useIsMobile } from '@src/hooks/useDevice'; 3 | import { Activity } from '@src/lib/constants/main'; 4 | import Tab from '../Tab'; 5 | import Card from './Card'; 6 | import MobileCard from './MobileCard'; 7 | import * as S from './style'; 8 | 9 | function CardHover(_props: unknown, ref: Ref) { 10 | const isMobileSize = useIsMobile('48rem'); 11 | const tab = isMobileSize ? 'Activity' : ''; 12 | 13 | return ( 14 |
15 | 22 | {isMobileSize ? ( 23 | 24 | ) : ( 25 | 26 | {Object.values(Activity).map(({ img, navKor, navEng, description }) => { 27 | return ( 28 | 35 | ); 36 | })} 37 | 38 | )} 39 |
40 | ); 41 | } 42 | 43 | export default forwardRef(CardHover); 44 | -------------------------------------------------------------------------------- /src/views/MainPage/components/Activity/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const CardWrapper = styled.main` 4 | display: grid; 5 | grid-template-columns: repeat(3, 1fr); 6 | justify-items: center; 7 | gap: 28px; 8 | 9 | @media (max-width: 90rem) and (min-width: 48rem) { 10 | grid-template-columns: repeat(2, 1fr); 11 | } 12 | `; 13 | -------------------------------------------------------------------------------- /src/views/MainPage/components/ActivitySection/index.tsx: -------------------------------------------------------------------------------- 1 | import useInView from '@src/hooks/useInView'; 2 | import { PartIntroType } from '@src/lib/types/admin'; 3 | import Activity from '@src/views/MainPage/components/Activity'; 4 | import OwnOrganization from '@src/views/MainPage/components/OwnOrganization'; 5 | import PartConfig from '@src/views/MainPage/components/PartConfig'; 6 | import Comment from '../Comment'; 7 | import * as S from './style'; 8 | 9 | interface ActivitySectionProps { 10 | activityInView: ReturnType; 11 | partInView: ReturnType; 12 | teamInView: ReturnType; 13 | reviewInView: ReturnType; 14 | partIntroduction: PartIntroType[]; 15 | } 16 | 17 | export default function ActivitySection({ 18 | activityInView, 19 | partInView, 20 | teamInView, 21 | reviewInView, 22 | partIntroduction, 23 | }: ActivitySectionProps) { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/views/MainPage/components/ActivitySection/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Wrapper = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | gap: 42px; 7 | 8 | padding-top: 68px; 9 | 10 | @media (max-width: 48rem) { 11 | padding-top: 65px; 12 | } 13 | 14 | @media (max-width: 26.75rem) { 15 | padding-top: 37.36px; 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /src/views/MainPage/components/Banner/RecruitButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, useState } from 'react'; 2 | import * as S from './style'; 3 | 4 | interface BannerColor { 5 | mainColor: string; 6 | highColor: string; 7 | } 8 | export default function RecruitButton({ 9 | children, 10 | mainColor, 11 | highColor, 12 | }: PropsWithChildren) { 13 | const [blurPosition, setBlurPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); 14 | 15 | const handleMouseMove = (event: React.MouseEvent) => { 16 | const rect = event.currentTarget.getBoundingClientRect(); 17 | const x = event.clientX - rect.left; 18 | const y = event.clientY - rect.top; 19 | setBlurPosition({ x, y }); 20 | }; 21 | 22 | return ( 23 | 24 | 31 |
{children}
32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/views/MainPage/components/Comment/Card/Desktop/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { motion } from 'framer-motion'; 3 | 4 | export const Wrapper = styled(motion.div)` 5 | display: flex; 6 | flex-direction: column; 7 | width: 712px; 8 | height: 324px; 9 | border-radius: 19px; 10 | padding: 32px 46px; 11 | 12 | font-family: SUIT; 13 | font-size: 20rem; 14 | font-style: normal; 15 | font-weight: 400; 16 | line-height: 35.5px; /* 177.5% */ 17 | letter-spacing: -0.8px; 18 | @media (max-width: 100rem) { 19 | width: 660px; 20 | height: 324px; 21 | padding: 32px 36px; 22 | } 23 | 24 | @media (max-width: 61rem) { 25 | width: 520px; 26 | height: 324px; 27 | padding: 32px 36px; 28 | font-size: 18rem; 29 | line-height: 30px; 30 | } 31 | 32 | @media (max-height: 53.75rem) and (max-width: 92.5rem) { 33 | height: 280px; 34 | padding: 20px 28px; 35 | } 36 | 37 | & > * { 38 | font-size: 100%; 39 | } 40 | `; 41 | 42 | export const Footer = styled.div` 43 | flex: 1; 44 | display: flex; 45 | align-items: flex-end; 46 | gap: 8px; 47 | font-weight: 600; 48 | 49 | & > * { 50 | font-size: 100%; 51 | } 52 | `; 53 | -------------------------------------------------------------------------------- /src/views/MainPage/components/Comment/Card/Mobile/index.tsx: -------------------------------------------------------------------------------- 1 | import { SoptCommentType } from '@src/lib/types/main'; 2 | import * as S from './style'; 3 | 4 | interface CommentCardProps { 5 | color: SoptCommentType['color']; 6 | comment: SoptCommentType['comment']; 7 | commenter: SoptCommentType['commenter']; 8 | } 9 | 10 | export default function CommentCard({ color, comment, commenter }: CommentCardProps) { 11 | return ( 12 | 13 |
{comment}
14 | 15 |
{commenter.name}
|
{commenter.history}
|
{commenter.part}
16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/views/MainPage/components/Comment/Card/Mobile/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | 4 | export const Wrapper = styled.div<{ color: string }>` 5 | background-color: ${({ color }) => color}; 6 | display: flex; 7 | flex-direction: column; 8 | height: 480px; 9 | border-radius: 21px; 10 | padding: 42px 40px; 11 | 12 | font-family: SUIT; 13 | font-size: 19rem; 14 | font-style: normal; 15 | font-weight: 400; 16 | line-height: 33.3px; /* 177.5% */ 17 | letter-spacing: -0.8px; 18 | color: ${colors.white}; 19 | 20 | @media (max-width: 32.5rem) { 21 | height: 280px; 22 | border-radius: 12px; 23 | padding: 24px 20px; 24 | font-size: 12.669rem; 25 | font-style: normal; 26 | font-weight: 400; 27 | line-height: 20.85px; /* 164.575% */ 28 | letter-spacing: -0.507px; 29 | } 30 | 31 | & > div { 32 | font-size: 100%; 33 | } 34 | `; 35 | 36 | export const Footer = styled.div` 37 | flex: 1; 38 | display: flex; 39 | align-items: flex-end; 40 | gap: 8px; 41 | font-weight: 600; 42 | font-size: 100%; 43 | 44 | & > div { 45 | font-size: 100%; 46 | } 47 | `; 48 | -------------------------------------------------------------------------------- /src/views/MainPage/components/IntroSection/index.tsx: -------------------------------------------------------------------------------- 1 | import { INTRO_CONTENT_LIST } from '@src/lib/constants/main'; 2 | import IntroContent from '@src/views/MainPage/components/IntroSection/IntroContent'; 3 | import * as S from './style'; 4 | 5 | export default function IntroCardList() { 6 | return ( 7 | 8 | 9 |
10 | {INTRO_CONTENT_LIST.map((content) => ( 11 | 12 | ))} 13 |
14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/views/MainPage/components/IntroSection/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const IntroSection = styled.section` 4 | position: relative; 5 | height: calc(300vh + 240px); 6 | `; 7 | 8 | export const Shadow = styled.div` 9 | position: sticky; 10 | z-index: 90; 11 | width: 100%; 12 | height: 120px; 13 | `; 14 | 15 | export const Header = styled(Shadow)` 16 | top: 0; 17 | background: linear-gradient(180deg, #0f1012 0%, rgba(15, 16, 16, 0) 100%); 18 | `; 19 | 20 | export const Footer = styled(Shadow)` 21 | bottom: 0; 22 | background: linear-gradient(360deg, #0f1012 0%, rgba(15, 16, 16, 0) 100%); 23 | `; 24 | -------------------------------------------------------------------------------- /src/views/MainPage/components/PartConfig/PartButton.tsx/index.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, Ref } from 'react'; 2 | import * as S from './style'; 3 | 4 | interface PartButtonProps { 5 | index: number; 6 | label: string; 7 | isSelected: boolean; 8 | handleSelectPart: (index: number) => void; 9 | } 10 | 11 | function PartButton( 12 | { index, label, isSelected, handleSelectPart }: PartButtonProps, 13 | ref: Ref, 14 | ) { 15 | return ( 16 | { 20 | handleSelectPart(index); 21 | e.currentTarget.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); 22 | }} 23 | > 24 | {label} 25 | 26 | ); 27 | } 28 | 29 | export default forwardRef(PartButton); 30 | -------------------------------------------------------------------------------- /src/views/MainPage/components/PartConfig/PartButton.tsx/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const PartButton = styled.button<{ isSelected: boolean }>` 4 | width: 144px; 5 | height: 50px; 6 | border-radius: 12px; 7 | background: ${({ isSelected }) => (isSelected ? '#242B46' : '#ecf0f5')}; 8 | 9 | color: ${({ isSelected }) => (isSelected ? '#fff' : '#909fac')}; 10 | font-family: SUIT; 11 | font-size: 21rem; 12 | font-style: normal; 13 | font-weight: 500; 14 | line-height: 26.083px; /* 124.206% */ 15 | letter-spacing: -0.84px; 16 | 17 | cursor: pointer; 18 | 19 | @media (max-width: 48rem) { 20 | width: 118.982px; 21 | height: 41.313px; 22 | 23 | font-size: 17.352rem; 24 | line-height: 21.552px; /* 124.206% */ 25 | letter-spacing: -0.694px; 26 | } 27 | 28 | @media (max-width: 26.75rem) { 29 | width: 68.38px; 30 | height: 23.743px; 31 | border-radius: 5.698px; 32 | 33 | font-size: 10rem; 34 | line-height: 12.386px; /* 123.859% */ 35 | letter-spacing: -0.4px; 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /src/views/MainPage/components/RecentNews/Card/index.tsx: -------------------------------------------------------------------------------- 1 | import { RecentNewsListType } from '@src/lib/types/main'; 2 | import * as S from './style'; 3 | 4 | export default function Card({ title, url, src }: RecentNewsListType) { 5 | return ( 6 | { 8 | window.open(url); 9 | }} 10 | > 11 | {title} 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/views/MainPage/components/RecentNews/Card/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | import Image from 'next/image'; 4 | 5 | export const Background = styled.main` 6 | width: 286px; 7 | height: 381px; 8 | border-radius: 14px; 9 | margin-top: 51px; 10 | margin-right: 27px; 11 | position: relative; 12 | flex-shrink: 0; 13 | 14 | cursor: pointer; 15 | 16 | transition: all 0.2s linear; 17 | 18 | @media (max-width: 48rem) { 19 | height: 315px; 20 | width: 237px; 21 | } 22 | 23 | @media (max-width: 27.75rem) { 24 | margin-top: 24px; 25 | width: 136px; 26 | height: 182px; 27 | margin-right: 12px; 28 | } 29 | `; 30 | 31 | export const Title = styled.h1` 32 | color: ${colors.white}; 33 | font-family: SUIT; 34 | font-size: 20rem; 35 | font-style: normal; 36 | font-weight: 700; 37 | line-height: normal; 38 | letter-spacing: -0.6px; 39 | 40 | position: absolute; 41 | top: 0; 42 | margin: 18px; 43 | 44 | z-index: 2; 45 | 46 | @media (max-width: 26.75rem) { 47 | font-size: 9rem; 48 | letter-spacing: -0.285px; 49 | margin: 14px; 50 | } 51 | `; 52 | 53 | export const CardImage = styled(Image)` 54 | object-fit: cover; 55 | border-radius: 14px; 56 | `; 57 | -------------------------------------------------------------------------------- /src/views/MainPage/components/RecentNews/index.tsx: -------------------------------------------------------------------------------- 1 | import { LatestNewsType } from '@src/lib/types/admin'; 2 | import Card from './Card'; 3 | import * as S from './style'; 4 | 5 | export default function RecentNews({ latestNews }: { latestNews: LatestNewsType[] }) { 6 | return ( 7 | 8 | 솝트의 최신 소식이 궁금하다면! 9 | 10 | 11 | 12 | 13 | {latestNews 14 | .concat(latestNews) 15 | .concat(latestNews) 16 | .map(({ title, link, image }, idx) => { 17 | return ; 18 | })} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 더 많은 소식이 궁금하다면 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/views/MainPage/components/RecruitMessage/index.tsx: -------------------------------------------------------------------------------- 1 | import { useIsMobile } from '@src/hooks/useDevice'; 2 | import RecruitButton from '../Banner/RecruitButton'; 3 | import * as S from './style'; 4 | 5 | interface RecruitMessageProp { 6 | generation: number; 7 | mainColor: string; 8 | highColor: string; 9 | ctaText: string; 10 | isRecruitEnd?: boolean; 11 | } 12 | export default function RecruitMessage({ 13 | generation, 14 | mainColor, 15 | highColor, 16 | ctaText, 17 | isRecruitEnd, 18 | }: RecruitMessageProp) { 19 | const isMobileSize = useIsMobile('48rem'); 20 | return ( 21 | 22 | 23 | 솝트의 {generation + (isRecruitEnd ? 1 : 0)}번째{isMobileSize ?
: ' '}열정이 24 | 되어주세요! 25 |
26 | {ctaText === '모집 알림 신청하기 ' && ( 27 | 28 | 아직 모집기간이 아니예요.{isMobileSize &&
}알림 신청을 하시면, 가을에 찾아갈게요! 29 |
30 | )} 31 | 32 | {ctaText}> 33 | 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/views/MainPage/components/ScrollInteractiveLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import { motion, useScroll, useTransform } from 'framer-motion'; 2 | import { useRef } from 'react'; 3 | import * as S from './style'; 4 | 5 | export default function ScrollInteractiveLogo() { 6 | const containerRef = useRef(null); 7 | const { scrollYProgress } = useScroll({ 8 | target: containerRef, 9 | offset: ['start end', 'end end'], 10 | }); 11 | 12 | const soptLogoScale = useTransform(scrollYProgress, [0, 0.5, 1], [1, 1, 2]); 13 | const whiteBackgroundOpacity = useTransform(scrollYProgress, [0, 0.48, 0.9, 1], [0, 0, 1, 1]); 14 | 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/views/MainPage/components/Tab/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from './style'; 2 | 3 | interface TabProps { 4 | tab?: string; 5 | title: string; 6 | description: string; 7 | } 8 | 9 | export default function Tab({ tab, title, description }: TabProps) { 10 | return ( 11 | 12 | {tab && {tab}} 13 | {title} 14 | {description} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/views/MainPage/hooks/useGetVisitor.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import { BASE_URL } from '@src/lib/constants/client'; 3 | import axios from 'axios'; 4 | 5 | const client = axios.create({ baseURL: BASE_URL }); 6 | 7 | const getVisitor = async () => { 8 | const { data } = await client.get('/visitor'); 9 | 10 | return data; 11 | }; 12 | 13 | export default function useGetVisitor() { 14 | const { data, isLoading } = useQuery({ 15 | queryKey: ['visitor'], 16 | queryFn: getVisitor, 17 | }); 18 | 19 | return { data, isLoading }; 20 | } 21 | -------------------------------------------------------------------------------- /src/views/MainPage/hooks/usePostVisitor.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@tanstack/react-query'; 2 | import { BASE_URL } from '@src/lib/constants/client'; 3 | import { queryClient } from '@src/pages/_app'; 4 | import axios from 'axios'; 5 | 6 | const client = axios.create({ baseURL: BASE_URL }); 7 | 8 | const postVisitor = async () => { 9 | await client.post('/visitor'); 10 | }; 11 | 12 | const usePostVisitor = () => { 13 | const { mutate } = useMutation({ 14 | mutationFn: postVisitor, 15 | onSuccess: () => { 16 | queryClient.invalidateQueries({ queryKey: ['visitor'] }); 17 | }, 18 | }); 19 | 20 | return mutate; 21 | }; 22 | export default usePostVisitor; 23 | -------------------------------------------------------------------------------- /src/views/ProjectDetailPage/types.ts: -------------------------------------------------------------------------------- 1 | export interface TeamMembersType { 2 | name: string; 3 | role: string; 4 | description: string; 5 | } 6 | 7 | export type LinkDetailType = 8 | | 'website' 9 | | 'appStore' 10 | | 'googlePlay' 11 | | 'github' 12 | | 'instagram' 13 | | 'media'; 14 | 15 | export interface UpButtonProps { 16 | isScrolled: boolean; 17 | } 18 | -------------------------------------------------------------------------------- /src/views/ProjectPage/assets/app-store-40x40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/ProjectPage/assets/youtube-30x30.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/RecentProjectList/Carousel/index.tsx: -------------------------------------------------------------------------------- 1 | import Carousel from '@src/components/common/Carousel'; 2 | import { useDeviceType, useIsDesktop, useIsMobile } from '@src/hooks/useDevice'; 3 | import { staticReleaseProjectList } from '@src/lib/constants/project'; 4 | import RecentProjectListItem from '@src/views/ProjectPage/components/RecentProjectList/Item'; 5 | 6 | export default function RecentProjectListCarousel() { 7 | const isDesktopSize = useIsDesktop('80rem'); 8 | const isMobileSize = useIsMobile('56.1875rem'); 9 | const deviceType = useDeviceType(); 10 | 11 | const isDesktop = deviceType === 'desktop'; 12 | 13 | return ( 14 | 20 | {staticReleaseProjectList.slice(0, 6).map((project, index) => ( 21 | 22 | ))} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/RecentProjectList/RecentProjectListSkeletonUI/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from './style'; 2 | 3 | export default function RecentProjectListSkeletonUI() { 4 | const recentProjectListDummyArray = [1, 2]; 5 | 6 | return ( 7 | 8 | 9 | 10 | {recentProjectListDummyArray.map((index) => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ))} 24 | 25 | 26 | 27 | {recentProjectListDummyArray.map((index) => ( 28 | 29 | ))} 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/RecentProjectList/index.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | import S from '../../styles'; 3 | import RecentProjectListCarousel from './Carousel'; 4 | import RecentProjectListSkeletonUI from './RecentProjectListSkeletonUI'; 5 | 6 | export default function RecentProjectList() { 7 | return ( 8 | <> 9 | 최근 출시한 프로젝트 10 | 11 | SOPT에서 출시한 프로젝트가 있으신가요? 12 | 16 | 릴리즈 소식 알리기 17 | 18 | 19 | }> 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectCard/ServiceInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactComponent as IcEllipseGreen } from '@src/assets/icons/ic_ellipse_green.svg'; 2 | import * as S from './style'; 3 | 4 | interface ServiceInfoProps { 5 | text: string; 6 | } 7 | 8 | export default function ServiceInfo({ text }: ServiceInfoProps) { 9 | return ( 10 | 11 | 12 | {text} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectCard/ServiceInfo/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | 4 | export const Info = styled.div` 5 | display: flex; 6 | align-items: center; 7 | gap: 4px; 8 | 9 | color: ${colors.gray100}; 10 | 11 | /* Label/3_SB */ 12 | font-family: SUIT; 13 | font-size: 12rem; 14 | font-style: normal; 15 | font-weight: 600; 16 | line-height: 16px; /* 133.333% */ 17 | letter-spacing: -0.24px; 18 | 19 | & > * { 20 | font-size: 100%; 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectCardList/index.tsx: -------------------------------------------------------------------------------- 1 | import { ProjectType } from '@src/lib/types/project'; 2 | import ProjectCard from '@src/views/ProjectPage/components/project/ProjectCard'; 3 | import * as S from './style'; 4 | 5 | interface ProjectCardList { 6 | projectList: ProjectType[]; 7 | } 8 | 9 | export default function ProjectCardList({ projectList }: ProjectCardList) { 10 | return ( 11 | 12 | {projectList?.map((project) => ( 13 | 14 | ))} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectCardList/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const CardList = styled.div` 4 | display: grid; 5 | grid-template-columns: 1fr 1fr 1fr; 6 | gap: 50px 28px; 7 | width: 1112px; 8 | 9 | @media (max-width: 79.9375rem) and (min-width: 56.25rem) { 10 | grid-template-columns: 1fr 1fr; 11 | width: 732px; 12 | } 13 | 14 | @media (max-width: 56.1875rem) { 15 | grid-template-columns: 1fr; 16 | gap: 16px; 17 | width: 100%; 18 | } 19 | `; 20 | 21 | export const SpinnerWrapper = styled.div` 22 | display: flex; 23 | justify-content: center; 24 | width: 100%; 25 | `; 26 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectCategoryDescription/index.tsx: -------------------------------------------------------------------------------- 1 | import { projectCategoryDescription } from '@src/lib/constants/project'; 2 | import { ProjectCategoryType } from '@src/lib/types/project'; 3 | import * as S from './style'; 4 | 5 | export interface ProjectCategoryDescriptionProps { 6 | selectedCategory: ProjectCategoryType; 7 | } 8 | 9 | export default function ProjectCategoryDescription({ 10 | selectedCategory, 11 | }: ProjectCategoryDescriptionProps) { 12 | if (selectedCategory === ProjectCategoryType.ALL) { 13 | return <>; 14 | } 15 | 16 | return ( 17 | 18 | {projectCategoryDescription[selectedCategory]} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectCategoryDescription/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | import { ProjectCategoryDescriptionProps } from '@src/views/ProjectPage/components/project/ProjectCategoryDescription'; 4 | 5 | export const Description = styled.div` 6 | color: ${colors.gray100}; 7 | 8 | /* Body/1_Medium_18 */ 9 | font-family: SUIT; 10 | font-size: 18rem; 11 | font-style: normal; 12 | font-weight: 500; 13 | line-height: 165%; /* 29.7px */ 14 | letter-spacing: -0.27px; 15 | 16 | /* 모바일 뷰 */ 17 | @media (max-width: 56.1875rem) { 18 | padding: 16px; 19 | border-radius: 12px; 20 | background: ${colors.gray800}; 21 | 22 | color: ${colors.gray30}; 23 | 24 | /* Body/3_Medium_14 */ 25 | font-family: SUIT; 26 | font-size: 14rem; 27 | font-style: normal; 28 | font-weight: 500; 29 | line-height: 165%; /* 23.1px */ 30 | letter-spacing: -0.21px; 31 | } 32 | `; 33 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectList/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { ProjectCategoryType } from '@src/lib/types/project'; 3 | 4 | export const ProjectListHeader = styled.div<{ selectedCategory: ProjectCategoryType }>` 5 | display: flex; 6 | justify-content: ${({ selectedCategory }) => 7 | selectedCategory === ProjectCategoryType.ALL ? 'end' : 'space-between'}; 8 | margin-bottom: 30px; 9 | 10 | /* 모바일 뷰 */ 11 | @media (max-width: 56.1875rem) { 12 | flex-direction: column; 13 | gap: 20px; 14 | margin-bottom: 28px; 15 | } 16 | `; 17 | 18 | export const SpinnerWrapper = styled.section` 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | margin: 50rem 0; 23 | `; 24 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectListCount/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from './style'; 2 | 3 | interface ProjectListCountProps { 4 | count: number; 5 | } 6 | 7 | export default function ProjectListCount({ count }: ProjectListCountProps) { 8 | return ( 9 | 10 | {count}개의 프로젝트 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectListCount/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | 4 | export const Count = styled.div` 5 | color: ${colors.gray100}; 6 | 7 | /* Body/1_Medium_18 */ 8 | font-family: SUIT; 9 | font-size: 18rem; 10 | font-style: normal; 11 | font-weight: 500; 12 | line-height: 165%; /* 29.7px */ 13 | letter-spacing: -0.27px; 14 | 15 | @media (max-width: 79.9375rem) and (min-width: 56.25rem) { 16 | & > span { 17 | display: none; 18 | } 19 | } 20 | 21 | /* 모바일 뷰 */ 22 | @media (max-width: 56.1875rem) { 23 | /* Body/3_Medium_14 */ 24 | font-family: SUIT; 25 | font-size: 14rem; 26 | font-style: normal; 27 | font-weight: 500; 28 | line-height: 165%; /* 23.1px */ 29 | letter-spacing: -0.21px; 30 | } 31 | 32 | & > * { 33 | font-size: 100%; 34 | } 35 | `; 36 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectListFallback/ProjectCardSkeletonUI/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from './style'; 2 | 3 | export default function ProjectCardSkeletonUI() { 4 | const array = new Array(9).fill(0); 5 | 6 | return ( 7 | 8 | {array.map((_, index) => ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ))} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectListFallback/index.tsx: -------------------------------------------------------------------------------- 1 | import ProjectCardSkeletonUI from '@src/views/ProjectPage/components/project/ProjectListFallback/ProjectCardSkeletonUI'; 2 | import * as S from './style'; 3 | 4 | export default function ProjectListFallback() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/views/ProjectPage/components/project/ProjectListFallback/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { colors } from '@sopt-makers/colors'; 3 | 4 | export const ProjectListHeader = styled.div` 5 | display: flex; 6 | justify-content: end; 7 | align-items: center; 8 | margin-bottom: 30px; 9 | 10 | /* 모바일 뷰 */ 11 | @media (max-width: 56.1875rem) { 12 | flex-direction: column; 13 | gap: 20px; 14 | margin-bottom: 28px; 15 | } 16 | `; 17 | 18 | export const ProjectListCountSkeletonUI = styled.div` 19 | width: 89px; 20 | height: 23px; 21 | background-color: ${colors.gray900}; 22 | border-radius: 8px; 23 | 24 | @media (max-width: 79.9375rem) and (min-width: 56.25rem) { 25 | width: 33px; 26 | } 27 | `; 28 | -------------------------------------------------------------------------------- /src/views/ProjectPage/hooks/useGetProjectList.ts: -------------------------------------------------------------------------------- 1 | import { useInfiniteQuery } from '@tanstack/react-query'; 2 | import { api } from '@src/lib/api'; 3 | import { ProjectCategoryType, ProjectPlatformType } from '@src/lib/types/project'; 4 | 5 | export const useGetProjectList = (category: ProjectCategoryType, platform: ProjectPlatformType) => { 6 | const queryKey = ['getProjectList', category, platform]; 7 | 8 | const { data, hasNextPage, fetchNextPage } = useInfiniteQuery({ 9 | queryKey, 10 | queryFn: ({ pageParam }: { pageParam: number }) => 11 | api.projectAPI.getProjectList(category, platform, pageParam), 12 | getNextPageParam: (lastPage) => (lastPage.hasNextPage ? lastPage.currentPage + 1 : undefined), 13 | initialPageParam: 1, 14 | }); 15 | 16 | return { data: data?.pages.flatMap((page) => page.data), hasNextPage, fetchNextPage }; 17 | }; 18 | -------------------------------------------------------------------------------- /src/views/RecruitPage/components/ActivityReview/hooks/queries/useGetSampleReviews.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import { api } from '@src/lib/api'; 3 | import { GetSampleReviewsResponse } from '@src/lib/types/review'; 4 | 5 | const getSampleReviews = async (): Promise => { 6 | const data = await api.reviewAPI.getSampleReviews(); 7 | 8 | return data; 9 | }; 10 | 11 | export default function useGetSampleReviews() { 12 | const { data, isLoading } = useQuery({ 13 | queryKey: ['sample-reviews'], 14 | queryFn: getSampleReviews, 15 | }); 16 | 17 | return { data, isLoading }; 18 | } 19 | -------------------------------------------------------------------------------- /src/views/RecruitPage/components/ApplySection/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { BrandingColorContext } from '../..'; 3 | import * as S from './style'; 4 | 5 | const ApplySection = ({ headerImg }: { headerImg: string }) => { 6 | const { main } = useContext(BrandingColorContext); 7 | return ( 8 | 9 | 10 | SOPT의 36번째 열정을  11 | 기다리고 있어요! 12 | 13 | 14 | 지원하기 15 | 16 | 17 | ); 18 | }; 19 | export default ApplySection; 20 | -------------------------------------------------------------------------------- /src/views/RecruitPage/components/BottomLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import Image from 'next/image'; 3 | import { logoAndSoptDark } from '@src/assets/mainLogo'; 4 | 5 | const BottomLogo = () => { 6 | return ; 7 | }; 8 | 9 | const ScaledImage = styled(Image)` 10 | width: 362px; 11 | height: auto; 12 | margin-top: 338px; 13 | margin-bottom: 86px; 14 | 15 | /* 태블릿 뷰 */ 16 | @media (max-width: 81.1875rem) and (min-width: 47.875rem) { 17 | width: 209px; 18 | height: 25px; 19 | } 20 | /* 모바일 뷰 */ 21 | @media (max-width: 47.86875rem) { 22 | width: 138px; 23 | height: 16px; 24 | } 25 | `; 26 | 27 | export default BottomLogo; 28 | -------------------------------------------------------------------------------- /src/views/RecruitPage/components/common/Tabs/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const TabBar = styled.div` 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: center; 7 | gap: 25px; 8 | 9 | @media (max-width: 47.86875rem) { 10 | gap: 10px; 11 | } 12 | `; 13 | 14 | export const Tab = styled.div<{ selected: boolean }>` 15 | cursor: pointer; 16 | text-align: center; 17 | padding: 20px 0; 18 | border-radius: 10px; 19 | color: ${({ selected }) => (selected ? '#FFFFFF' : '#cccccc')}; 20 | background-color: ${({ selected }) => (selected ? '#222220' : 'inherit')}; 21 | font-size: 23rem; 22 | 23 | min-width: 121px; 24 | &:hover { 25 | background-color: rgba(255, 255, 255, 0.1); 26 | } 27 | 28 | @media (max-width: 81.1875rem) and (min-width: 47.875rem) { 29 | padding: 12px 0; 30 | font-size: 14rem; 31 | } 32 | 33 | @media (max-width: 47.86875rem) { 34 | min-width: 101px; 35 | padding: 12px 0; 36 | font-size: 16rem; 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /src/views/RulesPage/components/CollapseLi/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import * as S from './style'; 3 | 4 | interface CollapseLiProps { 5 | title: string; 6 | contents: string; 7 | } 8 | 9 | function CollapseLi({ title, contents }: CollapseLiProps) { 10 | const [isOpened, setIsOpened] = useState(false); 11 | 12 | const handleClick = () => { 13 | setIsOpened((prev) => !prev); 14 | }; 15 | 16 | return ( 17 | 18 | 19 | {title} 20 | 21 | 22 | {contents} 23 | 24 | ); 25 | } 26 | 27 | export default CollapseLi; 28 | -------------------------------------------------------------------------------- /src/views/RulesPage/components/RulesHead/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | function RulesHead() { 4 | return ( 5 | 6 | SOPT Rules Page 7 | 8 | 9 | ); 10 | } 11 | 12 | export default RulesHead; 13 | -------------------------------------------------------------------------------- /src/views/RulesPage/components/RulesList/index.tsx: -------------------------------------------------------------------------------- 1 | import { RULES } from '@src/lib/constants/rules'; 2 | import CollapseLi from '../CollapseLi'; 3 | import * as S from './style'; 4 | 5 | function RulesList() { 6 | return ( 7 | 8 | {RULES.map((item) => { 9 | return ; 10 | })} 11 | ; 12 | 13 | ); 14 | } 15 | 16 | export default RulesList; 17 | -------------------------------------------------------------------------------- /src/views/RulesPage/components/RulesList/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Ul = styled.ul` 4 | margin-top: 130px; 5 | li { 6 | &:last-child { 7 | border: none; 8 | } 9 | } 10 | @media screen and (max-width: 80rem) { 11 | margin-top: 31px; 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /src/views/RulesPage/components/UnderlinedText/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import * as S from './style'; 3 | 4 | export interface TitleProps { 5 | fontSize?: string; 6 | } 7 | 8 | function UnderlinedText({ children, fontSize = '35px', ...props }: PropsWithChildren) { 9 | return ( 10 | 11 | {children} 12 | 13 | ); 14 | } 15 | 16 | export default UnderlinedText; 17 | -------------------------------------------------------------------------------- /src/views/RulesPage/components/UnderlinedText/style.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { TitleProps } from '.'; 3 | 4 | export const Root = styled.h1` 5 | border-bottom: 10px solid #fcfcfc; 6 | padding-bottom: 10px; 7 | 8 | width: max-content; 9 | line-height: 44px; 10 | 11 | white-space: pre-line; 12 | 13 | font-size: ${({ fontSize }) => fontSize}; 14 | font-weight: 800; 15 | 16 | @media (max-width: 79.9375rem) { 17 | border-bottom: 6px solid #fcfcfc; 18 | padding-bottom: 5px; 19 | 20 | line-height: 31px; 21 | font-size: 25rem; 22 | font-weight: 900; 23 | } 24 | `; 25 | -------------------------------------------------------------------------------- /src/views/RulesPage/components/index.tsx: -------------------------------------------------------------------------------- 1 | import CollapseLi from './CollapseLi'; 2 | import RulesHead from './RulesHead'; 3 | import RulesList from './RulesList'; 4 | import UnderlinedText from './UnderlinedText'; 5 | 6 | export { CollapseLi, RulesHead, RulesList, UnderlinedText }; 7 | -------------------------------------------------------------------------------- /src/views/RulesPage/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import PageLayout from '@src/components/common/PageLayout'; 3 | import { useIsDesktop } from '@src/hooks/useDevice'; 4 | import { RulesHead, RulesList, UnderlinedText } from './components'; 5 | 6 | const SOPT_RULES = 'SOPT 회칙'; 7 | 8 | function Rules() { 9 | const isDesktopOrTablet = useIsDesktop('48.0625rem'); 10 | 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | {SOPT_RULES} 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | const Content = styled.div` 27 | margin-top: 250px; 28 | margin-bottom: 130px; 29 | width: 100%; 30 | max-width: 1192px; 31 | @media screen and (max-width: 80rem) { 32 | width: 92%; 33 | } 34 | `; 35 | 36 | const Root = styled.div` 37 | display: flex; 38 | flex-direction: column; 39 | /* justify-content: center; */ 40 | align-items: center; 41 | `; 42 | 43 | export default Rules; 44 | -------------------------------------------------------------------------------- /src/views/SponsorPage/components/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as CorporatePartner } from './CorporatePartner'; 2 | -------------------------------------------------------------------------------- /src/views/SponsorPage/index.tsx: -------------------------------------------------------------------------------- 1 | import PageLayout from '@src/components/common/PageLayout'; 2 | import { CorporatePartner } from './components'; 3 | 4 | const SponsorPage = () => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default SponsorPage; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "jsxImportSource": "@emotion/react", 18 | "baseUrl": ".", 19 | "paths": { 20 | "@src/*": ["src/*"] 21 | } 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/components/common/BorderRoundButton"], 24 | "exclude": ["node_modules"] 25 | } 26 | --------------------------------------------------------------------------------