diff --git a/public/pngs/clan-background.png b/public/pngs/clan-background.png new file mode 100644 index 00000000..7271f6dd Binary files /dev/null and b/public/pngs/clan-background.png differ diff --git a/public/pngs/slider-battlegrounds.png b/public/pngs/slider-battlegrounds.png new file mode 100644 index 00000000..61708198 Binary files /dev/null and b/public/pngs/slider-battlegrounds.png differ diff --git a/public/pngs/slider-lol.png b/public/pngs/slider-lol.png new file mode 100644 index 00000000..6dae9170 Binary files /dev/null and b/public/pngs/slider-lol.png differ diff --git a/public/pngs/slider-minecraft.png b/public/pngs/slider-minecraft.png new file mode 100644 index 00000000..2ffba787 Binary files /dev/null and b/public/pngs/slider-minecraft.png differ diff --git a/public/pngs/slider-overwatch.png b/public/pngs/slider-overwatch.png new file mode 100644 index 00000000..29933773 Binary files /dev/null and b/public/pngs/slider-overwatch.png differ diff --git a/public/pngs/slider-stroke-active.png b/public/pngs/slider-stroke-active.png new file mode 100644 index 00000000..8274e2bb Binary files /dev/null and b/public/pngs/slider-stroke-active.png differ diff --git a/public/pngs/slider-stroke-default.png b/public/pngs/slider-stroke-default.png new file mode 100644 index 00000000..001b2f60 Binary files /dev/null and b/public/pngs/slider-stroke-default.png differ diff --git a/src/components/commons/Tag/index.tsx b/src/components/commons/Tag/index.tsx index 040c2236..e754a5d0 100644 --- a/src/components/commons/Tag/index.tsx +++ b/src/components/commons/Tag/index.tsx @@ -2,14 +2,14 @@ import classNames from 'classnames/bind'; import { formatPostTypeToKR } from '@/utils'; -import { ReservedPostTypesEN } from '@/types'; +import { PostTypesEN } from '@/types'; import styles from './Tag.module.scss'; const cx = classNames.bind(styles); type TagProps = { - postType: ReservedPostTypesEN | string; + postType: PostTypesEN | string; }; const Tag = ({ postType }: TagProps) => { diff --git a/src/components/commons/buttons/SliderButton.module.scss b/src/components/commons/buttons/SliderButton.module.scss new file mode 100644 index 00000000..5047a527 --- /dev/null +++ b/src/components/commons/buttons/SliderButton.module.scss @@ -0,0 +1,15 @@ +%arrow-btn-base { + @include flexbox; + + width: 4rem; + height: 4rem; + + background: $opacity-white-10; + backdrop-filter: $slider-button-blur; + border: 0.1rem solid $opacity-white-20; + border-radius: 0.4rem; +} + +.clan-slider-btn { + @extend %arrow-btn-base; +} diff --git a/src/components/commons/buttons/SliderButton.tsx b/src/components/commons/buttons/SliderButton.tsx new file mode 100644 index 00000000..6219205e --- /dev/null +++ b/src/components/commons/buttons/SliderButton.tsx @@ -0,0 +1,28 @@ +import Image from 'next/image'; + +import classNames from 'classnames/bind'; + +import { SVGS } from '@/constants'; + +import styles from './SliderButton.module.scss'; + +const cx = classNames.bind(styles); + +type SliderButtonProps = { + type: 'left' | 'right'; + onClick: () => void; +}; + +const SliderButton = ({ type, onClick }: SliderButtonProps) => { + const { active } = type === 'left' ? SVGS.arrow.left : SVGS.arrow.right; + + return ( +
+ +
+ ); +}; + +export default SliderButton; diff --git a/src/components/landing/ClanCard.tsx/ClanCard.module.scss b/src/components/landing/ClanCard.tsx/ClanCard.module.scss new file mode 100644 index 00000000..9d5645d1 --- /dev/null +++ b/src/components/landing/ClanCard.tsx/ClanCard.module.scss @@ -0,0 +1,66 @@ +.slider-banner-item { + &-content { + @include responsive(PC) { + &:hover { + background-image: url('/pngs/slider-stroke-active.png'); + box-shadow: $clan-card-shadow; + } + + &:hover .slider-banner-item-info-title { + color: $white; + } + } + + @include responsive(T) { + background-image: url('/pngs/slider-stroke-active.png'); + } + + cursor: pointer; + + position: relative; + + display: block; + + width: 100%; + height: 100%; + + background: url('/pngs/slider-stroke-default.png') no-repeat center/cover; + + transition: $base-transition; + } + + &-tag { + position: absolute; + top: 1.2rem; + left: 1.2rem; + } + + &-info { + position: absolute; + bottom: 0; + left: 0; + + overflow: hidden; + + width: 100%; + padding: 2.4rem; + + &-title { + @include responsive(T) { + color: $white; + } + + @include text-style(24, $gray30, bold); + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: $base-transition; + } + + &-createdAt { + @include flexbox(start, center, 0.4rem); + @include text-style(14, $gray10); + } + } +} diff --git a/src/components/landing/ClanCard.tsx/index.tsx b/src/components/landing/ClanCard.tsx/index.tsx new file mode 100644 index 00000000..eda2660f --- /dev/null +++ b/src/components/landing/ClanCard.tsx/index.tsx @@ -0,0 +1,41 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +import classNames from 'classnames/bind'; + +import { SVGS } from '@/constants'; +import { getFormatDate } from '@/utils'; + +import Tag from '@/components/commons/Tag'; + +import styles from './ClanCard.module.scss'; + +const cx = classNames.bind(styles); + +const { url, alt } = SVGS.calendar.active; + +export type ClanCardProps = { + id: number; + gameName: string; + clanTitle: string; + createdAt: string; +}; + +const ClanCard = ({ id, gameName, clanTitle, createdAt }: ClanCardProps) => { + return ( + +
+ +
+
+

{clanTitle}

+
+ {alt} + {getFormatDate(createdAt)} +
+
+ + ); +}; + +export default ClanCard; diff --git a/src/components/landing/ClanRecruitment/ClanRecruitment.module.scss b/src/components/landing/ClanRecruitment/ClanRecruitment.module.scss new file mode 100644 index 00000000..6a1c2de7 --- /dev/null +++ b/src/components/landing/ClanRecruitment/ClanRecruitment.module.scss @@ -0,0 +1,159 @@ +$slider-item-margin: 2.4rem; +$slider-banner-tablet: 389.5rem; +$slider-banner-mobile: 387.9rem; + +.clan { + @include responsive(T) { + height: 80rem; + } + + width: 100%; + height: 100vh; + background: url('/pngs/clan-background.png') no-repeat center/cover; + + .container { + @include responsive(T) { + max-width: 100%; + padding-top: 10.2rem; + } + + @include responsive(M) { + max-width: 100%; + padding-top: 8.8rem; + } + + max-width: 120rem; + height: 100%; + margin: 0 auto; + padding-top: 14.4rem; + } + + &-header { + @include column-flexbox($gap: 1.6rem); + + @include responsive(M) { + margin-bottom: 3.2rem; + } + + margin-bottom: 4.8rem; + + &-title { + @include text-style-quantico(40, $purple); + + @include responsive(M) { + font-size: 3.2rem; + } + } + + &-description { + @include text-style(24, $gray30); + + @include responsive(M) { + font-size: 1.6rem; + line-height: 2.4rem; + } + + max-width: 64rem; + text-align: center; + + &-highlight { + color: $white; + } + } + } + + &-slider { + position: relative; + + &-btn-prev, + &-btn-next { + @include pos-center-y; + + z-index: $slider-button-level; + } + + &-btn-prev { + left: -2rem; + } + + &-btn-next { + right: -2rem; + } + + .slider-banner { + @include responsive(T) { + @include no-scrollbar; + + overflow-x: scroll; + padding-left: 4rem; + } + + @include responsive(M) { + padding-left: 1.5rem; + } + + scroll-behavior: smooth; + + overflow: hidden; + + width: 100%; + height: 100%; + padding-top: 1.6rem; + + &-list { + @include responsive(T) { + width: $slider-banner-tablet; + } + + @include responsive(M) { + width: $slider-banner-mobile; + } + + width: calc(400vw + ($slider-item-margin * 11)); + } + + &-item { + @include responsive(T) { + width: 30rem; + height: 36rem; + margin-right: 4rem; + } + + @include responsive(T) { + margin-right: 1.5rem; + } + + @include responsive(PC) { + &:hover { + transform: translateY(-1.6rem); + } + } + + display: inline-block; + width: 38.4rem; + height: 46rem; + transition: $base-transition; + + &.league-of-legends { + background: url('/pngs/slider-lol.png') no-repeat center/cover; + } + + &.battlegrounds { + background: url('/pngs/slider-battlegrounds.png') no-repeat center/cover; + } + + &.overwatch-2 { + background: url('/pngs/slider-overwatch.png') no-repeat center/cover; + } + + &.minecraft { + background: url('/pngs/slider-minecraft.png') no-repeat center/cover; + } + + &:not(:last-child) { + margin-right: 2.4rem; + } + } + } + } +} diff --git a/src/components/landing/ClanRecruitment/index.tsx b/src/components/landing/ClanRecruitment/index.tsx new file mode 100644 index 00000000..b2cb0afb --- /dev/null +++ b/src/components/landing/ClanRecruitment/index.tsx @@ -0,0 +1,105 @@ +import { useRef, useState } from 'react'; + +import { useQuery } from '@tanstack/react-query'; +import classNames from 'classnames/bind'; + +import { getActivities } from '@/apis/queryFunctions'; +import { GAME_NAME_KR_TO_PATH_NAME } from '@/constants'; +import { splitTitleByDelimiter } from '@/utils'; + +import SliderButton from '@/components/commons/buttons/SliderButton'; +import ClanCard from '@/components/landing/ClanCard.tsx'; + +import styles from './ClanRecruitment.module.scss'; + +const cx = classNames.bind(styles); + +const SCROLL_SLIDER_WIDTH = 1224; +const MAX_DISPLAYED_CLAN_CARDS = 3; +const POST_CATEGORY_CLAN = 2; + +const ClanRecruitment = () => { + const { data: lol } = useQuery({ queryKey: ['activities', '스포츠'], queryFn: getActivities }); + const { data: battlegrounds } = useQuery({ queryKey: ['activities', '투어'], queryFn: getActivities }); + const { data: overwatch } = useQuery({ queryKey: ['activities', '관광'], queryFn: getActivities }); + const { data: minecraft } = useQuery({ queryKey: ['activities', '웰빙'], queryFn: getActivities }); + + const gamePostList = [lol, battlegrounds, overwatch, minecraft]; + + const filteredGameCategory = gamePostList.map((gamePosts) => { + if (!gamePosts) return []; + return gamePosts.activities.filter((postType) => postType.price === POST_CATEGORY_CLAN); + }); + + const filterSliderClanList = []; + + for (let i = 0; i < MAX_DISPLAYED_CLAN_CARDS; i++) { + for (const list of filteredGameCategory) { + if (list && list.length > i) { + filterSliderClanList.push(list[i]); + } + } + } + + const sliderRef = useRef(null); + + const [currentSliderIndex, setCurrentSliderIndex] = useState(0); + + const handleNextClick = () => { + if (sliderRef.current && currentSliderIndex < MAX_DISPLAYED_CLAN_CARDS) { + sliderRef.current.scrollLeft += SCROLL_SLIDER_WIDTH; + setCurrentSliderIndex((prev) => prev + 1); + } + }; + + const handlePrevClick = () => { + if (sliderRef.current && currentSliderIndex > 0) { + sliderRef.current.scrollLeft -= SCROLL_SLIDER_WIDTH; + setCurrentSliderIndex((prev) => prev - 1); + } + }; + + return ( +
+

클랜 모집

+
+
+

Clan Recruitment

+

+ 실시간으로 모집하는 온라인 클랜에 참여하여 +
+ 다양한 동료들과 함께 팀을 이뤄 미션을 + 격파하세요 +

+
+ +
+
+ +
+ +
+
    + {filterSliderClanList?.map(({ id, title, createdAt }) => { + const { category, title: clanTitle } = splitTitleByDelimiter(title); + const gameName = GAME_NAME_KR_TO_PATH_NAME[category]; + + return ( +
  • + +
  • + ); + })} +
+
+ +
+ +
+
+
+
+ ); +}; + +export default ClanRecruitment; diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx index f8145c9b..4312014c 100644 --- a/src/pages/landing/index.tsx +++ b/src/pages/landing/index.tsx @@ -1,5 +1,26 @@ +import { dehydrate } from '@tanstack/react-query'; + +import { getActivities } from '@/apis/queryFunctions'; +import { queryClient } from '@/utils'; + import Layout from '@/components/layout/Layout'; +import { Category } from '@/types'; + +export const getServerSideProps = () => { + queryClient.prefetchQuery({ queryKey: ['activities', ''], queryFn: getActivities }); + + return { + props: { + dehydratedState: dehydrate(queryClient), + }, + }; +}; + +export type LandingPageProps = { + category: Category; +}; + const LandingPage = () => { return
LandingPage
; }; diff --git a/src/styles/variables/_blur.scss b/src/styles/variables/_blur.scss index 0421052f..e62650b2 100644 --- a/src/styles/variables/_blur.scss +++ b/src/styles/variables/_blur.scss @@ -6,4 +6,5 @@ $alarm-blur: blur(80px); $drawer-blur: blur(60px); $input-blur: blur(20px); $confirm-schedule-blur: blur(32px); +$slider-button-blur: blur(32px); $landing-blur: blur(12px); diff --git a/src/styles/variables/_box-shadow.scss b/src/styles/variables/_box-shadow.scss index ba7ba837..61d25d92 100644 --- a/src/styles/variables/_box-shadow.scss +++ b/src/styles/variables/_box-shadow.scss @@ -3,3 +3,4 @@ $dropdown-shadow: 0 4px 12px 0 rgb(0 0 0 / 40%); $popup-shadow: 0 12px 24px 0 rgb(0 0 0 / 32%); $schedule-item-shadow: 0 6px 12px 0 rgb(0 0 0 / 30%); $icon-shadow: 0 0 4px 0 rgb(0 0 0 / 16%); +$clan-card-shadow: 0 8px 12px 0 rgb(0 0 0 / 60%); diff --git a/src/styles/variables/_levels.scss b/src/styles/variables/_levels.scss index f95998ae..358590c4 100644 --- a/src/styles/variables/_levels.scss +++ b/src/styles/variables/_levels.scss @@ -1,5 +1,6 @@ $glitch-2-level: -2; $glitch-1-level: -1; +$slider-button-level: 1; $chatbot-level: 4; $kebab-level: 5; $dropdown-level: 10;